Lands Darkwing's manifest round 1 candidate,37171f65b(base4ced5e63d), approved by Duckworth (duckworth-0088, REVIEW-m16afcee2a5, #1693 comment 29988, rev 2758). Five paths: scripts/case1-accept.sh (100755), packages/cli/tests/case1-accept.test.mjs, docs/TOOLS.md, agents/darkwing/work/queue-195/DESIGN.md (r7, approved duckworth-0087) and BUILD.md. No 195 path changed between4ced5e63dand this commit's parent. Patch-id 656e43ff25de, the same as 4ced5e63d..37171f65b. Evidence: Darkwing's slot runs: single 22/22 (sage-1829), 12/12 mutants killed and gate cli 662/0 plus selftests 429/0 onddf7dce6c(sage-1865, sage-1870). Gate on my slot (sage-r195land, base38b83ea5cplus 195's 5 and 213's 39 paths, 09:45:52-09:57:52Z): business 200/0, bus 542/0, harness 140/0, cli 663/0; scripts auth 15/0, conductor 17/0, config 31/0, discord 78/0, extension-package 18/0, foundation 44/0, land-work 86/0, queue 156/0 plus 28/0, release 14/0, task 98/0; the models mutant L1 killed (launch-settings 15/1), restore matched. Between the gate base38b83ea5cand4364dddb0only docs/plans/2026-10-20_host-credentials.md changed (01e1fe512,4364dddb0), which no test reads. Duckworth's notes N1-N3 go to docs/plans/DEFERRED.md in the next commit.
389 lines
27 KiB
Markdown
389 lines
27 KiB
Markdown
# Row 195 design: the Case 1 install on 10.1.1.53 (r7)
|
|
|
|
Darkwing, 2026-10-21. Brief: `docs/plans/2026-10-20_hub-own-uid.md`, blob 45be7f09afc1, section "Brief: the
|
|
Case 1 install on 10.1.1.53 (row 195)". Gitea #1693. Reviewer Duckworth, gate owner Sage. Branch
|
|
`darkwing/row195-r7` on c515273db. r6 (b7239bb66 on `darkwing/row195`, approved duckworth-0055) is the base,
|
|
carried unchanged as 7398b7dd7. The r6 text is in that commit and in b7239bb66.
|
|
|
|
## Changes from r6
|
|
|
|
Q187 (mos-1108) made install and upgrade product commands, and mos-1118 split r6 into the installer rows
|
|
(`docs/plans/2026-10-20_installer-rows.md`). That brief says row 195 "shrinks to the factory profile's
|
|
values and the Case 1 acceptance". So r7 is mostly a cut. What r6 designed now ships elsewhere:
|
|
|
|
| r6 piece | Now | State |
|
|
|---|---|---|
|
|
| `stack-release.sh --units external`: `confirm`, `revert`, `gate`, `rollback --to`, external `adopt` and `status`; `stack-copy.sh check --units external` and bundle copies | row 232, I1 | landed |
|
|
| `case1-repin.sh` (A1-A5, A2f, R1-R4) | `scripts/stack-upgrade.sh`, row 233, I3 | landed |
|
|
| the host steps 2a, 2a', 2a'' and the window's root steps | `docs/guides/host-run-sheet.md` sections 1-10, row 233 | landed |
|
|
| `case1-hostcheck.sh`, `case1-pins.sh`, `mosaic-hostcheck.service` (the drift stop, r6 B2) | row 240, I4 | landed c515273db |
|
|
| the unit render, `--profile factory`, `--pm`, `--claude`, the factory serve flags, the precheck | row 230, C2-13a, I2 | landed |
|
|
| the install as `mosaic`: bootstrap, business file, copy | row 226, C2-13b, I2, `scripts/case2-install.sh` | assigned to Gosalyn, briefed, not started |
|
|
| `docs/guides/case1-install.md` | the host run sheet, plus section 6 of this file | |
|
|
|
|
Other changes:
|
|
|
|
- Claude moves under `/opt` (sage-1522, run sheet section 3). `--claude` is
|
|
`/opt/mosaic-stack/claude-<pin>/node_modules/@anthropic-ai/claude-code/bin/claude.exe`, root's, and root
|
|
installs and upgrades it. r6's `/srv/mosaic/.local/bin/claude` is gone. F6 reads the new path.
|
|
- The args file is `/etc/mosaic-stack/dev.args` (run sheet section 7), not `case1-unit.args`. It holds
|
|
`--copy` too, and `stack-upgrade.sh` rewrites only that line, after confirm passes.
|
|
- Both units are `WantedBy=multi-user.target` and start at boot (Q189, mos-1122). r6's "the Console has no
|
|
`[Install]`, root starts it by hand after a reboot" no longer holds.
|
|
- The business id is stated, with the DEFERRED 240 N1 reasoning (sage-1809, sage-1813). Section 7.
|
|
- The window can't run until row 226 lands. Sage added 226 to row 195's after list at rev 2718
|
|
(sage-1813). Section 6 lists what row 195 needs from 226's install.
|
|
- The acceptance runs as root through ops-01, not as `localadmin`. `aa-status`, `sudo -l -U`,
|
|
`systemd-run --uid` and the hostcheck all need root. It gains four functional checks for items the run
|
|
sheet marks "precheck line missing" (F8-F11) and one host line (P11). Section 5.
|
|
- The Discord row in section 2 changed. The precheck's "no Discord binding" line is C2-13b's and is
|
|
library-only, so for the factory it's F11 in the acceptance.
|
|
|
|
What row 195 still builds: `scripts/case1-accept.sh` and its test (section 5). The rest of this file is
|
|
inputs, the window order and the record the reviewer checks.
|
|
|
|
## 0. What I checked on the host
|
|
|
|
Read only, as `localadmin` over ssh, 2026-10-20 between 20:40Z and 21:51Z. Nothing in r7 touched .53
|
|
(sage-1809). The window's first step re-reads these, since they can drift in a day: unattended-upgrades
|
|
runs daily.
|
|
|
|
| Item | 10.1.1.53 on 2026-10-20 | Needed |
|
|
|---|---|---|
|
|
| OS | Ubuntu 24.04.5, kernel 6.8.0-124, x86_64, 12 cores, 94 GiB, 217 GiB free on `/` | fits |
|
|
| `mosaic` user, group, `/srv/mosaic` | absent | run sheet section 1 |
|
|
| node | `/usr/local/bin/node` v22.22.2, root-owned | 24 or later, under `/opt` (run sheet section 2) |
|
|
| claude | not on localadmin's PATH | under `/opt` (run sheet section 3) |
|
|
| bwrap | absent; apt candidate `bubblewrap 0.9.0-1ubuntu0.3` | run sheet section 4 |
|
|
| user namespaces | `kernel.apparmor_restrict_unprivileged_userns = 1`, apparmor 4.0.1, no bwrap profile loaded | run sheet section 4 |
|
|
| profiles that name bwrap | none (`grep -rlw bwrap /etc/apparmor.d` exits 1) | hostcheck item 3 |
|
|
| `fs.protected_hardlinks` | 1 | hostcheck item 6 |
|
|
| `net.ipv4.ip_unprivileged_port_start` | 1024 | run sheet section 5 |
|
|
| listeners below 1024 | 22 (sshd) and 53 (the resolver); 732 is free | the Console's port |
|
|
| ports 7330-7333 | free | 7333 is the tester port |
|
|
| `localadmin` | NOPASSWD sudo (#423 step 1, mos-1066, mos-1077) | ops-01's root path |
|
|
| `jarvis` | groups sudo, docker, lxd, adm | #423 step 4 removes them (P5) |
|
|
| unattended-upgrades | on, with the security pocket, which carries bubblewrap | the drift stop (row 240) |
|
|
| journald | `SplitMode=uid` | the PM check reads `launches list`, not the journal (row 232) |
|
|
|
|
## 1. The factory profile's values
|
|
|
|
Instance `dev`, row 190's default (mos-1118 choice 4). `~` is `mosaic`'s home.
|
|
|
|
| Input | Value | Who gives it |
|
|
|---|---|---|
|
|
| user | `mosaic`, primary group `mosaic` with no other member | fixed |
|
|
| home | `/srv/mosaic`, 0700 | fixed |
|
|
| config dir | `/srv/mosaic/.config/mosaic-dev` | row 190 |
|
|
| data root | `/srv/mosaic/.mosaic-dev`, marker `{"instanceVersion":1,"name":"dev"}` | row 190 |
|
|
| business | `mosaic-stack`, file `<configDir>/businesses/mosaic-stack.json` | fixed (section 7) |
|
|
| profile | `factory` for `case2-install.sh`, `case2-unit.sh` and `case2-precheck.sh` | fixed |
|
|
| roles | the factory set from `roles/`, with a chief that has a harness var | the go names the set (section 2) |
|
|
| human id | Jason's, not `tester` (`broker.mjs:321`) | the go |
|
|
| model credential | `<configDir>/secrets/claude-session.env`, `mosaic` 0600, one line `CLAUDE_CODE_OAUTH_TOKEN=...` | Jason places it (section 3) |
|
|
| token slot path | none | section 2 |
|
|
| node | `/opt/mosaic-stack/node-<v>/bin/node`, `<v>` 24 or later | the go names `<v>` and its sha256 |
|
|
| claude | `/opt/mosaic-stack/claude-<pin>/node_modules/@anthropic-ai/claude-code/bin/claude.exe`, `<pin>` 2.1.304 unless the go names a newer one | the go |
|
|
| PM | on (`--pm`) | fixed |
|
|
| Console | `--port 732` on 127.0.0.1, with the unit's `CAP_NET_BIND_SERVICE` | fixed |
|
|
| tester | `--tester-port 7333` on 127.0.0.1 (P10) | fixed |
|
|
| board | none | fixed |
|
|
| release | a git bundle of `refs/heads/refactor` and the 40-hex commit (run sheet section 6) | the go names both and the bundle's sha256 |
|
|
|
|
The args file. `/etc/mosaic-stack/dev.args`, `root:root` 0644, one argument per line (run sheet section 7):
|
|
|
|
```
|
|
--profile
|
|
factory
|
|
--user
|
|
mosaic
|
|
--home
|
|
/srv/mosaic
|
|
--business
|
|
mosaic-stack
|
|
--copy
|
|
<40-hex commit>
|
|
--node
|
|
/opt/mosaic-stack/node-<v>/bin/node
|
|
--claude
|
|
/opt/mosaic-stack/claude-<pin>/node_modules/@anthropic-ai/claude-code/bin/claude.exe
|
|
--pm
|
|
--
|
|
--port
|
|
732
|
|
--business
|
|
mosaic-stack
|
|
--tester-port
|
|
7333
|
|
```
|
|
|
|
`case2-unit.sh` takes exactly these before `--` (`scripts/case2-unit.sh:54`), and `stack-upgrade.sh`
|
|
refuses `args-unreadable` on anything else (`scripts/stack-upgrade.sh:126-148`). The factory profile
|
|
refuses the preview trio, `--send` and the proof pair (`case2-unit.sh:169`). .53 has no proxy, and Jason
|
|
reaches the Console with `ssh -L` (sage-1451 Q3). Changing a serve flag is a re-install with its own go,
|
|
not an upgrade.
|
|
|
|
Why the port is 732 (r6, mos-1101 item 5). Role sessions share the host's network namespace
|
|
(`packages/seat/src/sandbox.mjs:67`), so while the Console is down a session could bind a port above the
|
|
floor and answer in its place. Below 1024 only the Console unit can bind, through its capability. The
|
|
tester port can't go below 1024 (`packages/webui/src/cli.mjs:33`), so P10 records it.
|
|
|
|
PATH for `mosaic`'s steps. `sudo -u mosaic -H` resets PATH to sudo's `secure_path`, which finds
|
|
`/usr/local/bin/node`, v22. So every `mosaic` step runs as
|
|
`sudo -u mosaic -H env PATH=/opt/mosaic-stack/node-<v>/bin:/usr/local/bin:/usr/bin:/bin ...`, the list
|
|
`case2-unit.sh` renders as the units' PATH. `stack-upgrade.sh` builds it from the args file's `--node`.
|
|
|
|
## 2. Where Case 1 differs from Case 2
|
|
|
|
| Case 2 (library) | Case 1 (factory) | Reason |
|
|
|---|---|---|
|
|
| business file with the library mapping | `mosaic-stack` with no `library` key. The precheck refuses a library project under `factory` (`profile-library-business`, `case2-precheck.sh:127-128`) | the factory has no books repo |
|
|
| `bean`, `chet`, persona vars and files, specialists | the factory roles from `roles/`, with a chief that has a harness var | the go names the set. I haven't read Jason's live business file and won't. The acceptance needs only the chief (F3); a chief with no harness var answers `no-route`. The human adds roles later with `instances.set`, no new row |
|
|
| token slot `library-gitea` | none | the factory's per-agent git identity is row 199's credential store (Megavolt, #1697). Until it lands, git work stays on dragon-lin (Q182) |
|
|
| `bus.tester` off | on; the precheck refuses it off under `factory` (`case2-precheck.sh:135`) | the acceptance and the Q173 test system need the tester |
|
|
| persona modes and digests, `library.sync`, `library.status` | skipped | no persona files, no library (C2-13b brief, `factory` profile) |
|
|
| `ZAI_API_KEY` in `claude-session.env` | not checked; the file holds `CLAUDE_CODE_OAUTH_TOKEN` | no instance has provider `zai` (run sheet section 9) |
|
|
| no Discord binding (C2-13b, library only) | none either. F11 checks that `<dataRoot>/discord/` holds no binding | the factory's connector stays on dragon-lin. Two connectors on one bot token would split its events |
|
|
| the claude marker prompt | runs, since the chief's harness is claude-code | |
|
|
| serve flags: preview trio, `--send`, proof pair | `--port 732`, `--business mosaic-stack`, `--tester-port 7333` | no proxy on .53 |
|
|
| bus: no `--pm`, no `MOSAIC_CLAUDE_BIN` | `--pm`, and `Environment=MOSAIC_CLAUDE_BIN=<--claude>` | the PM dispatches the factory's work; without the path the bus finds no claude on the unit's PATH |
|
|
| Console proof file (run sheet section 9, .54) | none | no proof pair |
|
|
|
|
All of these are inputs to the landed scripts. Row 230 implemented the four factory items r6 asked for
|
|
(serve flags, `--pm`, `--claude`, row 190's tokens), and nothing in them hardcodes a Case 1 value.
|
|
|
|
## 3. Model access
|
|
|
|
Q185: Case 1 uses the account the factory uses today, the Claude subscription behind dragon-lin's
|
|
`claude-session.env`. No script copies that file and none reads its value. In the window, Jason, or the
|
|
person he names under the Q173 secrets rule, makes a credential for .53 from that account and writes it
|
|
to `/srv/mosaic/.config/mosaic-dev/secrets/claude-session.env` as `mosaic`, mode 0600 (run sheet
|
|
section 9). Nobody prints it. The check is `stat -c '%U %a %F' PATH` only (F8).
|
|
|
|
The bus unit reads it through `EnvironmentFile=-` (`case2-unit.sh:243`). The leading `-` means a missing
|
|
file doesn't stop the bus, so the chief's first launch would fail instead. The window places the file
|
|
before the units start (section 6, W9), and the precheck's claude marker prompt then proves it works
|
|
without printing it.
|
|
|
|
Quota: the factory on dragon-lin and on .53 share one subscription. Q185's "use existing" accepts that.
|
|
The pi harness stays off on .53 until P19 #383 settles (mos-0865). The business file names claude only.
|
|
|
|
## 4. Upgrade, rollback and backup on .53
|
|
|
|
All three are `scripts/stack-upgrade.sh` (row 233), run as root by ops-01 from `/root/mosaic-stack` at the
|
|
reviewed commit. Usage, exits and the step log are in `docs/TOOLS.md`, "Root's upgrade driver", and run
|
|
sheet section 6.
|
|
|
|
| Action | Command | Who | Go |
|
|
|---|---|---|---|
|
|
| upgrade | `stack-upgrade.sh upgrade --instance dev --bundle /srv/mosaic/incoming/<sha>.bundle --sha <sha>` after run sheet section 6's per-upgrade lines | ops-01 | Mos per run; `--migrate` also needs Jason present (decision 96) |
|
|
| rollback | `stack-upgrade.sh rollback --instance dev --to <copy> [--restore <ACT> [--events N]]` | ops-01 | Mos per run. Mos or Sage picks `<copy>` from `stack-release.sh status mosaic-stack --units external`, read as `mosaic`. The plan refuses unless it is the previous distinct copy |
|
|
| backup of the store | A3's `backup_store` into `<dataRoot>/backups/stack/<ACT>`, inside every upgrade | the driver | the upgrade's |
|
|
| rehearsal | `stack-release.sh rehearse` against the log's newest backup, before anything stops, on `--rehearse-port`, default 7339, which section 0 found free | the driver | the upgrade's |
|
|
| backup of the VM | a PBS backup of VM 1008 before the install window and before any `--migrate` | ops-01 | the window's go (Q182, Q183: agents keep PBS) |
|
|
|
|
Three .53 specifics:
|
|
|
|
1. The first upgrade after install needs `--no-rehearsal` in its go. The install's adopt takes no backup,
|
|
so the log records none, the rehearsal exits 3 (`no-backup`), and the driver goes on only with the
|
|
flag (`stack-upgrade.sh:323-324`). Every later upgrade refuses the flag (`rehearsal-required`).
|
|
2. Exit 5 (`args-failed`): the new stack is live and confirmed, but `dev.args` still names the old copy.
|
|
ops-01 moves the file the result line names into place with `mv -f`, and nothing else (run sheet
|
|
section 6).
|
|
3. Exit 2 leaves both units stopped and the activation open. ops-01 sends Mos the exit and the last
|
|
lines of `/var/log/mosaic-stack/upgrade-dev.log`, and Jason or Mos settles it with `confirm` or `revert`
|
|
as on dragon-lin. A unit that doesn't stop within `--timeout` ends here on purpose (A2f's
|
|
`stale-start`, duckworth-0055 N1, now in run sheet section 6).
|
|
|
|
The store backup doesn't hold the config dir or the credential. PBS backups of VM 1008 hold the whole disk,
|
|
the credential included (P4). A restore swaps only the store and leaves `claude-session.env` in place.
|
|
|
|
Re-install, for a serve flag or role change, is not an upgrade. It needs its own go and runs: stop the
|
|
Console, then the bus; rewrite `dev.args` with the same `--copy`; `case2-unit.sh` with it; both units start
|
|
again from the render. Row 230 N1 (webby-0046) holds here: a re-render with the units running leaves each on
|
|
its old copy, so ops-01 stops both first. `stack-upgrade.sh` already stops both before it renders.
|
|
|
|
## 5. Acceptance: `scripts/case1-accept.sh`
|
|
|
|
New in this row. ops-01 runs it as root on .53, from `/root/mosaic-stack`:
|
|
|
|
```
|
|
case1-accept.sh --business mosaic-stack --args /etc/mosaic-stack/dev.args --claude-pin <pin> --node-pin <v>
|
|
```
|
|
|
|
It reads the user, home, node, claude and ports from the args file and refuses (exit 2) when that file
|
|
fails `stack-upgrade.sh`'s rules. Every check inside the instance runs as
|
|
`sudo -u mosaic -H env PATH=<node dir>:/usr/local/bin:/usr/bin:/bin`. It changes nothing on the host
|
|
except what F3's message writes to the store. It prints one line per check, `<id> <path> <mark>
|
|
<evidence>`, then a count of each mark. Marks: `closed`, `accepted Q182`, `accepted Q183`, `accepted
|
|
mos-1096`, `accepted mos-1101`, and `unnamed`, which means "not named in Q182, Q183 or a Mos ruling".
|
|
It exits 1 when any F check fails and 0 otherwise. A P check never changes the exit. Each `unnamed`
|
|
line goes to Mos, who takes it to Jason (R8 N1). Duckworth reads the whole output.
|
|
|
|
It prints no value from `claude-session.env` and never opens it.
|
|
|
|
Functional checks. Any failure fails the row:
|
|
|
|
| Id | Check | How |
|
|
|---|---|---|
|
|
| F1 | the units run as `mosaic` | for `[email protected]` and `mosaic-webui.service`: active, `User=mosaic`, and the uid in `/proc/<MainPID>/status` is `mosaic`'s |
|
|
| F2 | the Console answers on 127.0.0.1:732 only | `curl -fsS http://127.0.0.1:732/healthz` succeeds, and `ss -Hltnp` shows 732 bound to 127.0.0.1 only, by the Console's MainPID. Jason reaches it with `ssh -L 7332:127.0.0.1:732 [email protected]`. A LAN listener waits for Q175 SSO (sage-1451 Q3) |
|
|
| F3 | one tester message gets a reply | as `mosaic`: `mosaic talk chief <marker> --business mosaic-stack --tester --wait 300`. A stored reply to the marked message passes. The inbox shows decisions only, so it isn't used |
|
|
| F4 | the precheck passes | `case2-precheck.sh --business mosaic-stack --profile factory` as `mosaic` exits 0 |
|
|
| F5 | a role session runs under bwrap with the profile | while F3's reply is pending: a `/usr/bin/bwrap` process owned by `mosaic` has a claude child. Its `/proc/<pid>/attr/current` equals `bwrap (enforce)` and the child's equals `bwrap//&unpriv_bwrap (enforce)`, exact strings after the trailing newline is cut |
|
|
| F6 | claude is the pin | the args file's `--claude` path sits under `/opt/mosaic-stack/claude-<pin>/`, and its `--version` as `mosaic` prints `<pin> (Claude Code)` |
|
|
| F7 | the PM run is live | `launches list --json` as `mosaic` has exactly one live entry for the PM's instance (`launch.by`), the check `confirm` runs |
|
|
| F8 | the credential's file | `stat -c '%U %a %F'` on `claude-session.env` reads `mosaic 600 regular file`, and it isn't a symlink. Run sheet section 9 marks this "precheck line missing" |
|
|
| F9 | the autoupdater is off | `/etc/claude-code/managed-settings.json` is `root:root` 0644 and its `env.DISABLE_AUTOUPDATER` is `"1"`. Run sheet section 3 marks this "precheck line missing" |
|
|
| F10 | node is the pin | the args file's `--node` is `/opt/mosaic-stack/node-<v>/bin/node` with `<v>` the `--node-pin`, and its `--version` prints `<v>`. Run sheet section 2 marks this "precheck line missing" |
|
|
| F11 | no Discord binding | `/srv/mosaic/.mosaic-dev/discord/` is absent or holds no `*.json` (section 2) |
|
|
|
|
Host paths. These report and never fail the row:
|
|
|
|
| Id | Path | Mark | Evidence |
|
|
|---|---|---|---|
|
|
| P1 | `mosaic` has sudo | closed only when `sudo -l -U mosaic` prints "is not allowed to run sudo"; any other output is unnamed | the `sudo -l` result |
|
|
| P2 | `mosaic` has supplementary groups or a key | closed when `id -G mosaic` is one entry and `~mosaic/.ssh/authorized_keys` is absent or empty | `id`, file state |
|
|
| P3 | `localadmin` NOPASSWD sudo, reached with the dragon-lin key | accepted Q183 | `sudo -l -U localadmin` |
|
|
| P4 | root on pve over VM 1008, and PBS backups that hold `/srv/mosaic` with the credential | accepted Q182 and Q183 | a fixed line; the script can't see pve |
|
|
| P5 | `jarvis`, which runs Hermes, reaches `/srv/mosaic` through sudo, docker or lxd | closed when `jarvis` is in none of the three; unnamed otherwise. #423 step 4 removes all four groups before the install (mos-1091 4c) | `id jarvis` |
|
|
| P6 | other members of sudo, docker, lxd | closed when sudo holds only `localadmin` and docker and lxd are empty; each other member is its own unnamed line | `getent group` lines |
|
|
| P7 | a file under `/etc/sudoers.d` naming `mosaic` | closed when none | file names |
|
|
| P8 | the Console or tester reachable from the LAN | closed when 732 and 7333 are bound to 127.0.0.1 only, each by the Console's MainPID, and nothing listens on 7331 | listener table |
|
|
| P9 | a user namespace through `/usr/bin/bwrap` | accepted mos-1096 only while all eight hold, unnamed otherwise: hostcheck items 1 to 6, read from `/usr/local/lib/mosaic-stack/hostcheck.sh --check`'s own `ok N` lines (item 7 is P10's floor); `sudo -u jarvis /usr/bin/bwrap --version` fails with "Permission denied"; `getent group mosaic` lists no member and no other account has its gid. F5 adds the label of a real role session | the hostcheck lines and each item |
|
|
| P10 | a session binds the tester port 7333 while the Console is down | accepted mos-1101 while the port floor is above 732 and 7333 is held by the Console's MainPID | the floor, the owner pid |
|
|
| P11 | the boot check runs before the bus | closed when `mosaic-hostcheck.service` is enabled and `systemctl show -p Before` on it names `mosaic-bus@<business>.service` for the args file's business; unnamed otherwise (section 7) | the `Before=` value |
|
|
|
|
P9 reads hostcheck's output, which root's own script prints, by matching `^ok [1-6] ` lines. It runs `--check` only,
|
|
never `--enforce`, so the acceptance can't close bwrap.
|
|
|
|
Tests, in `packages/cli/tests/case1-accept.test.mjs`. The script takes a test seam, `--root DIR`, for
|
|
`/proc`, `/etc`, `/srv/mosaic` and `getent`, and refuses it when run as root, as the hostcheck does. Fake
|
|
`systemctl`, `curl`, `ss`, `sudo`, `bwrap`, `sysctl`, `stat`, `claude`, `node` and `hostcheck.sh` on PATH.
|
|
Nothing reaches .53 or dragon-lin's live state, and no test reads a real credential. Cases:
|
|
- every check passing exits 0, with the counts matching the lines;
|
|
- each F check failing alone exits 1 with its line;
|
|
- F5 fails on `unpriv_bwrap (enforce)`, `bwrap//&unpriv_bwrap (complain)` and a trailing space;
|
|
- F6 fails on a stub `claude.exe` that prints nothing and on a path outside `claude-<pin>`;
|
|
- F8 fails on mode 0640, on another owner and on a symlink, and the stand-in credential's value never
|
|
appears in the output;
|
|
- F11 fails on one `*.json` under `discord/`;
|
|
- each P check in each of its marks: P1 with output that grants nothing but lacks the refusal text
|
|
(unnamed), P9 unnamed once for each item missing, P10 unnamed with the floor at 700 and with 7333 held
|
|
by another pid, P11 unnamed with `Before=` naming another business;
|
|
- every P check `unnamed` still exits 0;
|
|
- a bad args file refuses with exit 2 before any check;
|
|
- every in-instance call goes through `sudo -u mosaic -H env PATH=<node dir>:...`, read from the fake
|
|
`sudo`'s recorded argv;
|
|
- the hostcheck fake records `--check` and never `--enforce`.
|
|
|
|
Mutants, each killed by a named case:
|
|
1. F1 passes on a MainPID owned by root.
|
|
2. F2 accepts a listener on 0.0.0.0.
|
|
3. F3 passes without a reply.
|
|
4. F5 matches a substring.
|
|
5. F6 skips the `--version` check.
|
|
6. F8 skips the owner check.
|
|
7. F11 ignores `discord/`.
|
|
8. P1 closes on any output.
|
|
9. P5 maps to `closed` with `jarvis` in docker.
|
|
10. P9 drops the jarvis item.
|
|
11. P11 accepts any `Before=`.
|
|
12. A P check's `unnamed` sets the exit to 1.
|
|
|
|
## 6. The .53 window
|
|
|
|
Nothing in this row touches .53 (sage-1809). Every root step below is ops-01's, over
|
|
`ssh [email protected]` with `sudo`, and waits on a Jason decision (mos-1086). The window needs:
|
|
- row 226 landed;
|
|
- Jason's or Mos's go naming the commit, the bundle's sha256, the node and claude pins, the role set and
|
|
Jason's human id;
|
|
- a backup first;
|
|
- a STATE report after.
|
|
|
|
| # | Step | Runs as | Source |
|
|
|---|---|---|---|
|
|
| W0 | re-read section 0's items; stop on any change and report it | `localadmin`, read only | section 0 |
|
|
| W1 | PBS backup of VM 1008 | ops-01 on pve | section 4 |
|
|
| W2 | the stack's user and home | root | run sheet 1 |
|
|
| W3 | node under `/opt` | root | run sheet 2 |
|
|
| W4 | claude under `/opt`, managed settings | root | run sheet 3 |
|
|
| W5 | bubblewrap, the profile, the statoverride, the no-new-privs test, the drift stop; `hostcheck.sh --check` exits 0 with item 7 at 1023 | root | run sheet 4 |
|
|
| W6 | the port floor (read) | root | run sheet 5 |
|
|
| W7 | the bundle, root's checkout at `<sha>`, `mosaic`'s clone | root, then `sudo -u mosaic -H` | run sheet 6 |
|
|
| W8 | `case2-install.sh --profile factory` with section 1's inputs: bootstrap, the business file, roles, human id, the copy | `sudo -u mosaic -H env PATH=...` | row 226 |
|
|
| W9 | place `claude-session.env` | Jason, or who he names | run sheet 9, section 3 |
|
|
| W10 | write `dev.args` (section 1); `case2-unit.sh` from it, which enables both units and starts the bus, then the Console | root | run sheet 7, 10 |
|
|
| W11 | `case2-precheck.sh --business mosaic-stack --profile factory` exits 0; `hostcheck.sh --check` exits 0 with item 7 at 732 | `mosaic`; root | row 230, 240 |
|
|
| W12 | `stack-release.sh adopt mosaic-stack --units external`, unless row 226's install does it | `sudo -u mosaic -H` | row 232 |
|
|
| W13 | `case1-accept.sh` (section 5); the output to Duckworth, then Mos confirms | root | this row |
|
|
| W14 | STATE report to Mos | ops-01 | brief |
|
|
|
|
Install rollback, any step before W13 passes. Stop and disable both units, remove their files and
|
|
`dev.args`, then the run sheet's rollbacks for sections 4, 3 and 2 in that order. Removing the user and
|
|
home (`userdel -r mosaic`) needs its own Mos go, since it deletes the home. W1's backup covers the rest.
|
|
|
|
What row 195 needs from row 226. Row 226 is assigned to Gosalyn, briefed and not started, so I'm listing what the window
|
|
needs from its design, not designing it:
|
|
|
|
1. `--profile factory` writes the business file with `bus.tester` on and no `library` key, and the roles
|
|
the go names.
|
|
2. It writes no token slot and no persona files (its brief says so).
|
|
3. It runs before the unit render, since the copy must exist for `case2-unit.sh --copy`, and it starts no
|
|
unit.
|
|
4. Who writes `dev.args`. The C2-13b brief says the install doesn't write unit files, and the run sheet
|
|
says "I2's install writes it". `mosaic` can't write `/etc/mosaic-stack`. So either root's part of I2
|
|
writes it, or ops-01 writes it from section 1 at W10. Either works for row 195. The file's content is
|
|
fixed above.
|
|
5. Whether it runs `adopt`. If it doesn't, W12 does.
|
|
|
|
I sent these to Gosalyn on 2026-10-21 (gosalyn-0085 acknowledged), and the run sheet takes whichever answer 226 gives.
|
|
|
|
## 7. The business id
|
|
|
|
.53's business id is `mosaic-stack` (sage-1813). It appears in:
|
|
- the args file, twice;
|
|
- the bus unit `[email protected]`;
|
|
- the business file name;
|
|
- `case1-accept.sh --business`.
|
|
|
|
The landed boot unit `packages/cli/systemd/mosaic-hostcheck.service` has
|
|
`[email protected]`. So at boot the drift stop runs before .53's bus, and DEFERRED 240
|
|
N1 (da3051ccc: `After=mosaic-hostcheck.service` in `mosaic-bus.service.in`) doesn't bind this row.
|
|
|
|
If a go ever names another id for .53, the window stops before W8, and the N1 fix lands first as its own
|
|
reviewed row. P11 is the check on the host. It reads `unnamed` when the boot unit's `Before=` doesn't
|
|
name the installed bus.
|
|
|
|
DEFERRED 240 N2 (the apt hook goes in before the first `--check`) is a run sheet order note. This row
|
|
doesn't edit the run sheet, so it stays deferred.
|
|
|
|
## 8. Rulings this design rests on
|
|
|
|
- sage-1451: Q1 system units; Q3 the Console on 127.0.0.1 with `ssh -L`; Q4 a rebase goes to a new branch
|
|
name (#426(a), mos-1086).
|
|
- mos-1091, mos-1096, mos-1098, mos-1101: the bwrap path, its profile and P9; the port squat and P10; .53
|
|
runs the PM. Their r6 sections stand, and rows 232, 233, 240 and 230 implemented them.
|
|
- mos-1108 (Q187) and mos-1118: the installer rows and the scope cut. mos-1122 (Q189): both units start at
|
|
boot.
|
|
- sage-1522: claude under `/opt`, root-owned.
|
|
- sage-1809: no step on .53, root steps named and left to ops-01, the business id stated.
|
|
- sage-1813: 226 in the after list (rev 2718); the id is `mosaic-stack` and 240 N1 doesn't apply.
|
|
- Q185: the factory's model account. Q173: credentials placed by Jason or his named person.
|
|
|
|
## 9. Manifest (planned)
|
|
|
|
- `scripts/case1-accept.sh` (new), mode 100755.
|
|
- `packages/cli/tests/case1-accept.test.mjs` (new). Section 5's cases and mutants.
|
|
- `docs/TOOLS.md`: a `case1-accept.sh` entry with usage, marks and exits, beside the hostcheck entry.
|
|
- `agents/darkwing/work/queue-195/BUILD.md` (new): run environment, the mutant results, deviations.
|
|
- No change to `case2-*.sh`, `stack-upgrade.sh`, `stack-release.sh`, `case1-hostcheck.sh` or the run
|
|
sheet. Two follow-ups stay out of this row:
|
|
- row 230 r2 N1. The "still on copy" line reads the id from the unit file. On .53 neither the install
|
|
nor `stack-upgrade.sh` reaches that line, since both start from stopped units.
|
|
- DEFERRED 240 N2.
|
|
- No change to `~/.mosaic-dev`, `~/.config/mosaic-dev`, dragon-lin's units or `~/.mosaic`.
|
|
|
|
Gate: Duckworth approves this design, then the manifest; the cli suite and every `scripts/test-*.sh`
|
|
green on Sage's gate; section 5's twelve mutants killed in a slot Sage grants.
|