# Stream K7: the launch path (staging to production)
(written 2026-09-16 Wed, from the accepted plan W17 and section 2; runs beside K6, after K1's migration id is known; rian runs the push itself on Sunday night or Monday)

**Runs:** Fri 18 Sep to Sat 19, after K1 (it needs the final migration chain) and in parallel with
K6. Touches nothing in the app.

## Goal
One scripted, rehearsed step replaces production's database with staging's for the soft launch,
errors fatal, sessions revoked, counts compared, and one checklist rian follows from the last
staging collection to the go-live flip; production takes the whole migration chain from 0.35.0 in
one pass with no `propose --file` line in it.

## What already exists, so you build on it
- `deploy/production.sh` (rsync over Tailscale, build, up, health) with `--seed-db` that runs
  `pg_restore --clean --if-exists ... || true` (errors swallowed; assumes an empty database).
- `main/scripts/staging-refresh.py` drops and recreates the `public` schema before a restore, and
  refuses to discard catalogue decisions without a flag: the shape K7 mirrors in the other direction.
- `RUNBOOK.md`'s Deploy, Production and Backups sections; the after-deploy blocks per migration;
  `deploy/production.sh` refuses while a collection runs.
- The 10 Sep egress test: every retailer answers both machines the same.

## Tasks, in order (ids on /plan; commit prefix `K7:`)
1. **K7.1 `deploy/production.sh --replace-db <dump>`.** In order, each step fatal on error: dump
   production first to `backups/production/pre-replace-<date>.dump` (the rollback); rsync and build
   the code; stop the app container; drop and recreate the `public` schema; `pg_restore --no-owner
   --no-privileges` with errors fatal; `alembic current` must equal the dump's head (printed by the
   script from the dump before restoring); revoke every session and every unused welcome or reset
   token (`app.cli sessions revoke --all`, `accounts tokens purge`); compare row counts per table
   between the dump (`pg_restore --list` plus a count query on staging at dump time, written to a
   sidecar file) and production, refusing on a mismatch; print the per-source settings (enabled,
   delay, identity mode, permission record) for rian to confirm against a printed staging list;
   start; health check prints the version; `srv-gw security-audit` reminder. `--seed-db` is
   removed.
2. **K7.2 The consolidated chain.** `RUNBOOK.md` Production: the migration chain from 0.35.0 to the
   branch's head in one `alembic upgrade head` (it is the restore that carries the data; the chain
   matters for the rollback path only), and the after-deploy commands that must run on production
   when it is NOT a copy (the rollback case): every block from 0.35.0 in order, with the three
   `propose --file` lines absent and the warning beside them.
3. **K7.3 The launch checklist**, one page in `RUNBOOK.md`, each line a command or a check with
   its expected output: freeze collections on both machines; the last staging collection and its
   verify; the review pass and approvals complete for the brands in scope; `main/check.sh` green on
   the merged branch; `srv-gw deploy` on staging and the after-deploy blocks (K1, K2, K3's) run
   there; the staging dump; `--replace-db`; the counts; sessions revoked; sources confirmed;
   `SITE_ACCESS` flip; the unauthenticated route sweep (W-readiness); the sitemap lists only
   approved pages (K6); IndexNow key present; the checklist's second pass; the announcement to
   Adam. And the rollback line: restore `pre-replace-<date>.dump`, redeploy the previous tag.
4. **K7.4 Rehearsal.** On `dfp-devdb`: create `dfp_prod_like` from production's newest dump
   (`backups/production/`), run the replace step's database half against it from a fresh staging
   dump with `--db-container dfp-devdb` and a `--dry-run` that prints every command; then for real;
   counts compared; sessions revoked; a deliberate mismatch (drop one staging table in the copy)
   proves the refusal.
5. **K7.5 Tests.** A shell test of the script's argument handling and its refusal paths where
   they can run without the droplet; the RUNBOOK checklist referenced from `brief.md`'s NOW block by
   the planning session.

## Owns
`deploy/production.sh`, `deploy/` helpers, `RUNBOOK.md`'s Deploy, Production, Backups and the new
Launch checklist sections, `main/scripts/` for any count helper, the rehearsal log under
`.logs/runs/`.

## Must not touch
The app, the schema, the SPA, `.app.env`, the live databases (the rehearsal is on `dfp-devdb`
only; production is never contacted by K7; rian runs the push).

## Rehearsal and acceptance
The K7.4 rehearsal log with the counts, the refusal proof and the timings; `main/check.sh` green.

## Rollback
The production dump the script takes first, and the previous code tag; both named on the
checklist's rollback line.

## Hand-back
Handoff (≤25 lines) with the checklist's location and the rehearsal numbers; "K7 green and ready;
rian runs the push from the checklist".
