diff --git a/docs/audit/phase3b-preflight/PHASE3B_GATE_EXECUTION_LOG.md b/docs/audit/phase3b-preflight/PHASE3B_GATE_EXECUTION_LOG.md new file mode 100644 index 00000000..2de85a9b --- /dev/null +++ b/docs/audit/phase3b-preflight/PHASE3B_GATE_EXECUTION_LOG.md @@ -0,0 +1,132 @@ +# Phase 3B — Safety Gate Execution Log (Evidence Record) + +> **EVIDENCE / DOCUMENTATION ONLY.** Records the results of executing the Phase 3B safety +> gates against production. **No migration, schema, data, restore, deploy, or credential +> rotation has been performed.** Production remains untouched. This log accompanies the +> [`PHASE3B_SAFETY_GATE_RUNBOOK`](./PHASE3B_SAFETY_GATE_RUNBOOK.md) and records what +> actually happened, plus corrections discovered during execution. + +Execution date: **2026-07-05** · Operator: platform operator (Railway prod admin) · +Assisted: read-only analysis. + +--- + +## Status summary + +| Gate | Scope | Result | +|---|---|---| +| **Gate 1** — schema-only snapshot | Read-only `pg_dump --schema-only` of production | ✅ **PASS** | +| **Gate 2 (inventory)** — backup inventory | Dashboard inventory + read-only DB facts + manual backup | ✅ **PASS** | +| Gate 2 (restore validation) | Throwaway restore of a backup | ⏳ pending approval | +| Gate 3 — PITR verification | Restore-to-timestamp drill | ⏳ pending approval | +| Gate 4 — ephemeral PG setup | Disposable PG for migration testing | ⏳ pending (see correction #1) | +| Credential rotation | Rotate screenshot-exposed staging + prod creds | ⏳ planned, after Gate 2 restore | + +**Production state:** untouched · no migrations · no restore · no deploy · no rotation. + +--- + +## Gate 1 — Schema-only snapshot — PASS + +| Evidence | Value | +|---|---| +| Production server version | **PostgreSQL 18.3** | +| Dump method | Docker `postgres:18-alpine` `pg_dump` over Railway public TCP proxy | +| Dump type | `--schema-only --no-owner --no-privileges` (DDL only) | +| File size | 285,166 bytes | +| `CREATE TABLE` count in dump | **125** | +| Data rows exported | **0** (schema only, verified) | +| `pgcrypto` extension | present | +| SHA-256 | recorded (starts `9BE513…`, ends `…C36A62`) | +| Artifact location | operator-local secure ops artifact (NOT committed to the repo) | + +**Read-only:** the dump was taken over the public proxy with a PG18 client; production +received only catalog reads. No writes. + +### 132 → 125 reconciliation (resolved — zero real delta) + +The earlier "132 baseline" was a **text/statement count**, never a table count: + +``` +132 "CREATE TABLE IF NOT EXISTS" text occurrences in apps/backend/src/db.ts + −1 a CODE COMMENT at db.ts:2202 ("...CREATE TABLE IF NOT EXISTS skips...") → 131 real DDL statements + −6 duplicate CREATE TABLE IF NOT EXISTS statements for 5 tables → 125 unique real tables + (security_events, event_timeline, investigation_sessions, + correlated_incidents, connector_approvals — repeated DDL) +───── +125 unique source tables == 125 CREATE TABLE in production dump ✅ EXACT MATCH +``` + +- No other deployed service issues `CREATE TABLE` (backend monolith owns all inline DDL). +- **Real delta = 0.** No production table is missing; production materializes all 125 + source-defined tables. +- **Correction to the freeze baseline figure:** the freeze CI guard (design in + `07_FREEZE_ENFORCEMENT_CI_DESIGN.md`) counts `CREATE TABLE IF NOT EXISTS` *statements* + (132) as its ceiling — that remains a valid ceiling, but the **true unique-table + baseline is 125**. Both numbers should be documented when the guard is wired. + +--- + +## Gate 2 (inventory) — Backup inventory — PASS + +| Item | Finding | +|---|---| +| Automated backups enabled | ✅ yes — **daily** schedule | +| Manual / on-demand backup | ✅ created successfully during this gate | +| Backup size | **1.12 GB** | +| Engine version of backups | PostgreSQL **18.3** (matches prod) | +| WAL level | **`replica`** | +| Operator credentials after gate | removed from local shell | + +**Read-only + one manual backup:** taking an on-demand backup is a provider-managed, +non-destructive operation (no schema/data change). No restore was run. + +### Implication for later gates +- **WAL level = `replica`** → continuous archiving / PITR is **technically feasible** at + the engine level. Confirms Gate 3 (PITR) is worth attempting; exact PITR availability + still depends on the Railway plan's continuous-backup feature (to verify in Gate 3). +- **Backup 1.12 GB** → informs restore-duration (RTO) expectations for the pending + throwaway-restore step and for sizing the eventual migration maintenance window. + +--- + +## Corrections discovered during execution (carry forward) + +### Correction #1 — Production is PostgreSQL 18.3, not 16 (Gate 4 impact) — **P1** +The runbook (`PHASE3B_SAFETY_GATE_RUNBOOK.md` Gate 4), the ephemeral-env design +(`04_EPHEMERAL_PG_TEST_ENV_DESIGN.md`), and CI (`.github/workflows/pr-check.yml:94-132`) +assume **`postgres:16-alpine`**. Production is **18.3**. Migration testing must run on +**PostgreSQL 18** to match prod fidelity. +- **Action (later, separately approved):** update the ephemeral test env to `postgres:18` + (Gate 4) and reconcile the CI service image. Also note the local client-tools + requirement: **`pg_dump`/`psql` must be v18+** (v16/17 clients cannot dump an 18 server — + this blocked Gate 1 until the operator used the `postgres:18-alpine` Docker client). + +### Correction #2 — Freeze baseline is 125 unique tables (132 = statement count) — **P2** +See the 132→125 reconciliation above. When the freeze guard is wired, document **both**: +the statement-count ceiling (132) and the unique-table baseline (125). + +--- + +## Pending — require explicit approval before execution + +1. **Gate 2 restore validation** — restore the 1.12 GB backup into a **throwaway** target + (fresh Docker `postgres:18-alpine` or a new Railway service), verify table count = + **125**, core rows non-zero, invalid-constraints = 0, and **measure restore duration**. + *No production impact — throwaway only.* +2. **Credential rotation** — rotate the screenshot-exposed **staging + production** Postgres + credentials as a deliberate, maintenance-window step (restarts DB; backend must + reconnect). Planned **after** Gate 2 restore. +3. **Gate 3 PITR** — confirm continuous-backup availability and run a restore-to-timestamp + drill on a throwaway (WAL level `replica` makes this feasible). +4. **Gate 4 ephemeral PG** — stand up `postgres:18` (per Correction #1); **stop before** + `db:migrate`/`drizzle-kit generate`. + +## Guardrails / mode + +- Mode: **documentation + validation only.** +- ❌ No migrations · ❌ No deploy · ❌ No restore (yet) · ❌ No schema/data change · + ❌ No credential rotation (yet) · ❌ No Phase 3B implementation. +- 🔒 Phase 3B implementation (author `0000_baseline`, generate journal, wire freeze guard) + remains **LOCKED** until all four gates are GREEN and explicitly approved. +- 📄 Gate 1 schema dump is a **local secure ops artifact** — never committed to the repo.