# Row 195 build: `scripts/case1-accept.sh` Row 195 (#1693), the Case 1 install on 10.1.1.53. Design r7 at 5425ea2f3, approved by Duckworth (duckworth-0087, `agents/duckworth/work/row195/REVIEW-r7.md` at 58d930f89; Gitea 29962). Sage's go to build is sage-1817. ## What this commit holds - `scripts/case1-accept.sh` (new, 100755): section 5's acceptance, F1-F11 and P1-P11. - `packages/cli/tests/case1-accept.test.mjs` (new): section 5's cases. - `docs/TOOLS.md`: the `case1-accept.sh` entry after the hostcheck entry. - This file. - `DESIGN.md`, doc-only fixes Sage named in sage-1817, with no new design round: - Duckworth's N1. Section 1's human-id row cited `broker.mjs:245`; the check is `broker.mjs:321`. - Row 226's state at :21 and :326. It said "unclaimed"; 226 is assigned to Gosalyn, briefed and not started (sage-1814). - :340 said I'd send the section 6 items to Gosalyn once 226 was claimed. I sent them on 2026-10-21 and Gosalyn acknowledged (gosalyn-0085), so the line now says that. Nothing else changes. No `case2-*.sh`, `stack-upgrade.sh`, `stack-release.sh`, `case1-hostcheck.sh` or run sheet edit, and nothing touches .53 (sage-1809). ## Deviations from section 5, and choices it left open 1. P4's mark is the single token `accepted Q182+Q183`. Section 5 lists `accepted Q182` and `accepted Q183` as marks and gives P4 both. One line can carry one mark, and P4 is the only Q182 path, so the count list has `accepted Q182+Q183` in place of `accepted Q182`. 2. The test seam replaces `getent`. Under `--root`, the script reads `DIR/etc/passwd` and `DIR/etc/group` with awk. Without it, the same helper calls `getent`. The groups of a user are its primary gid, then each group whose member list names it, which is what `id -G` prints. So there is no fake `id`, and the script never calls `id` for another user. 3. P10 reads the port floor from `/proc/sys/net/ipv4/ip_unprivileged_port_start` rather than calling `sysctl`, so the seam covers it the way hostcheck's item 7 is covered. There is no fake `sysctl`. 4. There is no fake `bwrap`, `claude`, `node` or `hostcheck.sh` on PATH. The script runs each by the absolute path the args file or the install names, so the test puts stubs at those paths under `--root`, and the fake `sudo` runs the stack's user's commands from the tree. 5. The args file must say `--profile factory` and name `--business`. Both are new refusals beyond `stack-upgrade.sh`'s rules. A library file, or one for another business, would make F4 and F7 check the wrong thing. After `--`, `--port` and `--tester-port` must be numeric. The args file is the only source of the ports. 6. F8, F9 and F11 read file metadata as root: `stat`, `-L` and `find -maxdepth 1 -name '*.json'`. None opens the credential. F9 parses `managed-settings.json` with the pinned node, run by root: that node and that file are both root's own, installed by ops-01. 7. F7 reads `launch.by` from `$HOME/.config/mosaic-dev/businesses/.json` as the stack's user. That path is `businessFilePath(id)` under the default config, which is what the unit uses. It doesn't load the copy's `business.mjs` as `stack-release.sh`'s `plugins launchby` does. The user runs the whole check and prints one line, and root accepts only `ok run ` (sage-1653: root doesn't parse the user's output beyond a fixed pattern). 8. F5 names a process claude when its argv0's basename or its `comm` starts with `claude`. The window confirms this against a real role session. If the bwrap'd claude shows another name, F5 fails closed and the fix is this match, not the label check. 9. F3 and F5 run together. `talk` goes out in the background under `timeout 330`, and the `/proc` scan repeats every half second until it finds a labelled pair or `talk` exits. F3's line prints first, then F4, then F5, so the output keeps id order. F3 passes on exit 0 with the `sent` line and a reply line, both counted on stdout only (`cli.mjs:395` and the `formatMessage` write); talk's error goes to stderr. 12. `--claude` must match `^/[A-Za-z0-9/._+@-]+$`. The `@` is for `node_modules/@anthropic-ai/...`; `stack-upgrade.sh` doesn't check `--claude` at all. 10. F1 requires all four uids in `/proc//status` (real, effective, saved, filesystem) to be the user's. 11. Evidence text the stack's user controls (talk's last line, the precheck's last line, F6's and F10's `--version`) is cut to printable ASCII, one line, 200 bytes. ## Tests `packages/cli/tests/case1-accept.test.mjs`, 22 tests. Every case builds a scratch host with every F check passing and every P check closed or accepted, then changes one thing: - all pass: exit 0, ids in order, each P mark, the count lines match the check lines, and the stand-in credential's value appears nowhere; - each F check failing alone exits 1 with only its line failing; - F3 on exit 0 with only the `sent` line, and its evidence taking talk's stderr; - F1 on a failed unit and on `User=root`; F2 on healthz failing and on another pid holding the port; - F5 on `unpriv_bwrap (enforce)`, `bwrap//&unpriv_bwrap (complain)`, a trailing space on either label, no bwrap of the user's, and passing on `comm` alone; - F6 on a stub that prints nothing and on a path outside `claude-`; - F7 on two live PM launches, a live one for another business, and no `launch.by`; - F8 on 0640, on another owner and on a symlink, never printing the credential; - F9 on a group-writable mode, another owner and a number for the flag; - F11 on one `*.json` under `discord/`, and passing on an empty directory; - each P check's unnamed mark, with the line count each fault makes: P6 one line per member, P9 one per missing item; P2, P5, P10 and P11 in more than one way; - every P check unnamed at once still exits 0; - nine bad args files refuse with exit 2 before any command runs, each with its own refusal text; - usage errors, and a user's run without `--root`, exit 2; - every in-instance call goes through `sudo -u mosaic -H env PATH=:/usr/local/bin:/usr/bin:/bin`, read from the fake `sudo`'s argv, and root's other `sudo` calls are exactly `-l -U mosaic`, `-l -U localadmin` and the jarvis bwrap probe; - the host check records `--check` and never `--enforce`. ## Mutants Section 5's twelve, each one exact replacement in `scripts/case1-accept.sh`, run by `/mnt/storage/scratch/darkwing-195-out/mutants.py` in Sage's slot (sage-1865). All twelve were killed. After each one the script's bytes were restored, and the final sha256 matched (`d434f15ff638...`). The killers below are the tests that failed in each mutant's saved log. | # | Mutant | Killed by | |---|---|---| | 1 | F1 passes on a MainPID owned by root | each F alone | | 2 | F2 accepts a listener on 0.0.0.0 | each F alone | | 3 | F3 passes without a reply | each F alone; F3 exit 0 with no reply line | | 4 | F5 matches a substring | F5 labels (the trailing space) | | 5 | F6 skips the `--version` check | each F alone; F6 stub and path | | 6 | F8 skips the owner check | F8 0640, owner and symlink | | 7 | F11 ignores `discord/` | each F alone; F11 `*.json` | | 8 | P1 closes on any output | P unnamed; every P unnamed | | 9 | P5 maps to `closed` with jarvis in docker | P unnamed; every P unnamed; P2 and P5 names docker | | 10 | P9 drops the jarvis item | P unnamed; P9 per item | | 11 | P11 accepts any `Before=` | P unnamed; every P unnamed | | 12 | a P check's `unnamed` sets the exit to 1 | P unnamed; every P unnamed; P10 and P11; P2 and P5; P9 per item | Mutant 1 survived the F1 variants test (a failed unit, `User=root`), because those faults fail F1 on the unit's state and `User=`, not on the uid line. Each F alone covers the uid line. ## Pre-slot read review Since every test run is slot work, a read-only subagent traced each case through the script before the first slot run, executing nothing. It found four defects, all fixed in this commit: - The `--claude` regex had no `@`, so every run refused the args file, the real .53 one included. - The fake `stat` logged the args-file call, so the bad-args test's "no command ran" check would fail. - The P5 and P6 faults each rewrote `etc/group` from the constant, so applying both lost P5's change. - F3 counted stderr into its line count, so a mutant dropping the exit check survived. F3 now counts stdout only, and a `sent`-only case covers the line count. ## Run environment The build commit ddf7dce6c sits on base 4ced5e63d (origin/refactor when it was pushed). It was first built on c515273db as 4a51d392c. Row 238 then changed `docs/TOOLS.md`, so I cherry-picked the three commits onto 4ced5e63d as `darkwing/row195-b`. `git range-diff` shows all three identical. node v26.8.1 on dragon-lin, every run behind my busy check and Sage's, chained with `&&`. - Step 1, sage-1829 (07:41:54Z to 07:42:05Z), TMPDIR `/tmp/dw195`: `node --test packages/cli/tests/case1-accept.test.mjs`, 22 pass, 0 fail, 11.0 s. - Step 2, sage-1865 (08:34:27Z to 08:36:21Z), TMPDIR `/mnt/storage/scratch/tmp/r195`: the twelve mutants above, all killed. - Step 3, the gate (08:36:21Z to 08:47:06Z), same TMPDIR, `COMPOSE_PROJECT_NAME=darkwing-r195`: | Suite | Result | |---|---| | packages/cli (`node --test tests/*.test.mjs`) | 662 pass, 0 fail, 0 skipped | | test-auth.sh | 15 passed, 0 failed | | test-conductor.sh | 17 passed, 0 failed | | test-config.sh | 31 passed, 0 failed | | test-discord.sh | 78 passed, 0 failed | | test-extension-package.sh | 18 passed, 0 failed | | test-foundation.sh | 44 passed, 0 failed | | test-land-work.sh | 86 passed, 0 failed | | test-queue.sh | 156 node pass, 0 fail; 28 passed, 0 failed | | test-release.sh | 14 passed, 0 failed | | test-task.sh | 98 passed, 0 failed | Summed: 818 node tests passed with 0 failed, and 429 selftests passed with 0 failed. The compose network was removed afterwards, and the sweep found no process left in the worktree, the output directory or the TMPDIR. This file's results are a doc-only commit on top of the build, so nothing that ran has changed. ## Correction to deviation 6 (follow-up, 2026-10-21) Deviation 6 says the node F9 runs is root's own. At 37171f65b the script didn't check that. F9 ran `--node` as root before F10's path check, so the header's "root itself only reads" was false (Duckworth's N1, `agents/duckworth/work/row195/REVIEW-m1.md` at 6afcee2a5). A host installed as designed was safe, because `case2-unit.sh` refused a non-root-owned `--node`. Row 245 carries the fix and its tests: `agents/darkwing/work/queue-245/BUILD.md`.