agents: Sage window plan for the re-pin to 3190ad762 (rows 152, 138, bus 3e to 3f)
The plan for Mos's A4 window: stopped-bus backup with sha256, rehearsal on disposable copies, live migration with migrate-schema.mjs, NEW units and wrapper, verify-new.sh and check-launch.mjs for the NEW copy (read_spec now required), and restore steps A and B. Nine scripts committed with SCRIPTS.sha256 so Mos can check the scratch copies before running. Not run. Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
@@ -0,0 +1,309 @@
|
||||
# Re-pin for rows 152 and 138, with the bus 3e to 3f migration
|
||||
|
||||
Sage, 2026-10-16. Plan for Mos's window, on Mos's directive re sage-0494.
|
||||
Mos runs every step, with Jason present (A4). Sage ran none of it; the
|
||||
controls and fixture tests Sage did run are listed at the end.
|
||||
|
||||
This window has a schema change, so it adds a stopped-bus backup, a rehearsal
|
||||
and a live migration to the shape of `repin-56639c99b.md`. The procedure is
|
||||
Rocko's row 138 MIGRATION.md, which lives at
|
||||
`agents/rocko/work/queue-138/r4/MIGRATION.md` (there is no
|
||||
`packages/bus/MIGRATION.md`), with the "Schema and verification" section of
|
||||
`packages/bus/README.md`.
|
||||
|
||||
- OLD, live now: `56639c99b03ddca8f5fa84826b19322cd09a713e`. Bus, Console
|
||||
and CLI all on it per `stack-copy.sh check` at 02:05Z. Bus and Console
|
||||
started 2026-10-16T00:48:12Z. Bus schema 3e.
|
||||
- NEW: `3190ad762b8fd5c09ad7515e4f7e609e81ae1af6`, committed
|
||||
2026-10-16T01:49:44Z, origin `refactor` per `git ls-remote`. Copy, made in P1:
|
||||
`/home/jwoltje/.local/share/mosaic-stack/copies/3190ad762b8fd5c09ad7515e4f7e609e81ae1af6`.
|
||||
Bus schema 3f.
|
||||
- Data root: `/home/jwoltje/.mosaic-dev` (config.json `dataRoot`). The
|
||||
store is `bus/bus.sqlite` with its -wal and -shm files.
|
||||
- Scripts: `P=/mnt/storage/scratch/sage-pin-3190ad762`. The reviewed copies
|
||||
are committed beside this file in `repin-3190ad762/`, with
|
||||
`SCRIPTS.sha256`. P0 checks that P matches them.
|
||||
|
||||
## What NEW carries over OLD
|
||||
|
||||
Nothing else landed after 138. `git log 56639c99b..3190ad762` holds rows 152
|
||||
and 138 plus queue, session and packet commits that change nothing in the
|
||||
copy. The one commit after 3190ad762, 7158d62ec (queue revisions 1510-1511),
|
||||
touches only `docs/plans/queue.json` and `docs/plans/QUEUE.md`, which are
|
||||
outside COPY_PATHS and change no schema. The window pins 3190ad762, so it is
|
||||
not in the copy either way.
|
||||
|
||||
- Row 152 (26a0b0fab): when a message waits for a role that `launch.by`
|
||||
can't launch because it isn't in the launch block, the bus says so at once.
|
||||
`broker.mjs` marks the waiting message `launchable`; `talk` prints
|
||||
"<role> isn't running and isn't in the launch block, so <launcher> can't
|
||||
launch it". No schema change.
|
||||
- Row 138 (3190ad762): file-backed north-star specs.
|
||||
`packages/project/src/read.mjs` reads a project's committed spec. The bus
|
||||
serves `spec.read` and `record.write`. The harness gives every role the
|
||||
`read_spec` typed tool and gives `write_record` only to a launch whose
|
||||
narrowed limits hold `record.write`. Eleven `roles/*.json` gained
|
||||
`record.write` in `withinRole`; `vocabulary.mjs` gained the word. Schema
|
||||
3e to 3f: `migrations/3e-3f.sql` adds nullable `decisions.spec_path` and
|
||||
`decisions.spec_commit`. Nothing else in the store changes.
|
||||
|
||||
Spec reads and record writes stay inert live. The live business has no
|
||||
`projects.<id>.repo`, so `spec.read` and `record.write` refuse until Jason
|
||||
runs `mosaic project init`, which is a separate human operation and not part
|
||||
of this window. The `read_spec` tool will be in the chief and pm manifests;
|
||||
calling it will refuse.
|
||||
|
||||
NEW opens nothing older than 3f. A normal bus start under NEW on the 3e store
|
||||
refuses with `schema-mismatch` and changes nothing. So the migration has to
|
||||
run before the bus starts on NEW, and OLD must never open the migrated file:
|
||||
OLD refuses 3f the same way. Both directions are in the rehearsal.
|
||||
|
||||
## Rollback set
|
||||
|
||||
P2 captures it at `/mnt/storage/scratch/sage-pin-rollback-56639c99b/`, the
|
||||
same layout as the 02ee7f640 set: the `mosaic` wrapper,
|
||||
`[email protected]`, `mosaic-webui.service`, both `systemctl --user show`
|
||||
outputs, DROPINS.txt, SOURCES.txt, SHA256SUMS and `verify-live.sh`.
|
||||
`capture-rollback.sh` refuses if the live files do not name OLD or if the
|
||||
directory exists. It ends by running `verify-live.sh`, which must print
|
||||
MATCH three times.
|
||||
|
||||
The bus backup from step 3 is the second half of the rollback set. It records
|
||||
the rollback set's path and SHA256SUMS, so the backup receipt names the code
|
||||
and launch settings it pairs with (MIGRATION.md step 3).
|
||||
|
||||
## Scripts
|
||||
|
||||
All in `$P`. None of them reads or prints a row of the store.
|
||||
|
||||
| Script | Used in | What it does |
|
||||
|---|---|---|
|
||||
| `capture-rollback.sh [OUT]` | P2 | Saves the OLD wrapper and units, as above. Read-only on live files. |
|
||||
| `stopped-check.sh DATA_ROOT` | 3, R | Fails if `bus/writer.lock` exists or any process has a file under `bus/` open (scans `/proc/*/fd`). Prints STOPPED_OK. Never opens the store and never removes a lock. |
|
||||
| `backup.sh DATA_ROOT OUT ROLLBACK_SET` | 3 | Refuses if OUT exists or the rollback set fails its sums. Runs stopped-check, then `cp -p` of `bus.sqlite` and any `-wal`/`-shm` into `OUT/bus` (umask 077, dirs 0700). Writes SOURCE.sha256 (sums of the live files) and SHA256SUMS (sums of the copies) and requires them equal. Writes MODES.txt, LISTING.txt, DATA_ROOT, STAMP, ROLLBACK_SET and ROLLBACK_SET.sha256. Runs stopped-check again, prints the sums and BACKUP_OK. |
|
||||
| `rehearse.sh BACKUP NEW_COPY OLD_COPY` | 4 | Checks the backup against its sums, makes two 0700 copies under `BACKUP/rehearsal-*`, then checks (a) NEW refuses the 3e copy at a normal open, (b) `migrate-schema.mjs` exits 0 ending `bus schema 3f ready`, with its run time, (c) NEW opens the migrated copy at 3f, (d) a second migrate is a validated no-op, (e) OLD refuses the 3f copy, (f) the backup is unchanged. Prints REHEARSAL_PASS or REHEARSAL_FAIL. Leaves the rehearsal copies. |
|
||||
| `verify-open.mjs COPY DATA_ROOT` | 4 | Opens a store with COPY's `store.mjs` the way the bus does at start and prints `OPEN_OK <version>` or `REFUSED <code>`. Refuses to run on `~/.mosaic-dev`. |
|
||||
| `restore.sh DATA_ROOT BACKUP` | R | Refuses a backup taken from another data root. Runs stopped-check, checks the backup sums, renames `bus` to `bus.post-attempt-<UTC stamp>` (deletes nothing), makes a 0700 `bus`, `cp -p` the backup files in, checks the sums and that the directory holds exactly the backup's files. Prints RESTORE_OK. |
|
||||
| `verify-new.sh` | 8 | The live wrapper and both units equal the P2 set with only the copy id swapped, and both units equal what `bus-service.sh render` and `webui-service.sh render` give for NEW. The renders run at check time because they need the NEW copy. 5 × MATCH. |
|
||||
| `check-launch.mjs INSTANCE SINCE COPY_ID` | 8, 9 | As in the 56639c99b window, plus a required check that the manifest's `typed` includes `mcp__mosaic__read_spec`. Keeps the row 140 text and pm `request_staff` checks. Prints `write_record` present or absent as INFO, never as a pass condition. CHECK3_PASS or CHECK3_FAIL; never prompt text. |
|
||||
| `verify-live.sh` | R | Copied into the rollback set by P2. MATCH per file against its SHA256SUMS. |
|
||||
|
||||
Nothing under `scripts/`, `adapters/`, `package*.json`, `RELEASE`,
|
||||
`packages/webui` or `packages/cli/systemd` changed between OLD and NEW. The
|
||||
OLD renders equal the live units byte for byte, so the NEW render is a fair
|
||||
reference.
|
||||
|
||||
## Steps
|
||||
|
||||
```
|
||||
REPO=/mnt/storage/src/mosaic-stack
|
||||
P=/mnt/storage/scratch/sage-pin-3190ad762
|
||||
D=/home/jwoltje/.mosaic-dev
|
||||
OLD=56639c99b03ddca8f5fa84826b19322cd09a713e
|
||||
NEW=3190ad762b8fd5c09ad7515e4f7e609e81ae1af6
|
||||
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-56639c99b
|
||||
```
|
||||
|
||||
### Before the window (P0 to P3, nothing stopped)
|
||||
|
||||
| # | Who | Step |
|
||||
|---|-----|------|
|
||||
| P0 | Mos | `cd $P && sha256sum -c $REPO/agents/sage/work/window-plan/repin-3190ad762/SCRIPTS.sha256`, 9 × OK. |
|
||||
| P1 | Mos | `$REPO/scripts/stack-copy.sh copy $NEW`, rc 0. Then the copy check below, rc 0 with no output. Then `test -f $C/packages/bus/src/migrations/3e-3f.sql && test -f $C/packages/project/src/read.mjs && echo NEW_COPY_HAS_138`. |
|
||||
| P2 | Mos | `bash $P/capture-rollback.sh`. DROPINS lines, then 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. |
|
||||
|
||||
The P1 copy check, the same `git archive` diff as last time:
|
||||
|
||||
```
|
||||
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=$?"
|
||||
```
|
||||
|
||||
P0 to P3 change nothing live and can run any time before the window. The copy
|
||||
and the rollback set stay if the window is postponed.
|
||||
|
||||
### 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` prints STOPPED_OK. Then `bash $P/backup.sh $D $B $RS`, BACKUP_OK. Record the `bus.sqlite` sha256 line it prints, and the -wal and -shm lines if any. A clean bus stop usually leaves only `bus.sqlite`. |
|
||||
| 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 3f 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 | Smoke from tmux, below. |
|
||||
|
||||
Between steps 5 and 7 the live store is 3f while the wrapper and units still
|
||||
name OLD. Run no `mosaic` command in that gap. If the bus unit were started by
|
||||
mistake on OLD it would refuse 3f and change nothing, which is a stop
|
||||
condition, not damage.
|
||||
|
||||
The migration takes the store's exclusive writer lock, checks the 3e source
|
||||
digest, schema version, WAL, quick_check, foreign keys and timestamps, then
|
||||
runs the 3e-3f step in one transaction. A refusal prints `migration refused:
|
||||
<code>` and exits 1. The step's transaction rolls back, so the store is still
|
||||
3e; 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
|
||||
test -f $C/packages/project/src/read.mjs && echo SPEC_READER_PRESENT
|
||||
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
|
||||
```
|
||||
|
||||
The bus start is itself the check that the live store is 3f. A normal open
|
||||
under NEW verifies the 3f digest, version, WAL, quick_check, foreign keys and
|
||||
timestamps; on any mismatch the bus does not start. The bus does not log a
|
||||
schema line at start.
|
||||
|
||||
`check-launch.mjs chief` must show sources under NEW, the prompt digest, the
|
||||
row 140 line and `mcp__mosaic__read_spec` in `typed`. Its INFO line says
|
||||
whether `write_record` is present. That depends on the launch block's limits
|
||||
for chief. Either answer is a fact to record, not a failure.
|
||||
|
||||
### Step 9, smoke from tmux
|
||||
|
||||
```
|
||||
mosaic talk chief "[mos test] re-pin 3190ad762: reply with one line, no staffing request"
|
||||
mosaic talk pm "[mos test] re-pin 3190ad762: reply with one line, no staffing request"
|
||||
node $P/check-launch.mjs pm <step 1 stamp> $NEW # CHECK3_PASS, also needs request_staff
|
||||
```
|
||||
|
||||
If pm is stopped, `talk pm` prints the row 146 waiting line and chief
|
||||
launches pm, as before. Row 152's new line appears only for a role outside
|
||||
the launch block. Exercising it live means talking to such a role, which the
|
||||
PM test limits keep out, so row 152 has no live smoke here. Its CLI and bus
|
||||
tests ran green in both landing gates. No `read_spec` call is part of the
|
||||
smoke: with no project repo it can only refuse.
|
||||
|
||||
## 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 P3 fail, or `stopped-check.sh` fails in step 3. A writer.lock with
|
||||
the bus stopped goes to the stale-lock procedure; nobody deletes it.
|
||||
- `backup.sh` prints anything but BACKUP_OK.
|
||||
- `rehearse.sh` prints REHEARSAL_FAIL.
|
||||
|
||||
From step 5 on, any one means rollback:
|
||||
|
||||
- Step 5 exits non-zero or does not end `bus schema 3f ready`: rollback A.
|
||||
- The bus fails to start on NEW, or the newest launch-log line for this start
|
||||
is `"event":"refused"`, or `journalctl --user -u mosaic-bus@mosaic-stack
|
||||
--since "<UTC stamp> UTC"` shows `PM launch refused` or a store code
|
||||
such as `schema-mismatch` or `integrity-failed`: rollback B. journalctl reads `--since` in local time
|
||||
unless the stamp says UTC.
|
||||
- `/healthz` fails, `stack-copy.sh check` shows anything not on NEW,
|
||||
`verify-new.sh` prints any DIFFER, or `check-launch.mjs chief` prints
|
||||
CHECK3_FAIL: rollback B.
|
||||
|
||||
A step 9 failure is a finding, not an automatic rollback, unless the live
|
||||
talk path is broken. Mos decides.
|
||||
|
||||
## 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
|
||||
|
||||
1. Bus and Console stay stopped.
|
||||
2. `bash $P/restore.sh $D $B`. RESTORE_OK. Note the `bus.post-attempt-*`
|
||||
path it prints; keep it for diagnosis.
|
||||
3. `bash $RS/verify-live.sh`, MATCH × 3 (nothing was installed yet).
|
||||
4. Start the bus, then the Console, on OLD. `stack-copy.sh check` shows OLD.
|
||||
5. Report the refusal code. Nobody retries the migration until it is
|
||||
diagnosed.
|
||||
|
||||
### B: after the start on NEW
|
||||
|
||||
1. Stop the Console, then the bus; both `inactive`.
|
||||
2. `bash $P/restore.sh $D $B`. RESTORE_OK. The post-attempt store, with
|
||||
everything NEW recorded, stays at the printed `bus.post-attempt-*` path.
|
||||
3. Repeat steps 6 and 7 with `$OLD` in place of `$NEW`.
|
||||
4. `bash $RS/verify-live.sh`, MATCH × 3. A DIFFER means stop and diff
|
||||
against the saved files before starting anything.
|
||||
5. Start the bus, then the Console. `stack-copy.sh check` shows OLD.
|
||||
|
||||
OLD never opens the 3f file: step 2 moves it aside before step 5 starts the
|
||||
bus. Data boundary: anything the bus recorded after the step 8 start (at the
|
||||
least, the chief launch) is in the post-attempt store only and is gone from
|
||||
the active one. Mos and Jason decide what happens to it before anyone
|
||||
reconciles. Nobody strips columns or edits evidence in place.
|
||||
|
||||
Fixing forward is the other option after a step 8 failure that is not about
|
||||
the store: the store is 3f and healthy, so another NEW-based copy can be
|
||||
pinned without a restore. That needs its own plan.
|
||||
|
||||
## Notes
|
||||
|
||||
- Land-work gap. From 3190ad762 on, the canonical checkout's
|
||||
`scripts/land-work/read.mjs` imports the 3f SCHEMA_VERSION and refuses
|
||||
against a 3e bus with `bus-schema`. Until step 5, land-work refuses on the
|
||||
live store. Any landing in that gap uses conductor-apply. After a rollback
|
||||
B the gap reopens.
|
||||
- Keep the copies: OLD and NEW, plus the earlier rollback copies. Nothing
|
||||
prunes copies.
|
||||
- Keep `$B` and any `bus.post-attempt-*` directory until Mos says otherwise.
|
||||
They hold live bus data, so they stay 0700 and nobody but Mos opens them.
|
||||
Deleting them is a data deletion and goes to Jason.
|
||||
- The rehearsal copies under `$B/rehearsal-*` also hold live bus data. Same
|
||||
rule.
|
||||
- Record for BUILD-LOG (MIGRATION.md step 7): step 1 stamp, the backup sums,
|
||||
the rehearsal output, the migrate exit and last line, the step 8 output,
|
||||
the chief and pm manifest paths and prompt digests, and the downtime from
|
||||
step 2 to step 8.
|
||||
|
||||
## What Sage ran for this plan
|
||||
|
||||
No part of the plan ran. Nothing live was stopped, started, installed,
|
||||
copied or migrated. No live store file was opened or read. P1 and P2 did not
|
||||
run; the NEW copy and the 56639c99b rollback set do not exist yet.
|
||||
|
||||
Read-only on live state:
|
||||
|
||||
- `stack-copy.sh check` and `systemctl --user show` for the header.
|
||||
- `bus-service.sh render` and `webui-service.sh render` for OLD equal the
|
||||
live units byte for byte.
|
||||
- `verify-new.sh` before the window: 5 × DIFFER, rc 1, as it should be with
|
||||
no rollback set and no NEW copy.
|
||||
- `check-launch.mjs` against the live chief (r3da8da09850d) and pm
|
||||
(r95d7c818d9c7) runs. With `$OLD`: sources, digest, row 140 text and pm
|
||||
request_staff PASS; `read_spec` FAIL; CHECK3_FAIL. With `$NEW`: the
|
||||
sources check fails as well. `write_record` INFO prints absent.
|
||||
- `capture-rollback.sh` to a fixture path, not `$RS`: MATCH × 3 and sums OK.
|
||||
It read the live wrapper and units and ran `systemctl --user show`.
|
||||
- `verify-new.sh` logic with NEW set to OLD against that fixture set:
|
||||
5 × MATCH, so the render commands and paths are right.
|
||||
- The P1 copy check against the OLD copy with `$OLD`: rc 0, no output.
|
||||
|
||||
On fixture data roots under `$P/fixture`, made fresh at 3e by the OLD copy's
|
||||
`store.mjs`, with the canonical checkout (3190ad762 plus the queue commit) as
|
||||
NEW:
|
||||
|
||||
- `stopped-check.sh` fails on a store held open by a process and on a
|
||||
writer.lock, and passes once both are gone.
|
||||
- `backup.sh` refuses a locked store, an existing OUT and a bad rollback set;
|
||||
backs up a store with a leftover WAL and SHM; BACKUP_OK.
|
||||
- `rehearse.sh`: REHEARSAL_PASS twice, migrate about 42 ms on the fixture.
|
||||
The live store is about 1.9 MB plus a 0.5 MB WAL.
|
||||
- A stand-in step 5 on the fixture: rc 0, `bus schema 3f ready`, backup
|
||||
intact.
|
||||
- `restore.sh` refuses a backup from another data root and a locked store;
|
||||
then RESTORE_OK. The restored store opens at 3e under OLD and refuses
|
||||
under NEW; the post-attempt directory is kept.
|
||||
- `verify-open.mjs` refuses `~/.mosaic-dev` before opening anything.
|
||||
@@ -0,0 +1,9 @@
|
||||
1fd4e10f1847fbae6e64ca5d9277ead5a68e2c575594993b919181c09d0b6b35 backup.sh
|
||||
979e4327603c53c484bdd1964a133dda9192b492b1089772b6c57b37dad9219a capture-rollback.sh
|
||||
fc41a82f78da8809977b57275b7d72cc1fa155f5dc83c797b412fad6f2d97ca7 rehearse.sh
|
||||
88fdb8624221bc8988d780f9212465c096df2980a46ac21c3799769d87bd5877 restore.sh
|
||||
422021b2e9fff30c3747d5fff7d1e8bd2ad16ed15cd3e11c97472c75b74e8efc stopped-check.sh
|
||||
8bf05f2d05166c535f87738c5a4bf2692e2324e107ee7e2e7f58af4658f212a9 verify-live.sh
|
||||
f2754716f7811cb0786af2d9476ebe6bf4a894a43e0a57cf6b74bd5d5353197d verify-new.sh
|
||||
0697d38cf2d97d2891be57a5feec55331c46de670e5cf25b452c67db619d77a5 check-launch.mjs
|
||||
775f57263ac236f22de0c3b12e5e19ef7ca66493dc0a4c4212cebb11a4e66b53 verify-open.mjs
|
||||
+27
@@ -0,0 +1,27 @@
|
||||
#!/bin/bash
|
||||
# Re-pin 3190ad762, step 3: an owner-only copy of the stopped bus store with sha256 sums.
|
||||
# Usage: backup.sh DATA_ROOT OUT ROLLBACK_SET. Copies bus.sqlite and any -wal/-shm file; never opens them.
|
||||
# OUT must not exist. ROLLBACK_SET is the P2 capture of the OLD wrapper and units; its path and SHA256SUMS
|
||||
# go into OUT so the backup receipt names the code and launch settings it pairs with (MIGRATION.md step 3).
|
||||
# Prints the sums and BACKUP_OK.
|
||||
set -o pipefail
|
||||
U='usage: backup.sh DATA_ROOT OUT ROLLBACK_SET'
|
||||
D=${1:?$U}; O=${2:?$U}; RS=${3:?$U}; H=$(cd "$(dirname "$0")" && pwd)
|
||||
[ -e "$O" ] && { echo "refusing: $O exists"; exit 1; }
|
||||
(cd "$RS" && sha256sum -c --quiet SHA256SUMS) || { echo "refusing: $RS is not an intact rollback set"; exit 1; }
|
||||
bash "$H/stopped-check.sh" "$D" || exit 1
|
||||
[ -f "$D/bus/bus.sqlite" ] || { echo "refusing: no $D/bus/bus.sqlite"; exit 1; }
|
||||
files=(); for f in bus.sqlite bus.sqlite-wal bus.sqlite-shm; do [ -f "$D/bus/$f" ] && files+=("$f"); done
|
||||
umask 077
|
||||
mkdir -m 700 "$O" "$O/bus" || exit 1
|
||||
for f in "${files[@]}"; do cp -p "$D/bus/$f" "$O/bus/$f" || { echo "FAIL copy of $f"; exit 1; }; done
|
||||
(cd "$D/bus" && sha256sum "${files[@]}") > "$O/SOURCE.sha256" || exit 1
|
||||
(cd "$O/bus" && sha256sum "${files[@]}") > "$O/SHA256SUMS" || exit 1
|
||||
cmp -s "$O/SOURCE.sha256" "$O/SHA256SUMS" || { echo "FAIL the copy differs from the source"; exit 1; }
|
||||
(cd "$D/bus" && stat -c '%n %a %U %s %y' . "${files[@]}") > "$O/MODES.txt"
|
||||
ls -1A "$D/bus" > "$O/LISTING.txt"
|
||||
echo "$D" > "$O/DATA_ROOT"; date -u +%FT%TZ > "$O/STAMP"
|
||||
echo "$RS" > "$O/ROLLBACK_SET"; cp "$RS/SHA256SUMS" "$O/ROLLBACK_SET.sha256"
|
||||
bash "$H/stopped-check.sh" "$D" > /dev/null || { echo "FAIL a holder appeared during the copy; this backup is not consistent"; exit 1; }
|
||||
cat "$O/SHA256SUMS"
|
||||
echo BACKUP_OK
|
||||
@@ -0,0 +1,21 @@
|
||||
#!/bin/bash
|
||||
# Re-pin 3190ad762, pre-window P2: save the live 56639c99b wrapper and unit files as the rollback set.
|
||||
# Read-only on live files. Usage: capture-rollback.sh [OUT]; OUT defaults to sage-pin-rollback-56639c99b
|
||||
# and must not exist. Ends with verify-live.sh, which must print MATCH three times.
|
||||
OUT=${1:-/mnt/storage/scratch/sage-pin-rollback-56639c99b}; H=$(cd "$(dirname "$0")" && pwd)
|
||||
OLD=56639c99b03ddca8f5fa84826b19322cd09a713e; U=$HOME/.config/systemd/user; W=$HOME/.npm-global/bin/mosaic
|
||||
[ -e "$OUT" ] && { echo "refusing: $OUT exists"; exit 1; }
|
||||
for f in "$W" "$U/[email protected]" "$U/mosaic-webui.service"; do
|
||||
grep -q "$OLD" "$f" || { echo "refusing: $f does not name $OLD; the live pin is not the expected one"; exit 1; }
|
||||
done
|
||||
mkdir "$OUT" || exit 1
|
||||
cp -p "$W" "$OUT/mosaic" && cp -p "$U/[email protected]" "$U/mosaic-webui.service" "$OUT/" || exit 1
|
||||
for u in [email protected] mosaic-webui.service; do systemctl --user show "$u" > "$OUT/show-$u.txt"; done
|
||||
for d in "$U/[email protected]" "$U/[email protected]" "$U/mosaic-webui.service.d"; do
|
||||
if [ -e "$d" ]; then echo "$d: present"; ls -la "$d"; else echo "$d: absent"; fi
|
||||
done > "$OUT/DROPINS.txt"
|
||||
printf 'mosaic\t%s\[email protected]\t%s\nmosaic-webui.service\t%s\n' "$W" "$U/[email protected]" "$U/mosaic-webui.service" > "$OUT/SOURCES.txt"
|
||||
cp "$H/verify-live.sh" "$OUT/verify-live.sh" && chmod 755 "$OUT/verify-live.sh"
|
||||
(cd "$OUT" && sha256sum mosaic [email protected] mosaic-webui.service DROPINS.txt SOURCES.txt verify-live.sh show-*.txt) > "$OUT/SHA256SUMS"
|
||||
cat "$OUT/DROPINS.txt"
|
||||
bash "$OUT/verify-live.sh"
|
||||
@@ -0,0 +1,35 @@
|
||||
// Re-pin 3190ad762, check 3: the newest launch of INSTANCE since SINCE ran from COPY and carries row 138's spec tool.
|
||||
// Usage: node check-launch.mjs INSTANCE SINCE_ISO COPY_ID. Prints hashes and PASS/FAIL/INFO lines only, never prompt text.
|
||||
// The row 140 text and pm's request_staff stay as regression checks from the 56639c99b window.
|
||||
import { readFileSync } from 'node:fs';
|
||||
import { createHash } from 'node:crypto';
|
||||
import { dirname, join } from 'node:path';
|
||||
const [instance, since, copyId] = process.argv.slice(2);
|
||||
if (!instance || !since || !/^[0-9a-f]{40}$/.test(copyId ?? '')) { console.error('usage: node check-launch.mjs INSTANCE SINCE_ISO COPY_ID'); process.exit(4); }
|
||||
const home = process.env.HOME, copy = `${home}/.local/share/mosaic-stack/copies/${copyId}`;
|
||||
const log = readFileSync(`${home}/.mosaic-dev/launches/mosaic-stack.jsonl`, 'utf8').trim().split('\n').map(l => JSON.parse(l));
|
||||
const launch = log.filter(l => l.event === 'launch' && l.instance === instance && l.at >= since).at(-1);
|
||||
if (!launch) { console.log(`FAIL no launch of ${instance} since ${since}`); process.exit(1); }
|
||||
const manifest = JSON.parse(readFileSync(launch.manifest, 'utf8')); // the log field is the path (9056fe5ad)
|
||||
const prompt = readFileSync(join(dirname(launch.manifest), 'prompt.md'), 'utf8');
|
||||
const sha = createHash('sha256').update(prompt).digest('hex');
|
||||
// One line row 140 added to each contract (git diff 02ee7f640 56639c99b -- roles/), as the prompt carries it.
|
||||
const want = {
|
||||
chief: { file: 'roles/chief-of-staff.md', text: 'A request never widens the limits.' },
|
||||
pm: { file: 'roles/pm.md', text: 'with the `request_staff` tool: name the task and each role with how' },
|
||||
}[instance];
|
||||
const typed = manifest.typed ?? [];
|
||||
const checks = [
|
||||
['run', true, `${launch.run} at ${launch.at} launchedBy ${launch.launchedBy} manifest ${launch.manifest}`],
|
||||
['sources from copy', Object.values(manifest.sources).every(s => s.path.startsWith(copy + '/')), Object.values(manifest.sources).map(s => s.path.split('/copies/')[1]?.slice(0, 9)).join(',')],
|
||||
['prompt.md digest matches manifest', manifest.files['prompt.md'] === sha, sha],
|
||||
[`prompt carries ${want?.file} row 140 text`, !!want && prompt.includes(want.text), want?.text ?? 'no expectation for this instance'],
|
||||
// Row 138: harness tools.mjs adds read_spec for every role, so its absence means the launch did not use NEW.
|
||||
['typed has read_spec (row 138)', typed.includes('mcp__mosaic__read_spec'), `${typed.length} typed tools`],
|
||||
];
|
||||
if (instance === 'pm') checks.push(['pm typed has request_staff', typed.includes('mcp__mosaic__request_staff'), `${typed.length} typed tools`]);
|
||||
let ok = true;
|
||||
for (const [name, pass, detail] of checks) { ok &&= pass; console.log(`${pass ? 'PASS' : 'FAIL'} ${name}: ${detail}`); }
|
||||
// write_record appears only when the launch's narrowed limits hold record.write, which the business launch block decides.
|
||||
console.log(`INFO write_record ${typed.includes('mcp__mosaic__write_record') ? 'present' : 'absent'} (depends on the launch limits; not a pass condition)`);
|
||||
console.log(ok ? 'CHECK3_PASS' : 'CHECK3_FAIL'); process.exit(ok ? 0 : 1);
|
||||
@@ -0,0 +1,30 @@
|
||||
#!/bin/bash
|
||||
# Re-pin 3190ad762, step 4: the 3e to 3f migration on disposable copies of the step 3 backup.
|
||||
# Usage: rehearse.sh BACKUP NEW_COPY OLD_COPY. Never touches the live store or the backup itself.
|
||||
# The rehearsal copies stay under BACKUP/rehearsal-*; they hold bus data, so they are 0700 like the backup.
|
||||
set -o pipefail
|
||||
O=${1:?usage: rehearse.sh BACKUP NEW_COPY OLD_COPY}; C=${2:?usage}; OC=${3:?usage}; H=$(cd "$(dirname "$0")" && pwd)
|
||||
(cd "$O/bus" && sha256sum -c --quiet ../SHA256SUMS) || { echo "FAIL the backup does not match its SHA256SUMS"; exit 1; }
|
||||
umask 077
|
||||
mk() { local r; r=$(mktemp -d "$O/rehearsal-$1.XXXXXX") && mkdir -m 700 "$r/bus" && cp -p "$O"/bus/* "$r/bus/" && echo "$r"; }
|
||||
R1=$(mk refuse) && R2=$(mk migrate) || { echo "FAIL could not make the rehearsal copies"; exit 1; }
|
||||
rc=0
|
||||
pass() { echo "PASS $1"; }
|
||||
fail() { echo "FAIL $1"; rc=1; }
|
||||
out=$(node "$H/verify-open.mjs" "$C" "$R1" 2>&1)
|
||||
[ "$out" = "REFUSED schema-mismatch" ] && pass "NEW refuses the 3e store at a normal open (no auto-migrate)" || fail "NEW open of the 3e copy: $out"
|
||||
t0=$(date +%s%N)
|
||||
out=$(node "$C/packages/bus/src/migrate-schema.mjs" "$R2" 2>&1); mrc=$?
|
||||
ms=$(( ($(date +%s%N) - t0) / 1000000 ))
|
||||
printf '%s\n' "$out" | sed 's/^/ migrate: /'
|
||||
[ $mrc = 0 ] && [ "$(printf '%s\n' "$out" | tail -1)" = "bus schema 3f ready" ] && pass "migrate-schema rc 0, bus schema 3f ready, ${ms} ms" || fail "migrate-schema rc $mrc"
|
||||
out=$(node "$H/verify-open.mjs" "$C" "$R2" 2>&1)
|
||||
[ "$out" = "OPEN_OK 3f" ] && pass "NEW opens the migrated copy: $out" || fail "NEW open of the migrated copy: $out"
|
||||
out=$(node "$C/packages/bus/src/migrate-schema.mjs" "$R2" 2>&1); mrc=$?
|
||||
[ $mrc = 0 ] && [ "$(printf '%s\n' "$out" | tail -1)" = "bus schema 3f ready" ] && pass "a second run is a validated no-op" || fail "second migrate-schema run rc $mrc: $out"
|
||||
out=$(node "$H/verify-open.mjs" "$OC" "$R2" 2>&1)
|
||||
[ "$out" = "REFUSED schema-mismatch" ] && pass "OLD refuses the 3f store" || fail "OLD open of the migrated copy: $out"
|
||||
(cd "$O/bus" && sha256sum -c --quiet ../SHA256SUMS) && pass "backup unchanged" || fail "the backup changed"
|
||||
echo "rehearsal copies: $R1 $R2"
|
||||
[ $rc = 0 ] && echo REHEARSAL_PASS || echo REHEARSAL_FAIL
|
||||
exit $rc
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
#!/bin/bash
|
||||
# Re-pin 3190ad762, rollback R2: put the step 3 backup back as DATA_ROOT's bus store.
|
||||
# Usage: restore.sh DATA_ROOT BACKUP. Deletes nothing: the post-attempt bus directory is renamed to
|
||||
# DATA_ROOT/bus.post-attempt-<UTC stamp> first, so it stays for reconciliation. Prints RESTORE_OK.
|
||||
set -o pipefail
|
||||
D=${1:?usage: restore.sh DATA_ROOT BACKUP}; O=${2:?usage: restore.sh DATA_ROOT BACKUP}; H=$(cd "$(dirname "$0")" && pwd)
|
||||
[ "$(cat "$O/DATA_ROOT" 2>/dev/null)" = "$D" ] || { echo "refusing: $O was not taken from $D"; exit 1; }
|
||||
bash "$H/stopped-check.sh" "$D" || exit 1
|
||||
(cd "$O/bus" && sha256sum -c --quiet ../SHA256SUMS) || { echo "FAIL the backup does not match its SHA256SUMS; stop and diagnose"; exit 1; }
|
||||
A="$D/bus.post-attempt-$(date -u +%Y%m%dT%H%M%SZ)"
|
||||
[ -e "$A" ] && { echo "refusing: $A exists"; exit 1; }
|
||||
mv "$D/bus" "$A" || { echo "FAIL could not move $D/bus aside"; exit 1; }
|
||||
echo "post-attempt store kept at $A"
|
||||
umask 077
|
||||
mkdir -m 700 "$D/bus" && cp -p "$O"/bus/* "$D/bus/" || { echo "FAIL copy-in; the post-attempt store is still at $A"; exit 1; }
|
||||
(cd "$D/bus" && sha256sum -c --quiet "$O/SHA256SUMS") || { echo "FAIL the restored files do not match the backup"; exit 1; }
|
||||
want=$(awk '{print $2}' "$O/SHA256SUMS" | sort); got=$(ls -1A "$D/bus" | sort)
|
||||
[ "$want" = "$got" ] || { echo "FAIL $D/bus holds files the backup does not"; exit 1; }
|
||||
echo RESTORE_OK
|
||||
@@ -0,0 +1,15 @@
|
||||
#!/bin/bash
|
||||
# Re-pin 3190ad762, step 3a: nothing holds DATA_ROOT's bus store. Read-only; it never opens the store.
|
||||
# Prints STOPPED_OK, or a FAIL line per problem.
|
||||
D=${1:?usage: stopped-check.sh DATA_ROOT}; B="$D/bus"; rc=0
|
||||
[ -d "$B" ] || { echo "FAIL no $B"; exit 1; }
|
||||
if [ -e "$B/writer.lock" ]; then
|
||||
echo "FAIL $B/writer.lock is present: diagnose it under the stale-lock procedure, never delete it"; rc=1
|
||||
fi
|
||||
holders=$(for f in /proc/[0-9]*/fd/*; do
|
||||
t=$(readlink "$f" 2>/dev/null) || continue
|
||||
case "$t" in "$B"/*) f=${f#/proc/}; echo "${f%%/*}";; esac
|
||||
done | sort -u | tr '\n' ' ')
|
||||
if [ -n "$holders" ]; then echo "FAIL files under $B are open by pid $holders"; rc=1; fi
|
||||
[ $rc = 0 ] && echo STOPPED_OK
|
||||
exit $rc
|
||||
@@ -0,0 +1,12 @@
|
||||
#!/bin/bash
|
||||
# Rollback B step 4: compare the live wrapper and unit files with the 56639c99b set saved before the window.
|
||||
# MATCH on all three means the reinstall reproduced the pre-window files byte for byte.
|
||||
R=$(cd "$(dirname "$0")" && pwd)
|
||||
cd "$R" || exit 9
|
||||
rc=0
|
||||
while IFS=$'\t' read -r name live; do
|
||||
want=$(awk -v f="$name" '$2 == f {print $1}' SHA256SUMS)
|
||||
got=$(sha256sum "$live" | cut -d' ' -f1)
|
||||
if [ -n "$want" ] && [ "$want" = "$got" ]; then echo "MATCH $name"; else echo "DIFFER $name ($live)"; rc=1; fi
|
||||
done < SOURCES.txt
|
||||
exit $rc
|
||||
@@ -0,0 +1,16 @@
|
||||
#!/bin/bash
|
||||
# Re-pin 3190ad762, step 8: the live wrapper and unit files are the 56639c99b set with only the copy id changed,
|
||||
# and the units equal what bus-service.sh and webui-service.sh render for NEW. The renders run at check time,
|
||||
# because they need the NEW copy, which P1 makes. Nothing under scripts/ changed between OLD and NEW.
|
||||
OLD=56639c99b03ddca8f5fa84826b19322cd09a713e; NEW=3190ad762b8fd5c09ad7515e4f7e609e81ae1af6
|
||||
R=/mnt/storage/scratch/sage-pin-rollback-56639c99b; S=/mnt/storage/src/mosaic-stack/scripts; U=$HOME/.config/systemd/user
|
||||
rc=0
|
||||
chk() { if sed "s/$OLD/$NEW/g" "$R/$1" | cmp -s - "$2"; then echo "MATCH $1 (id swapped)"; else echo "DIFFER $1 ($2)"; rc=1; fi; }
|
||||
chk mosaic "$HOME/.npm-global/bin/mosaic"
|
||||
chk [email protected] "$U/[email protected]"
|
||||
chk mosaic-webui.service "$U/mosaic-webui.service"
|
||||
"$S/bus-service.sh" render --copy "$NEW" --pm --claude /home/jwoltje/.local/bin/claude 2>&1 | cmp -s - "$U/[email protected]" \
|
||||
&& echo "MATCH bus render" || { echo "DIFFER bus render"; rc=1; }
|
||||
"$S/webui-service.sh" render --copy "$NEW" --port 7332 --public-host mosaic.woltje.com --auth-header X-authentik-username \
|
||||
--allow-user jason.woltje --send 2>&1 | cmp -s - "$U/mosaic-webui.service" && echo "MATCH webui render" || { echo "DIFFER webui render"; rc=1; }
|
||||
exit $rc
|
||||
@@ -0,0 +1,19 @@
|
||||
// Re-pin 3190ad762: open DATA_ROOT's bus store with COPY's store.mjs the way the bus does at start (no migration).
|
||||
// Usage: node verify-open.mjs COPY DATA_ROOT. Prints OPEN_OK <schema_version> or REFUSED <code>. Never reads rows.
|
||||
// Refuses the live data root: this is for disposable rehearsal copies only.
|
||||
import { realpathSync } from 'node:fs';
|
||||
import { join } from 'node:path';
|
||||
import { pathToFileURL } from 'node:url';
|
||||
const [copy, root] = process.argv.slice(2);
|
||||
if (!copy || !root) { console.error('usage: node verify-open.mjs COPY DATA_ROOT'); process.exit(4); }
|
||||
if (realpathSync(root) === realpathSync(join(process.env.HOME, '.mosaic-dev'))) {
|
||||
console.error('refusing: verify-open.mjs never opens the live data root'); process.exit(4);
|
||||
}
|
||||
const { Store } = await import(pathToFileURL(join(copy, 'packages/bus/src/store.mjs')).href);
|
||||
let store;
|
||||
try {
|
||||
store = new Store(root);
|
||||
console.log(`OPEN_OK ${store.get("SELECT value FROM meta WHERE key='schema_version'").value}`);
|
||||
} catch (e) {
|
||||
console.log(`REFUSED ${e.message}`); process.exitCode = 1;
|
||||
} finally { store?.close(); }
|
||||
@@ -1059,3 +1059,4 @@ are never rewritten or removed; corrections are new entries.
|
||||
2026-10-16 01:37:53 UTC | Rocko (Codex gpt-6.1-sol, d99326cc) | row 138 #1632 review round 2 | r4 on 26a0b0fab; D1-D4 tests, async socket deadline and wording fixes; project 30/bus 294/business 109/harness 105/cli 215 green, 59/59 mutants killed; 43 hashes restored, process sweep empty; slot clear 01:37:17Z; candidate e3f9a4008c8cd0098f028aa17e9cbde8b15d3b64bc13f3ee306632070a318f70; no live action.
|
||||
2026-10-16T01:29:26Z | Dewey (Claude Opus 5.5, 84fb346c) | row 138 round 1 review | Request changes on 75e07b65 (42 paths, base 26a0b0fab): design, grants, migration, adapter injection, snapshots and row 152 all hold; 16 touched files singly 118/118; four mutants D1-D4 survived full bus 289/289 and project 29/29 suites in the sage-0491 slot (01:24:34-01:25:25Z), so four tests are asked for. Comment 28752 as dewey; rev 1504 uncommitted for Sage's sweep; packet ff86a4473, not pushed; slot cleared 01:26:53Z.
|
||||
2026-10-16T01:43:02Z | Dewey (Claude Opus 5.5, 84fb346c) | row 138 round 2 review | Approve on e3f9a400 (43 paths, base 26a0b0fab): my round 1 D1-D4 mutants, rerun unchanged, and D5 (removes the N-a setTimeout(120_000)) all killed, each by its new test only, full bus 294 and project 30 suites, in my slot 01:40:40-01:41:55Z; 17 touched files singly 124/124; N-a confined to the async project branch. Notes: 120 s covers one git process, not a whole write; set before admission. Comment 28754 as dewey; rev 1510 uncommitted for Sage's sweep; packet de3e93ca6, not pushed.
|
||||
2026-10-16T02:08:59Z | Sage (Claude Opus 5.5, 1ef1e4f8) | window plan for the re-pin to 3190ad762 (rows 152, 138, bus 3e to 3f) | Wrote agents/sage/work/window-plan/repin-3190ad762.md and its 9 scripts with SCRIPTS.sha256, on Mos directive re sage-0494. Tested on fixture stores and read-only live controls; ran no step of the plan.
|
||||
|
||||
Reference in New Issue
Block a user