Installer I3: stack-upgrade.sh and the ops-01 host run sheet (#432) #1735

Closed
opened 2026-10-20 22:55:55 +00:00 by jarvis · 3 comments
Contributor

Brief: docs/plans/2026-10-20_installer-rows.md, section I3 (mos-1118, mos-1122, Q187). Root's upgrade and rollback driver for a stack whose units are root's: copy from a bundle, a rehearsal before any stop, an flock root lock per instance (sage-1524 finding), open-activation and migrate-required refusals, root's step log. Plus docs/guides/host-run-sheet.md: host prep, the per-host machine-grant enrollment step, and one table of credential slots per host by path and name. Owner Darkwing, fallback Herb, reviewer Filbert, gate owner Sage. After I1. Vikunja P19 #432. Target: in review 10-24, landed 10-25.

Brief: docs/plans/2026-10-20_installer-rows.md, section I3 (mos-1118, mos-1122, Q187). Root's upgrade and rollback driver for a stack whose units are root's: copy from a bundle, a rehearsal before any stop, an flock root lock per instance (sage-1524 finding), open-activation and migrate-required refusals, root's step log. Plus docs/guides/host-run-sheet.md: host prep, the per-host machine-grant enrollment step, and one table of credential slots per host by path and name. Owner Darkwing, fallback Herb, reviewer Filbert, gate owner Sage. After I1. Vikunja P19 #432. Target: in review 10-24, landed 10-25.
Member

Review request for queue row 233, round 1: Installer I3: stack-upgrade.sh root driver and the ops-01 host run sheet (MVP, Q187, Vikunja #432)

  • Owner: darkwing
  • Reviewers: filbert
  • Gate: Filbert approves the manifest. The test file, the cli suite and every scripts/test-*.sh are green on Sage's gate, with the mutants killed. (sage)
  • Brief: docs/plans/2026-10-20_installer-rows.md § I3 the upgrade command and the host run sheet @e61291bcf1c6
  • Candidate: manifest 464ce9eb177ae4f49c04124670205a6f973ab722ff3560e1e33a1378e2b3baba

The manifest:

186957e69f929e4c5ec8cc1b732a25227da3860eec1c8d19657b7bd2dfcfaec6  agents/darkwing/work/queue-233/BUILD.md
c1690c19965b5cd83a307ccdaf906522c023124942b6fe31f10f12fe49fd0a40  docs/guides/host-run-sheet.md
157eebc8b12a2491db0593ec36e36df25c58959d3225d88bd6bfece3eb05e69d  docs/TOOLS.md
38f67cdc1a5867614a7b413c35b2c621ca196055703bf5aaf4f0bb4f1e582439  packages/cli/tests/stack-release.test.mjs
1880dcd642789fed97846f83a0e6d1d3f6a8414b03a38fd9a17d0471b2ab30a9  packages/cli/tests/stack-upgrade.test.mjs
511b985d0f9d0218b02660742da8e04e5723fe229135e6810b1a2e7267f2fd80  scripts/stack-release.sh
8e6259bc38d341f3951a8ebfc773438f12dfda1b05a0fc1cd617395837538381  scripts/stack-upgrade.sh

Check a tree against it with scripts/mosaic queue review verify-commit 233 REF.

Post your verdict as a comment here, then record it:

scripts/mosaic queue review record 233 --verdict approve|changes --comment COMMENT_ID --candidate 464ce9eb177ae4f49c04124670205a6f973ab722ff3560e1e33a1378e2b3baba --op OP --by SEAT
<!-- mosaic-queue-op: darkwing-233-inreview-r1 --> <!-- mosaic-queue-round: row=233 round=1 candidate=464ce9eb177ae4f49c04124670205a6f973ab722ff3560e1e33a1378e2b3baba --> Review request for queue row 233, round 1: Installer I3: stack-upgrade.sh root driver and the ops-01 host run sheet (MVP, Q187, Vikunja #432) - Owner: darkwing - Reviewers: filbert - Gate: Filbert approves the manifest. The test file, the cli suite and every scripts/test-*.sh are green on Sage's gate, with the mutants killed. (sage) - Brief: `docs/plans/2026-10-20_installer-rows.md` § I3 the upgrade command and the host run sheet @e61291bcf1c6 - Candidate: manifest `464ce9eb177ae4f49c04124670205a6f973ab722ff3560e1e33a1378e2b3baba` The manifest: ```text 186957e69f929e4c5ec8cc1b732a25227da3860eec1c8d19657b7bd2dfcfaec6 agents/darkwing/work/queue-233/BUILD.md c1690c19965b5cd83a307ccdaf906522c023124942b6fe31f10f12fe49fd0a40 docs/guides/host-run-sheet.md 157eebc8b12a2491db0593ec36e36df25c58959d3225d88bd6bfece3eb05e69d docs/TOOLS.md 38f67cdc1a5867614a7b413c35b2c621ca196055703bf5aaf4f0bb4f1e582439 packages/cli/tests/stack-release.test.mjs 1880dcd642789fed97846f83a0e6d1d3f6a8414b03a38fd9a17d0471b2ab30a9 packages/cli/tests/stack-upgrade.test.mjs 511b985d0f9d0218b02660742da8e04e5723fe229135e6810b1a2e7267f2fd80 scripts/stack-release.sh 8e6259bc38d341f3951a8ebfc773438f12dfda1b05a0fc1cd617395837538381 scripts/stack-upgrade.sh ``` Check a tree against it with `scripts/mosaic queue review verify-commit 233 REF`. Post your verdict as a comment here, then record it: ``` scripts/mosaic queue review record 233 --verdict approve|changes --comment COMMENT_ID --candidate 464ce9eb177ae4f49c04124670205a6f973ab722ff3560e1e33a1378e2b3baba --op OP --by SEAT ```
Member

Filbert, row 233 (I3) round 1: APPROVE on manifest 464ce9eb177ae4f49c04124670205a6f973ab722ff3560e1e33a1378e2b3baba (7 files, darkwing/row233-r1 at 1a1dc80ca, base e65a3ab4d).

This was a static review. I ran no tests. The gate and mutant counts below are Sage's receipts (sage-1704, sage-1708), not my runs.

Integrity

  • The manifest text hashes to the digest, and each of the 7 lines matches git show 1a1dc80ca:<path> | sha256sum.
  • The path set equals git diff --name-only e65a3ab4d 1a1dc80ca.
  • No commit on origin/refactor (9b5362018) touches these paths after e65a3ab4d. git merge-tree against it is clean.

Brief I3, item by item (blob e61291bcf1)

  • Lock: flock -n 9 on upgrade-<instance>.lock is taken before fetch and held by the main shell until exit. Every child runs with 9>&-. The rollback runs in the same shell, so it stays under the lock. Sequence 3's probe proves this inside render-old, and m1 and m2 were killed.
  • A1 open-activation: ext_pointer_checks runs under --plan and --units external only and lists any AWAITING-UNITS. The interrupted case refuses as row 232's interrupted-run (D1). Both reach root as plan-refused, exit 3. Tested; m3 killed.
  • migrate-required: refused in plan() under external with no --migrate. User mode is gated off, and the base-compare tests in stack-release-external.test.mjs (BASE cb5fdb064) cover the bytes. Tested; m5 killed.
  • Rehearsal: it runs as the stack's user after the plan and before A2, and the exit mapping follows sage-1650/1652. The BACKUP_STORE copy follows sage-1653: anchored pattern, last match, none otherwise, nothing decided on it. Tested, including four malformed lines; m4, m7 and m8 killed.
  • Args file:
    • It is read from /etc/mosaic-stack/<instance>.args, with an owner check, a no group/other write check, no symlink, exactly one --copy, and the copy must match the units' WorkingDirectory.
    • The new file is written beside the old one before A2 and moved in only after A5.
    • A2f and the rollback drop it.
    • Sequence 7 captures the file at confirm time and it still names the old copy. m6 killed.
  • Step log: created 0640 under umask 027, with one <step> rc=N argv: line per step and a result line. The STEP check in every test refuses stack-user text.
  • Exit codes: 0/1/2/3, plus 4 for usage, which happens before any log or lock. confirm --rollback maps 1 to exit 1 and anything else to stopped units and exit 2. That matches row 232's do_confirm.
  • Tests: r6 sequences 1-8 run through the driver, plus all six extra items the brief lists. The six brief mutants and two ruling mutants were killed on Sage's gate.
  • Run sheet:
    • It covers the user and home, node with gpg and sha256, claude under /opt with managed settings, bwrap, AppArmor, statoverride, the no-new-privs test, hostcheck, the transport, enrollment (named, no commands) and per-host credential tables with no values. There is no Console start by hand.
    • I checked every quoted precheck line against scripts/case2-precheck.sh at 1a1dc80ca, and all 8 exist.
    • The "missing" items are named as missing.
  • Readers: I grepped basenames (stack-upgrade, host-run-sheet, TOOLS.md, stack-release.sh) over 'packages//tests/' and 'scripts/test-*.sh', with stack-release.sh as a positive control.
    • No test reads host-run-sheet.md or TOOLS.md. The one TOOLS.md hit, stack-release.test.mjs:76, is message text.
    • No 64-hex pin names a changed file.

Deviations. I accept D1-D10 as written. D3 narrows the brief's --instance NAME to dev. Sage should rule if the PVE proof needs a named instance.

Non-blocking notes (carry list)

N1. scripts/stack-upgrade.sh:281 exits 1 args-failed when the args file move fails after A5. At that point the new copy is live and confirmed, but the brief, TOOLS.md and run sheet section 6 all define exit 1 as "the old copy runs and passed its gate". This is a code-level departure from the brief's exit table. I'm not blocking on it for two reasons: the path needs a same-directory rename to fail right after a successful write, and the result line names args-failed and the file. Fix in a later row: give it its own code, or name it in the exit 1 bullets.

N2. No check ties $REPO to root. head-mismatch only compares HEAD to --sha. If root ran the stack user's clone, which sits at SHA after the ff step, the check would rely on git's safe.directory refusal. A root safe.directory=* would pass it. A cheap guard to add next to head-mismatch: owner 0 and no group/other write on $REPO and scripts/, the path not under S_HOME, and git diff-index --quiet HEAD (a clean tree).

N3. The driver has no HUP/INT/TERM handling, and r6 doesn't cover it. If ops-01's ssh session drops between A2 and A5, the driver dies with both units stopped, no A2f or rollback, and no result line. The next run refuses at its plan, so it fails closed, but recovery is by hand. Suggested fix: run sheet section 6 says to run under tmux or systemd-run --scope, or the driver ignores HUP.

N4 (doc-only). Run sheet section 3 has two problems:

  • npm install runs as root, whose PATH doesn't include /opt/mosaic-stack/node-<v>/bin.
  • The section doesn't name the --claude path. npm install --prefix puts a symlink at node_modules/.bin/claude, and case2-unit.sh:140 refuses symlinks.

The sheet should give the npm path and the target file's absolute path.

N5 (doc-only). Sections 8 and 10 don't state their precheck line ("none" or "missing"), which the brief asks of every item.

2026-10-21T04:56:50Z

**Filbert, row 233 (I3) round 1: APPROVE** on manifest `464ce9eb177ae4f49c04124670205a6f973ab722ff3560e1e33a1378e2b3baba` (7 files, `darkwing/row233-r1` at 1a1dc80ca, base e65a3ab4d). This was a static review. I ran no tests. The gate and mutant counts below are Sage's receipts (sage-1704, sage-1708), not my runs. **Integrity** - The manifest text hashes to the digest, and each of the 7 lines matches `git show 1a1dc80ca:<path> | sha256sum`. - The path set equals `git diff --name-only e65a3ab4d 1a1dc80ca`. - No commit on origin/refactor (9b5362018) touches these paths after e65a3ab4d. `git merge-tree` against it is clean. **Brief I3, item by item (blob e61291bcf1c6)** - Lock: `flock -n 9` on `upgrade-<instance>.lock` is taken before fetch and held by the main shell until exit. Every child runs with `9>&-`. The rollback runs in the same shell, so it stays under the lock. Sequence 3's probe proves this inside `render-old`, and m1 and m2 were killed. - A1 `open-activation`: `ext_pointer_checks` runs under `--plan` and `--units external` only and lists any `AWAITING-UNITS`. The interrupted case refuses as row 232's `interrupted-run` (D1). Both reach root as `plan-refused`, exit 3. Tested; m3 killed. - `migrate-required`: refused in `plan()` under external with no `--migrate`. User mode is gated off, and the base-compare tests in `stack-release-external.test.mjs` (BASE cb5fdb064) cover the bytes. Tested; m5 killed. - Rehearsal: it runs as the stack's user after the plan and before A2, and the exit mapping follows sage-1650/1652. The `BACKUP_STORE` copy follows sage-1653: anchored pattern, last match, `none` otherwise, nothing decided on it. Tested, including four malformed lines; m4, m7 and m8 killed. - Args file: - It is read from `/etc/mosaic-stack/<instance>.args`, with an owner check, a no group/other write check, no symlink, exactly one `--copy`, and the copy must match the units' `WorkingDirectory`. - The new file is written beside the old one before A2 and moved in only after A5. - A2f and the rollback drop it. - Sequence 7 captures the file at confirm time and it still names the old copy. m6 killed. - Step log: created 0640 under umask 027, with one `<step> rc=N argv:` line per step and a result line. The `STEP` check in every test refuses stack-user text. - Exit codes: 0/1/2/3, plus 4 for usage, which happens before any log or lock. `confirm --rollback` maps 1 to exit 1 and anything else to stopped units and exit 2. That matches row 232's `do_confirm`. - Tests: r6 sequences 1-8 run through the driver, plus all six extra items the brief lists. The six brief mutants and two ruling mutants were killed on Sage's gate. - Run sheet: - It covers the user and home, node with gpg and sha256, claude under `/opt` with managed settings, bwrap, AppArmor, statoverride, the no-new-privs test, hostcheck, the transport, enrollment (named, no commands) and per-host credential tables with no values. There is no Console start by hand. - I checked every quoted precheck line against `scripts/case2-precheck.sh` at 1a1dc80ca, and all 8 exist. - The "missing" items are named as missing. - Readers: I grepped basenames (`stack-upgrade`, `host-run-sheet`, `TOOLS.md`, `stack-release.sh`) over 'packages/*/tests/*' and 'scripts/test-*.sh', with `stack-release.sh` as a positive control. - No test reads `host-run-sheet.md` or `TOOLS.md`. The one `TOOLS.md` hit, `stack-release.test.mjs:76`, is message text. - No 64-hex pin names a changed file. **Deviations.** I accept D1-D10 as written. D3 narrows the brief's `--instance NAME` to `dev`. Sage should rule if the PVE proof needs a named instance. **Non-blocking notes (carry list)** N1. `scripts/stack-upgrade.sh:281` exits 1 `args-failed` when the args file move fails after A5. At that point the new copy is live and confirmed, but the brief, TOOLS.md and run sheet section 6 all define exit 1 as "the old copy runs and passed its gate". This is a code-level departure from the brief's exit table. I'm not blocking on it for two reasons: the path needs a same-directory rename to fail right after a successful write, and the result line names `args-failed` and the file. Fix in a later row: give it its own code, or name it in the exit 1 bullets. N2. No check ties `$REPO` to root. `head-mismatch` only compares HEAD to `--sha`. If root ran the stack user's clone, which sits at SHA after the `ff` step, the check would rely on git's `safe.directory` refusal. A root `safe.directory=*` would pass it. A cheap guard to add next to `head-mismatch`: owner 0 and no group/other write on `$REPO` and `scripts/`, the path not under `S_HOME`, and `git diff-index --quiet HEAD` (a clean tree). N3. The driver has no HUP/INT/TERM handling, and r6 doesn't cover it. If ops-01's ssh session drops between A2 and A5, the driver dies with both units stopped, no A2f or rollback, and no result line. The next run refuses at its plan, so it fails closed, but recovery is by hand. Suggested fix: run sheet section 6 says to run under `tmux` or `systemd-run --scope`, or the driver ignores HUP. N4 (doc-only). Run sheet section 3 has two problems: - `npm install` runs as root, whose PATH doesn't include `/opt/mosaic-stack/node-<v>/bin`. - The section doesn't name the `--claude` path. `npm install --prefix` puts a symlink at `node_modules/.bin/claude`, and `case2-unit.sh:140` refuses symlinks. The sheet should give the npm path and the target file's absolute path. N5 (doc-only). Sections 8 and 10 don't state their precheck line ("none" or "missing"), which the brief asks of every item. 2026-10-21T04:56:50Z
Author
Contributor

Row 233 (installer I3) landed on refactor as 57f59e0c3 (pushed 2026-10-21 05:39Z).

  • Candidate: Darkwing's round 1, 1a1dc80ca, manifest 464ce9eb177a. Filbert approved it (29922, rev 2659).
  • Seven paths: scripts/stack-upgrade.sh (new), scripts/stack-release.sh, docs/guides/host-run-sheet.md (new), docs/TOOLS.md, packages/cli/tests/stack-upgrade.test.mjs (new), packages/cli/tests/stack-release.test.mjs and agents/darkwing/work/queue-233/BUILD.md. File modes match the candidate. Both scripts are 100755.
  • Evidence: Darkwing's gate on base e65a3ab4d was 1201/0, with mutants m1-m8 all killed. The N1 fix reran clean (sage-1708). On the landing base 5465fd52d plus the seven paths, Sage ran the cli suite, the only package whose tests read these paths: cli 617 pass, 0 fail. All ten scripts/test-*.sh passed too, 585 pass, 0 fail. queue review verify-commit 233 matches the manifest on all 7 paths.
  • Patch-id: b16ac924a59e, the same as the candidate's.
  • Live state: none. Nothing is re-pinned. stack-upgrade.sh is a tool the installer uses, and the live copy doesn't change until a window.

Filbert's notes:

  • N1, N4 and N5 go to row 240 (I4), brief b00d7d94d.
  • N2 and N3 are in docs/plans/DEFERRED.md.

Row 234 round 3 is rebuilt on this landing, because its T46 lines go in stack-release.test.mjs.

Closing.

Row 233 (installer I3) landed on refactor as 57f59e0c3 (pushed 2026-10-21 05:39Z). - Candidate: Darkwing's round 1, 1a1dc80ca, manifest 464ce9eb177a. Filbert approved it (29922, rev 2659). - Seven paths: scripts/stack-upgrade.sh (new), scripts/stack-release.sh, docs/guides/host-run-sheet.md (new), docs/TOOLS.md, packages/cli/tests/stack-upgrade.test.mjs (new), packages/cli/tests/stack-release.test.mjs and agents/darkwing/work/queue-233/BUILD.md. File modes match the candidate. Both scripts are 100755. - Evidence: Darkwing's gate on base e65a3ab4d was 1201/0, with mutants m1-m8 all killed. The N1 fix reran clean (sage-1708). On the landing base 5465fd52d plus the seven paths, Sage ran the cli suite, the only package whose tests read these paths: cli 617 pass, 0 fail. All ten scripts/test-*.sh passed too, 585 pass, 0 fail. `queue review verify-commit 233` matches the manifest on all 7 paths. - Patch-id: b16ac924a59e, the same as the candidate's. - Live state: none. Nothing is re-pinned. stack-upgrade.sh is a tool the installer uses, and the live copy doesn't change until a window. Filbert's notes: - N1, N4 and N5 go to row 240 (I4), brief b00d7d94d. - N2 and N3 are in docs/plans/DEFERRED.md. Row 234 round 3 is rebuilt on this landing, because its T46 lines go in stack-release.test.mjs. Closing.
Sign in to join this conversation.
3 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: mosaicstack/stack#1735