docs(adr): ADR-0003 reset-demo-data schema-safe reset on prod, supersedes GRO-2705 removal (GRO-2721)

2026-08-16 13:07:30 +00:00
parent 74203073f4
commit 4416732e11
@@ -0,0 +1,44 @@
# ADR-0003 — `reset-demo-data` on prod: schema-safe reset (supersedes GRO-2705 removal)
- **Status:** Accepted (2026-08-16)
- **Context tickets:** GRO-2721 (this), GRO-2720 (CEO correction), GRO-2705 / infra PR #735 (superseded), GRO-2678 (original outage). Related: [ADR-0002](/groombook/org/wiki/ADR-0002+API+DB-health+endpoint+strategy.-).
- **Decider:** CTO (Dogfather).
## Context
`demo.groombook.dev` is a **demo site**; it relies on the `reset-demo-data` CronJob to periodically restore demo state. That CronJob runs the `git.farh.net/groombook/reset` image, whose entrypoint is `pnpm --filter @groombook/db reset`:
```
node ./scripts/wait-for-db.mjs && tsx src/reset.ts && drizzle-kit migrate && tsx src/seed.ts
```
`packages/db/src/reset.ts` was written **local-dev-only** and is destructive: `DROP TABLE ... CASCADE` (all public tables), `DROP TYPE` (all enums), `DROP TABLE drizzle.__drizzle_migrations`, and **`DROP SCHEMA IF EXISTS drizzle CASCADE`**. In **GRO-2678** it dropped the PROD schema; the following `drizzle-kit migrate` no-op'd (journal-driven, GRO-2672 class bug) → empty DB → outage.
**GRO-2705 (PR #735)** removed the CronJob from the prod overlay entirely. That was a correct *interim* safety measure but the wrong *permanent* fix — it left the demo site with no reset path.
## Decision
Re-add `reset-demo-data` to prod, but **only after making the reset path schema-safe**:
- `reset.ts` resets **data only** via `TRUNCATE ... RESTART IDENTITY CASCADE` of public tables, then reseeds.
- It issues **no** `DROP TABLE` / `DROP TYPE` / `DROP SCHEMA` / `DROP DATABASE` in any schema. The `drizzle` schema + migrations table are left intact, so `drizzle-kit migrate` stays a no-op on an already-migrated DB and the schema is **never** torn down — even if a later step fails.
- Production guard (`NODE_ENV=production && ALLOW_RESET!=true` → exit) retained.
### Sequencing (hard dependency)
Re-adding the CronJob against the current/`:latest` destructive image would reproduce GRO-2678. Therefore:
1. **GRO-2722** (api) — make `packages/db/src/reset.ts` schema-safe. Full SDLC; UAT must actually run the uat reset CronJob and prove the schema survives. Ships a new `reset:<CalVer>-<sha>` image.
2. **GRO-2723** (infra) — re-add `reset-cronjob.yaml` + kustomization entry to `apps/overlays/prod`, image pinned to GRO-2722's tag (**never `:latest`**). Blocked by GRO-2722.
Prod DB creds for the CronJob: secret `groombook-postgres-credentials`, key `uri`.
## Consequences
- Demo site regains automated reset with no schema-drop blast radius.
- Reset image path is no longer "local-dev-only"; the destructive full-drop behavior, if still wanted for local dev, moves to a separate `db:nuke` script.
- Overlay pins the reset image to CalVer (per DevOps no-`:latest`, no-image-automation policy); future reset changes require a new pin.
## Guardrail
Any job/script that runs against the prod `groombook` schema must be **additive or truncate-only**. `DROP SCHEMA` / `DROP TABLE` / `DROP DATABASE` against prod is prohibited outside an explicit, board-approved, backup-gated migration.