Squash of Gosalyn's96768766con gosalyn/c2-13a-r2 (base722136772), the 5 manifest paths, build.patch 56649f9a, manifest fbb0d7e42c67 (Webby approve round 2, rev 2563). Gosalyn's gate on96768766c: cli 541 pass 0 fail; scripts/test-*.sh 429 pass 0 fail (summed by script); mutants 10 of 10 killed. packages/cli/README.md is a 3-way merge with row 199 (cb5fdb064): the hunks don't overlap (line 710 vs 1022). host.test.mjs:855 is the only test that reads that file. Oncb5fdb064plus the patch, case2-unit.test.mjs 30/0 and host.test.mjs 42/0. Refactor moved to390ea5261since, a README fix touching no manifest path. The script runs on a host only in a separate window with Jason's go.
6.7 KiB
Row 230: C2-13a unit render and host precheck
Gitea #1732. Vikunja #416 (track T2). Brief: docs/plans/2026-10-20_case2-rows.md, section "C2-13a",
blob f6b4933f0f (01b40d357, sage-1562). Base: 00d4cc1f6 (row 190). Reviewer: Webby. Gate: the cli
suite plus every scripts/test-*.sh.
What ships
scripts/case2-unit.sh renders [email protected] and mosaic-webui.service from the two existing
templates into the system unit directory, for the default instance only. It writes each unit through
a temp file at mode 0644, runs daemon-reload, enables both units, starts the bus and then the
Console. If the bus fails to start, the script refuses and leaves the Console unstarted. Both units
get WantedBy=multi-user.target, and neither gets After= or Wants=. The script never links a unit
file into place. A re-install refuses unless each installed unit passes the same checks, both units
name one copy, and each equals the render apart from the copy id.
--profile library (the default) refuses --pm, --claude, --tester-port and --sso, and needs
the preview proxy flags plus the proof pair. --profile factory refuses the six proxy flags and takes
--pm and --claude. Both refuse --checkout and --board.
scripts/case2-precheck.sh --business ID [--profile factory|library] runs as <user>. It prints one
ok or REFUSED line per check and exits 1 if any check refused. It prints variable names only,
never values.
packages/cli/README.md:713 now says nothing creates the session env file, only the Case 2 precheck
reads it, and the precheck prints only variable names (webby-0034 N2, webby-0035).
Deviations for review
Sage accepted all six for the build (sage-1570). Webby rules on each.
- The precheck takes
--business ID. The brief names no other way to find the business file. - A run root
<dataRoot>/launches/<business>that doesn't exist passes, with a note that the first launch creates it at 0700 (session.mjs:47 and newRun). A symlink refuses, and so does an existing entry that isn't a 0700 directory. - The claude marker check passes when the reply holds the marker and input plus cache tokens are at most 2000. The 2000 ceiling is unmeasured. The tests use fakes at 40 and 9000 tokens. Measuring it on a real claude belongs to row 195's install run, not this row.
- A non-root test can't create a root-owned proof file. For the "proof not owned by the user" case, the fake getent reports another uid for the user, so the user's own file reads as someone else's.
- The unit script refuses
%hleft in any unit, rendered or installed. A system unit expands%hto root's home. The render replaces every%h, so the test edits an installed unit to reach it. - With
--auth-header X-Authentik-Uid, the allowed user must be 64 lower-case hex characters (lead decision 101). The test covers both the refusal and the pass.
Tests (packages/cli/tests/case2-unit.test.mjs, 30 cases after round 2)
The tests use a scratch unit directory and a scratch home, plus fake systemctl, getent, sudo, id, node, bwrap and claude. They never touch /etc/systemd, ~/.mosaic-dev, ~/.config/mosaic-dev or a live unit. Every precheck case asserts its output doesn't hold the fixture key.
- Unit: the clean library render, PATH for a node outside the default list and for /usr/bin/node, the
systemctl order, a failed bus start, no
systemctl link, factory--pmand--clauderendered and absent, re-install to another copy, edits to an installed unit, template restrictions, EnvironmentFile placement, unknown tokens, PermissionsStartOnly and+/-+Exec prefixes, symlinked unit paths, bad--node, re-install drift, a split copy pair, bad copy ids, every library and factory flag refusal, and bad--claude. - Precheck: a good host, node older than 24, more than one group, sudo succeeding, XDG_DATA_HOME, HOME mismatch, a run root mode, a bad config, bwrap failing, the port floor at or below the Console port, a missing Console unit, bus.tester per profile, profile-library-business, the claude check (no route, replaces, appends, missing, the unit's MOSAIC_CLAUDE_BIN beating PATH), and the session env file (missing, 0644, symlink, empty, emptied by a later line, quoted).
- Bind (mos-1100): inside
binds()plussandboxArgv(), a bind on port 980 gets EACCES, and a bind on a free port of 1024 or above succeeds. The test fails if the host's floor is at or below 980.
Verification
Round 1 singles, run before the gate: case2-unit.test.mjs passes 28/28 with no skips, and host.test.mjs (it reads the cli README) passes 42/42. No test pins a hash of the README. No other test reads the two scripts.
Round 2 (webby-0046, sage-1589, sage-1590, sage-1591)
Base: origin/refactor (rebased from 00d4cc1f6; the 15 commits between touch none of the five paths or the two unit templates). Branch gosalyn/c2-13a-r2.
B1. The precheck now has a case for each refusal Webby named: the user missing from passwd (:77),
a business file that is unparsable, missing or fails the schema (:137), a run root that is a symlink
to a 0700 directory (:151), a Console ExecStart with no --port (:179), an unreadable port floor
(:180), a claude reply without the marker (:223) and a claude reply that isn't JSON (:224). The fake
claude gained nomarker and text kinds. The no-claude-code case and the missing-claude case share
a PATH without /usr/bin/claude (pathWithoutClaude), so the no-claude-code case now shows a host
with no claude passes.
N1 (ruled in for this round). Before writing, case2-unit.sh asks systemctl is-active for both
units. For each unit that was running, it prints that the unit is still on its old copy, never
"started", and then that the bus and Console must restart together to reach the new copy. A unit
that wasn't running gets "started ... on copy". A fresh install prints the round 1 line. The script
still restarts nothing. The new test covers both units running and only the Console running.
N4. The README line at :713 is rewrapped.
Mutants, run in Sage's slot on a scratch worktree, one branch each: :77, :137, :151, :179, :180, :223, :224, a precheck that refuses with no claude on PATH (for the no-claude-code case), and an install that prints "started" for a running unit (sage-1591).
Carry items for row 195
- N1's restart. Row 195 restarts the bus and Console together after a re-install over running units, as the install line now asks.
- N2. A system drop-in check like
bus-service.sh:122-145, so a drop-in under[email protected]/ormosaic-webui.service.d/can't change what the checked unit runs. - N3. The precheck finds claude on the shell PATH. The bus unit's
PATH=is what a session uses whenMOSAIC_CLAUDE_BINis unset, so the check should search that PATH.