W2 adopt on Mos's go with Jason present, W2b pointer check (copy52f35e7a0, schema 3f) before W3, W3 plan line-by-line match against Darkwing's scratch rerun, and the W2 undo: remove the pointer only. Landing commit7e7f8e208. Co-Authored-By: Claude Opus 5.5 <[email protected]>
19 KiB
Re-pin for rows 148 and 141, with the bus 3f to 3g migration
Superseded 2026-10-18 by repin-8e7ea89a1.md (mos-0814): this window never ran. The rest stays as the record.
Sage, 2026-10-16. One shared window for rows 148 and 141, per mos-0638. Mos runs every step, with Jason present (A4). Sage ran none of the window steps. P1 and P2 are done; the controls and fixture tests Sage ran are listed at the end.
The shape is repin-3190ad762.md, the last window with a migration: a
stopped-bus backup, a rehearsal and a live migration. It adds the P4
business check from repin-52f35e7a0.md, a Console check for row 148 and
row 141's manifest checks.
- OLD, live now:
52f35e7a0933e9fa845f59860a5de1709f9e5756. Bus, Console and CLI all on it perstack-copy.sh checkat 22:48Z. Bus started 2026-10-16T21:12:54Z (pid 2536547), Console 21:12:58Z (pid 2536917). Bus schema 3f. - NEW:
de352dfaff539e7df9db7a360bda6921a3410fa3, committed 2026-10-16T22:35:27Z, on originrefactor. Copy, made in P1:/home/jwoltje/.local/share/mosaic-stack/copies/de352dfaff539e7df9db7a360bda6921a3410fa3. Bus schema 3g. - Data root:
/home/jwoltje/.mosaic-dev. The store isbus/bus.sqlitewith its -wal and -shm files: about 1.9 MB plus a 0.7 MB WAL at 22:48Z (ls -l, no file opened). - Scripts:
P=/mnt/storage/scratch/sage-pin-de352dfaf. The reviewed copies are committed beside this file inrepin-de352dfaf/, withSCRIPTS.sha256. P0 checks that P matches them.
What NEW carries over OLD
Two commits in 52f35e7a0..de352dfaf touch COPY_PATHS. The commits after
de352dfaf, up to d4c3b2838, are docs and queue only and touch none.
Nothing under scripts/, adapters/, package*.json, RELEASE,
packages/cli/systemd or packages/seat changed, so bus-service.sh,
webui-service.sh, stack-copy.sh and working.mjs are the same as in
the 52f35e7a0 window.
- Row 148 (
d4ea7e5a6, #1642): a Console editor for the project's spec and docs. The Console servesGET /api/project-filesandGET /api/project-filethrough the new human-only bus verbsproject.filesandproject.file, and a new staticmarkdown-preview.js.POST /api/project-filecallsproject.file.save, which commits to the project repo. The Console wires Save only under--send, which the live unit has, and serves it only to the public name with the signed-in header. Loopback and any other Host get 403read-onlybefore the body is read./api/configgainsedit. No schema change. - Row 141 (
de352dfaf, #1635): messages addressed by agent name. Schema 3f to 3g:migrations/3f-3g.sqladds nullablemessages.to_nameand an append-only STRICTinstance_namestable with four triggers (unique name per business, no update, no delete, no replace). New CLI verbsmosaic name(a human write) andmosaic names(a read).roles/chief-of-staff.jsongainsinstance.name. No bus tool covers it until row 155, so the harness lists it in the chief's manifestuntypedand the chief's prompt carries the line "Actions you hold that have no bus tool yet: instance.name. Say so instead of working around it." pm does not hold it.
Nothing names an instance during the window. mosaic name writes an
append-only row to the live store, so it is Jason's to do, after the
window. mosaic names must print [] with --json.
NEW opens nothing older than 3g. A normal bus start under NEW on the 3f
store refuses with schema-mismatch and changes nothing. OLD refuses 3g
the same way. Both directions are in the rehearsal.
No refs/heads/root section this time. packages/seat/src/working.mjs
is unchanged, so the bus start seeds working refs exactly as it did on
OLD. Row 148's packages/project changes add the human file read and
writeHumanFile, which commits only on a Save. Keep the 52f35e7a0
window's refs-window.tsv.
Rollback set
P2 captured it at /mnt/storage/scratch/sage-pin-rollback-52f35e7a0/: the
mosaic wrapper, [email protected], mosaic-webui.service, both
systemctl --user show outputs, DROPINS.txt (no drop-in directories),
SOURCES.txt, SHA256SUMS and verify-live.sh. It printed MATCH three times.
The bus backup from step 3 is the second half. It records the rollback set's path and SHA256SUMS.
Scripts
All in $P. None of them reads or prints a row of the store, and none
prints file content.
| Script | Used in | What it does |
|---|---|---|
capture-rollback.sh [OUT] |
P2 | Saves the OLD wrapper and units. Read-only on live files. Done. |
business-check.mjs COPY_ID |
P4 | Loads the live business file with COPY's loader and roles, as the bus does at start. Prints counts, HOLDS chief instance.name yes|no, HOLDS pm ... and LOAD_OK, or REFUSED. |
stopped-check.sh DATA_ROOT |
3, R | Fails on bus/writer.lock or any process holding a file under bus/. Prints STOPPED_OK. Never opens the store, never removes a lock. |
backup.sh DATA_ROOT OUT ROLLBACK_SET |
3 | As in the 3190ad762 window. BACKUP_OK. |
rehearse.sh BACKUP NEW_COPY OLD_COPY |
4 | On two 0700 copies of the backup: (a) NEW refuses the 3f copy at a normal open, (b) migrate-schema.mjs exits 0 ending bus schema 3g ready, with its run time, (c) NEW opens it at 3g, (d) a second migrate is a validated no-op, (e) OLD refuses the 3g copy, (f) the backup is unchanged. REHEARSAL_PASS or REHEARSAL_FAIL. |
verify-open.mjs COPY DATA_ROOT |
4 | OPEN_OK <version> or REFUSED <code>. Refuses ~/.mosaic-dev. |
restore.sh DATA_ROOT BACKUP |
R | As in the 3190ad762 window. Renames bus to bus.post-attempt-<UTC>, deletes nothing. RESTORE_OK. |
verify-new.sh |
8 | Live wrapper and units equal the P2 set with the copy id swapped, and equal the NEW renders. 5 × MATCH. |
check-launch.mjs INSTANCE SINCE COPY_ID |
8, 9 | Sources under COPY, prompt digest, row 140 text, read_spec, no land_work, send_message; new for row 141: untyped is ["instance.name"] for chief and [] for pm, and the prompt carries the untyped line for chief and lacks it for pm. pm also needs request_staff. write_record is INFO. CHECK3_PASS or CHECK3_FAIL. |
console-check.sh COPY_ID [PORT] |
8 | Row 148 on loopback: markdown-preview.js served and equal to the copy's file; /api/config gives send=false edit=false; project.files for project stack lists spec/north-star.md; project.file reads it with a 40-hex commit (prints the length only); a loopback POST gets 403 read-only, sent=false. CONSOLE_PASS or CONSOLE_FAIL. |
verify-live.sh |
R | In the rollback set. MATCH per file. |
The two project.* reads go through the human-only bus verbs, so a PASS
also shows the Console's human proof is accepted. That needs the bus,
Console and wrapper on one tree, which step 8 checks first.
Steps
REPO=/mnt/storage/src/mosaic-stack
P=/mnt/storage/scratch/sage-pin-de352dfaf
D=/home/jwoltje/.mosaic-dev
OLD=52f35e7a0933e9fa845f59860a5de1709f9e5756
NEW=de352dfaff539e7df9db7a360bda6921a3410fa3
COPIES=/home/jwoltje/.local/share/mosaic-stack/copies
C=$COPIES/$NEW; OC=$COPIES/$OLD
B=$P/backup # the bus backup; holds live bus data, owner-only, nobody but Mos opens it
RS=/mnt/storage/scratch/sage-pin-rollback-52f35e7a0
Before the window (P0 to P4, nothing stopped)
| # | Who | Step |
|---|---|---|
| P0 | Mos | cd $P && sha256sum -c $REPO/agents/sage/work/window-plan/repin-de352dfaf/SCRIPTS.sha256, 11 × OK. |
| P1 | Sage, done; Mos checks | The copy exists. Copy check below, rc 0 with no output. Then test -f $C/packages/bus/src/migrations/3f-3g.sql && test -f $C/packages/webui/src/public/markdown-preview.js && echo NEW_COPY_HAS_141_148. grep -l git.merge.protected $C/roles/*.json prints nothing. |
| P2 | Sage, done; Mos checks | bash $RS/verify-live.sh, MATCH × 3, rc 0. |
| P3 | Mos | node -e 'console.log(JSON.parse(require("fs").readFileSync(process.env.HOME+"/.config/mosaic-dev/config.json","utf8")).dataRoot)' prints a path that resolves to $D. test -e $B && echo B_EXISTS prints nothing. |
| P4 | Mos | node $P/business-check.mjs $NEW: counts, HOLDS chief instance.name yes, HOLDS pm instance.name no, LOAD_OK. REFUSED, or other HOLDS answers, stops the window before it starts. |
The P1 copy check:
paths=$(sed -n '/^COPY_PATHS=(/,/^)/p' $REPO/scripts/stack-copy.sh | grep -v '^ *#' | sed '1d;$d')
T=$(mktemp -d) && git -C $REPO archive $NEW -- $paths | tar -xf - -C $T && diff -rq --exclude=node_modules $T $C; echo "rc=$?"
The window
| # | Who | Step |
|---|---|---|
| 1 | Jason, then Mos | PM-idle check. Jason, in his own shell: mosaic agents --business mosaic-stack; chief and pm must not be working. Mos: mosaic launches list. Nobody talks to chief or pm until step 8 passes. Write down the UTC start stamp: date -u +%FT%TZ. |
| 2 | Mos | Stop the Console, then the bus: systemctl --user stop mosaic-webui.service, then systemctl --user stop [email protected]. systemctl --user is-active mosaic-webui.service [email protected] prints inactive twice. |
| 3 | Mos | bash $P/stopped-check.sh $D, STOPPED_OK. Then bash $P/backup.sh $D $B $RS, BACKUP_OK. Record the sha256 lines it prints. |
| 4 | Mos | bash $P/rehearse.sh $B $C $OC. REHEARSAL_PASS, six PASS lines. Record the migrate run time. Any FAIL ends the window here: start the bus and the Console again on OLD, nothing else changed. |
| 5 | Mos | Live migration, bus still stopped: node $C/packages/bus/src/migrate-schema.mjs $D; echo "rc=$?". rc 0, last line bus schema 3g ready. Then (cd $B/bus && sha256sum -c --quiet ../SHA256SUMS) && echo BACKUP_INTACT. |
| 6 | Mos | From the canonical checkout: $REPO/scripts/bus-service.sh install --copy $NEW --pm --claude /home/jwoltje/.local/bin/claude, then $REPO/scripts/webui-service.sh install --copy $NEW --port 7332 --public-host mosaic.woltje.com --auth-header X-authentik-username --allow-user jason.woltje --send. |
| 7 | Mos | $REPO/scripts/stack-copy.sh wrapper $NEW. |
| 8 | Mos | Start the bus, then the Console. Run the checks below in order. |
| 9 | Mos, then Jason | Smoke from tmux, then Jason's browser check, below. |
Between steps 5 and 7 the live store is 3g while the wrapper and units
still name OLD. Run no mosaic command in that gap. A bus started on OLD
by mistake would refuse 3g and change nothing: a stop condition, not
damage.
The migration takes the store's exclusive writer lock, checks the 3f
source digest, schema version, WAL, quick_check, foreign keys and
timestamps, then runs the 3f-3g step in one transaction. A refusal prints
migration refused: <code> and exits 1. The transaction rolls back, so the
store is still 3f; restore anyway (rollback A), because the backup is the
known state.
Step 8 checks
$REPO/scripts/stack-copy.sh check # bus, Console, CLI on NEW
bash $P/verify-new.sh # 5 x MATCH, rc 0
systemctl --user status [email protected] mosaic-webui.service
curl -fsS http://127.0.0.1:7332/healthz
mosaic launches list # one running chief, launched by the bus start
node $P/check-launch.mjs chief <step 1 stamp> $NEW # CHECK3_PASS
mosaic names --business mosaic-stack --json # []
bash $P/console-check.sh $NEW # CONSOLE_PASS
The bus start is itself the check that the live store is 3g: a normal open under NEW verifies digest, version, WAL, quick_check, foreign keys and timestamps, and the bus does not start on any mismatch.
check-launch.mjs chief must show untyped ["instance.name"] and the
untyped prompt line. P4 predicted it from the live business file. If the
chief's untyped list is [], NEW's roles did not reach the launch; that is
CHECK3_FAIL.
mosaic names printing anything but [] means a name row exists that
nobody should have written yet. Stop and report; do not roll back for it
alone without Mos's call.
Step 9, smoke and the browser check
mosaic talk chief "[mos test] re-pin de352dfaf: reply with one line, no staffing request" --tester
mosaic talk pm "[mos test] re-pin de352dfaf: reply with one line, no staffing request" --tester
node $P/check-launch.mjs pm <step 1 stamp> $NEW # CHECK3_PASS, untyped [], request_staff
Every talk starts with [mos test] and runs with --tester. If pm is stopped, talk pm prints
the row 146 waiting line and chief launches pm. Talk by name is not
smoked: no instance has a name, and naming one is Jason's live write.
Jason's browser check of row 148, at https://mosaic.woltje.com/#/docs
signed in: the file list shows, spec/north-star.md opens with its
preview, and the Save button is present. Nobody presses Save in the
window. A save commits to Jason's project repo
(~/.mosaic-dev/projects/mosaic-stack/stack, HEAD ae0b17c9 at plan time),
which is a smoke that leaves a commit in Jason's history and waits for
Jason outside the window.
Stop conditions
Before step 5, any one ends the window with nothing changed. Start the bus and the Console again on OLD and report:
- P0 to P4 fail, or
stopped-check.shfails in step 3. A writer.lock with the bus stopped goes to the stale-lock procedure; nobody deletes it. backup.shprints anything but BACKUP_OK.rehearse.shprints REHEARSAL_FAIL.
From step 5 on, any one means rollback:
- Step 5 exits non-zero or does not end
bus schema 3g ready: rollback A. - The bus fails to start on NEW, or the newest launch-log line for this
start is
"event":"refused", orjournalctl --user -u mosaic-bus@mosaic-stack --since "<UTC stamp> UTC"showsPM launch refusedor a store code such asschema-mismatchorintegrity-failed: rollback B. journalctl reads--sincein local time unless the stamp says UTC. /healthzfails,stack-copy.sh checkshows anything not on NEW,verify-new.shprints any DIFFER, orcheck-launch.mjs chiefprints CHECK3_FAIL: rollback B.
console-check.sh FAIL is a finding for Mos, not an automatic rollback.
The bus and the launches don't depend on the Console's new routes. Mos
decides between a rollback B and a Console-only fix forward. The same goes
for a step 9 failure, unless the live talk path is broken.
Rollback
Never start the bus after a restore that did not print RESTORE_OK. Under
either copy, a bus that starts on a data root with no bus/bus.sqlite
creates a fresh empty store.
A: the migration failed (step 5), units and wrapper still on OLD
- Bus and Console stay stopped.
bash $P/restore.sh $D $B. RESTORE_OK. Keep the printedbus.post-attempt-*path for diagnosis.bash $RS/verify-live.sh, MATCH × 3.- Start the bus, then the Console, on OLD.
stack-copy.sh checkshows OLD. - Report the refusal code. Nobody retries the migration until it is diagnosed.
B: after the start on NEW
- Stop the Console, then the bus; both
inactive. bash $P/restore.sh $D $B. RESTORE_OK. The post-attempt store stays at the printed path.- Repeat steps 6 and 7 with
$OLDin place of$NEW. bash $RS/verify-live.sh, MATCH × 3. A DIFFER means stop and diff against the saved files before starting anything.- Start the bus, then the Console.
stack-copy.sh checkshows OLD.
OLD never opens the 3g file: step 2 moves it aside before step 5 starts the bus. Anything the bus recorded after the step 8 start, at the least the chief launch, is in the post-attempt store only. Mos and Jason decide what happens to it. Nobody strips columns or edits evidence in place. No Save ran, so Jason's project repo has nothing to reconcile.
Notes
- Land-work gap. Since
de352dfafthe canonical checkout'sscripts/land-work/read.mjsimports the 3g SCHEMA_VERSION and refuses against the 3f bus. Until step 5, land-work refuses on the live store and any landing uses conductor-apply. After a rollback B the gap reopens. - Keep the copies: OLD, NEW and the earlier ones. Nothing prunes copies.
- Keep
$B, itsrehearsal-*copies and anybus.post-attempt-*directory until Mos says otherwise. They hold live bus data, stay 0700 and nobody but Mos opens them. Deleting them is a data deletion and goes to Jason. - Record for BUILD-LOG: step 1 stamp, backup sums, rehearsal output, the migrate exit and last line, the step 8 output, the chief and pm manifest paths and prompt digests, Jason's browser check, and the downtime from step 2 to step 8.
- After the window, still not in it: Jason's
mosaic namefor any instance he wants named, and a first Save from#/docsif he wants one. Row 143 smoke points 1-2 stay with Jason after 143 lands.
What Sage ran for this plan
Nothing live was stopped, started, installed, migrated or re-pinned. No live store file was opened.
- P1:
stack-copy.sh copy $NEW, rc 0. Thegit archivediff against the copy, rc 0, no output. NEW_COPY_HAS_141_148.grep git.merge.protectedover NEW's roles: none. - P2:
capture-rollback.shto$RS: no drop-in directories, MATCH × 3. - P4:
business-check.mjs. OLD: roles 6, projects 1, arbiters 2, chief no, pm no, LOAD_OK. NEW: the same counts, chief yes, pm no, LOAD_OK. verify-new.shnow: DIFFER × 5, rc 1. With NEW set to OLD: MATCH × 5.check-launch.mjsagainst the live chief (r2665bdaad973) and pm (r0271f4ae385b) runs. With$OLD: chief CHECK3_FAIL on exactly the two row 141 checks (untyped[], no untyped line), everything else PASS; pm CHECK3_PASS. With$NEW: the sources check fails as well, for both.write_recordINFO prints present.console-check.sh $OLDagainst the live Console, GETs and one refused loopback POST: 5 × FAIL, CONSOLE_FAIL. OLD has nomarkdown-preview.js(404), noeditin/api/config, no project routes (404), and its generic preview refusal has nosentfield.console-check.sh $NEWagainst a stub Console: NEW'sstartServeron a free loopback port with stub project reads and a recordingprojectSave(fixture/console-stub.mjs). 5 × PASS, CONSOLE_PASS, and the stub recorded no save or send call.
On fixture data roots under $P/fixture, made fresh at 3f by the OLD
copy's store.mjs (fixture/t.sh, output in fixture/t.out):
stopped-check.shfails on a store held open by a process and on a writer.lock, and passes once both are gone.backup.shrefuses a held store (no OUT made), a locked store, a tampered rollback set and an existing OUT. BACKUP_OK on two stores, each with a leftover WAL and SHM from a killed holder.rehearse.sh: REHEARSAL_PASS twice, migrate 43 ms on the fixture, on a backup that has a WAL.- A stand-in step 5: rc 0,
bus schema 3g ready, BACKUP_INTACT; then NEW opens it at 3g and OLD refuses it. restore.shrefuses a backup from another data root and a locked store; then RESTORE_OK. The restored store opens at 3f under OLD and refuses under NEW; the post-attempt directory is kept.verify-open.mjsrefuses~/.mosaic-dev, rc 4, before opening anything.