row 198 r3: rollback always drops --board (sage-2834 ruling a)

webui-service.sh install has no --board branch and refuses the flag, so a
rollback that kept a recorded --board failed at install-console. The rollback
now drops it unconditionally, with its own plan line naming the retired board.
An activate still keeps --board where the target parses it.

M13 now asserts the rollback drop line and that the Console install line
carries no --board. The webui README gets the pre-198 rollback note.
BUILD.md records the ruling and the M13 expectation.

UNVERIFIED: no node or tests run (Sage's gate).
This commit is contained in:
jason.woltje committed 2026-10-22 12:12:25 -05:00
1 parent b42d9cf360
commit 435b7b5597
4 files changed
+44 -33

No files matched your search

+17 -17
View File
@@ -11,22 +11,20 @@ No test suite, mutant, or node process has run for this commit. Sage's gate (sag
"no node of any kind and no https git; no test runs until my CLEAR." This commit is local.
It must not be pushed until the gate runs green and Sage opens a gap for the push.
## Open for Sage's ruling: rollback re-installs `--board` and install refuses it
## Ruled: rollback drops `--board` (Sage sage-2834, ruling a)
Design item 8 (rev 5, checkpoint 3f71b359b) keeps a recorded `--board` on rollback when the
rollback target's `cli.mjs` parses `--board`. But `scripts/webui-service.sh install` has no
`--board` branch: an unknown option hits `*) usage`, exit 4. So a rollback that keeps the flag
would fail at install-console. The M13 test checks only the rollback plan, so it does not
catch this. By reading the script, not by running it, the install fails closed.
Design item 8's "rollback keeps the flag" part is withdrawn. `webui-service.sh install` has no
`--board` branch (exit 4), and `c3b60e513` removed the flag from its parse_flags, so a kept flag
failed the rollback at install-console. Ruling (a): a rollback always drops `--board`, with its
own plan line: `drop --board ...; the 7331 board is retired, so the rolled-back Console runs
without the board`. A rollback to a pre-198 copy has no board until the next re-pin (README).
Options, for Sage:
- (a) Drop `--board` on rollback as well, named in the plan, the same way re-pin drops it.
Recommended: the retirement removes the flag from the Console's unit.
- (b) Teach `webui-service.sh` to accept `--board`. This contradicts the retirement.
- (c) Keep the rule as ruled. The rollback fails closed at install.
Code: `console_for_target` drops the pair on rollback whether or not the target parses it; an
activate still keeps it where the target parses it. `DROP_BOARD_WHY` carries the reason to both
print sites (plan and steps).
Implemented as ruled (c) for now. Choose (a) and the change is one line in
`console_for_target` plus the M13 expectation.
M13 now asserts the drop line, that the rollback `Console:` install line carries no `--board`,
and that the plan leaves the state unchanged.
## What changed in the source
@@ -56,8 +54,9 @@ Implemented as ruled (c) for now. Choose (a) and the change is one line in
- M12: `seed`, `setCli` to a refusal-only `cli.mjs`, `withBoard`, `activate --plan`. Expects the
drop line, no `--board` in the Console line, and no state change.
- M13: `setCli` to a parser, real `activate` without the flag, then the unit gets `--board` and
`rollback --plan`. Expects the flag in the Console line and no `drop --board`. Activation runs
without the flag because `webui-service.sh install` refuses it.
`rollback --plan`. Expects the rollback drop line with the retired-board reason and no `--board`
in the `Console:` install line. Activation runs without the flag because `webui-service.sh
install` refuses it.
- `packages/control-board/tests/serve.test.mjs` (B4, B5): `replyBoard(f, agentSend, isPidAlive)`
passes `isPidAlive` through. New refusal row: a stale registration (pid 4242, `isPidAlive` false)
answers 409 with `/stale/`. B5: the non-numeric `--port` test is deleted, since `serve` no
@@ -117,7 +116,7 @@ Each mutant runs on a scratch copy with the new tests. Not run yet.
| M10 | `/api/reply` and `/api/seen` branches move below the read-only refusal | `board-inprocess.test.mjs` positive control (200, exec once) |
| M11 | `COPY_PATHS` drops `packages/control-board/src` or `tools/tmux` | `pinned-copy.test.mjs` "serve runs from a copy": `/api/board` 200 |
| M12 | `console_for_target` keeps `--board` for a target that does not parse it | `stack-release.test.mjs` M12 |
| M13 | rollback drops a recorded `--board` from a target that parses it | `stack-release.test.mjs` M13 (plan only; see the open item above) |
| M13 | rollback keeps a recorded `--board` for a target that parses it | `stack-release.test.mjs` M13: rollback drop line and no `--board` in the `Console:` line |
| M14 | a board read blocks the loop (spawnSync or a blocking exec) | `board-inprocess.test.mjs` "healthz within 200 ms" catches only a pending read that blocks; see the gap below |
| M15 | `index()` joins a scan that started before the last write | `board.test.mjs` M15 (event log) |
@@ -163,5 +162,6 @@ Still in the README, on purpose: `packages/webui/README.md` line 826 runs the co
- control-board, webui and cli suites; `scripts/test-*.sh`; the mutants M1-M15 on a scratch copy.
All wait for Sage's CLEAR.
- The rollback-install open item needs Sage's ruling before the M13 expectation is final.
- The rollback drop (sage-2834 ruling a) is implemented and documented; M13 has its expectation.
- M14: Sage accepted the gap as recorded (sage-2834). The mutant run is the check.
- Push: not done. Sage's gaps govern it.
+13 -8
View File
@@ -851,9 +851,10 @@ test('T44 row 180 item 2: --plan lists tester-port-invalid and tester-port-targe
});
});
// Row 198 (design item 8, B2): --board stays only where the target's cli.mjs parses it. A target that
// names the flag only in its refusal drops it, and the plan names the drop. A rollback to a copy that
// parses it keeps the pointer's flag. setCli rewrites a shared copy's cli.mjs and restores it after.
// Row 198 (design item 8, B2; sage-2834 ruling a): an activate keeps --board only where the target's
// cli.mjs parses it. A target that names the flag only in its refusal drops it. A rollback always drops
// it, because webui-service.sh install refuses the flag. The plan names each drop. setCli rewrites a
// shared copy's cli.mjs and restores it after.
const BOARD='--board http://127.0.0.1:9';
const REFUSES_BOARD=`// fixture Console, never run\n// serve accepts '--tester-port'\nif (flag === '--board') throw new Error('--board retired: the board runs in the Console (row 198)');\n`;
const PARSES_BOARD=`// fixture Console, never run\n// serve accepts '--tester-port'\nelse if (flag === '--board') board = value;\n`;
@@ -870,16 +871,20 @@ test('row 198 item 8 (M12): a re-pin plan drops a --board the target Console doe
assert.deepEqual(fp(e),before);assert.deepEqual(startsStops(e,n),[]);assert.equal(logLines(e).length,logs);
});
test('row 198 item 8 (M13): a rollback keeps the pointer\'s --board for a copy that parses it',async(t)=>{
// The activation runs without --board, since webui-service.sh install has no branch for the flag. The
// pointer and unit then carry it, and the rollback target B parses it, so the rollback plan keeps it.
test('row 198 item 8 (M13): a rollback drops the pointer\'s --board even for a copy that parses it, and its install line has none',async(t)=>{
// Sage sage-2834 ruling (a): webui-service.sh install refuses --board, so a rollback always drops it and
// names the drop with the rollback reason. The activation runs without --board; the unit then carries it,
// and the rollback target B parses it, which must not keep the flag.
const e=await mk(t);setCli(t,B,PARSES_BOARD);
await seed(e);
let r=await run(e,'activate',C1,'acme');assert.equal(r.code,0,r.out+r.err);
edit(join(e.units,CON),'--business acme',`--business acme ${BOARD}`);withBoard(e);
const before=fp(e);
r=await run(e,'rollback','acme','--plan');assert.equal(r.code,0,r.out+r.err);
assert.ok(r.out.split('\n').find((l)=>l.startsWith('Console: ')).includes(BOARD),r.out);
assert.doesNotMatch(r.out,/drop --board/);
const lines=r.out.split('\n');
assert.ok(lines.includes(`drop ${BOARD}; the 7331 board is retired, so the rolled-back Console runs without the board`),r.out);
assert.doesNotMatch(lines.find((l)=>l.startsWith('Console: ')),/--board/);
assert.deepEqual(fp(e),before);
});
// Row 181 (#1679, P19 390): COUNTED and STORE_JS as the script under test has them. The fixture
+2
View File
@@ -939,6 +939,8 @@ own page or a repo seat terminal in DEFERRED.md. Pass is Jason's end-of-day
say-so, not a test result. Keep #1507 open pending that ruling.
Rollback requires only stopping the WebUI. The board runs inside it, so there is no separate board to keep running.
A rollback to a pre-198 copy has no board until the next re-pin. The rollback plan drops a recorded `--board` (the 7331 board is retired and the install refuses the flag), and the rolled-back Console runs without a board until a copy that has the in-process board is activated.
Revert the scoped WebUI commit to remove it; there is no data migration to undo.
Seen and reply actions already taken belong to the board and are not rolled back.
+12 -8
View File
@@ -809,15 +809,19 @@ console_for_target() {
local cli="$COPIES/$TO/packages/webui/src/cli.mjs"
local -a without_tester=()
sso_base
# Row 198 (design item 8): --board stays only where the target's Console parses it, and a drop is
# named in the plan and the steps. The check reads the target's own cli.mjs, as --tester-port does.
DROP_BOARD=""
# Row 198 (design item 8, Sage sage-2834 ruling a): --board stays only for an activate whose target's
# Console parses it. A rollback always drops it: 7331 is retired and webui-service.sh install refuses
# the flag, so a kept flag would fail the rollback at install-console. Each drop is named in the plan
# and the steps. The check reads the target's own cli.mjs, as --tester-port does.
DROP_BOARD="" DROP_BOARD_WHY=""
local -a kept=()
for ((i = 0; i < ${#BASE_CONSOLE[@]}; i++)); do
if [ "${BASE_CONSOLE[i]}" != --board ]; then kept+=("${BASE_CONSOLE[i]}"); continue; fi
i=$((i + 1))
if parses_flag "$cli" --board; then kept+=(--board "${BASE_CONSOLE[i]:-}")
else DROP_BOARD="--board ${BASE_CONSOLE[i]:-}"; fi
if [ "$OP" != rollback ] && parses_flag "$cli" --board; then kept+=(--board "${BASE_CONSOLE[i]:-}"); continue; fi
DROP_BOARD="--board ${BASE_CONSOLE[i]:-}"
if [ "$OP" = rollback ]; then DROP_BOARD_WHY="the 7331 board is retired, so the rolled-back Console runs without the board"
else DROP_BOARD_WHY="the target Console does not parse it, so the board runs in the Console"; fi
done
BASE_CONSOLE=("${kept[@]}")
if grep -q 'MOSAIC_TESTER_SOCKET_V1' "$cli"; then socket_supported=1
@@ -1452,7 +1456,7 @@ plan() {
[ "$RUN_PENDING" = 0 ] || echo "pm: the bus starts without --pm and the PM stays pending; next: Jason's plugins set of harness, then pm-on"
echo "bus: scripts/bus-service.sh install $flags--copy $TO --copies $COPIES$([ "$RUN_PM" = 0 ] || echo ' --pm') --claude $P_CLAUDE"
[ -z "$DROP_TESTER" ] || echo "drop $DROP_TESTER; the target Console does not support it"
[ -z "$DROP_BOARD" ] || echo "drop $DROP_BOARD; the target Console does not parse it, so the board runs in the Console"
[ -z "$DROP_BOARD" ] || echo "drop $DROP_BOARD; $DROP_BOARD_WHY"
echo "$PROOF_PLAN"
echo "$SEND_PLAN"
[ -z "$SSO_PLAN" ] || echo "$SSO_PLAN"
@@ -1473,7 +1477,7 @@ run_steps() {
else log_line result started; fi
capture || refuse capture-failed "could not write the rollback set $RS; nothing was stopped"
[ -z "$DROP_TESTER" ] || say "drop $DROP_TESTER; the target Console does not support it"
[ -z "$DROP_BOARD" ] || say "drop $DROP_BOARD; the target Console does not parse it, so the board runs in the Console"
[ -z "$DROP_BOARD" ] || say "drop $DROP_BOARD; $DROP_BOARD_WHY"
[ -z "$PROOF_STEP" ] || say "$PROOF_STEP"
[ -z "$SEND_STEP" ] || say "$SEND_STEP"
[ -z "$SSO_STEP" ] || say "$SSO_STEP"
@@ -2327,7 +2331,7 @@ STORE="$DATA/bus/bus.sqlite"
BUS_UNIT="${BUS_TEMPLATE%.service}$BUSINESS.service"
FROM="" TO="" ACT="" RS="" POST="" BACKUP="" TAKEN="" RESTORED="" STEPS="" COUNT="" UNCLEAN="" FAIL=""
STORE_SCHEMA="" START_EPOCH="" START_MS=0 GATE="" GATE_WHY="" S_CHANGED=""
P_COPY="" P_PM=0 P_PENDING=0 P_SCHEMA="" P_CLAUDE="" P_CONSOLE=() RUN_PM=0 RUN_PENDING=0 RUN_CONSOLE=() DROP_TESTER="" DROP_BOARD="" PROOF_PLAN="" PROOF_STEP="" SEND_PLAN="" SEND_STEP="" PLAN_REFUSE=()
P_COPY="" P_PM=0 P_PENDING=0 P_SCHEMA="" P_CLAUDE="" P_CONSOLE=() RUN_PM=0 RUN_PENDING=0 RUN_CONSOLE=() DROP_TESTER="" DROP_BOARD="" DROP_BOARD_WHY="" PROOF_PLAN="" PROOF_STEP="" SEND_PLAN="" SEND_STEP="" PLAN_REFUSE=()
BASE_CONSOLE=() SERVE_FLAGS=() SSO_PLAN="" SSO_STEP="" SSO_DROP="" SSO_NO_TARGET=""
R="" R_RC=0 R_EARLY=() R_BK="" R_HOST_PID="" R_CONSOLE_PID="" R_STARTED="" LIVE_CFG="" LIVE_BIZ=""
O_ACT="" FLOOR=0