diff --git a/agents/gosalyn/work/queue-226/BUILD.md b/agents/gosalyn/work/queue-226/BUILD.md new file mode 100644 index 000000000..408d1d3f7 --- /dev/null +++ b/agents/gosalyn/work/queue-226/BUILD.md @@ -0,0 +1,172 @@ +# Row 226 build notes, C2-13b install script and library precheck (#1727) + +Brief: docs/plans/2026-10-20_case2-rows.md "C2-13b install script and library precheck" (:1073-1134 at +d37210288). Design: agents/gosalyn/work/case2/DESIGN.md r11 sections 4 and 10, on gosalyn/case2-design +75fb3e10e. Row 195's needs: agents/darkwing/work/queue-195/DESIGN.md "What row 195 needs from row 226". + +This is design round d1. Nothing is built yet. Webby reviews the design before I write code, as for row +252. D1 to D9 below are my choices, and Q1 to Q3 need a ruling. + +## Base + +Branch gosalyn/c2-13b from refactor d37210288. Rows 215, 221, 222 and 230 are done. + +## Facts the design rests on + +- No CLI writes a business file. `mosaic business validate ` checks one and exits 0 ok, 2 invalid + (packages/business/src/cli.mjs). A business file needs `human`, `arbiters`, `projects`, `tracker` + with a sync bot and a Vikunja credential ref, and `roles`, each role with its tracker bot when its + definition uses Vikunja (business.mjs:23, :84-100, :131-149). +- The library is a project, `projects..kind === "library"`, checked by `checkMapping` + (packages/library/src/mapping.mjs). There is no top-level `library` key. +- `library.sync` and `library.status` are bus verbs, human only (broker.mjs:92, :2452-2476). The only + client is `packages/bus/src/human-cli.mjs` over the bus socket, so they need a running bus. The + library package exports `sync(context, { slot })` (sync.mjs:92) and `libraryStatus(context)` + (mirror.mjs:236), which take the lock the bus takes (`withLibraryLock`, mirror.mjs:145). +- The token slot is `/secrets//library-gitea`, a regular file owned by the uid with + no group or other bit (sync.mjs:17, :51-52). +- `case2-persona-import.mjs install --staging DIR --config-dir DIR --business ID` writes + `/personas//.md` at 0600 in 0700 dirs, never overwriting, and prints + `instance bean: persona.name Bean, persona.sha256 ` per persona. +- A Discord binding is `/discord/.json` (packages/discord/src/binding.mjs:70-83). +- `case2-unit.sh --copy ID` needs `/.local/share/mosaic-stack/copies/`, which + `stack-copy.sh copy COMMIT --copies DIR` makes. + +## Design + +### D1. The business file is an input + +`case2-install.sh` takes `--business-file FILE`, a JSON business document written for the host from +Jason's go: human id, arbiters, tracker, roles, the library project with its mapping, and any +specialists she picks. The script doesn't build one from flags. Bots, botIds and credential refs are host +values, and the brief says every host value is an input. A flag per field would copy business.mjs's +schema into bash. The script checks the input, adds what only it knows, writes it, and then runs +`mosaic business validate`, so business.mjs stays the only schema. + +What the script checks before it writes anything, beyond `validate`: +- `id` equals `--business`. +- `library`: at least one library project, `bus.tester` not true, roles `bean` and `chet` with + definition `companion`, and no `persona.*` var in the input, since D3 adds them. +- `factory`: no library project, and `bus.tester` true. These are the precheck's rules at + case2-precheck.sh:127-135, so the install refuses what the precheck would refuse. + +### D2. Install steps, in order + +1. Refuse to run as root. +2. `scripts/bootstrap.sh`. It's idempotent and never rewrites an existing config. +3. `scripts/release.sh package`, then `scripts/release.sh activate` (design 10 step 2b). +4. `scripts/stack-copy.sh copy <--commit> --copies /.local/share/mosaic-stack/copies`. The copy + `case2-unit.sh --copy` names has to exist before root renders the units (row 195 need 3). +5. `library` only: the persona import's `install` step from `--personas DIR` (D3). +6. Write `/businesses/.json` at 0600 with O_EXCL (D4). +7. `library` only: make `/secrets//` at 0700, empty. The token is Jason's, step 4. +8. `mosaic business validate `. If it refuses, remove the file step 6 wrote, then refuse. +9. Print the copy id and the next steps, which are root's 2c and Jason's 2d, 3 and 4. + +It starts no unit, writes no unit file, doesn't write `/etc/mosaic-stack/dev.args` and doesn't run +`adopt`. These are my answers to row 195's needs 4 and 5 (gosalyn-0087), unchanged. + +### D3. Personas come from the staging dir + +`library` requires `--personas DIR`, the staging dir from `case2-persona-import.mjs stage`. The install +runs `case2-persona-import.mjs install --staging DIR --config-dir --business ` and reads +its two `instance` lines into `roles.bean.vars` and `roles.chet.vars`. Only those two lines go into the +business file. They hold a name and a digest, never text. + +This moves the persona import's stage part of design 10 step 6 ahead of 2b. That part is the read of +her Hermes profiles and Jason's check of the printed pairing. The install part stays as ``, as +design 9.1 says. See Q1. + +### D4. Re-runs + +Each step is safe to run again. bootstrap and the copy are idempotent. An existing business file that +matches the one the script would write passes. One that differs refuses +`business-file-differs`, and the script doesn't touch it. The persona import already refuses to +overwrite. On a re-run the install compares each existing persona file's sha256 with the staging +manifest, and refuses on a mismatch. + +### D5. Inputs and seams + +``` +case2-install.sh --business ID --business-file FILE --commit SHA [--profile factory|library] + [--personas DIR] [--home DIR] +``` + +`--home` defaults to `$HOME`, which is `/srv/mosaic` under `sudo -u mosaic -H`. `MOSAIC_CONFIG` is +honoured as bootstrap.sh honours it. Test seams follow case2-unit.sh's `CASE2_*` pattern: +- `CASE2_RELEASE` is the release script. The test uses a stub that logs its argv, because the real one + needs Docker. +- `CASE2_STACK_COPY` is the copy script. The test uses a stub that makes the copy dir. + +Exit codes: 0 ok, 1 refused, 4 usage, as case2-unit.sh uses. + +### D6. Precheck library checks + +These are added to case2-precheck.sh under `--profile library` only. Each is a `refuse` line in the +script's style. +- The business file is owned by the uid, with no group or other bit (`business-file-mode`). + `businesses/.history//` is 0700 and its `changes.jsonl` is 0600, when present (`history-mode`). +- The persona dirs are 0700. Each creative role's persona file is regular, owned and 0600 + (`persona-mode`), and its sha256 equals the role's `persona.sha256` (`persona-mismatch`). The output + prints names and digests, never text. +- `library.sync` calls the library package's `sync(context, { slot })` from a node block with the bus's + context, then `libraryStatus`. A sync error refuses with its code (`library-sync: `). Each + R-problem prints as `ok library R `. The paths are her file names, and no text + prints. R-problems don't refuse, since the brief says the check lists them. See Q2. +- No `/discord/*.json` exists (`discord-binding`). + +The sync check needs the token from step 4. So the library precheck passes only after step 4, and the +host run sheet says so. + +### D7. host-run-sheet.md fix + +`docs/guides/host-run-sheet.md:239` says "I2's install writes it" about `dev.args`. It becomes: root +writes it at the unit render, from the values the go names, and the install never writes it, because +`` can't write `/etc` and root reads nothing `` wrote. Sage asked in sage-1832 for this fix +to go in this build commit, with its path in the manifest. Section 9's `library-gitea` line (:270) is +"C2-13b's `library.sync` (not landed)". It becomes the precheck's `library-sync` line. + +### D8. Tests, `packages/cli/tests/case2-install.test.mjs` + +These follow case2-unit.test.mjs: scratch HOME, MOSAIC_CONFIG, stub bin dir and seams, a local bare repo +cloned `--bare --no-local` as sync.test.mjs does, and the fixture's key never in output. +- Install `library`: + - writes the business file at 0600 with bean's and chet's persona vars; + - writes the persona files at 0600 in 0700 dirs and the empty slot dir at 0700; + - logs the release then copy calls in order; + - starts nothing; + - passes on a re-run; + - refuses `business-file-differs` and leaves the file unchanged. +- Install `factory`: + - writes no library mapping, no persona file and no slot dir, which is the brief's case; + - refuses a library project and `bus.tester` off. +- Install refuses: run as root, a business id mismatch, missing `--personas` under library, and a + `validate` refusal, after which no business file is left. +- Precheck library refusals, one case each, as the brief lists: + - a persona file at 0644; + - a digest mismatch; + - a business file at 0644; + - a sync failure, which is no token in the slot and a remote that doesn't resolve; + - a Discord binding. +- Precheck library pass: a bare repo with one R2 path prints its `R2` line and passes. + +Every new refusal gets a mutant that removes it. The count goes in the slot request. + +### D9. Files + +Owned by the brief: `scripts/case2-install.sh` (new), `scripts/case2-precheck.sh` (library checks +only) and `packages/cli/tests/case2-install.test.mjs` (new). Also `docs/guides/host-run-sheet.md` (D7, +sage-1832) and this BUILD.md. Before the slot request I'll grep for outside readers of +case2-precheck.sh, since case2-unit.test.mjs runs it. + +## Questions + +- Q1. Design 10 order. D3 puts the persona stage and Jason's pairing check before 2b. The other way + is for the install to write bean and chet without persona vars and a second run to add them after step + 6. That needs a business file change after install, and D4 refuses that. I recommend D3. +- Q2. Should an R-problem refuse the library precheck? The brief says the check "lists" them, so D6 + doesn't refuse. Her repo may well have R2 paths on day one, before she confirms the mapping in step 7. +- Q3. Design 10 step 2b names "the pinned harness" as part of the install. I read that as the pinned + pi from package.json. The copy installs it with `npm ci` from package-lock.json (stack-copy.sh:70), + so it isn't a separate install step. That `npm ci` needs the npm registry from the host. Does + anyone read it as more than that?