diff --git a/agents/gosalyn/work/case2/DESIGN.md b/agents/gosalyn/work/case2/DESIGN.md index 3052f4ed0..4a1860e69 100644 --- a/agents/gosalyn/work/case2/DESIGN.md +++ b/agents/gosalyn/work/case2/DESIGN.md @@ -1,4 +1,4 @@ -# Case 2 design: the creative writing hub (r9) +# Case 2 design: the creative writing hub (r10) 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 r9 + +Darkwing's row 195 question (darkwing to gosalyn, after Duckworth's B1) found a gap. r9 named one +system unit, `mosaic-stack.service`, and never said what it runs. The stack is two units today: the bus +host (`packages/cli/systemd/mosaic-bus.service.in`) and the Console +(`packages/webui/systemd/mosaic-webui.service.in`). The slice needs both, because C2-10b is Console +chat. Now 2c renders both from those templates as system units, `mosaic-bus@.service` and +`mosaic-webui.service`, with the same copy-id rule, and no new `mosaic-stack.service.in` exists. The +bus unit carries the `EnvironmentFile=` line. The Console unit sets no credential variable, as its +template says. The node path and the Console's serve flags are install inputs, fixed across re-pins. +The copy id is the only value a re-pin changes. + +One thing this makes explicit. The Console template says whoever starts it holds the human's capability, +so Jason starts it, or Mos under lead decision 102. A system unit that systemd starts at boot holds that +capability with no human starting it. On her host the human is Melanie, signing in through step 5. So +the step 9 go is the human's start, and a reboot restarts the Console under that same go. If Mos or +Webby reads decisions 62 and 94 as needing a human at every start, the Console unit gets no `[Install]` +section and a human starts it after each boot. + ## Changes from r8 Jason's Q7 answer, "Q7: yes" (DECISIONS Q186, mos-1090, sage-1469). O4 is the model path. @@ -977,7 +996,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 `` | 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 `` as ``: 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, `` 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 `` 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-13 | S | Install script and precheck for `` as ``: 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`, which renders the bus and Console system units from their existing templates and 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, `` 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 `` 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 either unit path, and a render whose `User=`, `Group=`, `ExecStart=`, the bus unit's `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 | @@ -1008,7 +1027,7 @@ compaction count and clear context (GAP-MAP C2.12, C2.13). Until then, a new thr Where it runs (Jason, Q181, relayed in mos-1031). `` is her VM 10.1.1.54, W-Melanie-OC, VM 1012 on pve. `` is `mosaic`, a system user with no sudo, no supplementary groups and no keys, home -`/srv/mosaic` at mode 0700. It runs as the system unit `mosaic-stack.service` with `ProtectHome`, so nothing it needs may +`/srv/mosaic` at mode 0700. It runs as two system units, `mosaic-bus@.service` and `mosaic-webui.service`, both with `ProtectHome`, so nothing it needs may live under `/home`. Its config directory, ``, is `/srv/mosaic/.config/mosaic-dev`, and the token slot and persona files live under it. ops-01's plan, section 3, set this layout. That plan stopped with #421, and Q183 keeps a `mosaic` user without sudo on each VM. Section 10 step 2a creates it. The rest of the design @@ -1119,16 +1138,24 @@ What it adds is one bot account with read access to that one repo. 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 - home as inputs. It checks `User=`, `Group=`, `ExecStart=` and the hardening lines against fixed - values and writes `/etc/systemd/system/mosaic-stack.service` as `root:root` 0644. It never uses - `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. + `scripts/case2-unit.sh`, which renders two system units from the checkout's existing templates: + `mosaic-bus@.service` from `packages/cli/systemd/mosaic-bus.service.in`, enabled as + `mosaic-bus@.service`, and `mosaic-webui.service` from + `packages/webui/systemd/mosaic-webui.service.in`. Inputs: `--user`, `--home`, `--business`, + `--copy`, `--node` and the Console's serve flags. In both, `@REPO@` is the copy + `/.local/share/mosaic-stack/copies/`. `%h` becomes the literal home, because a system + unit's `%h` is root's home. The render adds `User=`, `Group=`, the hardening lines and + `WantedBy=multi-user.target`. It checks `User=`, `Group=`, `ExecStart=`, the bus unit's + `EnvironmentFile=` and the hardening lines against fixed values, and the copy id is the one part + that may change between renders. It writes both under `/etc/systemd/system/` as `root:root` 0644. + It never uses `systemctl link`, and it refuses a symlink at either 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 both. They start only at step 9, the bus first + and then the Console, and they stop in the reverse order. - 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 + as `ZAI_API_KEY=`, owned by `mosaic`, mode 0600, the file the bus 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 diff --git a/docs/plans/2026-10-20_case2-rows.md b/docs/plans/2026-10-20_case2-rows.md index fa147ae78..6296b6070 100644 --- a/docs/plans/2026-10-20_case2-rows.md +++ b/docs/plans/2026-10-20_case2-rows.md @@ -710,8 +710,8 @@ Dewey approves the manifest. The webui suite is green on Sage's gate, along with ### Problem -The hub runs on 10.1.1.54 as `mosaic` under `/srv/mosaic`, as `mosaic-stack.service` with -`ProtectHome` (design 9, Q181). Nothing installs a library business there, or checks the host before it +The hub runs on 10.1.1.54 as `mosaic` under `/srv/mosaic`, as two system units, +`mosaic-bus@.service` and `mosaic-webui.service`, with `ProtectHome` (design 9 and 10 step 2c, r10). Nothing installs a library business there, or checks the host before it starts. ### Owner and reviewer @@ -722,8 +722,8 @@ starts. ### Files owned -`scripts/case2-install.sh`, `scripts/case2-precheck.sh` and `scripts/case2-unit.sh` (new), the template -`packages/cli/systemd/mosaic-stack.service.in` (new), `packages/cli/tests/case2-install.test.mjs` +`scripts/case2-install.sh`, `scripts/case2-precheck.sh` and `scripts/case2-unit.sh` (new), +`packages/cli/tests/case2-install.test.mjs` (new), and one line of `packages/cli/README.md`: at `:713`, "nothing in the repository creates or reads it" changes to say that nothing creates it, that only the Case 2 precheck reads it, and that the precheck prints only variable names, never values (webby-0034 N2, webby-0035). @@ -749,20 +749,31 @@ 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=`, `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`, +- It renders two system units from the checkout's existing templates and changes neither template: + `mosaic-bus@.service` from `packages/cli/systemd/mosaic-bus.service.in` (enabled as + `mosaic-bus@.service`) and `mosaic-webui.service` from + `packages/webui/systemd/mosaic-webui.service.in`. Flags: `--user`, `--home`, `--business`, `--copy ID`, + `--node PATH` and the Console's serve flags after `--`. Row 195 calls it with those names. `@REPO@` is + the copy dir. `%h` becomes the literal `--home`, because in a system unit `%h` is root's home. + `@PM_FLAGS@` and `@CLAUDE_ENV@` render empty. The render adds `User=`, `Group=`, the hardening lines + and `WantedBy=multi-user.target`. +- It refuses unless `User=`, `Group=`, `ExecStart=`, the bus unit's `EnvironmentFile=` and the + hardening lines match fixed values. `EnvironmentFile=` is the line in + `packages/cli/systemd/mosaic-bus.service.in:24`, with the literal home. The Console unit has none, + and it refuses one. `--node` must be an absolute path to a root-owned file outside ``. The + serve flags pass `scripts/webui-service.sh`'s own flag checks. On a re-install, the node path and + serve flags must equal the installed unit's. The copy id is the one value that may change, so a + re-pin or a rollback to another copy re-renders both units (row 195). It must be exactly 40 lowercase hex characters naming a copy that holds `scripts/mosaic`, directly under `/.local/share/mosaic-stack/copies`. That is the rule `copy_root` uses (`scripts/stack-copy.sh:6`, `:20`). Anything else refuses before the render. - It refuses any `PermissionsStartOnly=`, and any `Exec*=` value whose leading run of prefix characters (`@`, `-`, `:`, `+`, `!`) holds a `+` or `!`. systemd takes those prefixes in any order, so `-+/srv/mosaic/x` runs as root even with `User=mosaic`. The named-line check doesn't read these lines (webby-0026 N1, webby-0027, defence in depth). -- It writes `/etc/systemd/system/mosaic-stack.service` as `root:root` 0644. It never uses - `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. +- It writes both units under `/etc/systemd/system/` as `root:root` 0644. It never uses + `systemctl link`, and it refuses a symlink at either unit path. +- On a re-install, it refuses an installed unit that differs from the render, except for the copy id. +- Order: start the bus, then the Console. Stop in reverse. - It reads nothing from under ``'s home. The precheck refuses unless all of these hold: @@ -805,8 +816,12 @@ output for the fixture key value finds nothing. - 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 bus template with the `EnvironmentFile=` line removed or pointing elsewhere, and a Console template + with one added +- a symlink at either unit path +- a `--node` that is relative, not root-owned, or under `` +- a re-install with a different `--node` or different serve flags +- an unrendered `%h` left in either unit - a template with `ExecStartPre=+/srv/mosaic/x` - a template with `ExecStartPre=-+/srv/mosaic/x` - a copy id that is short, uppercase, holds `/` or `..`, or names no directory