# Phase 15: Cutover - Discussion Log > **Audit trail only.** Do not use as input to planning, research, or execution agents. > Decisions are captured in CONTEXT.md. This log preserves the alternatives considered. **Date:** 2026-10-04 **Phase:** 15-cutover **Areas discussed:** MariaDB → Postgres data move, production switch and rollback, the "all routes green" gate, daily-use acceptance Before the discussion started, the scout found that production runs on MariaDB (with a Redis queue), not Postgres. It also found that the parity manifest holds 175 routes (172 ported, 3 pending), not 154, and that Phases 14.1 and 12.1 have not started. --- ## MariaDB → Postgres data move | Option | Description | Selected | |--------|-------------|----------| | Go import command | Reads MariaDB via DSN and writes into the migrated schema. Re-encrypts inline, testable with testcontainers, adds a MySQL driver | ✓ | | pgloader + Go fix-up pass | Staging schema copy, then a Go transform. Needs an external tool and runs in two steps | | | mariadb-dump → SQL rewrite | Dump, rewrite the dialect, load. The most fragile option | | | Option | Description | Selected | |--------|-------------|----------| | Framework capability + per-plugin mappers | Generic `summer import:winter` runner, plus `HasWinterImport` per plugin | ✓ | | App-only command in fonoteka.go | One-off command that knows every table | | **Not carried over (multi-select):** transient queue/job state ✓, sessions/caches/JWT blacklist ✓, Typesense index (reindex instead) ✓, old notifications ✓ | Option | Description | Selected | |--------|-------------|----------| | Rehearse on a prod dump + verify | Counts, checksums, decrypt round-trip and file existence, then a timed dress rehearsal | ✓ | | Rehearse + replay Nuxt/MCP flows on imported data | Adds a manual click-through before the switch | | | Unit/integration tests only | The first real-data run is the cutover itself | | --- ## Production switch and rollback | Option | Description | Selected | |--------|-------------|----------| | Freeze, import, swap upstream | Maintenance page, final dump, import, then repoint the nginx API locations on the same origin | ✓ | | Staging subdomain first, then swap | Needs a second Nuxt build | | | Shadow/mirror then swap | nginx mirror, which only validates GET requests | | | Option | Description | Selected | |--------|-------------|----------| | Keep PHP+MariaDB frozen; rollback window, no reverse sync | Rollback swaps nginx back; Go-side writes are lost | ✓ | | Reverse export Go→MariaDB | Doubles the import work | | | No rollback path | Fix forward only | | | Option | Description | Selected | |--------|-------------|----------| | Supervisor, serve + worker as two programs | Matches the 11.2 deploy and `albumy-queue` | ✓ | | Supervisor, single process | Simpler, but a deploy restarts jobs mid-flight | | | systemd units | A new pattern on rome | | | Option | Description | Selected | |--------|-------------|----------| | User runs a runbook; Claude writes it | CUTOVER.md plus per-step scripts, with output pasted back as evidence | ✓ | | Claude runs it over SSH | Needs SSH access to rome | | --- ## "All routes green" gate | Option | Description | Selected | |--------|-------------|----------| | Every manifest route, zero pending | 175 entries; API-09/QA-05 reworded | ✓ | | Exactly the 154 fonoteka routes.php routes | The literal requirement | | | Option | Description | Selected | |--------|-------------|----------| | Existing corpus + a fresh PHP re-record | Re-record before the freeze, with a drift report | ✓ | | Existing committed corpus only | Could miss PHP changes made after recording | | | Option | Description | Selected | |--------|-------------|----------| | Hard prerequisites (14.1, 12.1, Phase 14 UAT) | A preflight gate in the first plan; 12.1 is added to depends-on | ✓ | | Only 14.1 is a prerequisite | 12.1 can land after cutover | | | Fold the remaining work into Phase 15 | 14.1 would be removed | | | Option | Description | Selected | |--------|-------------|----------| | Yes, a read-only smoke run on the rehearsal import | PHP on MariaDB against Go on imported Postgres from the same dump, diffed | ✓ | | No, the seeded corpus is enough | | | --- ## Daily-use acceptance | Option | Description | Selected | |--------|-------------|----------| | 7 days of normal household use | The scheduled jobs each run at least once, plus a CSV import and a Discogs match | ✓ | | 48 hours | | | | 14 days | | | **Scripted session (multi-select):** Nuxt core session ✓, MCP + OAuth ✓, Sharing & invitations (not selected), Admin & ops (not selected) | Option | Description | Selected | |--------|-------------|----------| | Stop & archive, delete later | | | | Leave PHP running but unrouted | Keep it up for a late rollback | ✓ | | Full removal in-phase | | | | Option | Description | Selected | |--------|-------------|----------| | Yes, a pg_dump timer replaces the MariaDB one | Installed at the swap, with a restore drill | ✓ | | No, handle separately | | | **PHP scheduler/queue worker follow-up:** the user wrote: "I'll manage php sign off / stopping cron / workers, outside the plans, don't mind it." --- ## Claude's Discretion - The runner's internals (batching, FK ordering, the coercion table, the interface naming) and the verify-report format. - How the runbook is split into scripts and how evidence is laid out; the supervisor program names. - Whether the smoke diff reuses tide replay or adds a new `parity:*` subcommand. ## Deferred Ideas - The WinterCMS vs SummerCMS benchmark (existing todo) can reuse the rehearsal dump and the dual-backend setup. - PHP/MariaDB decommission: the user handles it outside the plans.