Files
awoooi/docs/runbooks/AWOOOP-RLS-CANARY-WAVE1-2.md
2026-05-12 21:41:14 +08:00

114 lines
3.6 KiB
Markdown

# AwoooP RLS Canary Wave 1.2
This wave targets:
- `awooop_projects`
Status: applied in production on 2026-05-12.
Production state:
- API image gate: `192.168.0.110:5000/awoooi/api:7d92f0acd705451d99b4413ab9748482e3675c00`
- `awooop_projects`: `rls=true force=true policies=4`
- Operator Console tenants endpoint: `total=2`
- Direct table reads:
- no `app.project_id`: `[]`
- `app.project_id='awoooi'`: `['awoooi']`
- `app.project_id='ewoooc'`: `['ewoooc']`
- `awooop_operator_list_projects()` still returns both reviewed projects.
## Safety Model
`awooop_projects` is special. Runtime checks such as MCP Gate 1 and budget
lookup should be tenant-scoped, but Operator Console needs a cross-tenant
project list.
Wave 1.2 keeps normal table access tenant-scoped and adds an explicit platform
read function:
```sql
public.awooop_operator_list_projects()
```
The function is `SECURITY DEFINER`, has a fixed `search_path`, returns only the
Operator Console project-list columns, and grants execute only to `awooop_app`.
## App Changes
- `platform_operator_service.list_tenants()` reads from
`awooop_operator_list_projects()`.
- `budget_service._get_tenant_budget_limit(project_id)` now opens
`get_db_context(project_id)`, so tenant budget reads match RLS context.
## Apply
Before applying, verify the API deployment has the code that calls
`awooop_operator_list_projects()`:
```bash
ssh wooo@192.168.0.120 \
'sudo kubectl -n awoooi-prod get deploy awoooi-api -o wide'
```
The image tag must be `7d92f0ac` or later. If the deployment is older, do not
enable RLS; Operator Console will only see the current tenant row.
```bash
psql "$DATABASE_URL" -v ON_ERROR_STOP=1 \
-f scripts/ops/awooop-rls-canary-wave1-2-projects.sql
```
The SQL aborts if:
- table is missing,
- `project_id` is missing,
- any `project_id` is NULL,
- row count exceeds the reviewed canary cap of 20 rows.
## Verification
Expected after apply:
- `app.project_id='awoooi'`: direct table read sees only `awoooi`.
- `app.project_id='ewoooc'`: direct table read sees only `ewoooc`.
- `awooop_operator_list_projects()`: returns both projects for Operator Console.
- tenant budget lookup can read the matching tenant row.
- global RLS preflight remains blocked only by later-wave tables.
Production verification from 2026-05-12:
- `/api/v1/platform/tenants` from API pod: HTTP 200, `total=2`.
- `/api/v1/health` from API pod: HTTP 200, `status=healthy`.
- `scripts/ops/awooop-rls-preflight.sh --exact-counts`:
- `PASS=7 WARN=1 BLOCKED=1`
- `awooop_projects rls=true force=true policies=4`
- remaining blocker tables: `audit_logs`, `awooop_outbound_message`,
`awooop_run_state`, `incidents`, `knowledge_entries`, `playbooks`.
- Direct app-role behavior:
- `projects_no_context=[]`
- `projects_awoooi_context=['awoooi']`
- `projects_ewoooc_context=['ewoooc']`
- `operator_function_awoooi_context=['awoooi', 'ewoooc']`
## Apply / Rollback Note
An earlier production apply attempt was rolled back immediately because the
live API image was still `ff30c61c...`, before the Operator Console code path
had deployed. The symptom was `/api/v1/platform/tenants` returning only the
`awoooi` row. Rollback restored `total=2`.
After Gitea CD rolled out `7d92f0ac...`, the same SQL was re-applied and the
post-apply verification above passed. Keep this deployment-order gate for any
future changes to cross-tenant read helpers.
## Rollback
```bash
psql "$DATABASE_URL" -v ON_ERROR_STOP=1 \
-f scripts/ops/awooop-rls-canary-wave1-2-projects-rollback.sql
```
Rollback disables RLS and removes the Wave1.2 policies on `awooop_projects`.
It intentionally keeps `awooop_operator_list_projects()` for deployed API
compatibility.