# AwoooP RLS Canary Wave 1.3 This wave targets one outbound evidence table: - `awooop_outbound_message` Status: applied in production on 2026-05-12. Production state: - `awooop_outbound_message`: `rls=true force=true policies=1` - Operator Console Run detail smoke: - run: `d385b7fe-8666-58ec-9072-9ac917adb6cf` - `project_id=awoooi` - HTTP 200 - `outbound_messages=1` ## Why This Table `awooop_outbound_message` stores outbound notification evidence for Run Timeline. It is less risky than `awooop_run_state` for this wave because it is not the worker lease/state-machine source of truth. Latest live evidence before staging: ```text project_id=awoooi rows=290 send_status=sent rows=290 null_project_id_rows=0 ``` `awooop_run_state` remains blocked because it is used by worker lease, state-machine transitions, approvals, and Operator Console list/detail paths. It needs a separate query-path and cross-tenant Operator Console design pass. ## Runtime Paths Write paths: - `TelegramGateway._mirror_outbound_message()` opens `get_db_context(project_id)` and then calls `record_outbound_message()`. - `ChannelHub._interim_feedback_task()` opens `get_db_context(project_id)` and then calls `record_outbound_message()`. - `record_outbound_message()` creates a shadow run if needed and inserts the outbound row with the same `project_id`. Read path: - `platform_operator_service.get_run_detail()` reads outbound rows for the selected `run_id`. Operator Console links from the runs list include `project_id`, so the DB context matches the selected tenant. ## Apply ```bash psql "$DATABASE_URL" -v ON_ERROR_STOP=1 \ -f scripts/ops/awooop-rls-canary-wave1-3-outbound-message.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 1000 rows. ## Verification Expected after apply: - no `app.project_id`: direct reads return no rows. - `app.project_id='awoooi'`: current outbound rows remain visible. - `app.project_id='ewoooc'`: no `awoooi` outbound rows are visible. - rollback-only insert under `app.project_id='awoooi'` is allowed and rolled back. - rollback-only insert under mismatched context is blocked by RLS. - `/api/v1/platform/runs/{run_id}/detail?project_id=awoooi` still returns the selected run timeline and outbound evidence. - global RLS preflight remains blocked only by later-wave tables. Production verification from 2026-05-12: - `/api/v1/health` from API pod: HTTP 200, `status=healthy`. - `/api/v1/platform/runs/d385b7fe-8666-58ec-9072-9ac917adb6cf/detail?project_id=awoooi`: - HTTP 200 - `counts.outbound_messages=1` - `scripts/ops/awooop-rls-preflight.sh --exact-counts`: - `PASS=7 WARN=1 BLOCKED=1` - `awooop_outbound_message rls=true force=true policies=1` - remaining blocker tables: `audit_logs`, `awooop_run_state`, `incidents`, `knowledge_entries`, `playbooks`. - Direct app-role behavior: - `outbound_no_context=0` - `outbound_awoooi_context=290` - `outbound_ewoooc_context=0` - `insert_awoooi_context_awoooi_row=allowed` - `insert_ewoooc_context_awoooi_row=blocked:InsufficientPrivilegeError` - `outbound_after_probe=290` ## Rollback ```bash psql "$DATABASE_URL" -v ON_ERROR_STOP=1 \ -f scripts/ops/awooop-rls-canary-wave1-3-outbound-message-rollback.sql ``` Rollback removes the Wave1.3 policy and disables RLS on `awooop_outbound_message`. It does not modify data.