# AwoooP RLS Manual Script Review

Manual scripts are not API runtime, but they can still break after fail-closed
RLS if they connect directly with `DATABASE_URL` and do not set
`app.project_id`.

Run:

```bash
python3 scripts/ops/awooop-rls-manual-script-audit.py --show-pass
```

Use strict mode for CI or pre-apply review:

```bash
python3 scripts/ops/awooop-rls-manual-script-audit.py --strict-review
```

## 2026-05-12 Result

```text
AwoooP RLS manual script audit: BLOCKED=0 REVIEW=5 PASS=13
```

`BLOCKED=0` means no hardcoded PostgreSQL URL with inline credentials was found
in the scanned manual scripts.

`REVIEW=5` are intentional operator paths:

- `apps/api/scripts/awooop_phase1_batch1_backfill.py`: RLS/project_id bootstrap
  backfill; use migration/operator role.
- `apps/api/scripts/run_migration.py`: DDL migration script; use
  migration/operator role.
- `scripts/ops/awooop-rls-role-bootstrap.sql`: role bootstrap; requires
  postgres/CREATEROLE operator.
- `scripts/ops/awooop_rls_preflight.py`: read-only preflight inside API pod.
- `scripts/sync_dev_db.py`: dev schema sync; requires `DEV_DATABASE_URL` and
  must not target production.

The common tenant-data manual scripts now set `app.project_id` or use
`get_db_context()`.

## Operator Rule

- Direct tenant table access must set `app.project_id` on every connection, or
  use an approved migration/operator role with BYPASSRLS.
- Direct `asyncpg` scripts should use session-level context:

```python
await conn.execute("SELECT set_config('app.project_id', $1, FALSE)", project_id)
```

- SQLAlchemy runtime/service code should use `get_db_context()` or `get_db()`.
- Do not add PostgreSQL URLs with inline credentials to scripts, comments, or
  examples.
