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

6.8 KiB
Raw Blame History

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.