Files

9.8 KiB

Row 321: F7 wrapper environment and F10 first adoption (#1817)

Owner Gladstone; reviewer Boyd (separate Codex thread); queue/review/landing Sage. Authorized by sage-3791. Branch gladstone/row321 starts at fetched origin/refactor 833ec1cc8d. The row320 branch is unchanged. Brief: docs/plans/2026-10-23_host-fixes-1816-brief.md, section "Brief: F7, the wrapper step's PATH and umask under sudo", including F10; verified blob 7333f94ba9 (requested pin 7333f94ba9). Only the run sheet and this BUILD.md change. No scripts, tests, config, credentials, queue state, deployment or live host operation changed. No tests or Node commands run.

Before and after

F7: sudo secure_path omits the pinned Node directory, so wrapper creation reaches instance.sh's bare node and exits127. An inherited umask can leave the bin dir0775. The MOSAIC_WRAPPER heredoc now sets umask077 and the sheet's existing pinned Node PATH before mkdir. One sentence names secure_path and why the step sets both. The existing Mos go remains explicit. No wrapper command is executed here.

F10: first install omits adoption, so the first activate refuses no-pointer. The release-transport section now places explicit adoption after both units are running (section10) and before any activate/upgrade, run as mosaic on Mos's go by Jason or Mos, citing stack-release.sh:14. It uses the configured dev instance and external units, with the same pinned Node PATH under sudo. Adjacent to the upgrade command, the sheet now says only the first upgrade after adopt takes --no-rehearsal: adopt writes no backup; missing flag refuses no-backup; later upgrades omit it, because a backup with that flag refuses rehearsal-required. Source checks: stack-release.sh pointer_for_business refuses no-pointer and directs adopt; stack-upgrade.sh rehearse handles rc3/no-backup and rc0/rehearsal-required. This records existing behavior and authority; it adds no requirement or live go.

Test-reference grep (negative and positive control)

The required negative grep covers both F7 and F10 because neither is read by any matched test. The positive control locates the precheck path that the unit/install tests actually execute, showing the pathspecs reach their test files.

Negative grep:

git grep -n 'host-run-sheet' -- 'packages/*/tests/*' 'scripts/test-*.sh'
# exit 1
# no matches

Positive control:

git grep -n 'case2-precheck.sh' -- 'packages/*/tests/*' 'scripts/test-*.sh'
# exit 0
packages/cli/tests/case1-accept.test.mjs:26:const PRECHECK = `${HOME}/.local/share/mosaic-stack/copies/${COPY}/scripts/case2-precheck.sh`;
packages/cli/tests/case2-install.test.mjs:2:// scripts/case2-precheck.sh's library checks and --library-status mode. Brief:
packages/cli/tests/case2-install.test.mjs:23:const PRECHECK = join(REPO, "scripts", "case2-precheck.sh");
packages/cli/tests/case2-install.test.mjs:271:// ---- case2-precheck.sh, the library profile (D6a) ----
packages/cli/tests/case2-install.test.mjs:334:// ---- case2-precheck.sh --library-status (D6b) ----
packages/cli/tests/case2-unit.test.mjs:2:// and scripts/case2-precheck.sh checks the host before they start. Brief: docs/plans/2026-10-20_case2-rows.md
packages/cli/tests/case2-unit.test.mjs:20:const PRECHECK = join(REPO, "scripts", "case2-precheck.sh");
packages/cli/tests/case2-unit.test.mjs:588:// ---- case2-precheck.sh ----

Run-sheet diff against the pinned base (git diff --unified=0)

diff --git a/docs/guides/host-run-sheet.md b/docs/guides/host-run-sheet.md
index d2ba28d7c..96fd4f363 100644
--- a/docs/guides/host-run-sheet.md
+++ b/docs/guides/host-run-sheet.md
@@ -209,0 +210,9 @@ sudo -u mosaic -H git clone -q -b refactor /srv/mosaic/incoming/<sha>.bundle /sr
+After a first install, once both units are running (section 10), adopt the installed copy before
+any activate or upgrade. This step runs only on Mos's go, with Jason or Mos running it
+(`scripts/stack-release.sh:14`):
+
+```sh
+sudo -u mosaic -H env PATH=/opt/mosaic-stack/node-<v>/bin:/usr/sbin:/usr/bin:/sbin:/bin \
+  /srv/mosaic/src/mosaic-stack/scripts/stack-release.sh adopt <business> --instance dev --units external
+```
+
@@ -211,0 +221,4 @@ Each upgrade, before `stack-upgrade.sh upgrade`:
+Only the first upgrade after adopt takes `--no-rehearsal`: append it to the upgrade command below.
+Adopt writes no backup, so omitting the flag then refuses `no-backup`. Subsequent upgrades omit it;
+when a backup exists, supplying it refuses `rehearsal-required`.
+
@@ -246,0 +260 @@ step requiring Mos's go (`scripts/stack-copy.sh:3`); the install does not create
+The step runs under sudo's `secure_path`, so it sets its own PATH and umask.
@@ -250,0 +265,2 @@ cd / || exit 1
+umask 077
+PATH=/opt/mosaic-stack/node-<v>/bin:/usr/sbin:/usr/bin:/sbin:/bin; export PATH

Verification

No matching test reads the changed lines, so the stop condition did not apply. No test run or slot is required under sage-3791. Verification is static: the required grep and positive control above, exact brief/base pins, scoped diff, and git diff --check. Reviewer Boyd and Sage own review/landing; no approval or landing is claimed here.

Receipt correction: the first commit embedded ordinary diff context lines that contain a single space. git diff --check flagged those as trailing whitespace. This follow-up uses the exact zero-context diff instead. The run sheet is unchanged, no tests or Node commands run, and no amend or force-push is used.

F11 addition (sage-3805, mos-1549, ops-0275)

The brief was extended after F7/F10 drafting and re-pinned at revision3733: canonical local commit a9bae9af2, same F7 section anchor, full verified blob 0e684ef210 (requested pin0e684ef21034). Read using git show from the canonical checkout; no fetch, rebase or queue write is required. This new pin supersedes the earlier brief pin for this follow-up.

Defect: a precheck one second after unit start found no bus.sqlite yet. Section10 now requires loopback healthz success after a unit start before any case2-precheck.sh, including section9's --library-status. It uses the same 127.0.0.1:/healthz curl form as scripts/case1-accept.sh:458. The subshell tries at most30 times, each curl capped at2 seconds, with1 second between failed attempts (at most89 seconds plus scheduling overhead). Exhaustion prints STOP and exits only the subshell. The instruction says to stop before precheck/adopt; it does not exit the interactive operator shell. The section9 library-gitea row now explicitly requires this bounded wait before --library-status. is the installed Console unit's --port, not an assumed port or business name. No host action, test, Node command or credential access occurred.

Required grep repeated for the widened scope:

git grep -n 'host-run-sheet' -- 'packages/*/tests/*' 'scripts/test-*.sh'
# exit1, no matches
git grep -n 'case2-precheck.sh' -- 'packages/*/tests/*' 'scripts/test-*.sh'
# exit0, positive control; output follows
packages/cli/tests/case1-accept.test.mjs:26:const PRECHECK = `${HOME}/.local/share/mosaic-stack/copies/${COPY}/scripts/case2-precheck.sh`;
packages/cli/tests/case2-install.test.mjs:2:// scripts/case2-precheck.sh's library checks and --library-status mode. Brief:
packages/cli/tests/case2-install.test.mjs:23:const PRECHECK = join(REPO, "scripts", "case2-precheck.sh");
packages/cli/tests/case2-install.test.mjs:271:// ---- case2-precheck.sh, the library profile (D6a) ----
packages/cli/tests/case2-install.test.mjs:334:// ---- case2-precheck.sh --library-status (D6b) ----
packages/cli/tests/case2-unit.test.mjs:2:// and scripts/case2-precheck.sh checks the host before they start. Brief: docs/plans/2026-10-20_case2-rows.md
packages/cli/tests/case2-unit.test.mjs:20:const PRECHECK = join(REPO, "scripts", "case2-precheck.sh");
packages/cli/tests/case2-unit.test.mjs:588:// ---- case2-precheck.sh ----

No test reads the changed run-sheet lines; the stop condition does not apply. No test run is needed under the docs-only brief. Static verification checks both locations, retry limit/timeout, the loopback URL, unchanged F7/F10 and scoped diff.

F11 delta against d9400f0d3 (git diff --unified=0):

diff --git a/docs/guides/host-run-sheet.md b/docs/guides/host-run-sheet.md
index 96fd4f363..e9fa87f24 100644
--- a/docs/guides/host-run-sheet.md
+++ b/docs/guides/host-run-sheet.md
@@ -322 +322 @@ file with `stat -c '%U %a %n' PATH`, never `cat`. `<business>` is the host's bus
-| `/srv/mosaic/.config/mosaic-dev/secrets/<business>/` | `library-gitea` | 0600 | mosaic | library | `case2-precheck.sh --business <business> --library-status`, after the bus is up: `library-last-error library-credential-missing` until it's placed, then `library <project> last fetched ...` |
+| `/srv/mosaic/.config/mosaic-dev/secrets/<business>/` | `library-gitea` | 0600 | mosaic | library | After a unit start, wait for loopback `/healthz` with the bounded retry in section 10 before `case2-precheck.sh --business <business> --library-status`: `library-last-error library-credential-missing` until it's placed, then `library <project> last fetched ...` |
@@ -357,0 +358,16 @@ None by hand. I2's `case2-unit.sh` enables both units, starts the bus, then the
+After a unit start, wait for the Console's loopback `/healthz` to succeed before running
+`case2-precheck.sh`, including `--library-status` in section 9. Replace `<port>` with the installed
+Console unit's `--port`. This retry is bounded to 30 attempts, each with a 2-second timeout, with
+1 second between failed attempts. If it prints STOP, stop here; do not run the precheck or adopt.
+
+```sh
+(
+  attempts=0
+  until curl -fsS --max-time 2 -o /dev/null 'http://127.0.0.1:<port>/healthz'; do
+    attempts=$((attempts + 1))
+    [ "$attempts" -lt 30 ] || { echo "STOP: loopback healthz did not become ready; do not continue" >&2; exit 1; }
+    sleep 1
+  done
+)
+```
+