docs(host): close five first-install run-sheet gaps (#1822)

(cherry picked from commit a1b13c60e1)
This commit is contained in:
jason.woltje committed 2026-10-23 23:30:30 -05:00
1 parent 4a4bef6c13
commit 33f49f7d5a
2 files changed
+148 -1

No files matched your search

+56
View File
@@ -0,0 +1,56 @@
# Row 326 / #1822 — five run-sheet gaps
Goldie, 2026-10-24. Branch goldie/row-run-sheet in /home/jwoltje/.t3/worktrees/mosaic-stack/goldie-row-run-sheet. Base local refactor4ec279dc43922b338a94718a2afdaba87ef89c15, explicitly assigned by sage-3893. Brief docs/plans/2026-10-24_run-sheet-doc-brief.md at4ec279dc4, blobce3e24fa, section Brief: five run-sheet gaps. Reviewer Gladstone; Sage posts review and owns landing/full gate.
## Before / after and choices
Before: the sheet omitted proof bytes/newline handling, treated render refusal without distinguishing metadata/content, omitted missing-project warning consequences and a pre-install key check, offered no first-install retry form, and omitted the bus-relative backup checksum form.
After: the sheet covers all five cases in their existing sections6/9. Numbered section headings are unchanged. Only this BUILD.md and docs/guides/host-run-sheet.md are edited, per the brief's explicit two-file boundary. Session/build-phase registration and the manifest are recorded here rather than adding changes to SESSIONS.md, BUILD-LOG.md or a third manifest file. No host action or credential read. No suite or product command executed, and no CLEAR needed for this doc-only row (decision137).
| Gap / claim | Source read and how it supports the text |
| --- | --- |
| Proof format, optional LF | webui/src/serve.mjs:257-267 opens no-follow, checks regular/uid/mode600-or400, strips exactly one trailing LF at263, then requires32+ ASCII base64url chars at264. Table states the allowed alphabet and one optional trailing LF. Extra newline/CR is outside that alphabet. |
| Render versus start | case2-unit.sh:397-408 explicitly stats/realpaths the proof and never opens it. webui/src/serve.mjs:267 throws the content error; webui/src/cli.mjs:138-140 catches and sets exit2. Table now distinguishes metadata refusal at render from bad bytes at Console start. |
| Missing project warning | business/src/cli.mjs:74-78 reports absent plus the no-project-variables warning, then continues. case2-install.sh:217-221 captures validate output and prints business-validates on success. Text makes that incomplete install possible and tells operators to stop on the missing-file warning, per Duckworth row323 N2. |
| Pre-install key check | business/src/cli.mjs:71-82 checks every file-backed project, skips kind library, and requires loaded id==business key at80. Read-only inline Node uses only builtins and section2's pinned node, as mosaic. It reads the exact intended business input and files under each project's root, prints a match, and exits nonzero with STOP for read/JSON/id failure. Expected .53 output comes from .mosaic/project.json:3 and case1-business.mosaic-stack.json:10-11. Fixing business rather than project id is mos-1563. |
| Why check before install | case2-install.sh:178 makes the copy before validation at217. New check is explicitly before case2-install.sh and needs no existing Mosaic config, npm modules or installed wrapper. |
| Refused first-install residue | case2-install.sh:172/174 creates/bootstraps the config; bootstrap.sh:13 dispatches to mosaic-config.mjs, whose412-424 creates config.json or validates an existing one without rewriting it. case2-install.sh:200-211 may create businesses/file and library slots. case2-persona-import.mjs:300-308 may create library personas. Validation cleanup at case2-install.sh:217-219 removes only this run's new business file, retaining dirs. Copy may already exist under the path at install:80/178; stack-copy.sh:68 reuses an existing copy. |
| Retry paths / refs | Mos-1565/ops-0291, as pinned in the brief, names exactly root checkout, mosaic clone and default config dir to move aside. UTC-suffixed mv is root-only documentation; no delete, chown or chmod. Destination collision/move failure prints STOP. Retry starts section6's first-install block again. The existing upgrade fetch targets only refs/remotes/origin/refactor; stack-copy.sh:44-48 requires reachability from both it and refs/heads/refactor. |
| Credential re-placement | Existing sheet section9 names claude-session.env, library proof and library-gitea with owner/mode. Moving config aside preserves them but the new config needs Jason to re-place any already-filled files, under mos-1565. Existing per-agent slots still require credentials issue/Approve, never hand placement. No value or command to read a credential added. |
| Backup hand check | stack-release.sh:475-482 creates activation/bus and writes SHA256SUMS from inside bus. restore at502 and rehearsal at2244 cd bus and check ../SHA256SUMS. New hand command does the same as mosaic, without --quiet so the expected bus.sqlite:OK is visible; ops-0292 records why checking in activation itself fails. |
## Read-only grep gate
Executed after editing, with no test/suite run:
```
git grep -n host-run-sheet -- 'packages/*/tests/*' 'scripts/*'
# no output, exit1
git grep -n case1-business.mosaic-stack.json -- 'packages/*/tests/*' 'scripts/*'
packages/business/tests/case1-business-file.test.mjs:13:const DOCUMENT = join(REPO, "docs/guides/case1-business.mosaic-stack.json");
packages/business/tests/case1-project-key.test.mjs:9:const CASE1 = join(REPO, "docs/guides/case1-business.mosaic-stack.json");
# exit0: positive control, same command form, both tests read this document
git grep -n -E '(host-run-sheet.*[0-9a-f]{64}|[0-9a-f]{64}.*host-run-sheet)' -- 'packages/*/tests/*'
# no output, exit1: no 64-hex test pin beside the sheet name
```
An earlier positive control with case2-install.sh also found the real installer spawn in case1-business-file.test.mjs:39 and the CLI installer tests. The sheet-name search finds no reference at all in tests/scripts, stronger than just finding no same-line pin. No read/pin blocker.
Static syntax only: extracted the three new shell snippets, replaced only the version placeholder for parsing, and ran bash-n3/3. Extracted Node heredoc parsed with node --check --input-type=module. No snippet body executed. git diff --check passes. Source citations checked against this base; no new em dash and section numbering unchanged.
## Session / claimant receipt
2026-10-24 | Goldie a13dd6d5-5e73-4b7d-8318-02abeff24940 | Row326/#1822 doc-only build | Five gaps documented with source lines and grep control; no suites/host/credential action. Claim goldie-0326-start-1 rev3793, briefed→in-progress. In-review follows the pushed candidate; expected tokenless pre-send failure will be handed to Sage.
## Inline manifest
Only the sheet needs a content pin; BUILD.md is this receipt in the same candidate commit. An inline manifest honors the brief's two-file limit:
```
e4c14516edff3bfbc81ea553a5a6ccebb6154942473540659d4681c2d56c9711 docs/guides/host-run-sheet.md
```
Reviewer validates the five source chains and operational order. Sage's landing gate owns suites. No code row265 work started; its brief comes separately.
+92 -1
View File
@@ -207,6 +207,43 @@ install -o mosaic -g mosaic -m 0600 /root/<sha>.bundle /srv/mosaic/incoming/<sha
sudo -u mosaic -H git clone -q -b refactor /srv/mosaic/incoming/<sha>.bundle /srv/mosaic/src/mosaic-stack
```
If the first install refuses, use Mos's retry path (mos-1565, ops-0291). Bootstrap can already have
written `/srv/mosaic/.config/mosaic-dev/config.json` (`scripts/case2-install.sh:174`,
`scripts/bootstrap.sh:13`, `scripts/mosaic-config.mjs:412-424`). The config dir can also hold
`businesses/`, the business file, and library persona files and slot dirs
(`scripts/case2-install.sh:200-211`, `scripts/case2-persona-import.mjs:300-308`). A validation refusal
removes only the business file this run created; its directory remains (`scripts/case2-install.sh:217-219`).
The copy may already exist under `/srv/mosaic/.local/share/mosaic-stack/copies/<sha>`
(`scripts/case2-install.sh:80`, `scripts/stack-copy.sh:68`).
As root, move these three paths aside with a UTC suffix. Move, never delete, so their owner and mode
stay with them (mos-1565). This is the default-instance retry for the paths in this sheet:
```sh
(
cd / || exit 1
retry_stamp="$(date -u +%Y%m%dT%H%M%SZ)" || exit 1
for retry_path in /root/mosaic-stack /srv/mosaic/src/mosaic-stack /srv/mosaic/.config/mosaic-dev; do
if [ -e "$retry_path" ] || [ -L "$retry_path" ]; then
retry_saved="$retry_path.$retry_stamp"
if [ -e "$retry_saved" ] || [ -L "$retry_saved" ]; then
echo "STOP: retry destination already exists: $retry_saved" >&2
exit 1
fi
mv -T -- "$retry_path" "$retry_saved" || exit 1
fi
done
) || echo "STOP: could not move the first-install paths aside; do not continue"
```
Then rerun the first-install block from its first line. Never use the upgrade fetch below on clones
whose first install has not finished: it updates `refs/remotes/origin/refactor`, while copy requires
the commit to be reachable from both that ref and `refs/heads/refactor` (`scripts/stack-copy.sh:44-48`; mos-1565).
Recheck section 9 before starting units. If Jason already filled `claude-session.env`, the library
proof file or `library-gitea`, have him re-place those files under the new config dir with section 9's
owner and mode. Per-agent slots still use `mosaic credentials issue` or Approve, never a hand copy
(section 9; mos-1565). The moved config dir preserves the earlier files.
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`):
@@ -254,6 +291,19 @@ When a run doesn't exit 0, send Mos the exit and the last lines of `/var/log/mos
the old copy. The next run refuses `args-differ`, and a render from that file would put the old copy back.
Move the file the result line names into place with `mv -f`. The two differ only in the `--copy` value.
To check a backup by hand before rollback or restore, run as the stack's user from its `bus/` directory:
```sh
sudo -u mosaic -H /bin/sh -c \
'cd "<dataRoot>/backups/stack/<activation>/bus" && sha256sum -c ../SHA256SUMS'
```
Expect `bus.sqlite: OK`; stop on a failed check. The set holds `bus/bus.sqlite`, and `SHA256SUMS` names
`bus.sqlite` relative to `bus/` (`scripts/stack-release.sh:475-482`). Running `sha256sum -c SHA256SUMS`
from the backup set's directory looks for `bus.sqlite` one directory too high (ops-0292). Restore and
rehearsal use the same `bus/` form
(`scripts/stack-release.sh:502`, `scripts/stack-release.sh:2244`).
After `case2-install.sh` succeeds, create the human CLI wrapper before running
`stack-copy.sh check --units external`. Creating this wrapper is a separate live
step requiring Mos's go (`scripts/stack-copy.sh:3`); the install does not create it. Run only on that go:
@@ -275,11 +325,52 @@ it. The wrapper must name the same `<sha>` copy as the units.
Precheck lines: none. Root's checkout at `<sha>` and the stack's user's clone: precheck line missing.
After install, `stack-copy.sh check --units external` reads the copy the units run.
Before running `case2-install.sh`, check the exact file you will pass as `--business-file`. Each
non-library `projects` key must equal the `id` in `<root>/.mosaic/project.json`, and that file must exist
in the stack's user's clone. Library entries are skipped by validate (`packages/business/src/cli.mjs:72`).
Run this read-only check as `mosaic` with section 2's pinned node:
```sh
sudo -u mosaic -H /opt/mosaic-stack/node-<v>/bin/node --input-type=module - '<business-file>' <<'MOSAIC_PROJECT_KEYS'
import { readFileSync } from 'node:fs';
import { join } from 'node:path';
try {
const business = JSON.parse(readFileSync(process.argv[2], 'utf8'));
const projects = Object.entries(business.projects);
if (!projects.length) throw new Error('business file declares no projects');
for (const [id, entry] of projects) {
if (entry.kind === 'library') continue;
const file = join(entry.root, '.mosaic', 'project.json');
const project = JSON.parse(readFileSync(file, 'utf8'));
if (project.id !== id)
throw new Error(`project file ${file} has id ${project.id}, but business ${business.id} declares it as ${id}`);
console.log(`ok project ${id}: ${file} has id ${project.id}`);
}
} catch (error) {
console.error(`STOP: ${error.message}; do not install`);
process.exitCode = 1;
}
MOSAIC_PROJECT_KEYS
```
For .53 expect `ok project stack: /srv/mosaic/src/mosaic-stack/.mosaic/project.json has id stack`
(`.mosaic/project.json:3`, `docs/guides/case1-business.mosaic-stack.json:10-11`). Any STOP or nonzero exit
stops the install. Fix a mismatched business key, not the project id (mos-1563). The install otherwise
reaches that refusal at step 7, after making the copy (`packages/business/src/cli.mjs:80`,
`scripts/case2-install.sh:178`, `scripts/case2-install.sh:217`).
The stack's user's `scripts/case2-install.sh` (C2-13b, design 10 step 2b) makes the first copy with
`stack-copy.sh copy <sha>`. The copy runs `npm ci` from `package-lock.json`, which installs the pinned
harness, so the host needs to reach the npm registry during the install. The install runs no
`release.sh package` or `activate`.
A missing `.mosaic/project.json` only warns: `project <id>: no project file at <path>; it has no project
variables`. Validate reports that project `absent` and continues (`packages/business/src/cli.mjs:74-78`).
An install can therefore pass step 7 without the project's variables. It captures validate's output
and prints `business <id> validates` (`scripts/case2-install.sh:217-221`). Treat the missing-file warning as a stop,
place the correct project file and repeat the check above before continuing (Duckworth 323 N2;
`scripts/case2-install.sh:217-221`).
## 7. The args file
`/etc/mosaic-stack/dev.args` holds the `case2-unit.sh` arguments, one per line, with the serve flags after a
@@ -318,7 +409,7 @@ file with `stat -c '%U %a %n' PATH`, never `cat`. `<business>` is the host's bus
| Path | File | Mode | Owner | Profile | Precheck line |
|---|---|---|---|---|---|
| `/srv/mosaic/.config/mosaic-dev/secrets/` | `claude-session.env`, one line `ZAI_API_KEY=...` | 0600 | mosaic | library | `<path> sets ZAI_API_KEY` |
| `/srv/mosaic/.config/mosaic-dev/secrets/` | the Console's proof file, the path the args file's `--proof-file` names | 0600 or 0400 | mosaic | library | none. `case2-unit.sh` refuses at render. Precheck line missing. |
| `/srv/mosaic/.config/mosaic-dev/secrets/` | the Console's proof file, the path the args file's `--proof-file` names: one base64url line, at least 32 `[A-Za-z0-9_-]` characters, with one optional trailing LF (`packages/webui/src/serve.mjs:263-264`) | 0600 or 0400 | mosaic | library | none. Render checks metadata only and never opens the file (`scripts/case2-unit.sh:397-408`). Bad bytes refuse at Console start with exit 2 (`packages/webui/src/serve.mjs:267`, `packages/webui/src/cli.mjs:138-140`). Precheck line missing. |
| `/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 ...` |
| `/srv/mosaic/.config/mosaic-dev/secrets/agents/<business>/<agent>/` | one file per service | 0600, dirs 0700 | mosaic | both | none. Filled only by `mosaic credentials issue`, or Approve after #436. Never by hand. |