Move the Writer, Not the File
On 2026-09-27 Coop took ownership of a ledger that had been written by many hands for months. The first decision was what not to change. The database stayed at its old path, on purpose: the architecture says keeping it there avoids combining a path migration with a writer migration, and preserves the established backup location. The pathname confers no authority. What moved was who may write, and everything in this track exists to make that move safe: the lock and the fence, the owner file, the keyed commands, the feed.
The Sequence
- Record where you stand. The intended source commits and the rehearsal receipts were written down first. Running Pippa turns were left alone, because the compatibility client looks for the owner file on every call.
- Fence, back up, publish.
prepare()takes the engine lock itself, so no engine can be running. It opensBEGIN IMMEDIATE, which waits for in-flight legacy transactions, and installs maintenance fences,coop_transfer_*BEFORE triggers on the seven legacy tables. From that moment every legacy write getsCoop ownership transfer in progress; retry through the owner. Then it takes the final backup with SQLite's online backup API from a read-only source, checks integrity and foreign keys (a failure leaves everything fenced), and publishes the owner file atomically: temp file, fsync, rename, fsync of the directory. If an owner file already exists, it refuses to start at all. - Start the one writer, and let it check its inheritance. The preview service was retired and the real service started. The engine installs its additive schema and its own connection fences first. Then, while the maintenance fences still stand, it compares the backup with the live store: for each legacy table, a SHA-256 over every row with its storage types, the row count and the first and last id, plus the autoincrement sequences and both integrity checks. Only on exact parity does it record
migration_verifiedand drop the maintenance fences. On any mismatch it stops withMigration parity failed; writes remain fenced. - Verify the boring things. Both listeners up, one process, the
migration_verifiedreceipt stored, and an old SQLite connection's write refused. Takes that predated the engine became visible obligations for the chief to review, never ownership inferred from a shared actor name. - Canaries. A Codex canary and a Claude canary each acknowledged an invitation before an atomic take, read a file they had never seen, and reported; both obligations resolved to done. Almanac's existing bridge created, took, replied to and completed a third. Vestige's adapters read the transferred store. Then a graceful restart of the service preserved the epoch, reports, bindings and pending obligations.
- Replicas. A fresh, verified snapshot went to eight fleet Macs. Each one refused a direct write and had no owner receipt.
Notice the overlap in step 3. The connection fences go in before the maintenance fences come out, so there is no instant, however short, when an outside write could land. And notice why the replicas refuse writes with nothing installed on them: a replica has no owner file, so the compatibility client opens SQLite directly, reads work fine, and every write hits the copied coop_fence_* triggers, which call a function no connection on that Mac defines. Lesson 1's missing function, doing its job eight times over.
Recovery That Never Restores Stale
Every migration needs a way back. The interesting thing about Coop's is what it refuses to do. restore_legacy_before_writes() takes the engine lock (so the writer must be stopped first) and compares the pre-transfer backup with the live store. If they differ, it stops: New canonical writes exist: use current-state service rollback. If any outcome obligation is still pending, it stops: Pending outcomes require the current-state engine. Only when no legacy record has changed since the transfer, and no outcome is owed, does it act. The comparison covers the seven legacy tables and nothing else, so writes confined to the engine's own additive tables (a session enrollment, say) do not block it; those tables and their audit state are kept. And even then it acts in place: it drops the triggers and retires the owner file by renaming it. It never copies the backup over the database. The backup is the yardstick that proves no legacy record changed, never the source of the rollback.
The runbook says it in one line: The old snapshot must never replace newer truth. The rehearsal proved both halves before the real cutover. A pre-write rollback preserved the file in place, and after new writes, restoring the pre-transfer state was refused while a restarted engine kept the newer records. After real writes, the way back is forward: a compatible service revision, or a repair.
What Stood the Next Day
On 2026-09-28 an audit concluded that the cutover report had overstated completion: native canaries had proved transport contracts, not that Dad could choose a useful worker from the product. That work was reopened. The transfer was not, and the audit said so in words: the production writer transfer remains valid; its evidence is not retracted or rolled back. It stood because its evidence was specific (parity hashes, refused writes, named canaries), and specific evidence survives a skeptical rereading.