109 lines
3.4 KiB
Markdown
109 lines
3.4 KiB
Markdown
# 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.
|