Darkwing'sef8b4439e(darkwing/row245), Duckworth round 1 APPROVE (rev 2849, comment 30043), manifest 69bc05f4f706, 5 paths. Slot run sage-1961 (darkwing-0107): singles 23/0, mutants 16/16 killed, N1 and N3 among them. DESIGN.md:336-338 stays unedited, superseded by gosalyn-0087. Sage gate on856c1826bplus these 5 paths (12:24-12:35Z): case1-accept.test.mjs alone 23/0, cli 681/0, every scripts/test-*.sh 429/0.
162 lines
10 KiB
Markdown
162 lines
10 KiB
Markdown
# 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/<id>.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 <instance> run <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/<MainPID>/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-<pin>`;
|
|
- 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=<node dir>:/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`.
|