Unclaimed files left in the canonical checkout. Inventory: canonical-cleanup-inventory.md (commit-as-evidence groups). detect-secrets scan clean on 2026-11-02: hits are sha256 pins, git object ids, test sentinels already in tracked tests and unit-file lines.
6.1 KiB
Row 188: stack-release tests for a carried proof pair and a proof file inside the repository
Brief: docs/plans/2026-10-20_proof-test-gaps.md (on origin/refactor 525990ecb). Gitea #1686, P19 409 (M8) and
410 (M9), Filbert's N1 and N2 on row 187 (#1685 comment 29424). Owner Darkwing, reviewer Filbert, gate owner
Sage. Mos ruled this row lands before the Part B window runs (mos-0956).
Files
| File | What |
|---|---|
build.patch |
the change, git diff 525990ecb over the one path below |
candidate-manifest.sha256 |
one line for that path |
- Base:
525990ecb. The brief names742e98976, and the three commits above it touch only docs and the queue. - Changed:
packages/cli/tests/stack-release.test.mjsonly.scripts/stack-release.shis untouched, and its blob is still2586a2a37cd2e7c7aeaba6d31490e5b7920fb4b2, the one the Part B window file names.scripts/webui-service.sh,docs/TOOLS.mdandpackages/webuiare untouched too. P19 407 and 408 are not in this row.
Script line numbers below are at 525990ecb, which match the brief's (same blob).
The tests
One new top-level test, T54 row 188, after T53, with six subtests. Each one makes its own stand-in with
mk and the preview flags with seedPreview, and ends with noLeak. That check fails on any open or read of
a watched proof file, and on SENTINEL in any output, unit, pointer, log or steps log.
| Subtest | Brief test | Setup | What it pins |
|---|---|---|---|
| test 1, chmod 644 | 1 | activate B with the pair, then chmod the file 644 | --plan for activate C1 lists proof: carried and exactly one refusal, proof-file-invalid: ... has mode 644, not 600 or 400; the run refuses the same before anything stops; the pointer stays on B with the pair |
| test 1, symlink | 1 | same, then the path becomes a symlink to a watched mode-600 file | the same, with ... is a symlink |
| test 1, removal | 1 | same, then the path is removed | the same, with ... cannot be found |
| test 2 | 2 | activate C1 with the pair, then chmod 644 | --plan for rollback (to B, whose Console takes the pair) lists proof: carried and the one refusal; the run refuses before anything stops; the pointer stays on C1 with the pair |
| test 3 | 3 | a mode-600 file of the test's uid at <repo>/proof-r188/proof.188, with the pair on activate C1 |
the plan and the run refuse ... is inside the repository or the copies |
| test 4 | 4 | <e.root>/repo-link.188, a directory symlink to <repo>/proof-r188, with the file given through it |
the test asserts that the given path's last part is not a symlink and the path is outside REPO; the plan and the run refuse on its realpath with the same reason |
"The run refuses before anything stops" is the existing refused helper (:881 of the test file): exit 3,
the exact message, no unit, wrapper, pointer or store change, no start or stop, and one log line with
result: refused.
The other change to the test file: proofFile takes an optional fourth argument, the directory, with the old
e.root as its default, so tests 3 and 4 can put a watched file inside the fixture REPO. Each of those
subtests removes its proof-r188 directory in t.after. linkSync joins the node:fs import.
The uid case is out of reach without root, as the brief says, and has no test.
Readings of the brief
- "No log line" in brief test 1. A precheck refusal in a real run writes one log line,
result: refusedwith the code, as every refusal does since the log began (T53 test 5 and every otherrefusedcase pin it). I read the brief as no activation line, whichrefusedpins: one line,refused, and no unit or pointer change. Changing that would need a script change, which this row can't make. - Removal and the symlink swap keep a hard link to the old file under
e.rootbefore they unlink its path. The read watch is on the inode. When a watched inode is freed, inotify sendsIN_IGNORED, which no mask filters out, and the watch counts it as an event. A probe of the T53 watch counted 1 after a plain unlink and 0 with a hard link kept. The script sees the same thing either way: the path is gone, or it is a symlink.
Results
All runs are from ~/darkwing-scratch/r188/run188.sh, in a git worktree of 525990ecb plus the candidate, with
TMPDIR under ~/darkwing-scratch. The runs took 10:38:55Z-10:46:50Z.
- The whole
stack-release.test.mjspasses 151/151 (144 before this row, plus T54 and its six subtests). - Control: on the unchanged script (blob
2586a2a37c), T53 and T54 pass 19/19. The brief's base control is this run. The row changes no script, so base and candidate run the same script, and the new tests must pass on it. - Mutants. The runner runs T53 and T54 under each one, then puts the script back and checks its sha256. The
restore matched every time, and the worktree script is blob
2586a2a37cafter the run.
| Mutant | Replacement | Fails | Failing subtests |
|---|---|---|---|
| M8 | return 0 before the fi that ends the carried branch (:738) |
5 | T54 test 1 (all three changes) and test 2 |
| M9 | "$REPO" | "$REPO"/* | dropped from the realpath case (:775) |
3 | T54 tests 3 and 4 |
| M10 | why=""; [ -z "$PROOF_HEADER" ] || why="$(proof_file_problem "$2")" (:759) |
5 | T54 test 1 (all three changes) and test 2 |
The fail counts include the T54 parent. Each failing subtest stops at its first --plan (exit 0, not 3).
T53 passes under all three mutants, which matches Filbert's finding that M8 and M9 survived it. The new
tests are what kill them.
Candidate
candidate-manifest.sha256(sha256 86aa549bad75...):d38a2f9d6e4ece57907e95b9c24601ec823cfab8d22ab783a8ee64c79104100b packages/cli/tests/stack-release.test.mjsbuild.patch(sha256 db7e41b2ba76..., 85 lines) applies on525990ecb, andsha256sum -cof the manifest passes on the result.
For Sage's gate
- The one changed path is
packages/cli/tests/stack-release.test.mjs. No test or script reads it as a file (grep -rln stack-release.test packages/*/tests scripts/test-*.shfinds nothing), and no file pins the base blob's sha256 (95e44c2f...). - Run the cli suite and every
scripts/test-*.shfrom a git worktree of the candidate, as the brief says.