gosalyn: Case 2 design r9, the O4 model path (Q186): pi on zai glm-5.3 for both, hub key step 2d, Chet's Codex login to #428, C2-20 Hermes name count; C2-13 brief follows

This commit is contained in:
2026-10-20 15:43:25 -05:00
parent f325657c7a
commit 29e85196b2
2 changed files with 67 additions and 12 deletions
+51 -7
View File
@@ -1,4 +1,4 @@
# Case 2 design: the creative writing hub (r8)
# Case 2 design: the creative writing hub (r9)
Gosalyn, 2026-10-20 (from `date -u`). Track T2 in jason-master-restart `projects/mosaic-mvp/PLAN.md`,
Vikunja #416, DECISIONS Q176-Q178. The requirement is Jason's text in TARGET.md (addendum 2026-10-20).
@@ -8,6 +8,25 @@ Base: `refactor` at 9ae8c73ac. r1 cited 5d019d876. `git diff --stat 5d019d876 9a
adapters roles scripts` is empty (Mos in mos-0980, Webby in webby-0001), so every `file:line` holds at
9ae8c73ac. Paths are relative to the repository root.
## Changes from r8
Jason's Q7 answer, "Q7: yes" (DECISIONS Q186, mos-1090, sage-1469). O4 is the model path.
- Model path (9). For 10-27, Bean and Chet both run harness pi with provider `zai` on `glm-5.3`. Pi
reads the key from `ZAI_API_KEY`, and row A puts that name in `ENV_ALLOW`
(`packages/seat/src/session.mjs:83-88`). Chet moves to his ChatGPT/Codex login after 10-27, in row B
(Vikunja #428), which I design. Running a pi role still waits on #383.
- 10 step 2. 2b no longer leaves "which model accounts" open. New 2d: Jason places the hub's own key on
the existing Z.ai account, with a spend cap if the account offers one. Whose account it is stays his
input. `melanie`'s sudo can read the key until #423 step 5.
- Two consequences in C2-13. A pi launch has no credential check, so the precheck checks the key file
by name and mode and never prints the value. The claude marker prompt now runs only when some
creative instance's harness is claude-code, because with the model path above the host may have no
Claude login. The unit template's `EnvironmentFile=` line is one of the fixed lines `case2-unit.sh`
checks.
- C2-20 (9.1). The dry run also counts lines naming a Hermes tool or channel, counts only (gosalyn-0030,
mos-1087).
## Changes from r7
Webby's review (webby-0025, CHANGES, notes at agents/webby/work/case2-r7/REVIEW.md, 19bb3ae36).
@@ -591,8 +610,9 @@ two copies come with that:
as the prompt when the file doesn't exist (`:21`, `:30`, no warning) or can't be read (`:24-27`). So
the adapter checks `[ -r ]` on the file first, which covers both, and refuses otherwise. Claude Code 2.1.304's `--help` names `--system-prompt-file`. I read the help
but didn't run it, and the repo doesn't pin the claude version. C2-08's adapter tests check that argv
holds the path and not a marker from the prompt. C2-13 runs one marker prompt through the host's claude
with `--system-prompt-file` and checks that it replaces the default prompt rather than adding to it. If the
holds the path and not a marker from the prompt. When some creative instance's harness is claude-code,
C2-13 runs one marker prompt through the host's claude with `--system-prompt-file` and checks that it
replaces the default prompt rather than adding to it. With the r9 model path, Bean and Chet run pi. If the
host's claude lacks the flag, the argv copy is a residual for Jason, stated with that version.
Modes, from code. The writer creates the business file's candidate and `changes.jsonl` with mode 0600
@@ -957,7 +977,7 @@ Chains follow Q177 merge-chaining. S marks a slice row.
| C2-10b | S | Console chat: cited-item renderer through `renderItems`, the status line | C2-06, C2-09a, C2-03a | Haiku high | webui tests for items, scope badges, notices, "changed since" and the status line |
| C2-12 | S | The setup sheet for Melanie (7.1, including N2 and N4, and what changes for Bean and Chet in 4) and the survey run sheet for `<host>` | C2-01 | Luna | doc only, reviewed against the survey's real flags |
| C2-20 | S | Persona import (9.1): a script that reads only the persona definitions of the two Hermes profiles, confirms profile to name, and writes each persona file (4) with its `persona.name` and `persona.sha256` for the business file. Prints names, byte counts and sha256 only. A `--dry-run` mode, run before Jason's go, prints only counts of files, lines and lines starting `# ` or `## ` | C2-08 | Opus (Gosalyn), run under Jason's go | fixture Hermes profile trees: the right fields are taken, memory and session files are never opened (a read trace on the fixture), a profile with no persona field refuses, a persona that would fail C2-08's fence check refuses, output holds no persona text, each written file is 0600 and its digest matches, `--dry-run` writes nothing and prints the right counts for a fixture with two heading lines and no marker string from it |
| C2-13 | S | Install script and precheck for `<host>` as `<user>`: bootstrap, release activate, business file with the mapping, `bean` and `chet` with their persona vars and files, any specialists she picks, her human id, token slot path, and `scripts/case2-unit.sh` with the template `packages/cli/systemd/mosaic-stack.service.in`, which root runs from a root-owned checkout in 10 step 2c. The install writes no unit. Jason or his delegate runs it (10) | C2-04, C2-03a, C2-20 | Opus (Gosalyn), Jason's go | precheck: config validates, `<user>` has no sudo and `id -G` prints exactly one entry, `bus.tester` is not on, the business file, `.history`, `changes.jsonl` and persona files have the modes in 4 and each persona digest matches, the run root is 0700, one marker prompt run through the host's claude with `--system-prompt-file` shows it replaces the default prompt (its version printed), free memory printed, `library.sync` fetches her repo read-only, `library.status` lists R-problems, no Discord binding exists. `case2-unit.sh` refuses an installed unit that differs from its render (one edited to `User=root` is the test), a symlink at the unit path, and a render whose `User=`, `Group=`, `ExecStart=` or hardening lines are off their fixed values. Tested against a scratch data root and a local bare repo |
| C2-13 | S | Install script and precheck for `<host>` as `<user>`: bootstrap, release activate, business file with the mapping, `bean` and `chet` with their persona vars and files, any specialists she picks, her human id, token slot path, and `scripts/case2-unit.sh` with the template `packages/cli/systemd/mosaic-stack.service.in`, which root runs from a root-owned checkout in 10 step 2c. The install writes no unit. Jason or his delegate runs it (10) | C2-04, C2-03a, C2-20 | Opus (Gosalyn), Jason's go | precheck: config validates, `<user>` has no sudo and `id -G` prints exactly one entry, `bus.tester` is not on, the business file, `.history`, `changes.jsonl` and persona files have the modes in 4 and each persona digest matches, the run root is 0700, when some creative instance's harness is claude-code, one marker prompt run through the host's claude with `--system-prompt-file` shows it replaces the default prompt (its version printed), the key file in 10 step 2d exists, is owned by `<user>` at 0600 and sets a nonempty `ZAI_API_KEY` when an instance's provider is `zai` (name only, never the value), free memory printed, `library.sync` fetches her repo read-only, `library.status` lists R-problems, no Discord binding exists. `case2-unit.sh` refuses an installed unit that differs from its render (one edited to `User=root` is the test), a symlink at the unit path, and a render whose `User=`, `Group=`, `ExecStart=`, `EnvironmentFile=` or hardening lines are off their fixed values. Tested against a scratch data root and a local bare repo |
| C2-14 | S | End-to-end acceptance on a scratch data root and a bare repo in her layout: sync, ask a creative agent in its thread, a reply whose cites verify, a playground cite as canon refused, a force-push held as diverged, the Console shows the reply as items | slice rows | Opus | one script, which the reviewer also runs |
| C2-02 | | Bus writes: `writeLibraryFile`, `commitFile` refactor, r1's S0 rule on the working tree | C2-04 | Sol (Rocko) | writer tests, `packages/project/tests` unchanged and green |
| C2-03b | | Push path, race retry, `unpushed`, `library-remote-invalid` on writes | C2-02, C2-03a | Opus | push tests against a bare repo |
@@ -997,6 +1017,18 @@ mirror and runners live under `<user>`. It needs outbound HTTPS to git.mosaicsta
providers. It needs nothing from her user, because her writing reaches it through the Gitea remote she
already uses.
Model path (Q186, mos-1090). For 10-27, `bean` and `chet` both run harness pi with provider `zai` on
`glm-5.3`, through one key for the hub (10 step 2d). Pi's provider comes from the instance's session
(`packages/plugins/harness/pi/index.mjs:57`), and pi-ai reads a zai key from `ZAI_API_KEY`
(`env-api-keys.js:89`, with `glm-5.3` in `data/zai.json`). `ENV_ALLOW` doesn't pass that name today
(`packages/seat/src/session.mjs:83-88`), so row A adds it. A pi launch has no credential check
(`pi/index.mjs:42`, `packages/cli/src/launcher.mjs:299`), so a missing key shows up only at the first
turn. The C2-13 precheck catches that before the start. The creative roles' web search goes through the
broker's search source, not the harness, so pi having no web tool of its own (`pi/index.mjs:44-45`)
changes nothing here (5.2). After 10-27 Chet moves to his own ChatGPT/Codex login. That needs a
per-instance pi agent dir, which is row B (Vikunja #428), and it starts after a live check that two
clients on one login don't revoke each other's refresh tokens. Running any pi role waits on #383.
Memory. From mos-1000, her two Hermes services use about 1.8 GB together, and each Mosaic agent session
measured 300 to 390 MB on dragon-lin. I estimated at least 2 GB more than the 3.8 GiB her guest saw, and
4 GB more with specialists running at the same time. ops-01 is setting VM 1012 to 16 GiB fixed with 6
@@ -1032,7 +1064,11 @@ The in-VM tightening is Vikunja #423. Only its step 1, the `localadmin` account,
until prune, and in the harness argv if the host's claude lacks `--system-prompt-file` (4).
- Dry run first (N2, mos-1051 item 3). Before Jason's go to import, the row runs a dry pass that reads the
same two persona definitions and prints counts only: files, lines, and lines starting `# ` or `## `.
No text and no line numbers. If any count of heading lines is nonzero, the fence rule in 4 would refuse
No text and no line numbers. It also counts lines that name a Hermes tool or channel, from a fixed
list in the script: the tool names in the hermes-agent checkout at df5700ebe, read from its code and
not her profiles, plus `Hermes` and `Discord`. That prints one count per persona file, never the line,
its number or the word. A nonzero count changes nothing in the import. It tells Jason a persona refers
to things Bean and Chet won't have in Mosaic. If any count of heading lines is nonzero, the fence rule in 4 would refuse
a real persona, so the rule changes, in a reviewed C2-08 follow-up, before the import runs. The text is
never rewritten to fit the rule unless Melanie says so through Jason.
- Hermes keeps running. The row changes nothing in the Hermes checkout, its profiles, its services or its
@@ -1079,8 +1115,8 @@ What it adds is one bot account with read access to that one repo.
groups and no keys. Create `/srv/mosaic` at 0700, owned by `mosaic`.
- 2b: the install as `<user>`, by him or his delegate, run as `sudo -u mosaic -H` from `localadmin`.
`mosaic` has no keys and no login shell, and nobody adds a key to make 2b work. `scripts/bootstrap.sh`, release package and
activate, the pinned harness, and which model accounts its agents use. Model use is spend, so the
accounts are his call.
activate, and the pinned harness. The model path is set (9, Q186). Chet's ChatGPT/Codex login is
not part of 10-27. It is row B (#428).
- 2c, ops-01 via `localadmin`: root makes a checkout of the reviewed commit outside `/srv/mosaic`,
owned by root, and checks that its HEAD is the commit Jason's go names. From that checkout it runs
`scripts/case2-unit.sh`, which renders `mosaic-stack.service` from the template with the user and
@@ -1089,6 +1125,14 @@ What it adds is one bot account with read access to that one repo.
`systemctl link`, and it refuses a symlink at the unit path. On a re-install it refuses an
installed unit that differs from the render. Root runs nothing from `/srv/mosaic` and reads no
unit that `mosaic` wrote. Then it enables the unit. The unit starts only at step 9.
- 2d, Jason: the hub's own key on the existing Z.ai account, with a spend cap if the account offers
one. Model use is spend, so the key and the cap are his. Whose Z.ai account it is hasn't been said,
and that is his input. The key goes in `/srv/mosaic/.config/mosaic-dev/secrets/claude-session.env`
as `ZAI_API_KEY=`, owned by `mosaic`, mode 0600, the file the unit's `EnvironmentFile=` line loads
(the same line as `packages/cli/systemd/mosaic-bus.service.in:24`). The name says claude, but it is
the one file decision 119 and `packages/cli/README.md` document, so I kept it. He places it, or
says who may, per the Q173 secrets rule. Agents never print it. Until #423 step 5, `melanie`'s
sudo can read it, and so can her Hermes processes (9).
3. A new Gitea bot account for the hub, with read access to the books repo only. Not bean-brain, and not
her other three repos. Her repo's access list is hers, so adding the bot means asking her or acting as
admin, which is his call.
+16 -5
View File
@@ -748,7 +748,8 @@ precheck take `--profile factory|library`, default `library`:
`case2-unit.sh` is the 2c step. Root runs it from a root-owned checkout of the reviewed commit outside
`/srv/mosaic`, never from the release, because `mosaic` can write the release. It does the following:
- It renders the unit from the checkout's template, with the user, the home and the copy id as inputs.
- It refuses unless `User=`, `Group=`, `ExecStart=` and the hardening lines match fixed values. The
- It refuses unless `User=`, `Group=`, `ExecStart=`, `EnvironmentFile=` and the hardening lines match
fixed values. `EnvironmentFile=` is the line in `packages/cli/systemd/mosaic-bus.service.in:24`. The
copy id is the one part of `ExecStart=` that varies, so a re-pin to a new copy can re-render the
unit (row 195). It must be exactly 40 lowercase hex characters naming a copy that holds `scripts/mosaic`,
directly under `<home>/.local/share/mosaic-stack/copies`. That is the rule `copy_root` uses
@@ -774,19 +775,29 @@ The precheck refuses unless all of these hold:
- `bwrap --unshare-all --ro-bind / / true` exits 0 as `<user>`. Without it no session starts. Row 195
found no bwrap on .53, and `kernel.apparmor_restrict_unprivileged_userns=1`, and .54 may be the same.
On a refusal, the precheck prints the sysctl's value and whether `bwrap` is on PATH.
- One marker prompt run through the host's claude with `--system-prompt-file` shows that it replaces
the default prompt. The claude version is printed.
- When some creative instance's harness is claude-code, one marker prompt run through the host's claude
with `--system-prompt-file` shows that it replaces the default prompt. The claude version is printed.
With design r9's model path, Bean and Chet run pi, so this check is skipped unless she adds a
claude-code specialist.
- When an instance's provider is `zai`, `<configDir>/secrets/claude-session.env` exists, is a regular
file owned by `<user>` at 0600, and sets a nonempty `ZAI_API_KEY` (design 10 step 2d). A pi launch
has no credential check, so without this the first turn is where a missing key shows. The precheck
prints the name and never the value.
- `library.sync` fetches her repo read-only, and `library.status` lists R-problems.
- No Discord binding exists.
It also prints free memory and verifies HOME is `/srv/mosaic`.
Tests run against a scratch data root and a local bare repo. Each precheck refusal has a case. The
claude check runs against a fake claude that appends rather than replaces, and that case refuses.
claude check runs against a fake claude that appends rather than replaces, and that case refuses. With
no claude-code instance the claude check doesn't run, and a test shows a missing claude passes. The key
file refuses when missing, at 0644, a symlink, or with `ZAI_API_KEY=` empty. A search of the test
output for the fixture key value finds nothing.
`case2-unit.sh` runs against a scratch unit directory. These cases refuse:
- an installed unit edited to `User=root`
- an edited `ExecStart=`
- a template with a hardening line removed
- a template with the `EnvironmentFile=` line removed or pointing elsewhere
- a symlink at the unit path
- a template with `ExecStartPre=+/srv/mosaic/x`
- a template with `ExecStartPre=-+/srv/mosaic/x`
@@ -882,7 +893,7 @@ its first turn, as `packages/cli/README.md:439-444` says.
### Out of scope
Placing a key anywhere. The hub's own key on the Z.ai account is design 10 step 2, and it is Jason's.
Placing a key anywhere. The hub's own key on the Z.ai account is design 10 step 2d, and it is Jason's.
Any pi launch or live check: running a pi role waits on #383. Chet's Codex login (row B, #428). A
credential check for pi launches.