# 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.