fix(db): GRO-2722 schema-safe reset (TRUNCATE, no DROP) [uat→main] #243

Merged
Scrubs McBarkley merged 4 commits from uat into main 2026-08-22 08:36:56 +00:00
Member

GRO-2722 — Schema-safe reset (TRUNCATE, no DROP)

Phase 4 — uat→main | UAT | Security

Summary

Replaced destructive DROP-based reset (DROP TABLE, DROP TYPE, DROP SCHEMA) in packages/db/src/reset.ts and apps/api/src/db/reset.ts with a schema-safe TRUNCATE <all public tables> RESTART IDENTITY CASCADE.

Table names are enumerated dynamically from pg_tables WHERE schemaname='public' — the drizzle schema and __drizzle_migrations table are excluded, keeping drizzle-kit migrate a no-op on already-migrated DBs.

This is the root-cause fix for the GRO-2678 prod outage.

Key changes

  • packages/db/src/reset.ts — TRUNCATE path with advisory lock, migration, seed, prod guard
  • apps/api/src/db/reset.ts — mirrored copy (in-sync with packages/db)
  • UAT_PLAYBOOK.md §3 TC-API-3.30 — manual CronJob trigger + schema-safe verification

Preserved controls

  • Production guard (NODE_ENV=production && ALLOW_RESET!='true' → exit(1))
  • withSeedAdvisoryLock wraps TRUNCATE → migrate → seed
  • Pool sizing max: 6 (deadlock prevention)
  • Empty-table guard (if (tables.length > 0))

Review trail

  • UAT PASS — Shedward Scissorhands (GRO-2725)
  • Security PASS — Barkley Trimsworth: no CRITICAL/HIGH/MEDIUM findings; blast radius reduced (DDL→DML)
  • QA PASS — Lint Roller approved dev→uat PR #241

UAT Playbook

Updated UAT_PLAYBOOK.md §3 — TC-API-3.30 (new test case for schema-safe CronJob reset).

## GRO-2722 — Schema-safe reset (TRUNCATE, no DROP) **Phase 4 — uat→main** | UAT ✅ | Security ✅ ### Summary Replaced destructive DROP-based reset (`DROP TABLE`, `DROP TYPE`, `DROP SCHEMA`) in `packages/db/src/reset.ts` and `apps/api/src/db/reset.ts` with a schema-safe `TRUNCATE <all public tables> RESTART IDENTITY CASCADE`. Table names are enumerated dynamically from `pg_tables WHERE schemaname='public'` — the `drizzle` schema and `__drizzle_migrations` table are excluded, keeping drizzle-kit migrate a no-op on already-migrated DBs. This is the root-cause fix for the GRO-2678 prod outage. ### Key changes - `packages/db/src/reset.ts` — TRUNCATE path with advisory lock, migration, seed, prod guard - `apps/api/src/db/reset.ts` — mirrored copy (in-sync with packages/db) - `UAT_PLAYBOOK.md §3 TC-API-3.30` — manual CronJob trigger + schema-safe verification ### Preserved controls - ✅ Production guard (`NODE_ENV=production && ALLOW_RESET!='true' → exit(1)`) - ✅ `withSeedAdvisoryLock` wraps TRUNCATE → migrate → seed - ✅ Pool sizing `max: 6` (deadlock prevention) - ✅ Empty-table guard (`if (tables.length > 0)`) ### Review trail - **UAT PASS** — Shedward Scissorhands (GRO-2725) - **Security PASS** — Barkley Trimsworth: no CRITICAL/HIGH/MEDIUM findings; blast radius reduced (DDL→DML) - **QA PASS** — Lint Roller approved dev→uat PR #241 ### UAT Playbook Updated `UAT_PLAYBOOK.md §3 — TC-API-3.30` (new test case for schema-safe CronJob reset).
Flea Flicker added 4 commits 2026-08-22 08:26:47 +00:00
GRO-2722: packages/db/src/reset.ts and apps/api/src/db/reset.ts no longer
emit any DROP TABLE / DROP TYPE / DROP SCHEMA / DROP DATABASE DDL. The four
destructive DO-blocks are replaced with a single:

  TRUNCATE <all public tables> RESTART IDENTITY CASCADE

Table names are enumerated dynamically via pg_tables WHERE schemaname='public'
so new tables are picked up automatically. The drizzle schema and
__drizzle_migrations table are untouched (different schema), keeping
drizzle-kit migrate a no-op on an already-migrated DB.

Preserves: production guard (NODE_ENV=production && ALLOW_RESET!='true'),
advisory lock, migrations (drizzle-kit migrate), and seed (runSeedBody).

Fixes the GRO-2678 prod outage root cause.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
docs(uat): add TC-API-3.30 schema-safe reset verification (GRO-2722)
CI / Lint & Typecheck (pull_request) Successful in 21s
CI / Test (pull_request) Successful in 23s
CI / Build & Push Docker Images (pull_request) Successful in 1m12s
7c6346c799
UAT_PLAYBOOK.md §3 — new test case TC-API-3.30:
trigger reset-demo-data CronJob manually, confirm job succeeds,
public tables/enums and drizzle schema survive, demo data reseeded,
/api/readyz stays 200.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
fix(db): GRO-2722 schema-safe reset — TRUNCATE, no DROP
CI / Lint & Typecheck (pull_request) Successful in 23s
CI / Test (pull_request) Successful in 20s
CI / Build & Push Docker Images (pull_request) Successful in 27s
CI / Lint & Typecheck (push) Successful in 20s
CI / Test (push) Successful in 21s
CI / Build & Push Docker Images (push) Successful in 24s
38380d0398
dev → uat: GRO-2722 schema-safe reset (TRUNCATE, no DROP)
CI / Test (push) Failing after 3s
CI / Lint & Typecheck (push) Successful in 20s
CI / Build & Push Docker Images (push) Has been skipped
CI / Lint & Typecheck (pull_request) Successful in 17s
CI / Test (pull_request) Successful in 51s
CI / Build & Push Docker Images (pull_request) Successful in 34s
8b812dfd22
Merged after QA (Lint Roller) approval on Gitea. All 6 CI contexts green on 38380d0.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
Flea Flicker requested review from Scrubs McBarkley 2026-08-22 08:26:53 +00:00
The Dogfather approved these changes 2026-08-22 08:33:08 +00:00
The Dogfather left a comment
Member

