Build patch against c6570f142, candidate manifest (4 files), repro,
mutant (killed, 2 of 191), targeted gate (cli 191/0, webui 23/0) and
build.md.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
4.9 KiB
Row 130 (#1625) round 1: human and tester commands name the tree the bus runs from
Brief: docs/plans/2026-10-14_clone-cli-copy-path.md (blob 8120438a6, re-pinned at 8089b98c4). Sage assigned the row in sage-0242 and granted the slot in sage-0246.
BASE: c6570f142. The candidate is build.patch against BASE, and candidate-manifest.sha256 lists its 4 files. repro.sh exports BASE with git archive, applies build.patch and checks every manifest digest: DIGESTS_OK (repro-run.log).
| File | Change |
|---|---|
| packages/cli/src/cli.mjs | the entry block becomes an exported run(argv, io, deps), plus the OTHER_TREE text and a set of the two codes. Nothing above the entry changes |
| packages/cli/tests/other-tree.test.mjs | new, 4 tests |
| packages/cli/README.md | one sentence in the --tester bullet, one paragraph at the end of "One pinned copy" |
| docs/TOOLS.md | one sentence at the end of "Pinned stack update", linking the README section |
The code
run() calls main() and catches a CliError, as the entry did. It writes mosaic: <message> as before. When the error's code is human-required or tester-required, it writes one more line:
mosaic: the bus accepts these commands only from the tree it runs from; on a pinned host run `mosaic` on PATH
It returns the error's exit code, which busExit sets to 3 for both codes. The entry sets process.exitCode from it only when it isn't 0, so a success leaves the exit code unset, as before. run()'s default io is the same as main()'s.
The hint goes at the print site, not into the error. app.mjs (lines 347-379) compares e.message to e.code, so the message has to stay the bare code. The code comes from the transport's own stderr parse (error.code), so the agent-check refusal from refuseInsideAgent, which has no code, never gets the line. The line names no path. run() is exported so the tests drive the same print path the command line uses.
Tests
other-tree.test.mjs, 4 of 4 passing, and 191 of 191 in the cli suite (gate/pkg-cli.log).
- "a human command refused with human-required ...":
inbox --business demothrough the realhumanTransport, whose spawn is a stand-in that exits 1 withhuman-requiredon stderr, so the real code parse runs. It asserts exit 3, stderr exactlymosaic: human-requiredand then the hint line, and empty stdout. - "a --tester command refused with tester-required ...":
agents --tester --business demofrom an agent environment, throughdeps.testerSpawn. It asserts exit 3 and stderr exactly the code line plus the hint. - "other exit-3 refusals print no other-tree line":
tester-offthe same way gives exit 3 and stderr exactlymosaic: tester-off. The plain humaninboxfrom an agent environment gives the agent-check refusal, exit 3, with no hint. - "the line names no path":
OTHER_TREEhas no/.
No existing test changes. stack-copy.test.mjs:144 reads human-required from human-cli.mjs run on its own, not through cli.mjs, so it is unaffected.
Mutant
mutants.py drops the if (OTHER_TREE_CODES.has(...)) line from run(), runs the cli suite, restores the file from .orig with a sha256 check and then kills any process whose cwd is under this row's TMPDIR. mutants-run.log has the run.
| Mutant | Change | Result |
|---|---|---|
| m1-drop-other-tree-line | drop the extra stderr line | killed, 2 of 191 fail: tests 1 and 2 |
The run ended restored: True, and the sweep killed nothing.
Gate
A targeted builder gate under #271 and decision 136 (gate.sh, logs in gate/). Sage's landing gate stays full.
| Step | Result |
|---|---|
packages/cli node --test tests/*.test.mjs |
191 pass, 0 fail |
packages/webui node --test tests/reads.test.mjs tests/views.test.mjs tests/pinned-copy.test.mjs |
23 pass, 0 fail |
GATE_PASSED 00:27:55Z (gate-run.log). webui/src/reads.mjs imports readNotifyConfig from cli.mjs, and those three webui tests import reads.mjs directly. serve.mjs and webui's cli.mjs also import reads.mjs, and Sage's full gate covers the rest of webui. No scripts/test-*.sh reads a changed path. packages/queue/tests/dispatch.test.mjs names /packages/cli/src/cli.mjs only as a string. host.mjs:375 matches a process argv ending in that path, and the path doesn't change. No step failed, so the flake rule had nothing to count.
Leftovers (sweep.log)
No process was left under the TMPDIR. The cli suite left 6 mosaic-launch-* directories, 3 from the mutant run and 3 from the gate. They come from the cli tests' own launches (#1624) and are unrelated to this change. sweep.log lists them with their mtimes and records that I removed them.
Going live
This row has no live step. The refusal the line explains happens on the checkout's scripts/mosaic, which runs the checkout's own cli.mjs, so the line shows there once Sage lands the row. mosaic on PATH runs the pinned copy and gets the change only at the next re-pin, on Mos's go (decision 109). Per the brief, that re-pin is when the row counts as live.