Files
stack/agents/rocko/work/queue-75/BUILD.md
T
jason.woltjeandClaude Opus 5.5 bce20fcbbe review(rocko): row 75 round 1 packet, the Console as a user service (#1569)
Committed by sage for rocko (no credential). Candidate manifest 7dc571db, packet manifest 0bc9a870, base 43d2328f.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-11 17:33:54 -05:00

116 lines
6.8 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Row 75 (#1569): Console as a user service, candidate packet, round 1
Author: Rocko. Reviewer: Filbert. Brief: `docs/plans/2026-10-11_dogfood-cutover.md`,
section "Console as a user service" (blob 1b124173 at 57f36a96; the rev 501
re-pin to blob f5d56554 changes only row 76's section). Lead decisions 94 and
95. Base: `43d2328f` (`base.txt`). At 2026-10-11T22:32Z the checkout HEAD
`2d04426c` has no change to any candidate path since the base, and
`build.patch` applies to it cleanly. The packet is uncommitted. I made no
commits, pushes or Gitea calls, read no token or private binding, and
started no unit.
## Files (`files.txt`, `candidate-manifest.sha256`)
| File | What |
|---|---|
| `scripts/webui-service.sh` (new, 0755) | `render`, `install [--dir DIR] [--no-reload]`, `uninstall [--dir DIR] [--no-reload]`, `status [--dir DIR]` on the `bus-service.sh` pattern; exit 0 ok, 1 failed, 4 usage |
| `packages/webui/systemd/mosaic-webui.service.in` (new) | the unit template |
| `packages/webui/tests/service.test.mjs` (new) | 10 tests |
| `docs/TOOLS.md` | one new section, "Console service (`scripts/webui-service.sh`)", before the Discord connector section; nothing else touched |
No `src/` file and no README. Row 74 owns those.
## What it does
- The serve flags are `--port N` (default 7332, 1..65535), `--business ID`,
`--board URL` and the preview set `--public-host NAME --auth-header
HEADER --allow-user ID`, all three or none.
- Each value has its own plain character set:
- business: serve's ID pattern, `[a-z0-9][a-z0-9._-]{0,63}`.
- board: `http://` followed by `[A-Za-z0-9.:/_-]`. Serve checks the rest itself: a loopback origin with no path.
- public host: `[A-Za-z0-9.-]` with a leading alphanumeric, so no port and no scheme.
- header: `[A-Za-z0-9-]`.
- user: `[A-Za-z0-9._@+-]`.
- None of the sets allows a space, newline, `%`, `$`, quote, backslash, `&` or `|`. That means neither systemd nor the `sed` render can read a value as anything other than the value.
- Every refusal exits 1 and writes nothing. A refused `install` doesn't create the unit directory.
- `ExecStart=@NODE@ @REPO@/packages/webui/src/cli.mjs serve @FLAGS@`. NODE is
`command -v node` at render time, kept as given (here `/usr/bin/node`).
systemd doesn't look up an ExecStart command in `Environment=PATH`, so a bare
`node` would not be found the way the brief's wording suggests. The REPO
and node paths are held to `[A-Za-z0-9/._+-]`.
- `WorkingDirectory` and `PATH` are filled in as the bus unit fills them:
`/usr/local/bin:/usr/bin:/bin`, with node's directory prepended if it isn't
already there. The other settings are `Restart=on-failure`,
`RestartPreventExitStatus=2`, `KillSignal=SIGTERM`, `TimeoutStopSec=30`,
`NoNewPrivileges=yes` and `WantedBy=default.target`. There is no other
`Environment=` line and no `MOSAIC_*`, `CLAUDE*`, `CODEX*` or `PI_*` variable.
- `install` prints the `systemctl --user enable --now mosaic-webui` line for
the human to run. The script has no start, stop, enable or restart verb; the
header and TOOLS.md say why (lead decisions 62 and 94).
- `uninstall` refuses while `list-units` shows the unit active, activating or
deactivating, unless `--no-reload` is given, as in bus-service.
- `status` prints is-enabled, is-active and `show`, then reads `--port` from the
installed unit's ExecStart and prints `GET http://127.0.0.1:<port>/healthz`:
the status code and body, or `unreachable (<code>)`. It exits 0 either way,
as bus-service status does.
## Points for the reviewer
1. **Exit 2 isn't retried.** `serve` exits 2 for usage, config and listen errors,
EADDRINUSE included (`serve.mjs` lines 149–150 reject, `cli.mjs` sets
exitCode 2). With `RestartPreventExitStatus=2`, a port conflict stops the
unit rather than looping. I chose that on purpose, and the template comment
says so. Serve has no separate config exit code: a missing system config
still serves, and the bus routes answer not-configured.
2. **The preview flags need row 74.** Until row 74 lands, a unit rendered with
the preview set makes `serve` refuse with exit 2 (`unknown option`) and not
restart. Rendering and installing are unaffected.
3. **Serve runs under systemd.** It runs as written. The user manager's
environment (`systemctl --user show-environment`) carries none of the
human.mjs markers, and the ancestry walk accepts at ppid 1. That is the gap
lead decisions 62 and 94 name, and it's why only Jason starts the unit. I did
not start it to prove this; `systemd-analyze --user verify` on the rendered
unit exits 0.
4. **An empty value counts as unset.** `--board ""` and `--business ""` are
treated as unset, the way bus-service treats an empty value. An empty member
of the preview set counts as missing, so a set with one empty member is
refused as partial.
## Tests (`packages/webui/tests/service.test.mjs`)
The brief asks for six things:
- render with and without the preview flags;
- a partial set refused, in all three two-of-three shapes;
- space, `%`, newline, `$`, quote, backslash, `|` and `&` refused for each value flag, plus bad ports, business ids, boards, hosts and headers;
- the rendered unit names none of the `MARKERS` that the test parses from `packages/bus/src/human.mjs`, and its only Environment key is PATH;
- install into `--dir` with `--no-reload` writes the file whole and identical to `render`, at mode 0644, with no temp file left and no systemctl call; a second install reports unchanged; a refused install leaves the installed unit alone;
- uninstall refuses for active, activating and deactivating through a stand-in `systemctl` on PATH, then removes the unit once it is inactive.
I added three tests beyond the brief:
- install without `--no-reload` calls only `daemon-reload`;
- status against an in-process `/healthz` server on port 0, then with the server down, then with no unit installed;
- usage exits 4, including the `start`, `stop`, `enable` and `restart` verbs.
No test reaches the real systemd or spawns `human-cli.mjs`. `XDG_CONFIG_HOME` and `HOME` point into a temp directory.
## Gate (`out/gate/`, sequential, scratch worktree at 43d2328f with the patch, `node_modules` linked)
| Suite | Result |
|---|---|
| webui `node --test 'packages/webui/tests/*.test.mjs'` | 50/50 (service: 10/10) |
| test-auth | 15 passed, 0 failed |
| test-conductor | 17 passed, 0 failed |
| test-config | 24 passed, 0 failed |
| test-discord | 66 passed, 0 failed (node 178 pass) |
| test-extension-package | 18 passed, 0 failed |
| test-foundation | 44 passed, 0 failed |
| test-queue | 27 passed, 0 failed |
| test-release | 14 passed, 0 failed |
| test-task | 98 passed, 0 failed (Docker) |
After the gate I removed the `rocko-r75_default` Docker network.
`out/gate/start.txt` and `out/gate/end.txt` hold the times. shellcheck isn't
installed on dragon-lin; `bash -n` passes.