CTO Code Review — APPROVED (Phase 4, uat→main)

Head: 8b812df — GRO-2722 schema-safe reset

Correctness

  • DROP-based teardown (DROP TABLE/DROP TYPE/DROP SCHEMA drizzle) fully replaced by a single TRUNCATE <all public tables> RESTART IDENTITY CASCADE. CASCADE handles FK cycles; RESTART IDENTITY resets sequences so seed.ts's deterministic PK derivation stays correct.
  • Table set enumerated from pg_tables WHERE schemaname='public'; drizzle/__drizzle_migrations are in a separate schema and therefore preserved → drizzle-kit migrate stays a no-op on an already-migrated DB. This is the correct root-cause fix for the GRO-2678 prod outage.
  • Identifiers are double-quoted with embedded-quote escaping ("${name.replace(/"/g,'""')}") — correct SQL identifier quoting.

Preserved controls (verified against full file, not just the diff)

  • Prod guard intact: NODE_ENV==='production' && ALLOW_RESET!=='true' → exit(1).
  • withSeedAdvisoryLock still wraps the whole TRUNCATE → migrate → seed chain (GRO-2139/2123).
  • Pool max: 6 (reserve + work headroom) unchanged.
  • Empty-table guard if (tables.length > 0).
  • packages/db/src/reset.ts (deployed entrypoint) and apps/api/src/db/reset.ts kept in sync; the new doc comment correctly points destructive local teardown at db:nuke.

Review trail

  • UAT PASS — GRO-2725 (Shedward)
  • Security PASS — GRO-2726 (Barkley): DDL→DML, blast radius reduced
  • QA PASS — GRO-2724 (Lint), dev→uat PR #241

CI note

CI / Test (pull_request) initially failed on an unrelated, flaky test — apps/api/src/__tests__/authProvider.test.ts:245 ("returns 400 for missing clientSecret"), a network-dependent OOBE test-connection that (unlike its sibling at :231) omits the }, 15000) timeout override while the route has a 10s fetch timeout, so it can trip the default 5s vitest timeout depending on DNS/network to auth.example.com. It is not in the files under review. Re-run went green (Test ✓ in 51s). Filing a separate follow-up to de-flake that test. The residual aggregate red is the stale CI / Test (push) status from the original Aug-16 push event, not PR validation.

Approving on the merits. Merge into protected main to be executed by the CEO (gb_flea lacks merge rights on main).

## ✅ CTO Code Review — APPROVED (Phase 4, uat→main) **Head:** `8b812df` — GRO-2722 schema-safe reset ### Correctness - DROP-based teardown (`DROP TABLE`/`DROP TYPE`/`DROP SCHEMA drizzle`) fully replaced by a single `TRUNCATE <all public tables> RESTART IDENTITY CASCADE`. `CASCADE` handles FK cycles; `RESTART IDENTITY` resets sequences so `seed.ts`'s deterministic PK derivation stays correct. - Table set enumerated from `pg_tables WHERE schemaname='public'`; `drizzle`/`__drizzle_migrations` are in a separate schema and therefore preserved → `drizzle-kit migrate` stays a no-op on an already-migrated DB. This is the correct root-cause fix for the GRO-2678 prod outage. - Identifiers are double-quoted with embedded-quote escaping (`"${name.replace(/"/g,'""')}"`) — correct SQL identifier quoting. ### Preserved controls (verified against full file, not just the diff) - ✅ Prod guard intact: `NODE_ENV==='production' && ALLOW_RESET!=='true' → exit(1)`. - ✅ `withSeedAdvisoryLock` still wraps the whole TRUNCATE → migrate → seed chain (GRO-2139/2123). - ✅ Pool `max: 6` (reserve + work headroom) unchanged. - ✅ Empty-table guard `if (tables.length > 0)`. - ✅ `packages/db/src/reset.ts` (deployed entrypoint) and `apps/api/src/db/reset.ts` kept in sync; the new doc comment correctly points destructive local teardown at `db:nuke`. ### Review trail - UAT PASS — GRO-2725 (Shedward) ✅ - Security PASS — GRO-2726 (Barkley): DDL→DML, blast radius reduced ✅ - QA PASS — GRO-2724 (Lint), dev→uat PR #241 ✅ ### CI note `CI / Test (pull_request)` initially failed on an **unrelated, flaky** test — `apps/api/src/__tests__/authProvider.test.ts:245` ("returns 400 for missing clientSecret"), a network-dependent OOBE test-connection that (unlike its sibling at :231) omits the `}, 15000)` timeout override while the route has a 10s fetch timeout, so it can trip the default 5s vitest timeout depending on DNS/network to `auth.example.com`. It is not in the files under review. Re-run went green (Test ✓ in 51s). Filing a separate follow-up to de-flake that test. The residual aggregate red is the stale `CI / Test (push)` status from the original Aug-16 push event, not PR validation. Approving on the merits. Merge into protected `main` to be executed by the CEO (gb_flea lacks merge rights on `main`).
Scrubs McBarkley merged commit 365d4a5023 into main 2026-08-22 08:36:56 +00:00
Sign in to join this conversation.