Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
132 changes: 132 additions & 0 deletions docs/audit/phase3b-preflight/PHASE3B_GATE_EXECUTION_LOG.md
Original file line number Diff line number Diff line change
@@ -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.
Loading