gosalyn: row 226 C2-13b design d1, the business file as an input, install steps, persona staging before 2b, re-run rules, precheck library checks and tests, Q1-Q3 for review (#1727)
This commit is contained in:
1 parent
45dca69f6e
commit
bfed0aa6c7
1 file changed
+172
@@ -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 <id>` 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.<id>.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 `<configDir>/secrets/<business>/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
|
||||
`<configDir>/personas/<business>/<instance>.md` at 0600 in 0700 dirs, never overwriting, and prints
|
||||
`instance bean: persona.name Bean, persona.sha256 <hex>` per persona.
|
||||
- A Discord binding is `<dataRoot>/discord/<name>.json` (packages/discord/src/binding.mjs:70-83).
|
||||
- `case2-unit.sh --copy ID` needs `<home>/.local/share/mosaic-stack/copies/<ID>`, 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 <home>/.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 `<configDir>/businesses/<id>.json` at 0600 with O_EXCL (D4).
|
||||
7. `library` only: make `<configDir>/secrets/<business>/` at 0700, empty. The token is Jason's, step 4.
|
||||
8. `mosaic business validate <id>`. 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 <configDir> --business <id>` 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 `<user>`, 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/<id>/` 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: <code>`). Each
|
||||
R-problem prints as `ok library R<n> <path>`. 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 `<dataRoot>/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
|
||||
`<user>` can't write `/etc` and root reads nothing `<user>` 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?
|
||||
Reference in new issue
Block a user