6.6 KiB
Brief: activate carries the row 178 proof pair
Sage, 2026-10-20, step A0 of mos-0928 Part B (agents/sage/work/window-plan/repin-rows-177-185.md
§ Part B). Gitea #1685. Owner Darkwing, reviewer Filbert, gate owner Sage. Starts after row 186, which touches the same
script. Base: the refactor head once row 186 lands. Line numbers are at 1d7fd75e2.
This row builds the tool only. Turning the proof on for the live Console is Part B, which waits for Jason
(J1-J6 on P19 385) and is not part of this row. Nothing here runs against ~/.mosaic-dev, the live units
or the slot file. Every test uses the stand-in systemctl, units, stores and data root that
packages/cli/tests/stack-release.test.mjs already builds, with a proof file the test writes itself.
Why
Row 178 gave the Console --proof-header NAME --proof-file PATH (packages/webui/src/serve.mjs
proofOptions, :217). On the pinned stack the Console flags live in the pointer. flags_drift (:627)
refuses unit-flags-drift when the unit differs, and console_for_target (:645) carries the pointer's
flags unchanged. So a hand webui-service.sh install with the pair would make every later activate,
pm-on and rollback refuse. activate --tester-port N is the only path that writes a new Console flag
into the pointer today. Part B's B3 needs the same path for the pair.
Part B's rollback also needs a way to turn the proof off. A plain rollback goes to the log's previous
distinct copy (from_log previous, :907). Today that is 89af38a83, which predates row 178, so the
rollback would drop the proof only by also dropping rows 177-185. An activate of the live copy without
the pair is the narrow way back, and today an activate carries the pair unchanged. So this row adds
--no-proof too.
The change
activate ID BUSINESS --proof-header NAME --proof-file PATHwrites the pair intoRUN_CONSOLE, soinstall_stackputs it in the Console unit andwrite_pointerputs it in the pointer, the same way--tester-portdoes (:659-668). It replaces any pair the pointer already holds. Both flags or neither; one alone is a usage error. Onlyactivatetakes them.activate ... --no-proofdrops any pair fromRUN_CONSOLEand says so in the steps log, the wayDROP_TESTERdoes. With no pair in the pointer it is a no-op that says so. It can't go with the pair flags (usage).- Before anything stops, the run refuses (exit 3, through
check_refuseso--planlists them) when:proof-target: the target copy'spackages/webui/src/cli.mjsdoesn't take--proof-header.proof-needs-preview: the Console flags the run would install lack any of--public-host,--auth-headeror--allow-user, whichproofOptionsneeds.proof-header-invalid: the lowercased name doesn't match^[a-z0-9][a-z0-9-]{0,63}$, or ishost,origin,x-mosaic-testeror the lowercased--auth-headervalue. These areproofOptions' own rules (serve.mjs :203, :220-222).proof-file-invalid: the path isn't absolute, holds a character outside[A-Za-z0-9/._+-](the unit argv can't quote, :166), is a symlink, isn't a regular file, isn't mode 600 or 400, or isn't owned by the uid the script runs as (the Console unit's uid today; J2 changes that and gets its own row).
- The script never reads the proof file's contents. It checks the file with
statonly. The value never appears in the pointer, the log, the steps log, the plan output or any refusal text. - A
rollbackto a copy whose Console doesn't take--proof-headerdrops the pair and says so, asDROP_TESTERdoes for--tester-port, since a rollback is the way back and must not refuse on it. An activate to such a copy with a pair in the pointer refusesproof-targetunless--no-proofis given, so an activate never turns the proof off unless the operator asked. Unlike--tester-port, the proof decides who can reach the Console. The plan prints the drop line. A copy that takes the pair carries it unchanged, as today. --planprints oneproof:line for an activate or rollback:proof: on, header NAME, file PATHfor a new pair,proof: carried, header NAMEwhen the pointer's pair goes on unchanged,proof: dropped; ...with the reason, orproof: off.pm-onis unchanged. It already writes the pointer's Console flags back as they are (:1008).docs/TOOLS.md"Pinned stack update" gets the two flags in the usage block and one paragraph on the pair,--no-proofand the drop.
Tests
- Activate with the pair: the pointer's
consoleand the Console unit's argv end with--proof-header NAME --proof-file PATH, after the preview flags and with--tester-portkept. - Activate of the live copy with the pair (Part B's B3 shape) passes and writes the pair.
- A later activate without pair flags carries the pair;
pm-onkeeps it. --no-proofdrops it from the pointer and the unit, and the steps log says so. With no pair it says so and passes.- Each refusal in change 3 exits 3 with no unit start or stop, and
--planlists it with any other refusals. - Usage errors: one of the pair alone,
--no-proofwith the pair, either flag onrollbackorpm-on. - Rollback to a copy without proof support drops the pair and says so. Rollback to one with it carries
the pair. Activate to a copy without support, with a pair in the pointer, refuses
proof-targetwithout--no-proofand passes with it. - The value never leaks: the test's proof file holds a sentinel line, and no file under the test's state or data root, and no stdout or stderr of any run, holds it.
--planprints theproof:line for each case and writes nothing.
Mutants, each killed by some gate test (mos-0862):
- the run reads the proof file (any
catornoderead of PATH) - a target without proof support keeps the pair on rollback
- an activate to a target without proof support drops the pair without
--no-proof --no-proofleaves the pair in the pointer but not the unit (drift)- the mode check accepts 644
Gate
Filbert approves the candidate manifest. Cli suite and every scripts/test-*.sh green on Sage's gate, run
from a git worktree of the candidate. Touched files: scripts/stack-release.sh,
packages/cli/tests/stack-release.test.mjs, docs/TOOLS.md, and the packet. Anything else gets named in
BUILD.md with the reason. No change to packages/webui.
Out of scope
Running Part B or any step of it, writing the slot file (A1), the uid separation (J2), the k3s middleware (J3, J4), and any change to the Console's proof rules. Going live needs a re-pin window that Mos checks (A2), after Jason's go on Part B.