4.6 KiB
AwoooP RLS Preflight Runbook
Purpose: verify whether production is ready for PostgreSQL Row-Level Security without enabling RLS or changing data.
Command
Default path runs the probe inside the production API pod through the 120
control-plane host. DATABASE_URL stays inside Kubernetes and is not printed.
bash scripts/ops/awooop-rls-preflight.sh
Before enabling RLS, run exact backfill counts:
bash scripts/ops/awooop-rls-preflight.sh --exact-counts
Useful variants:
bash scripts/ops/awooop-rls-preflight.sh --json
bash scripts/ops/awooop-rls-preflight.sh --local
AWOOOP_RLS_SSH_TARGET=wooo@192.168.0.120 bash scripts/ops/awooop-rls-preflight.sh
Exit code 2 means the gate is blocked and RLS must not be enabled yet.
Exact Count Scope
After any target table has RLS enabled, --exact-counts runs as the production
app DB user and is filtered by the current app.project_id. The output marks
these rows with:
scope=rls_filtered project_context=...
Treat those counts as tenant-visible evidence, not global row counts. Use a reviewed postgres/operator path for global counts after RLS is enabled.
2026-05-12 Initial Production Result
--exact-counts returned:
PASS current_role_rls_enforced: current DB user isawoooi, not superuser and notBYPASSRLS.PASS project_context_set_config:set_config('app.project_id', 'awoooi', TRUE)works in the API pod.BLOCKED required_roles:awooop_app,awooop_platform_admin, andawooop_migrationdo not exist.WARN role_bootstrap_authority: current API DB userawoooiis notCREATEROLE; role bootstrap requirespostgresor aCREATEROLEoperator.WARN app_role_membership:awooop_appis missing, so membership cannot be evaluated yet.PASS project_id_columns: every existing target table hasproject_id.BLOCKED rls_enabled_forced_policy: existing target tables are not yet RLS enabled, forced, or policied.PASS fail_open_policies: production DB currently has no fail-open policy expressions.PASS project_id_backfill: exact counts found zeroNULL project_idrows in counted target tables.
Current blocker summary:
PASS=5 WARN=2 BLOCKED=2
Important exact counts from the same run:
| Table | Rows | NULL project_id |
|---|---|---|
audit_logs |
686 | 0 |
awooop_mcp_tool_registry |
4 | 0 |
awooop_outbound_message |
235 | 0 |
awooop_projects |
2 | 0 |
awooop_run_state |
113 | 0 |
incidents |
1518 | 0 |
knowledge_entries |
2102 | 0 |
playbooks |
220 | 0 |
2026-05-12 Role Bootstrap Applied
At 19:33 CST, the manual role bootstrap was applied through the host
PostgreSQL socket as postgres. It did not enable RLS policies.
Post-bootstrap --exact-counts returned:
PASS current_role_rls_enforced: current DB user is stillawoooi, not superuser and notBYPASSRLS.PASS project_context_set_config:set_config('app.project_id', 'awoooi', TRUE)works.PASS required_roles:awooop_app,awooop_platform_admin, andawooop_migrationnow exist.PASS app_role_membership: current API DB user is a member ofawooop_app.PASS project_id_columns: every existing target table hasproject_id.BLOCKED rls_enabled_forced_policy: target tables are still not RLS enabled, forced, or policied.PASS fail_open_policies: no fail-open policy expressions detected.PASS project_id_backfill: exact counts found zeroNULL project_idrows in counted target tables.
Current blocker summary:
PASS=7 WARN=0 BLOCKED=1
Updated exact counts:
| Table | Rows | NULL project_id |
|---|---|---|
audit_logs |
686 | 0 |
awooop_mcp_tool_registry |
4 | 0 |
awooop_outbound_message |
248 | 0 |
awooop_projects |
2 | 0 |
awooop_run_state |
126 | 0 |
incidents |
1524 | 0 |
knowledge_entries |
2103 | 0 |
playbooks |
220 | 0 |
Remediation Order
- Verify all DB access paths use
get_db()/get_db_context()or otherwise setapp.project_idbefore queries. - Apply policies first in staging or a canary DB.
- In production, enable one batch at a time.
- After each batch, run:
bash scripts/ops/awooop-rls-preflight.sh --exact-counts
- Validate AwoooP Runs, Approvals, Monitoring, Tickets, Cost, alert ingestion, background workers, and TelegramGateway mirror paths.
Do Not
- Do not enable all policies in production before the role path is decided.
- Do not rely on fail-open
IS NULLor empty-string policies as the target state. - Do not run destructive rollback SQL unless the incident commander explicitly approves it.