Files
awoooi/docs/runbooks/AWOOOP-RLS-CANARY-WAVE1-3.md
Your Name de16c88418
All checks were successful
Code Review / ai-code-review (push) Successful in 11s
chore(rls): 套用 outbound message canary
2026-05-12 21:55:23 +08:00

3.4 KiB

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:

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

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

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.