sage: merged B+C window plan draft 9 (sha256 523f20e03be8aa11), Mos's formal check closed with the W0 go (mos-0907); A1-A4 text fixes to draft 8 (mos-0906)

This commit is contained in:
2026-10-19 20:31:36 -05:00
parent d3996adea7
commit c49de08e86
@@ -0,0 +1,346 @@
# Merged window B+C: re-pin to the tip after rows 175 and 176, bus 3h to 3i, as the tester
Sage, 2026-10-19 (draft 9, 2026-10-20T01:29Z, Mos's four text fixes A1-A4 to draft 8, mos-0906; draft 8, 2026-10-20T01:23Z, NEW and the tool blob after row 176 landed as 89af38a83; draft 7, 2026-10-20T00:55Z, its header said 01:05Z in error, Mos's read of snapshot.mjs 639fd28a, mos-0899; draft 6: Mos's early read mos-0888 of draft 1, Darkwing's operator read of draft 2, the P3 dry run finding on 8e888014d with Mos's agreement mos-0894, the whole-store snapshot for D3 with Mos's four checks mos-0896, and its sha 639fd28a), for mos-0870 under DECISIONS Q173, with Q174 (mos-0882) for
tonight. This plan replaces Window B (`repin-ca15d73e9.md`), which never ran, and the "first window past
c216f47db" section at its end. Window B's commit table, stop conditions and D3 rule carry over and are restated
here so this file stands alone.
Status: for Mos's formal plan check. Rows 169, 175 and 176 have landed on origin `refactor`, 176 as NEW. It doesn't
run until Mos has checked this file at NEW. Any change to this file or to the tool blob after the check means Mos
checks again (mos-0849/0850).
## Who, and what nobody does
- Darkwing operates, as the tester. Mos verifies each step's output before the next step starts. Sage owns the
plan and files the STATE report in Vikunja.
- Jason has no step. Under Q173 agents may re-pin, migrate, roll back and restore this test system without him.
Decision 96 is Mos's (mos-0716). Q173 lifts its conditions 1 to 3 for this business (Jason or Mos runs it, Jason
present for `--migrate`, the `--events` wait) and keeps condition 4, no pruning, which this plan follows. The
tool's "needs: Jason present for this run (decision 96 condition 2)" line on a `--migrate` plan only echoes
(`stack-release.sh:763`) and isn't a gate (mos-0888 Q1).
- Nobody runs anything as Jason: no `--by jason`, no human shell, no typing into a tmux pane or a human Console
session. Every bus read and write in this plan carries `--tester`, which row 175 adds.
- `bus.tester` stays as it is. It's Jason's switch. If it reads anything but `true`, the window stops before W0.
- No pi. No role var and no `harness` entry names pi, and nobody maps CLAUDE_CODE_OAUTH_TOKEN into a pi variable
(mos-0865 D2, P19 #383).
- Nobody presses Save in the human Console, and `mosaic name` stays out of the window (mos-0641).
- Who can reach the Console from outside doesn't change. The tunnel still forwards 7332 only, and P4 and W10
check that. A change there is Jason's (Q173 exclusions).
## Names
- OLD, live: `5fa9cc8f2c66b52408b652b59c3070f7210b44d0`. Bus schema 3h. Window A's activation
`20261019T214524Z-5fa9cc8f2c66`, plug-ins usage-source `["transcript"]` and channel `["cli","webui"]`.
- NEW: `89af38a83afa22a8a4f0d368a0a4030ad15a0efa`, the origin `refactor` commit that lands row 176. `NEW=<sha>` is
the bare sha (`console-check.sh` takes the sha, mos-0866 F3). Bus schema 3i. Commits after NEW on `refactor` are
records only and don't move NEW.
- REPO: the canonical checkout. Tool blob: `git rev-parse NEW:scripts/stack-release.sh` is
`adcb68477c25822968ec9856add55a6a5f674355`. Row 176 changed it (98d29769e had 0b501ffbc247). P3 runs on that blob.
`stack-release.sh:N` references in this file are at NEW unless a section says otherwise.
- `COPIES`, `C_OLD`, `C_NEW` as in Window A, with `C_OLD` on OLD and `C_NEW` on NEW. `C_NEW` is
`node $COPIES/$NEW/packages/cli/src/cli.mjs`. From row 175 on, `$COPIES/$NEW/scripts/mosaic` dispatches
`plugins`, `name` and `names` to the CLI, so either form works for NEW.
- `DATA`: the config's data root, `node -p 'require(process.env.HOME+"/.config/mosaic-dev/config.json").dataRoot'`,
absolute. The store is `$DATA/bus/bus.sqlite`, as `stack-release.sh:1006` reads it.
- `T=7333`, the tester port.
- `WIN`: `agents/darkwing/work/window-merged-bc/`, where every transcript goes.
## What NEW carries over OLD
`git rev-list --count 5fa9cc8f2..NEW` is 143 (133 at 716ddcf1e). The path-limited log over `packages`, `scripts`,
`roles`, `contracts` and `adapters` (tests left out) lists these 14 code commits, read again at NEW. Row 179 is
docs only and listed for completeness.
| Row | Commit | Live effect |
|---|---|---|
| 126 | ca15d73e9 | A daily token quota, the `limit-check` module `daily-quota`. It stays off: nobody sets `plugins.limit-check` here. Schema 3h to 3i (`usage_records.account` loses its NULL check, `packages/bus/src/migrations/3h-3i.sql`). |
| ledger | efb51169d | `packages/ledger` only, which Sage runs by hand from the checkout each Monday (row 7). No bus, Console or launch effect. |
| 65 | c216f47db | A launch takes harness, model and thinking from the instance vars, and refuses until `plugins.harness` lists the harness. This is why the window has two phases. |
| 170 | cb43dcb45 | A tester message to a role nobody holds refuses `tester-target-not-running`. |
| 171 | 078518e4e | A thread project named like a business-file repo refuses `project-name-is-repo` unless `--repo` matches. |
| 134 | 3a600cf5b, a85e573d4 | A role holding `organization.change` reads the organization revision itself with the `organization_revision` tool. |
| 172 | be10e0c83 | Console: a Seen click during a Board read waits for the read. |
| 64 | 9f0039711, 6b6567b8b | A managed session gets its role's skills as prompt text. The launcher refuses `skill-not-enabled` for a listed skill not in `<dataRoot>/skills-enabled`. W8 carries the skills. |
| 166 | 4b738e2da | Tool only: activate refuses unreadable drop-ins and quoted unit values, rechecks the bus after a held read, and gates on the target copy. |
| 169 | 264756a30 | Tool only: `activate --pm-after-harness`, `pm-on` and the pending pointer. |
| 179 | 9e74925c2 | `docs/TOOLS.md` only. |
| 175 | 98d29769e | Tester reads and the tester writes W1-W7, recorded as `tester`. `scripts/mosaic` dispatches `plugins`, `name` and `names`. |
| 176 | 89af38a83 | The Console's tester listener on 127.0.0.1, `--tester-port`, and 7332 refusing `x-mosaic-tester`. `activate --tester-port N` writes the flag into the pointer and the unit. Its bus.js change (b0ffec0ce to 01c12b9e1bf2) is inside the decision 123 go (mos-0900). An activate and a pm-on in the same second refuse `capture-failed` (row 169's race, P19 391): wait a second and retry (mos-0902). |
`SCHEMA_VERSION` (`packages/bus/src/store.mjs:17`) is 3h at OLD and 3i at ca15d73e9, c216f47db and HEAD. There's
one migration step.
## A dependency on row 176: the pointer's Console flags
This section explains the brief change, so its line numbers are at 716ddcf1e, before row 176. At NEW the same
lines are :109, :812 (now `RUN_CONSOLE`), :952 and :609.
The Console unit's flags live in the pointer (`P_CONSOLE`, `scripts/stack-release.sh:101`). `activate`, `pm-on`
and `rollback` all install the Console with the pointer's flags (:774, :914), and `flags_drift` (:601) refuses
`unit-flags-drift` when the unit and the pointer differ. So adding `--tester-port 7333` by hand with
`webui-service.sh install` would make the next run refuse, and nothing today writes a new flag into the pointer.
A rollback is worse. It installs OLD's Console with the pointer's flags, and OLD's `serve` doesn't know
`--tester-port`, so the Console wouldn't start and the run would end `rollback-failed` with both units down.
Sage added this to the row 176 brief before Rocko started (sage-1107 told Mos), and 89af38a83 lands it:
- `activate ... --tester-port N` adds or replaces `--tester-port N` in the Console flags it installs and writes
to the pointer. It refuses `tester-port-target` before anything stops when the target copy's own Console
CLI doesn't take the flag. The check reads the target copy, not REPO.
- `activate` and `rollback` to a target whose Console doesn't take the flag drop `--tester-port` from the
flags, and say so in `--plan` and in the steps log. `rollback` back to such a copy is the only place this
happens in practice.
- A re-pin or rollback between two copies that both take it keeps it. Test 11's rollback case becomes these
two cases.
So the window adds the tester port on the activate itself (W5). The activate's store backup comes first, which
is what the 176 brief's Gate section asks for.
## Backups (Q173: a backup before every change)
| Before | Backup | Taken by |
|---|---|---|
| W5 activate, which migrates | the store, `<dataRoot>/backups/stack/<activation id>` | `stack-release.sh` itself (`backup_store`, :329). W5 records the path and stamp |
| W6 harness set | the business file, `cp -p` to `~/.config/mosaic-dev/businesses/mosaic-stack.json.pre-window-<stamp>` and its sha256 | Darkwing, in W0b. The organization writer's receipt also logs the old and new values |
| W8 skill carry | `<dataRoot>/skills-enabled`, `tar -C <dataRoot> -cf $WIN/skills-enabled-<stamp>.tar skills-enabled` | Darkwing, in W0b |
| W7 pm-on | the rollback set `pm-on` captures (:944; the activation's own capture is in `run_steps`, :784). It changes no store schema | `stack-release.sh` |
## Before the window (nothing stopped)
| # | Who | Step |
|---|-----|------|
| P0 | Mos | As Window A: two OK lines. |
| P1 | Darkwing | The live file, read only, printing these values and nothing else: `vars["bus.tester"]` is `true`; `plugins` has no `harness` key; `launch.by` is `chief` and `roles.chief.vars.harness` is `claude-code`; no role's `vars.harness` or `vars["fallback.harness"]` is `pi`. Sage read them on 716ddcf1e before this draft: `bus.tester` true, kinds `usage-source` and `channel` only, `chief` and all six roles `claude-code`. Then `node $P/business-check.mjs` for OLD and, after W0, for NEW. Both LOAD_OK. A `harness` key here would make phase 1 refuse `pm-after-harness-set`. A chief without a `harness` var would make `pm-on` fail `no-route` (Darkwing's row 169 rehearsal, finding 3). |
| P2 | Sage | `console-check.sh $NEW` against a stub Console of NEW on a scratch port, once without and once with `--tester-port` on a second scratch port. CONSOLE_PASS both times. |
| P3 | Darkwing | The rehearsal, on the day's tool blob, on a scratch root seeded at OLD and 3h. Its business file has Window A's plug-ins, no `harness`, a chief with `harness` `claude-code`, `bus.tester` true, and the example file plus the plug-in keys (mos-0847). Run W3, W5, W6 and W7 on it with a scratch tester port, then the rollback path after the harness set (harness to null as the tester, `business-check` under OLD, `rollback --restore <id> --plan`, `rollback --restore <id> --events N` with N from the D3 snapshot). Before the restore it makes one write that adds an event after the stamp, so N is above 0. The real run without `--events` must refuse `post-backup-events` and the rerun with `--events N` must pass. The 8e888014d dry run had N = 0 and didn't test that case. Then confirm the pointer, the store schema and the Console flags are back on OLD with no `--tester-port`. The chief launch is emulated as in queue-169. Unlike that rehearsal, the harness set here is the real `plugins set --tester`, not a stand-in, so P3 is its first live test. Output in `$WIN/p3/`, with sha256 values Sage checks. |
| P4 | Darkwing | The tunnel, read only: `systemctl --user show mosaic-preview-tunnel.service -p ExecStart -p DropInPaths`. ExecStart has exactly one `R:` spec, `R:0.0.0.0:7332:127.0.0.1:7332`, and DropInPaths is empty. Anything else stops the window. Sage read it before this draft: one spec, as above. |
| P5 | Darkwing | `git show $NEW:roles/<r>.json` for coder, reviewer, cto and pm. The seven skills W8 carries are still the lists' union minus ms-communications: ms-unslop, ms-sdlc, ms-file-read, ms-file-write, ms-archify, ms-grill-me, ms-goal (checked at 716ddcf1e). If a list changed, W8 changes and Mos checks again. |
## The window
| # | Who | Step |
|---|-----|------|
| W0 | Darkwing | `cd $REPO && scripts/stack-copy.sh copy $NEW`. Note the full id. Then P1's `business-check` for NEW. |
| W0b | Darkwing | The two backups in the table above, each with its sha256 in `$WIN/backups.txt`. |
| W1 | Darkwing | The PM-idle check: `mosaic agents --business mosaic-stack --tester` (the chief must not be `working`) and `mosaic launches list`. OLD's broker takes `agents --tester` (it is in OLD's TESTER_VERBS). Record the start stamp with `date -u +%FT%TZ`. Then the before-snapshot S1 (below), `node $WIN/snapshot.mjs --store "$DATA/bus/bus.sqlite" --held`. Nobody talks to the chief until W9, so no turn writes a usage record before the gate (D3). |
| W2 | Darkwing | `scripts/stack-release.sh status mosaic-stack`: pointer on OLD, schema 3h, no pending line, last line ok, unit flags match the pointer. |
| W3 | Darkwing | `scripts/stack-release.sh activate $NEW mosaic-stack --pm-after-harness --migrate --tester-port 7333 --plan`. It must match P3 on: from OLD, to NEW, store schema 3h read from the store the running bus holds, target 3i, one step 3h to 3i, `--migrate` given, the commit count, and the Console flags with `--tester-port 7333` added. No `pm-after-harness-target`, `pm-after-harness-set`, `unknown-kind-for-target` or `tester-port-target` refusal. Any other difference stops the window here. |
| W4 | Darkwing | The units, read only: `systemctl --user show [email protected] -p ExecStart -p WorkingDirectory -p DropInPaths -p Environment` and `systemctl --user show mosaic-webui.service -p ExecStart -p WorkingDirectory -p DropInPaths`. DropInPaths empty on both. Bus ExecStart has `--pm`. Both name the OLD copy. No `"`, `'` or `\` in the bus `Environment=` or either ExecStart (row 166's `unit-flags-drift`). |
| W5 | Darkwing, on Mos's go | Phase 1: `scripts/stack-release.sh activate $NEW mosaic-stack --pm-after-harness --migrate --tester-port 7333`. Exit 0, final `result ok`, every gate check except `pm_launched`. Record the activation id and the backup path and stamp. Then `status`: pointer on NEW, `pm: false`, `pmPending: true`, schema 3i, Console flags with `--tester-port 7333`, and the pending line. The bus is live without its PM. |
| W6 | Darkwing, after Mos verifies W5 | The harness set, as the tester: `$C_NEW plugins --business mosaic-stack --tester --json` for the revision, then `$C_NEW plugins set --file $WIN/harness.json --business mosaic-stack --tester` with `{"revision":"<that revision>","plugins":{"harness":["claude-code"]}}`. Read it again: `harness` is `["claude-code"]`, `usage-source` and `channel` unchanged. The organization receipt names actor `tester`. Then Mos runs `business-check` under NEW on the patched file: LOAD_OK. |
| W7 | Darkwing, on Mos's go | Phase 2: `scripts/stack-release.sh pm-on mosaic-stack`. Exit 0: the chief launched and the pointer is `pm: true` with no `pmPending`. Exit 1 or 2: see the stop conditions. |
| W8 | Darkwing, after Mos verifies W7 | The row 64 skill carry, from the pinned copy (`carry-past-row64.md`): `cd $COPIES/$NEW`, install and activate the seven, then `scripts/skill.sh list` shows eight under `enabled:` and ms-tools under `available:`. A STOP line is a stop condition. |
| W9 | Darkwing | The checks and the turn below. |
| W10 | Darkwing | The browser check below, then P4's tunnel read again. |
| W11 | Darkwing, then Sage | The STATE report (below). Sage files it on P19 386 and 382 and closes 382 if W5 to W9 passed. |
### S1 and S2, the store snapshots
The before-reads can't go through the bus. OLD's broker doesn't take `--tester` on `usage`, `names` or
`threads`, and row 175 only adds those on NEW. So S1 (in W1) and S2 (in W9) read the live store directly with
Darkwing's `$WIN/snapshot.mjs` at 639fd28a, which Mos read (mos-0899). It reads every table in `sqlite_master`
(type table, less `sqlite_*`). For each it prints the row count, the max `seq` and a sha256 of the rows, in `seq`
order, or in column order for a table without `seq`. Every table hashes all its columns in name order except
`usage_records`, which hashes the explicit list the 3h-3i copy carries (`3h-3i.sql:9`), so S1 under 3h and S2
under 3i hash the same old rows. Only `usage_records` refuses `columns-changed`, when its column set differs from
that list. It adds per-run input and output sums for `usage_records`, and `messages` and `message_threads` print
counts only. It prints no message body, no instance, project or thread name, no credential and no file content.
It reads no config, so `--store` takes the absolute store path.
`--held` is a plain `readOnly` open of the store the running bus holds, as `stack-release.sh` does, and refuses
`store-not-held` if `-wal` or `-shm` is absent. `--stopped` opens it with `immutable=1` and refuses `store-unclean`
if either is present. `--after` takes only the STAMP form with milliseconds (`2026-10-20T00:11:14.123Z`, `now_ms`
at `stack-release.sh:44`). The count is a text compare, as the tool's own count at :298 is, so a timed table with
any `at` in another form refuses `at-format` and names the table. `eventsAfter` is the query the tool runs for
`--events`. S1 and S2 run against the live store with `--held`. The first version (909a11f65c42de1d, three
tables, approved in mos-0892) is replaced.
Draft 5 changes the D3 side (sage-1123). Darkwing found that a tester `project.create` writes `projects` and a
tester `message.send` writes `messages` and maybe `message_threads`, with no event, so the three-table stop
misses them. The new `snapshot.mjs` reads the table list from `sqlite_master`, so a table added later can't slip
by. It gives a `rowsAfter` count for every table with an `at` column. Each table without one is named in the
output with its count against the backup's `bus.sqlite`, opened read-only. It still prints counts only, and
`usage_records` keeps its explicit column list. Mos read all 235 lines and approved it at 639fd28a for S1, S2
and D3 (mos-0899). Before each of S1, S2 and D3, Darkwing runs `sha256sum $WIN/snapshot.mjs` and refuses to run
the snapshot on anything other than 639fd28a2674aaa03ce824e3394b4052f21cdef7d6fa49bae61a9927ed341e67.
The whole-store script is sha256 639fd28a2674aaa03ce824e3394b4052f21cdef7d6fa49bae61a9927ed341e67 (Darkwing,
2026-10-20, snaptest run 5: 63 pass, 0 fail, records in `$WIN/snaptest/`, SHA256SUMS 10a4a5861e39f32e). Mos's
four checks (mos-0896) and the tests for each:
- The backup opens with immutable=1 and readOnly, as `--stopped` does, and refuses `backup-unclean` when a `-wal`
or `-shm` sits beside it. A test reads the backup five times and checks that files, sizes, mtimes, modes and
sha256 values are unchanged. A missing or relative `--backup` refuses.
- Counts only. `messages` and `message_threads` print rows, maxSeq, rowsAfter and counts against the backup,
with no sha256 and no 64-hex value anywhere in their output.
- `--after` must be the STAMP form with milliseconds (`now_ms`, `stack-release.sh:44`). With it, any `at` in a
timed table that isn't in that form refuses `at-format`, naming the table and printing no value.
- The table list is `type='table' AND name NOT LIKE 'sqlite\_%' ESCAPE '\'`, so `sqlite_sequence` is skipped
and a table named like `sqlitex` is still listed. A table the backup lacks stops the window.
A mutant for each rule is caught by the named tests (17, 3, 1, 7 and 2 failures, `$WIN/snaptest/mut/`). The
untimed tables are `message_threads`, `meta` and `task_comment_cursors`. Every bus table has `no_update`,
`no_delete` and `no_replace` triggers, so an old row can't change in place without a schema change. The script
still compares the rows at or below the backup's max seq as a backstop.
### W9 checks and the turn
```
scripts/stack-release.sh status mosaic-stack # pointer on NEW, pm true, schema 3i, last line ok
bash $P/console-check.sh $NEW # CONSOLE_PASS
mosaic launches list # one running chief from this start
mosaic agents --business mosaic-stack --tester # chief idle, pm not running
mosaic talk chief "[mos test] re-pin $NEW: reply with one line, no staffing request" --tester
$C_NEW plugins --business mosaic-stack --tester --json # harness, usage-source, channel; no limit-check
$C_NEW names --business mosaic-stack --tester --json
$C_NEW threads --business mosaic-stack --tester --json
mosaic limits --business mosaic-stack --tester --json # exit 0
mosaic usage --business mosaic-stack --tester --json # one new byRun entry
node $WIN/snapshot.mjs --store "$DATA/bus/bus.sqlite" --held # S2, after the coder session ends
```
Then one start of a role that lists skills (`carry-past-row64.md`, sage-1064 R4, mos-0863 D3): a coder session
the tester starts with the task "List the skills in your prompt, then stop." The task makes no repo write, push,
PR, issue or outward message (mos-0888 F2). Its bundle `manifest.json` lists the coder's four skills with their
sha256 values. Darkwing records how the session ends: it exits by itself, or Darkwing stops it, with the stamp.
S2 runs after it ends and shows its `usage_records` rows. A refusal here is a stop.
What passes:
- The 3i migration kept the rows: S2 has S1's `usage_records` rows unchanged, plus this start's chief run and
the coder run. `instance_names` and threads are unchanged.
- The transcript source works under 3i: one new `byRun` entry for the chief turn, input > 0, output > 0 and
`unknownTurns` 0.
- The tester reads answer. Their values agree with S2 where both report the same thing.
- `byAccount` isn't compared with S1, since ca15d73e9 writes an account on new rows.
### W10 browser check, on the tester listener
On http://127.0.0.1:7333/ with the webui browser harness, from Darkwing's run:
1. The page shows the tester banner and the instance, routes and name editors only.
2. The Board and Settings read. Settings shows Token limits with no daily quota entry.
3. A request to 7332 with `x-mosaic-tester: 1` and a loopback Host gets 403.
4. No write through the listener. The routes editor's write path is row 176's test 12 on a fixture, not a live
change in this window.
Then P4 again: the tunnel's ExecStart still has the one `R:0.0.0.0:7332:127.0.0.1:7332` spec.
### W11 STATE report
Darkwing writes `$WIN/STATE.md`: the stamps, the activation ids for W5 and W7, the backup paths and sha256
values, the pointer before and after, the unit ExecStart lines (no Environment values), S1 and S2, the W9 and
W10 outputs, and any stop. Sage checks it against the transcripts, then posts a summary on P19 386 and 382 and
on Gitea #1673 and #1674.
## Stop conditions
Before W5, any refusal or mismatch ends the window with nothing stopped.
- W5 exit 1: the run rolled back inside itself, restored the backup and started OLD. Report the log line and the
failing check to Mos.
- W5 exit 2, `rollback-failed`: both units stay stopped. Mos settles it from the rollback set the message names.
- W6 fails: the pointer stays pending and the bus runs without its PM. Mos decides between fixing the set and
the rollback from pending, which needs no harness undo.
- W7 exit 1, `pm-on-failed`: the bus stays live without its PM and the pointer stays pending. Mos decides between
fixing the `harness` entry and running `pm-on` again, or the rollback below. Exit 2, `pm-on-down`: both units
are stopped, and Mos settles it.
- W8, W9 or W10 failure after W7 passed: Mos decides between a fix forward and the rollback.
- Two failures in one window: stop and report (mos-0823).
### The rollback, after W6
OLD refuses the unknown `harness` kind when it loads the file (ca15d73e9 `kinds.mjs:78`). So:
1. Darkwing sets `harness` to `null` as the tester with NEW's CLI, since OLD has no `plugins --tester` and
tester-cli checks its own copy through argv[1]: `$C_NEW plugins --business mosaic-stack --tester --json` for the
revision, then `$C_NEW plugins set --file $WIN/harness-null.json --business mosaic-stack --tester` with
`{"revision":"<that revision>","plugins":{"harness":null}}`. Then a read again.
2. Mos runs `business-check` under OLD. LOAD_OK.
3. If W8 ran, its rollback: deactivate and uninstall the seven, and `list` shows ms-communications alone.
4. The D3 counts below, then `rollback mosaic-stack --restore <W5 activation id> --plan`, then the real run
with `--events N`, N from D3. The tool refuses `unknown-kind-for-target` by name until step 1 is done. With the
row 176 change, `--plan` says it drops `--tester-port 7333`. `--plan` doesn't count the events after the
backup: it exits at `stack-release.sh:854`, before the count at :866-875. A real run without `--events`, or
with a count that no longer matches, refuses `post-backup-events` with the count before anything stops. That
refusal is expected if the count moved after D3. Read it, take Mos's go again, and rerun with the new N.
From pending before W6, `rollback` installs OLD with `--pm` and gates `pm_launched`, with nothing else first.
It still needs `--restore`, since the store is 3i.
### Before any `rollback --restore` (mos-0838 D3)
`--restore` counts only the `events` table after the backup stamp (`stack-release.sh:298`). `usage_records`
(`usage.mjs:93`), `instance_names` (`broker.mjs:2237`), `projects`, `messages` and `message_threads` are written
outside it, and a restore drops their rows written after the stamp from the live store. The post-attempt copy
the run keeps still has them.
1. Darkwing runs `node $WIN/snapshot.mjs --store "$DATA/bus/bus.sqlite" --held --after "$(cat <backup>/STAMP)"
--backup <backup>/bus/bus.sqlite`, where `<backup>` is `$DATA/backups/stack/<W5 activation id>`, with the
snapshot.mjs sha Mos approved for D3, 639fd28a (mos-0899). Darkwing reports to Mos every table's rows after the backup
stamp, the count difference against the backup for each table without an `at` column, and `eventsAfter`,
which is the `--events N` value, plus `stopTables` and every table with `priorSame` or `same` false. STAMP is
ISO with milliseconds, which `--after` takes. When the bus is stopped, after W5 exit 2 or W7 exit 2
(`pm-on-down`), both units are down and `--held` refuses `store-not-held`, so D3 runs with `--stopped` in place
of `--held`. A `store-unclean` refusal stops the window and goes to Mos.
2. If `stopTables` is not empty, Darkwing stops before the restore, whatever `eventsAfter` says (mos-0899 A1).
`stopTables` names every table other than `events` with rows after the stamp, a count difference, or a
changed row at or below the backup's max seq. The last case has no row after the stamp and no count
difference, so a check on those two alone would miss it (Darkwing's m5 mutant: `usage_records` with
`priorSame` false). The tool won't refuse: a write that adds no event passes its guard. In the P3 dry run on 8e888014d, one
`name --tester` after the stamp gave `instance_names` rowsAfter 1 and eventsAfter 0. The restore printed
"0 event(s) were written after the backup" and dropped that row from the live store with no refusal. The row
survived only in the post-attempt copy.
3. Mos decides. Mos's go names the rows the restore drops, table by table. Under Q173 losing test-system data
is allowed, so Jason isn't asked, but the counts go in the STATE report. Nobody opens or changes the
post-attempt copy. The tool fix, counting these tables in the `--restore` guard, is a follow-up row Sage
files after the window with its own brief and issue, tracked as Vikunja P19 390 (mos-0894). It isn't part of
this window.
4. A backstop: nobody makes a tester `project.create` or `message.send` between W5 and a rollback unless a
window step needs it, so the stop in step 2 has as little to name as possible.
5. Past W7 the stop always fires. A real PM launch writes `role_claims` and similar rows after the stamp
(Darkwing, 2026-10-20). That is expected, not a fault: a rollback after W7 always needs Mos's go naming
those rows. A rollback before W7 can pass step 2 with only `events` moved.
## Rollback copies
Keep 5fa9cc8f2, 8e7ea89a1, 52f35e7a0 and 3190ad762 (mos-0866). Don't touch 66ea40580. Nothing is pruned: the
rollback sets, the backups and any post-attempt store stay.
## Questions for the plan check
Mos answered these in the early read of draft 1 (mos-0888):
- Q1, `--migrate` without Jason: yes. Conditions 1 to 3 of decision 96 are lifted, 4 stands (see "Who").
- Q2, the row 176 brief change: approved. `pm-on` keeps the flag through `P_CONSOLE` (:914). Dewey's review
covers how `tester-port-target` decides the target's Console takes the flag, and that it reads the target
copy, not REPO.
- Q3, S1 and S2 as a read-only snapshot: approved with F1 (the explicit column lists above).
- Q4, the W9 coder start: approved under Q173 with F2 (the task above).
- F3: W6 and W8 run after Mos verifies the step before, like W5 and W7.
Mos agreed the restore-guard handling in mos-0894: stop on any nonzero rowsAfter (widened to a non-empty
`stopTables` in mos-0899), the tool fix as a row after
the window (P19 390), and P3 on the 176 landing tests `--events N` with N above 0.
Mos approved the whole-store `snapshot.mjs` at 639fd28a for S1, S2 and D3 (mos-0899) and agreed D3 step 5
with no allowlist.
Draft 8 puts NEW, the row 176 table line and the tool blob at NEW in this file. The P3 output sums from the run on
NEW with `--events N` above 0 go in Darkwing's P3 transcript under `$WIN` and to Mos by message, not in this file,
so this file's sha doesn't change when they arrive. The formal check covers this file's sha and those sums.
## What Sage ran for this plan
Read only, on 716ddcf1e, before 2026-10-19T23:59Z. Nothing live was changed.
- `git rev-list --count 5fa9cc8f2..HEAD` (133) and the path-limited log above.
- `SCHEMA_VERSION` at 5fa9cc8f2, ca15d73e9, c216f47db and HEAD, and the one migration file.
- `scripts/stack-release.sh` at HEAD: `parse_opts` (:60-80), `plugin_checks` (:264-277), `flags_drift`
(:594-610), `run_steps`, `do_rollback` (:811-845), `do_pm_on` (:890-915), `backup_store` (:321).
- The row 176 brief, the row 169 packet (`agents/darkwing/work/queue-169/BUILD.md`, findings 1-3) and
`carry-past-row64.md`.
- `roles/*.json` skill lists and `<dataRoot>/skills-enabled` (ms-communications alone).
- The live business file's `vars["bus.tester"]`, plug-in kind names, `launch.by` and each role's `vars.harness`,
printing those values only.
- `systemctl --user show mosaic-preview-tunnel.service -p ExecStart`.