Files
stack/docs/plans/2026-10-20_proof-test-gaps.md

4.1 KiB

Brief: tests for the two proof-file paths row 187 left open

Sage, 2026-10-20, for mos-0956. P19 tasks 409 (M8) and 410 (M9), Filbert's N1 and N2 on row 187 (#1685 comment 29424). Owner Darkwing, reviewer Filbert, gate owner Sage. Base: refactor at 742e98976. Line numbers are at that commit.

Mos's ruling: this row lands before the Part B window runs (agents/sage/work/window-plan/repin-partB-proof.md). Part B is the first time a real proof file goes through these checks, and that file holds a credential.

Why

Row 187's proof_checks (scripts/stack-release.sh:745) refuses a bad proof pair before anything stops. Two of its paths have no test that fails when they break:

  • M8: on a carried pair (the pointer's pair, with no pair flags given), proof_for_target (:694) reaches the else branch and then proof_checks at :739. Filbert's mutant makes that branch return before the checks. It survived T53 12/12. So a proof file that was moved, chmod-ed or replaced after the activate that set the pair would pass the plan and the precheck and fail only after the stop, in webui-service.sh render (:194-201).
  • M9: proof_file_problem (:765) refuses a path whose realpath is inside REPO or the copies (:775). T53 covers the copies (:1595) but not REPO. Filbert's mutant drops the two REPO cases. It survived.

webui-service.sh render still refuses both files after the stop, so neither mutant leaks the value today. The gap is that the refusal comes after both units are down, which is what row 187 exists to prevent.

The change

Tests only, in packages/cli/tests/stack-release.test.mjs, as new subtests of T53 or a new T54. No change to scripts/stack-release.sh, scripts/webui-service.sh, docs/TOOLS.md or packages/webui. The script's blob must stay 2586a2a37cd2e7c7aeaba6d31490e5b7920fb4b2, since the Part B window file names it and Mos's A2 check reads it. P19 407 and 408 (a comment at :689 and the row 178 paragraph in docs/TOOLS.md) are not in this row for the same reason.

Tests

  1. A carried pair whose file changed since it was set. Set a pair with an activate, then for each of these changes run --plan and the real activate to another copy with no pair flags, on a fresh stand-in each:
    • chmod the file to 644;
    • replace it with a symlink to a mode-600 file;
    • remove it;
    • chown is out of reach in a test, so the uid case is not required. Each plan lists proof-file-invalid with the reason, and each run exits 3 with no unit stop or start, no log line and the pointer unchanged.
  2. The same for a rollback that carries the pair to a copy whose Console takes it. One case (chmod 644) is enough.
  3. A proof file inside the test's REPO (join(base,'repo'), :238), mode 600 and owned by the test's uid, given with activate ... --proof-header NAME --proof-file PATH. The plan and the run both refuse proof-file-invalid with is inside the repository or the copies, with no stop.
  4. A path outside REPO that resolves inside it: a directory symlink outside REPO pointing into it, with the proof file under it. The file itself is not a symlink, so the -L check passes and the realpath check must catch it.
  5. Each new case runs noLeak.

Base control: run the new tests against the mutants below, and on base (where they must pass). Record each result in BUILD.md.

Mutants, each one exact replacement in scripts/stack-release.sh, each killed by a new test:

  • M8: in proof_for_target, return 0 at the end of the carried branch, before proof_checks.
  • M9: drop "$REPO" | "$REPO"/* | from the realpath case at :775.
  • M10: proof_checks skips proof_file_problem when PROOF_HEADER is empty (the carried case only).

Put the script back after each mutant and check its sha256.

Gate

Filbert approves the candidate manifest. The cli suite and every scripts/test-*.sh pass on Sage's gate from a git worktree of the candidate. The only touched files are packages/cli/tests/stack-release.test.mjs and the packet. Name anything else in BUILD.md with the reason.

Out of scope

The Part B window and any step of it, the slot file, the uid separation (J2), and P19 407 and 408.