Files

5.8 KiB

Row 126 (#1620): bus schema 3h → 3i, for Mos

Round 4, under Mos's go (mos-0835, relayed in sage-0972) and Sage's scope (sage-0970). This step lands on refactor only. It is not in the 8e7ea89a1 window. It goes live later through stack-release.sh activate ID BUSINESS --migrate with Jason present, on Mos's go (decision 96, conditions 1 and 2).

What the step does

3h reserved usage_records.account as account TEXT CHECK (account IS NULL) (packages/bus/src/migrations/3g-3h.sql:10). Row 126 writes the run's account name there, so the CHECK has to go. SQLite cannot drop a CHECK in place, so packages/bus/src/migrations/3h-3i.sql rebuilds the table:

  1. drops the three append-only triggers: usage_records_no_update, _no_delete and _no_replace. Mos checked that nothing else references the table: no index, view or foreign key;
  2. renames usage_records to usage_records_3h;
  3. creates usage_records with text byte-identical to schema.sql. The only change from 3h is account TEXT,;
  4. copies every row in seq order, seq included;
  5. sets the table's sqlite_sequence value to the old table's value. It is not rebuilt from max(seq), so a seq that was handed out and then rolled back is never reused;
  6. drops usage_records_3h, then recreates the three triggers with their 3h text.

Why it renames first: ALTER TABLE … RENAME stores the new name quoted in the table's SQL. The open-time digest compares sqlite_master text, so renaming the new table into place would leave "usage_records" in its text and fail migration-target-mismatch at import.

The Store runs each step inside one BEGIN IMMEDIATE transaction (store.mjs #migrateSchema): the step's SQL, the meta version and digest rewrite, and the schema-mismatch digest check before commit. Any failure rolls the whole step back, and the store stays at 3h.

The checks behind it

packages/bus/tests/account-migration.test.mjs works on a 3h store built from tests/fixtures/schema-3h.sql. That file is pinned from 81f26551b's schema.sql, sha256 bb2ba581…6950. No live store is read or copied.

  • Test 1. The plain open refuses with schema-mismatch, and migrateSchema then reaches 3i. Every table's rows are unchanged, seq 1-3 included, and the sequence stays at 50. sqlite_master equals 3h's except the account line of usage_records; the three triggers are back. quick_check and foreign_key_check are clean. The next insert gets seq 51 and takes an account. UPDATE, DELETE, INSERT OR REPLACE and a reused seq all abort with usage_records is append-only.
  • Test 2. A step that fails partway, after the triggers are dropped and the copy is done, leaves version 3h, sqlite_master and every row as they were. The real step then migrates the same store.
  • Test 3. A 3h engine (3h schema and SHA, no 3h→3i step) refuses a 3i store with schema-mismatch, with or without migrateSchema. The store is left at 3i, unchanged.

Running it

The only supported path is scripts/stack-release.sh activate ID BUSINESS --migrate, run by Jason or Mos on Mos's go, with Jason present. Run --plan first: it prints migration steps: 3h to 3i from the target copy's store.mjs, plus needs: --migrate and needs: Jason present.

The activate path takes the pre-migrate backup itself, so nothing extra is needed. In run_steps (stack-release.sh:663-669), with both units stopped and the store clean:

  1. in_chain checks that 3h is in the target's chain, or the run rolls back store-newer;
  2. without --migrate, the run rolls back migration-needs-flag;
  3. backup_store copies bus.sqlite to <dataRoot>/backups/stack/<ACTIVATION>/, together with SOURCE.sha256, SHA256SUMS, MODES.txt, LISTING.txt, DATA_ROOT, STAMP, SCHEMA (3h) and ROLLBACK_SET, and checks that the copy's sha equals the source's. It sets TAKEN;
  4. only then does migrate-schema.mjs run from the target copy.

The way back

There is no 3i → 3h step, and no down-migration will be written. 3h code meeting a 3i store refuses:

  • the 3h Store throws schema-mismatch (test 3);
  • scripts/land-work/read.mjs in a 3h tree refuses bus-schema;
  • stack-release.sh rollback to a 3h copy refuses schema-differs unless --restore is given.

The way back is restoring the pre-migrate backup:

  • Inside the activate run. If migrate-schema.mjs fails, or a later step or the gate fails, rollback_in_run restores the backup this run took (TAKEN), puts back the previous units and wrapper, and gates the old stack. A failed migration also rolls back its own transaction, so the store is still 3h before the restore.
  • After a successful activate. stack-release.sh rollback BUSINESS --restore <ACTIVATION> [--events N], where <ACTIVATION> is the activate run's id (the backup directory's name). The backup must be of this data root at the target's schema (3h), or the run refuses restore-mismatch. If events were written after the backup's STAMP, the run needs --events N with that count and waits for Jason (decision 96, condition 3). The store from before the restore moves aside to the run's post-attempt directory; nothing is deleted.

Restoring drops whatever the 3i bus wrote after the backup, including any usage records with an account. --events N counts events only. Usage records written in the same period are lost too.

Between landing and the window

scripts/land-work/read.mjs imports SCHEMA_VERSION from the checkout's packages/bus/src/store.mjs and refuses bus-schema when the store's meta differs. After 3i lands on refactor, scripts/land-work.sh run from the canonical checkout against the live 3h store will refuse bus-schema until the 3i window has run. This is the same gap 3g→3h had. Sage decides whether land-work runs from a 3h tree in that period, or whether this row lands just before the window.