# 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 `/backups/stack//`, 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 [--events N]`, where `` 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.