sage: window plan for the 52f35e7a0 re-pin, rows 135, 139, 150 and 137
Re-pin3190ad762->52f35e7a0, no migration, per Mos's 20:58Z message. The plan puts row 137's refs/heads/root write in the window. One working repo, mosaic-stack/stack.git. Its seed root is the canonical checkout. Jason's ~/.mosaic-dev/projects/mosaic-stack/stack is named and is not touched. working-refs.sh records the refs before and after; root-rollback.sh deletes only the seed-created refs/heads/root, matched by sha, and leaves main alone. Copy and rollback set (sage-pin-rollback-3190ad762) are made and checked. Nothing live ran. Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
@@ -0,0 +1,335 @@
|
||||
# Re-pin for rows 135, 139, 150 and 137, no migration
|
||||
|
||||
Sage, 2026-10-16. Plan for Mos's window, per Mos's 20:58Z message: copy and
|
||||
rollback set first, then the plan, nothing live before Mos's go. Both are
|
||||
done and checked. Rows 139 and 137 share this re-pin and both SMOKE runs
|
||||
(Mos's earlier ruling). Mos runs every step. Sage ran none of it; the
|
||||
controls Sage did run are listed at the end.
|
||||
|
||||
No schema, migration, store.mjs, bus-service.sh, webui-service.sh,
|
||||
stack-copy.sh, systemd, adapters, package*.json or RELEASE change between OLD
|
||||
and NEW, so this is the `repin-56639c99b.md` shape. The one new part is the
|
||||
working repository's `refs/heads/root`, which Mos asked to have in the plan.
|
||||
It has its own section, its own pre and post record, and its own rollback.
|
||||
|
||||
- OLD, live now: `3190ad762b8fd5c09ad7515e4f7e609e81ae1af6`, bus schema 3f.
|
||||
Bus (MainPID 3575632) and Console (3575635) started 2026-10-16 12:29:51
|
||||
CDT. `stack-copy.sh check` at 21:06:55Z: bus, Console and CLI on OLD.
|
||||
- NEW: `52f35e7a0933e9fa845f59860a5de1709f9e5756`, on origin `refactor`
|
||||
since 20:59:59Z. Copy, made by Sage:
|
||||
`/home/jwoltje/.local/share/mosaic-stack/copies/52f35e7a0933e9fa845f59860a5de1709f9e5756`.
|
||||
Bus schema 3f, the same as OLD.
|
||||
- Rollback set: `RS=/mnt/storage/scratch/sage-pin-rollback-3190ad762`, made by
|
||||
Sage with `capture-rollback.sh`. MATCH × 3.
|
||||
- Scripts: `P=/mnt/storage/scratch/sage-pin-52f35e7a0`. The reviewed copies
|
||||
are committed beside this file in `repin-52f35e7a0/`, with
|
||||
`SCRIPTS.sha256` (7 files). P0 checks that P matches them.
|
||||
|
||||
```
|
||||
REPO=/mnt/storage/src/mosaic-stack
|
||||
P=/mnt/storage/scratch/sage-pin-52f35e7a0
|
||||
RS=/mnt/storage/scratch/sage-pin-rollback-3190ad762
|
||||
D=/home/jwoltje/.mosaic-dev
|
||||
OLD=3190ad762b8fd5c09ad7515e4f7e609e81ae1af6
|
||||
NEW=52f35e7a0933e9fa845f59860a5de1709f9e5756
|
||||
C=/home/jwoltje/.local/share/mosaic-stack/copies/$NEW
|
||||
```
|
||||
|
||||
## What NEW carries over OLD
|
||||
|
||||
Four commits change the copy. Everything else between OLD and NEW is queue,
|
||||
session, BUILD-LOG and packet commits outside COPY_PATHS.
|
||||
|
||||
- Row 135 (7473dcd2a): PRD 1.5 text and `packages/tasks/requirements.json`.
|
||||
No runtime behavior change.
|
||||
- Row 139 (63c82f1c4): a gated decision that cites a confirmed spec commit
|
||||
goes to the business's arbiter for its domain instead of Jason. Hard waits
|
||||
still go to Jason. Under Q169, a revoke of the assistant is Jason's or the
|
||||
chief's alone. `GATED_ONLY` and `vocabulary.mjs` changed, which is why P4
|
||||
loads the live business file under NEW.
|
||||
- Row 150 (0f7bca083): `mosaic inbox --read <id>...` and `--read-all` mark
|
||||
the human's messages read, and the Console's send section names them. The
|
||||
Console's `public/bus.js` changes; Mos's decision 123 go on public diff
|
||||
096d922c covers it.
|
||||
- Row 137 (f1ee9a989): the `land_work` tool, the broker's `landHeld`
|
||||
host op, called before every launch, and the new working-repository seed.
|
||||
No role definition in NEW's `roles/` grants git.merge.protected, so no
|
||||
launch can get `land_work` and nothing can land through the bus before
|
||||
row 149.
|
||||
|
||||
## refs/heads/root (Mos's 20:58Z item 1)
|
||||
|
||||
### What writes it
|
||||
|
||||
`seed()` in `packages/seat/src/working.mjs:164`. Under NEW it runs
|
||||
`fetch <root> +HEAD:refs/heads/root`, forced, into the working repository.
|
||||
Then it creates `main` if `main` is absent, or moves `main` to the new root
|
||||
with a compare-and-swap `update-ref` only when `main` is an ancestor of it.
|
||||
Otherwise `main` stays where it is, with no log line (Dewey's finding 2,
|
||||
below). OLD's seed instead forced `+HEAD:refs/heads/main` on every call.
|
||||
|
||||
The seed runs only inside a push (working.mjs:207), a review fetch (263) or a
|
||||
land (317). It never runs at bus start, and no window step and no read-only
|
||||
smoke step causes one. A land needs `land_work`, which no launch can have. So
|
||||
the first write of `refs/heads/root` is the first coder push or reviewer
|
||||
fetch through the git socket after the re-pin.
|
||||
|
||||
### Every repository involved
|
||||
|
||||
There is one working repository. Its seed root is the canonical checkout,
|
||||
not Jason's project repo.
|
||||
|
||||
| Repository | Role | Now | After the first seed under NEW |
|
||||
|---|---|---|---|
|
||||
| `/home/jwoltje/.mosaic-dev/working/mosaic-stack/stack.git` | working repo the seed writes (`<dataRoot>/working/<business>/<project>.git`) | HEAD `refs/heads/master` (dangling); `main` fc5b342f839498b46499fe8f00c79dd78ba9e1e4; no `refs/heads/root`; 15 `refs/heads/work/*` refs | `refs/heads/root` := the seed root's HEAD at that moment; `main` fast-forwards fc5b342f8 → that commit; work refs untouched |
|
||||
| `/mnt/storage/src/mosaic-stack` | seed root, `projects.stack.root` in `~/.config/mosaic-dev/businesses/mosaic-stack.json`; only read | HEAD 22dbc2b28169cf3652126b8c51bdfc8f7c433b57 at planning time | unchanged by the seed |
|
||||
| `/home/jwoltje/.mosaic-dev/projects/mosaic-stack/stack` | Jason's project repo, row 138's spec repo (`packages/project/src/paths.mjs:32`) | HEAD ae0b17c9586e51da7bc1b7e548c6acf913a55665 ("Initialize project layout v1"); no `refs/heads/root` | **not touched.** The seed never reads or writes it. `read_spec` only reads it. |
|
||||
|
||||
Mos's 20:58Z premise was that the write could land in Jason's
|
||||
`~/.mosaic-dev/projects/mosaic-stack/stack`. It can't. The seed's root is
|
||||
`projects.<id>.root`, the canonical checkout. The pre and post records still
|
||||
read Jason's repo and fail if its HEAD moves or a root ref appears there.
|
||||
|
||||
### The sha it gets
|
||||
|
||||
`refs/heads/root` gets the canonical checkout's HEAD at the moment of the
|
||||
first seed. That was 22dbc2b28 at planning time, and the commit of this plan
|
||||
moves it again. Every landing and every seat commit in the canonical checkout
|
||||
moves it, including local commits not yet pushed. OLD's seed carried those
|
||||
into `main` the same way. So the sha can't be fixed in advance. P5 records the
|
||||
HEAD at window time; `working-refs.sh post` prints the actual sha as a
|
||||
`ROOT_SHA` line, and the rollback deletes the ref only if it still names that
|
||||
sha.
|
||||
|
||||
`main` is fc5b342f8, an ancestor of the canonical HEAD, so the first seed
|
||||
fast-forwards `main` to the root. OLD's next push would force the same move.
|
||||
The rollback leaves `main` where the seed put it, as Mos asked. The repo's
|
||||
dangling `HEAD -> refs/heads/master` predates this window and NEW doesn't
|
||||
change it. I saw it; I didn't investigate it.
|
||||
|
||||
### The record (P5 and step 7)
|
||||
|
||||
`working-refs.sh` is read-only: `git rev-parse`, `symbolic-ref`,
|
||||
`for-each-ref` and `merge-base`, with no system or global git config and hooks
|
||||
off. It reads only the business file's `projects` block, and never the bus
|
||||
store.
|
||||
|
||||
- `pre OUT` lists every `$D/working/*/*.git` with its path, HEAD, `main`, root
|
||||
ref and work-ref count. For each it lists the seed root and that root's HEAD
|
||||
now, plus Jason's project repo with its HEAD and root ref. It prints PRE_STOP
|
||||
and writes nothing if any of these repos already has `refs/heads/root`. It
|
||||
prints the predicted first seed and writes OUT plus OUT.sha256. It refuses an
|
||||
existing OUT.
|
||||
- `post RECORD` checks the record's sum, then for each repo:
|
||||
- `main` must be unchanged, or a fast-forward of the recorded `main`; else
|
||||
DIFFER.
|
||||
- `ROOT_SHA <repo> <sha>`, or "absent: no seed has run under NEW".
|
||||
- Jason's repo must be unchanged with no root ref; else DIFFER.
|
||||
It ends with POST_OK or POST_DIFFER.
|
||||
|
||||
### The root rollback
|
||||
|
||||
`root-rollback.sh RECORD REPO SHA` for each `ROOT_SHA` line from `post`:
|
||||
|
||||
- The record must pass its sum and name REPO, so the ref can only be one that
|
||||
didn't exist at P5.
|
||||
- The bus unit must be `inactive` or `failed`, and if
|
||||
`$D/bus-host/host.lock` exists, its pid must not be running. The lock file
|
||||
can outlive a stop, so the script checks the pid, not the file.
|
||||
- If `refs/heads/root` is absent, it prints NOTHING and exits 0. If it names
|
||||
another sha, it prints STOP and deletes nothing.
|
||||
- Otherwise `git update-ref -d refs/heads/root SHA`, a compare-and-delete. It
|
||||
then checks every other `refs/heads/*` ref, `main` included, is as it was,
|
||||
and prints `ROOT_REMOVED <repo> <sha>`.
|
||||
|
||||
It never touches `main`, the work refs, the seed root or Jason's repo.
|
||||
|
||||
## Steps
|
||||
|
||||
### Before the window (P0 to P5, nothing stopped)
|
||||
|
||||
| # | Who | Step |
|
||||
|---|-----|------|
|
||||
| P0 | Mos | `cd $P && sha256sum -c $REPO/agents/sage/work/window-plan/repin-52f35e7a0/SCRIPTS.sha256`, 7 × OK. |
|
||||
| P1 | Sage, done; Mos checks | The copy exists. Copy check below, rc 0 with no output. Then `grep -l git.merge.protected $C/roles/*.json` prints nothing (no role can land). |
|
||||
| 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`. |
|
||||
| P4 | Mos | `node $P/business-check.mjs $NEW` prints counts and LOAD_OK. REFUSED means the live business file fails under NEW's loader and roles; stop. |
|
||||
| P5 | Mos | `bash $P/working-refs.sh pre $P/refs-window.tsv`. PRE_OK, one repo, the predicted first seed, and Jason's repo HEAD ae0b17c9 with no root ref. PRE_STOP ends the window before it starts. Run P5 last, close to step 1, so the recorded root HEAD is current. |
|
||||
|
||||
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=$?"
|
||||
```
|
||||
|
||||
P0 to P4 change nothing live and can run any time. The copy and the
|
||||
rollback set stay if the window is postponed. P5 writes only under `$P`.
|
||||
|
||||
### 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 7 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 | `$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`. |
|
||||
| 4 | Mos | `$REPO/scripts/stack-copy.sh wrapper $NEW`. |
|
||||
| 5 | Mos | Start the bus, then the Console. |
|
||||
| 6 | Mos | Step 6 checks, below. |
|
||||
| 7 | Mos | `bash $P/working-refs.sh post $P/refs-window.tsv`. POST_OK, `main unchanged`, `refs/heads/root absent`, Jason's repo unchanged. |
|
||||
| 8 | Mos | Smokes from tmux, below. |
|
||||
|
||||
### Step 6 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
|
||||
```
|
||||
|
||||
`verify-new.sh` checks that the live wrapper and both units equal the saved
|
||||
3190ad762 files with only the copy id swapped, and that the units equal what
|
||||
`bus-service.sh render` and `webui-service.sh render` give for NEW. It prints
|
||||
DIFFER × 5 today, as it should before the window.
|
||||
|
||||
`check-launch.mjs` now also fails if `typed` has `mcp__mosaic__land_work`.
|
||||
That is Dewey's row 137 step A for each launch. It keeps the earlier checks:
|
||||
sources under NEW, the prompt digest, the row 140 text, `read_spec`, and
|
||||
`request_staff` for pm. Its `write_record` INFO line is a fact to record, not
|
||||
a pass condition.
|
||||
|
||||
### Step 8, smokes from tmux
|
||||
|
||||
Every `talk` starts with `[mos test]` and runs with `--tester`. Dewey's plans
|
||||
say business "stack"; the live business is `mosaic-stack` and the project is
|
||||
`stack`. Use the same `--business` the earlier windows used.
|
||||
|
||||
First, the pm launch:
|
||||
|
||||
```
|
||||
mosaic talk pm "[mos test] re-pin 52f35e7a0: reply with one line, no staffing request" --tester
|
||||
node $P/check-launch.mjs pm <step 1 stamp> $NEW # CHECK3_PASS, also request_staff, no land_work
|
||||
```
|
||||
|
||||
**Row 139**, `agents/dewey/work/queue-139/SMOKE.md`. Mos checks its
|
||||
preconditions read-only, including that `arbiters.delivery` names pm and that
|
||||
pm's new manifest has `mcp__mosaic__resolve_decision` in `typed`.
|
||||
|
||||
- A, cited `policy.change` from chief: goes to pm, not Jason's inbox. pm
|
||||
resolves it `no`. Run it.
|
||||
- B, cited `network.expose`: goes to Jason's inbox. Needs Jason's agreement
|
||||
first, and Jason resolves it `no`.
|
||||
- C and D are optional. D, the assistant revoke, also puts an item in Jason's
|
||||
inbox, so it also needs Jason.
|
||||
|
||||
**Row 137**, `agents/dewey/work/queue-137/SMOKE.md`, read-only as Mos ruled.
|
||||
|
||||
- Preconditions 1 to 6 as written, read-only. Precondition 5 is P5's record.
|
||||
- A: every launch in this window has no `land_work`. `check-launch.mjs`
|
||||
already checks chief and pm; it's a FAIL line there, not a separate step.
|
||||
- B isn't in the window. No step here pushes or fetches. B is observed at the
|
||||
first coder push or reviewer fetch after the window, with
|
||||
`working-refs.sh post $P/refs-window.tsv`. That push fast-forwards `main`
|
||||
fc5b342f8 → root HEAD in the live working repo. Dewey's plan puts it behind
|
||||
Jason's agreement. OLD would force the same move today.
|
||||
- C, the cited gated merge from coder, needs a coder relaunch. The Q134 PM
|
||||
test limits keep that out, and it leaves an approved, unspent
|
||||
git.merge.protected decision in the store (Dewey's finding 3). Skip it in
|
||||
this window.
|
||||
- D: Sage's next land-work landing shows `Resolved ... via the CLI`. It isn't
|
||||
tied to the window.
|
||||
|
||||
## Stop conditions
|
||||
|
||||
Before step 2, any one ends the window with nothing changed:
|
||||
|
||||
- P0 to P5 fail. PRE_STOP is a stop and a report; nobody deletes a root ref
|
||||
that was there before the window.
|
||||
|
||||
From step 5 on, any one means rollback:
|
||||
|
||||
- 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.
|
||||
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.
|
||||
- Step 7 prints POST_DIFFER.
|
||||
|
||||
A step 8 failure is a finding, not an automatic rollback, unless the live
|
||||
talk path is broken. Mos decides. A `refs/heads/root` that appears before
|
||||
step 8 means something pushed or fetched during the window; stop and report.
|
||||
|
||||
## Rollback
|
||||
|
||||
The 3190ad762 plan had rollbacks A and B. With no migration, only the B shape
|
||||
applies, and the root step runs in both places Mos named.
|
||||
|
||||
1. Stop the Console, then the bus; both `inactive`.
|
||||
2. `bash $P/working-refs.sh post $P/refs-window.tsv`. Write down each
|
||||
`ROOT_SHA <repo> <sha>` line.
|
||||
3. For each one: `bash $P/root-rollback.sh $P/refs-window.tsv <repo> <sha>`.
|
||||
ROOT_REMOVED, or NOTHING if the ref is absent. STOP means diagnose; nobody
|
||||
deletes the ref by hand.
|
||||
4. Repeat steps 3 and 4 with `$OLD` in place of `$NEW`.
|
||||
5. `bash $RS/verify-live.sh`, MATCH × 3. A DIFFER means stop and diff
|
||||
against the saved files before starting anything.
|
||||
6. Start the bus, then the Console. `stack-copy.sh check` shows OLD.
|
||||
|
||||
`main` stays where it is. If a seed ran, that is the fast-forward to the
|
||||
root, which OLD's next push would force anyway. Nothing prunes copies, so
|
||||
OLD and NEW stay on disk. Any decision row 139 routed to pm and pm resolved
|
||||
under NEW stays resolved after the rollback.
|
||||
|
||||
The same `post` and `root-rollback.sh` steps apply to a rollback after the
|
||||
window, once a seed has run, as long as `$P/refs-window.tsv` is kept. Keep it
|
||||
with the rollback set.
|
||||
|
||||
## Notes
|
||||
|
||||
- Dewey's row 137 findings: 1, no live landing before row 149, goes into
|
||||
row 149's plan with Jason. 2, the seed declines silently when `main` isn't
|
||||
an ancestor of the root; follow-up issue, not a 137 blocker. 3, step C would
|
||||
leave an approved, unspent merge decision; C is skipped here.
|
||||
- Dewey's row 139 finding 1, the raise tool has no `domain` field, goes to
|
||||
row 149.
|
||||
- Keep the copies: OLD and NEW, plus all earlier rollback copies.
|
||||
- Record for BUILD-LOG: step 1 stamp, the P5 record and its sum, the step 6
|
||||
output, the step 7 output, the chief and pm manifest paths and prompt
|
||||
digests, the smoke trails, and the downtime from step 2 to step 6.
|
||||
|
||||
## What Sage ran for this plan
|
||||
|
||||
Nothing live was stopped, started, installed or re-pinned. No store file was
|
||||
opened. No working ref was written in the live repo.
|
||||
|
||||
- P1: `stack-copy.sh copy $NEW`, rc 0, and the `git archive` diff against
|
||||
the copy, rc 0. `grep git.merge.protected` over NEW's `roles/*.json`: none.
|
||||
- P2: `capture-rollback.sh` to `$RS`, MATCH × 3.
|
||||
- P4: `business-check.mjs` under OLD and NEW, LOAD_OK both.
|
||||
- `working-refs.sh pre` on the live repos to `$P/refs-plan.tsv`: PRE_OK, one
|
||||
working repo, prediction "main fast-forwards fc5b342f8 -> 22dbc2b28".
|
||||
Then `post` against it: POST_OK, root ref absent, Jason's repo unchanged.
|
||||
A second `pre` to the same OUT refused, rc 1.
|
||||
- `root-rollback.sh` on a fixture repo under `$P/fixture`, as `t.sh`: `sed`
|
||||
changed 2 lines, the bus state to `inactive` and the lock path to a fixture
|
||||
lock. Cases 1 to 6:
|
||||
1. a root ref with another sha: STOP, nothing deleted, rc 1;
|
||||
2. a repo not in the record: STOP, rc 1;
|
||||
3. a lock whose pid is running: STOP, rc 1;
|
||||
4. a stale lock and the matching sha: ROOT_REMOVED, `main` and the work ref
|
||||
unchanged, rc 0;
|
||||
5. the same again: NOTHING, rc 0;
|
||||
6. a tampered record: STOP, fails its sum, rc 1.
|
||||
Case 7 is the unchanged script on the fixture with the record restored and
|
||||
the root ref recreated, while the live bus runs: "STOP the bus is active;
|
||||
stop it first", rc 1, the ref still there.
|
||||
- `verify-new.sh` now: DIFFER × 5, rc 1. With NEW set to OLD: MATCH × 5.
|
||||
- `check-launch.mjs` against the live chief (rb9e5865e56ff) and pm
|
||||
(r673f8b6b62ba) runs with `$OLD`: CHECK3_PASS both, `land_work` absent,
|
||||
`write_record` present (INFO).
|
||||
@@ -0,0 +1,7 @@
|
||||
19bc98f6c2a24a69ddeeb4865d560d1e4099368abfaa9c3ec488daacd2801dce business-check.mjs
|
||||
b74067b5573eeac37824c1c427115623b9a506cde6d2597aef770d62ca0f9bb5 capture-rollback.sh
|
||||
7cff3b62dcfb32d7ade3218adb11ba689a817f488c71924a3adc8c12ed5d561c check-launch.mjs
|
||||
110d9fd2d7bb4dddd431d3e94e2c8de26b8e1d8c24c44fcc5a7a1d75ac2932b5 root-rollback.sh
|
||||
f1f02b49532afbef46250ed79b4805bb4d1d0020abdcaa3f83be456841abf038 verify-live.sh
|
||||
caf8cd34324e49651de8d0675ef3f2efde74e174ba4870af179727867973b328 verify-new.sh
|
||||
7d8eed95220ca3f777863d9309efeea4b7206def3618e6aac73246a9d42670f1 working-refs.sh
|
||||
@@ -0,0 +1,17 @@
|
||||
// Re-pin 52f35e7a0, P4: the live business file loads under COPY's loader and roles, the way the bus loads it at
|
||||
// start. Row 139 changed GATED_ONLY and vocabulary.mjs, so a file OLD takes could refuse under NEW.
|
||||
// Usage: node business-check.mjs COPY_ID. Read-only. Prints counts and LOAD_OK, or REFUSED with the loader's
|
||||
// message, never a value from the file.
|
||||
const [copyId] = process.argv.slice(2);
|
||||
if (!/^[0-9a-f]{40}$/.test(copyId ?? '')) { console.error('usage: node business-check.mjs COPY_ID'); process.exit(4); }
|
||||
const copy = `${process.env.HOME}/.local/share/mosaic-stack/copies/${copyId}`;
|
||||
const { loadBusiness } = await import(`${copy}/packages/business/src/business.mjs`);
|
||||
try {
|
||||
const b = loadBusiness('mosaic-stack', { rolesDir: `${copy}/roles` });
|
||||
const count = (o) => Object.keys(o ?? {}).length;
|
||||
console.log(`copy ${copyId.slice(0, 9)}: roles ${count(b.roles)}, projects ${count(b.projects)}, arbiters ${count(b.arbiters)}`);
|
||||
console.log('LOAD_OK');
|
||||
} catch (e) {
|
||||
console.log(`REFUSED ${e.message}`);
|
||||
process.exit(1);
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
#!/bin/bash
|
||||
# Re-pin 52f35e7a0, pre-window P2: save the live 3190ad762 wrapper and unit files as the rollback set.
|
||||
# Read-only on live files. Usage: capture-rollback.sh [OUT]; OUT defaults to sage-pin-rollback-3190ad762
|
||||
# and must not exist. Ends with verify-live.sh, which must print MATCH three times.
|
||||
OUT=${1:-/mnt/storage/scratch/sage-pin-rollback-3190ad762}; H=$(cd "$(dirname "$0")" && pwd)
|
||||
OLD=3190ad762b8fd5c09ad7515e4f7e609e81ae1af6; 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,38 @@
|
||||
// Re-pin 52f35e7a0, check 3: the newest launch of INSTANCE since SINCE ran from COPY, still carries row 138's spec tool,
|
||||
// and has no land_work, since no role in roles/ holds git.merge.protected before row 149 (row 137, Dewey's SMOKE.md step A).
|
||||
// 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, read_spec and pm's request_staff stay as regression checks from the 56639c99b and 3190ad762 windows.
|
||||
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`],
|
||||
// Row 137: land_work goes only to a launch whose limits hold git.merge.protected, and no role grants it yet.
|
||||
['typed has no land_work (row 137)', !typed.includes('mcp__mosaic__land_work'), `${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,32 @@
|
||||
#!/bin/bash
|
||||
# Re-pin 52f35e7a0, rollback A or B: delete the refs/heads/root that row 137's seed created in one working repo,
|
||||
# matched by sha, and nothing else. main and every work ref stay where they are.
|
||||
# Usage: root-rollback.sh RECORD REPO SHA, where RECORD is working-refs.sh's pre record (it proves REPO had no
|
||||
# refs/heads/root before the window) and SHA is the ROOT_SHA line working-refs.sh post printed for REPO.
|
||||
# The bus must be stopped: only the bus host writes a working repo, under its own lock.
|
||||
rec=$1; repo=$2; sha=$3
|
||||
[ $# -eq 3 ] && [[ "$sha" =~ ^[0-9a-f]{40}$ ]] || { echo "usage: root-rollback.sh RECORD REPO SHA (40-hex)"; exit 4; }
|
||||
(cd "$(dirname "$rec")" && sha256sum -c --quiet "$(basename "$rec").sha256") || { echo "STOP record fails its sum"; exit 1; }
|
||||
awk -F'\t' -v r="$repo" '$1 == r {f=1} END {exit !f}' "$rec" || { echo "STOP $repo is not in the pre record"; exit 1; }
|
||||
state=$(systemctl --user is-active [email protected])
|
||||
[ "$state" = inactive ] || [ "$state" = failed ] || { echo "STOP the bus is $state; stop it first"; exit 1; }
|
||||
# host.lock can outlive a stop (host.mjs claims a stale one), so check its holder, not the file.
|
||||
L=/home/jwoltje/.mosaic-dev/bus-host/host.lock
|
||||
if [ -e "$L" ]; then
|
||||
pid=$(node -e 'try { const p = JSON.parse(require("fs").readFileSync(process.argv[1], "utf8")).pid; if (Number.isInteger(p)) console.log(p) } catch {}' "$L")
|
||||
[ -n "$pid" ] || { echo "STOP $L has no readable pid"; exit 1; }
|
||||
[ -e "/proc/$pid" ] && { echo "STOP the bus host lock's pid $pid is running"; exit 1; }
|
||||
fi
|
||||
g() { env -i HOME="$HOME" PATH=/usr/bin:/bin GIT_CONFIG_NOSYSTEM=1 GIT_CONFIG_GLOBAL=/dev/null \
|
||||
git -c core.hooksPath=/dev/null -c core.fsmonitor=false --git-dir="$repo" "$@"; }
|
||||
before=$(g for-each-ref --format='%(refname) %(objectname)' refs/heads | grep -v '^refs/heads/root ')
|
||||
now=$(g rev-parse -q --verify refs/heads/root)
|
||||
[ -n "$now" ] || { echo "NOTHING refs/heads/root is absent in $repo"; exit 0; }
|
||||
[ "$now" = "$sha" ] || { echo "STOP refs/heads/root is $now, not $sha; nothing deleted"; exit 1; }
|
||||
# Compare and delete: git deletes the ref only if it still names $sha.
|
||||
g update-ref -d refs/heads/root "$sha" || { echo "STOP update-ref -d failed; nothing else touched"; exit 1; }
|
||||
after=$(g for-each-ref --format='%(refname) %(objectname)' refs/heads)
|
||||
[ "$before" = "$after" ] || { echo "DIFFER refs other than root changed:"; diff <(echo "$before") <(echo "$after"); exit 1; }
|
||||
g rev-parse -q --verify refs/heads/root >/dev/null && { echo "STOP refs/heads/root still exists"; exit 1; }
|
||||
echo "main $(g rev-parse -q --verify refs/heads/main) unchanged; $(echo "$after" | grep -c .) refs as before"
|
||||
echo "ROOT_REMOVED $repo $sha"
|
||||
@@ -0,0 +1,12 @@
|
||||
#!/bin/bash
|
||||
# Rollback B step 4: compare the live wrapper and unit files with the 3190ad762 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 52f35e7a0, step 8: the live wrapper and unit files are the 3190ad762 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. Nothing in bus-service.sh, webui-service.sh or stack-copy.sh changed between OLD and NEW.
|
||||
OLD=3190ad762b8fd5c09ad7515e4f7e609e81ae1af6; NEW=52f35e7a0933e9fa845f59860a5de1709f9e5756
|
||||
R=/mnt/storage/scratch/sage-pin-rollback-3190ad762; 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,67 @@
|
||||
#!/bin/bash
|
||||
# Re-pin 52f35e7a0: the refs/heads/root record Mos asked for at 20:58Z. Row 137's seed (working.mjs seed())
|
||||
# fetches each project's root HEAD into <dataRoot>/working/<business>/<project>.git as refs/heads/root, forced,
|
||||
# and moves main only by fast-forward. OLD's seed instead forced main to the root HEAD on every push and review.
|
||||
# The seed runs only inside a push, a review fetch or a land, never at bus start.
|
||||
#
|
||||
# working-refs.sh pre OUT every working repo: path, HEAD, main, root ref (must be absent, else STOP),
|
||||
# the project root it seeds from and that root's HEAD now, which is the sha
|
||||
# refs/heads/root gets if the seed runs before the root moves. Also Jason's
|
||||
# project repo <dataRoot>/projects/<business>/<project>, which the seed never
|
||||
# reads or writes. Writes OUT (refuses if it exists) and OUT.sha256.
|
||||
# working-refs.sh post RECORD the same repos now: main unchanged or fast-forwarded from the recorded main,
|
||||
# refs/heads/root absent or its sha, Jason's repo HEAD unchanged with no root ref.
|
||||
#
|
||||
# Read-only: git rev-parse, symbolic-ref, for-each-ref and merge-base only, with no system or global config and
|
||||
# hooks off. Reads the business file's projects block and nothing else of it. Never opens the bus store.
|
||||
D=/home/jwoltje/.mosaic-dev; BF=$HOME/.config/mosaic-dev/businesses
|
||||
g() { env -i HOME="$HOME" PATH=/usr/bin:/bin GIT_CONFIG_NOSYSTEM=1 GIT_CONFIG_GLOBAL=/dev/null \
|
||||
git -c core.hooksPath=/dev/null -c core.fsmonitor=false "$@"; }
|
||||
ref() { g --git-dir="$1" rev-parse -q --verify "$2^{commit}" 2>/dev/null; }
|
||||
root_of() { node -e 'const [f,p]=process.argv.slice(1); const b=JSON.parse(require("fs").readFileSync(f,"utf8")); const e=(b.projects??{})[p]; if(!e?.root) process.exit(3); console.log(e.root)' "$BF/$1.json" "$2"; }
|
||||
mode=$1; out=$2
|
||||
[ "$mode" = pre ] || [ "$mode" = post ] || { echo "usage: working-refs.sh pre OUT | post RECORD"; exit 4; }
|
||||
[ -n "$out" ] || { echo "usage: working-refs.sh pre OUT | post RECORD"; exit 4; }
|
||||
if [ "$mode" = pre ]; then [ -e "$out" ] && { echo "refusing: $out exists"; exit 1; }; rec=$(mktemp); else
|
||||
[ -f "$out" ] || { echo "no record at $out"; exit 4; }; (cd "$(dirname "$out")" && sha256sum -c --quiet "$(basename "$out").sha256") || { echo "STOP record fails its sum"; exit 1; }; fi
|
||||
rc=0; n=0
|
||||
for repo in "$D"/working/*/*.git; do
|
||||
[ -d "$repo" ] || continue; n=$((n+1))
|
||||
business=$(basename "$(dirname "$repo")"); project=$(basename "$repo" .git)
|
||||
head=$(g --git-dir="$repo" symbolic-ref -q HEAD); main=$(ref "$repo" refs/heads/main); rootref=$(ref "$repo" refs/heads/root)
|
||||
root=$(root_of "$business" "$project") || { echo "STOP $repo: the business file has no projects.$project.root"; rc=1; continue; }
|
||||
roothead=$(g -C "$root" rev-parse -q --verify 'HEAD^{commit}')
|
||||
jr="$D/projects/$business/$project"
|
||||
echo "working repo: $repo"
|
||||
echo " HEAD $head; main ${main:-absent}; refs/heads/root ${rootref:-absent}; work refs $(g --git-dir="$repo" for-each-ref --format=x refs/heads/work | wc -l)"
|
||||
echo " seeds from projects.$project.root = $root, whose HEAD is now ${roothead:-unreadable}"
|
||||
if [ -d "$jr" ]; then
|
||||
jhead=$(g -C "$jr" rev-parse -q --verify 'HEAD^{commit}'); jroot=$(g -C "$jr" rev-parse -q --verify refs/heads/root)
|
||||
echo " Jason's project repo $jr: HEAD ${jhead:-none}, refs/heads/root ${jroot:-absent}; the seed does not read or write it"
|
||||
else jhead=none; jroot=; echo " Jason's project repo $jr: absent"; fi
|
||||
if [ "$mode" = pre ]; then
|
||||
[ -n "$rootref" ] && { echo "STOP $repo already has refs/heads/root at $rootref"; rc=1; }
|
||||
[ -n "$jroot" ] && { echo "STOP $jr has refs/heads/root at $jroot"; rc=1; }
|
||||
if [ -n "$main" ] && [ -n "$roothead" ]; then
|
||||
if [ "$main" = "$roothead" ]; then echo " first seed under NEW: refs/heads/root := $roothead; main stays (equal)"
|
||||
elif g --git-dir="$repo" merge-base --is-ancestor "$main" "$roothead" 2>/dev/null || g -C "$root" merge-base --is-ancestor "$main" "$roothead" 2>/dev/null; then
|
||||
echo " first seed under NEW: refs/heads/root := $roothead; main fast-forwards $main -> $roothead (OLD's seed would force the same move)"
|
||||
else echo " first seed under NEW: refs/heads/root := $roothead; main stays at $main (not an ancestor of the root)"; fi
|
||||
fi
|
||||
printf '%s\t%s\t%s\t%s\t%s\t%s\n' "$repo" "${main:-absent}" "$root" "${roothead:-none}" "$jr" "${jhead:-none}" >> "$rec"
|
||||
else
|
||||
IFS=$'\t' read -r _ m0 _ r0 _ j0 < <(awk -F'\t' -v r="$repo" '$1 == r' "$out")
|
||||
[ -n "$m0" ] || { echo "STOP $repo is not in the record"; rc=1; continue; }
|
||||
if [ "${main:-absent}" = "$m0" ]; then echo " main unchanged ($m0)"
|
||||
elif [ "$m0" != absent ] && g --git-dir="$repo" merge-base --is-ancestor "$m0" "$main"; then echo " main fast-forwarded $m0 -> $main"
|
||||
else echo " DIFFER main $m0 -> ${main:-absent} is not a fast-forward"; rc=1; fi
|
||||
if [ -n "$rootref" ]; then echo " ROOT_SHA $repo $rootref (recorded root HEAD before the window: $r0)"; else echo " refs/heads/root absent: no seed has run under NEW"; fi
|
||||
[ "${jhead:-none}" = "$j0" ] && [ -z "$jroot" ] && echo " Jason's project repo unchanged" || { echo " DIFFER Jason's project repo HEAD $j0 -> ${jhead:-none}, root ref ${jroot:-absent}"; rc=1; }
|
||||
fi
|
||||
done
|
||||
[ $n -gt 0 ] || { echo "STOP no working repository under $D/working"; rc=1; }
|
||||
if [ "$mode" = pre ]; then
|
||||
if [ $rc -eq 0 ]; then mv "$rec" "$out" && (cd "$(dirname "$out")" && sha256sum "$(basename "$out")" > "$(basename "$out").sha256") && echo "PRE_OK $n repo(s), record $out"
|
||||
else rm -f "$rec"; echo "PRE_STOP"; fi
|
||||
else [ $rc -eq 0 ] && echo "POST_OK" || echo "POST_DIFFER"; fi
|
||||
exit $rc
|
||||
Reference in New Issue
Block a user