diff --git a/agents/gosalyn/work/queue-230/BUILD.md b/agents/gosalyn/work/queue-230/BUILD.md new file mode 100644 index 000000000..3c4f7de33 --- /dev/null +++ b/agents/gosalyn/work/queue-230/BUILD.md @@ -0,0 +1,70 @@ +# 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 f6b4933f0f47 (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 `mosaic-bus@.service` 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 ``. 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. + +1. The precheck takes `--business ID`. The brief names no other way to find the business file. +2. A run root `/launches/` 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. +3. 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. +4. 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. +5. The unit script refuses `%h` left in any unit, rendered or installed. A system unit expands `%h` + to root's home. The render replaces every `%h`, so the test edits an installed unit to reach it. +6. 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, 28 cases) + +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 `--pm` and `--claude` rendered 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()` plus `sandboxArgv()`, 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 + +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.