Files
stack/agents/rocko/work/queue-77/BUILD.md
T
jason.woltjeandClaude Opus 5.5 436d581118 rocko: row 77 (#1571) packet, round 1; BUILD-LOG and SESSIONS appends
Rocko's work committed by Sage (Rocko has no git authority). Candidate
manifest sha256 4927de66, base 27a1a2bb.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-12 13:39:36 -05:00

11 KiB

Row 77 (#1571): Console pinned copy, update step and service hardening, candidate packet, round 1

Author: Rocko. Reviewers: Darkwing and Filbert. Gate: Sage. Brief: docs/plans/2026-10-12_console-pinned-copy.md, section "Console pinned copy, update step and service hardening" (lead decision 112). Base: 27a1a2bb (base.txt); refactor has not touched any of the files below since (git log 27a1a2bb..1d4ba6bb on them is empty). The packet is uncommitted. I made no commits, pushes or Gitea calls, read no token or private binding, and never started, stopped, restarted or installed the live unit. Every test run uses --dir, a stand-in systemctl and fixture copies.

Files (files.txt, candidate-manifest.sha256)

File Change
scripts/webui-service.sh copy verb; install/render --copy ID [--copies DIR]; --allow-worktree; N1 to N5; the uid rule; status copy and drift lines
packages/webui/systemd/mosaic-webui.service.in header comment only: @REPO@ is the copy, --checkout, and decision 102's wording (Jason or Mos) in lines 6-9
packages/webui/src/cli.mjs serve --checkout PATH (absolute directory, else exit 2), passed to consoleReads as queueRoot; a Queue: start line
packages/webui/src/reads.mjs consoleReads({ root, queueRoot = root }); the queue reader takes queueRoot, nothing else changes
packages/webui/tests/service.test.mjs existing tests moved to --copy, the uid and the state read; +7 tests
packages/webui/tests/pinned-copy.test.mjs new, 6 tests
packages/webui/README.md one paragraph at the end of the service section
docs/TOOLS.md the "Console service" entry only
agents/rocko/work/queue-77/mutants.py the mutant rerun: python3 mutants.py <tree>
agents/rocko/work/queue-77/byte-check.sh render without --copy, base script against candidate: byte-check.sh <tree> <base> <out>

The copy

copy COMMIT [--copies DIR]:

  • Refuses outside the canonical checkout ($REPO/.git must be a directory) unless --allow-worktree (tests only; the help text says so).
  • COMMIT must start with a letter or digit and use only A-Za-z0-9._/-; it resolves with rev-parse --verify "$commit^{commit}" to the full id, and merge-base --is-ancestor against refs/heads/refactor refuses anything off the branch. --copies must be absolute, in the unit's character set.
  • An existing DIR/<id> prints exists: and exits 0, untouched.
  • Otherwise git archive <id> -- COPY_PATHS | tar -x into mktemp -d DIR/.<id>.XXXXXX (removed by a trap on any failure), chmod a-w on every file, chmod 0755 on the temp directory, then mv -T to DIR/<id>. A rename that loses a race to another copy prints exists:.

Two choices against the brief's wording, both stricter:

  • chmod before the rename. The brief says rename, then remove write permission. I do it the other way round, so the final path is never seen whole-but-writable, and a crash between the steps leaves only a temp directory, not a writable copy at the id.
  • Files read-only, directories not. Directories keep their write bit, so the copy can still be removed with plain rm -rf ("copies are kept until someone removes them"). A file can't be edited in place; a new file could be added to a directory. Making directories read-only too is a one-line change if the reviewers want it.

COPY_PATHS (in the script, with a comment): the src of bus, business, cli, conversation, discord, harness, seat and webui, packages/webui/README.md, scripts/mosaic-config.mjs, roles and RELEASE. packages/queue is deliberately left out: the queue read runs in the checkout.

The unit

  • install and render take --copy ID (40 lower-case hex) and --copies DIR. The copy must hold packages/webui/src/cli.mjs, else "no copy ... make it with: scripts/webui-service.sh copy ID". @REPO@ becomes the copy path, and the flags get --checkout <REPO> in front.
  • install refuses without --copy. render without --copy is the base unit byte for byte (byte check below), and render takes no --allow-worktree (it only prints).
  • status reads WorkingDirectory. If its last part is 40 hex it prints copy: <id> and one of refactor: <sha>, the copy's commit, refactor: <sha>, another commit; the copy is <id>, or refactor: unknown; .... Otherwise copy: none; the unit runs from <wd>.

N1 to N5 and the uid rule

  • N1, broader than the brief. running() reads systemctl --user list-units --no-legend --plain mosaic-webui.service and treats any ACTIVE state but inactive or failed as running, so reloading (and any state systemd adds) refuses too, not only the brief's three. install refuses with "refusing: mosaic-webui.service is running (STATE); Jason or Mos stops it first (systemctl --user stop mosaic-webui), then install, then start it", and nothing is written.
  • N2. copy and install call canonical().
  • N3, I chose "always checks". uninstall reads the state with or without --no-reload; --no-reload skips only daemon-reload. install does the same. The docs say so.
  • N4. A failed state read dies with one line: "refusing: systemctl --user list-units --no-legend --plain mosaic-webui.service failed, so the unit's state is unknown; nothing was changed". systemctl's own stderr is dropped so the line stays one line.
  • N5. --public-host and --auth-header already had leading-character checks that no test covered; --allow-user gains one ("allowed user must start with a letter or digit"). Tests cover - and . on all three. This also retires the case row 83 documented, --allow-user --send without sending: it is now refused, and the old test that installed it now asserts the refusal.
  • uid rule. With --auth-header equal to x-authentik-uid in any case (${AUTH_HEADER,,}), --allow-user must match ^[0-9a-f]{64}$, or render and install refuse naming the rule and decision 101.

Order in install: flags, canonical, --copy present, render (all value checks), then the state read, then the write. A refused value never reaches systemctl.

Tests

node --test tests/service.test.mjs tests/pinned-copy.test.mjs: 28 pass.

service.test.mjs: every run now gets a stand-in systemctl (a quiet one that lists nothing by default), since install reads the unit's state; no run can reach the real systemd. New tests: install refuses in four running states with and without --no-reload and goes ahead for inactive, failed, another unit and nothing; a failed state read refuses install and uninstall with one exact line; --copy refusals, --copies, and the worktree refusal (the suite runs in a worktree, so it asserts the refusal when .git is a file); render --copy byte for byte; the uid rule in four header spellings, six bad values, render and install, and the username header still taking a username; leading - and .; status copy, drift and copy: none.

pinned-copy.test.mjs builds a fixture canonical repository on refactor from this tree's packages, scripts and roles, with its own RELEASE. It copies by short id, checks the top level, that every file is read-only, exists: on repeats, the default XDG directory, and that the checkout is untouched; refuses off-branch commits, bad names, bad copies directories and a linked worktree, and accepts the worktree with --allow-worktree; leaves the copies directory empty when git archive fails; serves from the copy; refuses a bad --checkout; and checks consoleReads uses each root.

The serve test against the brief: it starts cli.mjs serve from the copy and gets /healthz 200, Bus: acme (the config check, from the copy), RELEASE 0.0.0-fixture and the business read through the copy's roles, and the queue read in the --checkout directory with its cwd there. Without --checkout the copy's own queue read fails closed (503), since the copy has no packages/queue. Two departures, stated plainly: there is no literal stand-in socket, because /healthz and the reads above make no bus call (serve only builds the caller at start); and the bus child, packages/bus/src/human-cli.mjs, spawned per bus call from the copy's packages/cli/src/transport.mjs, is not exercised, because tests never spawn the real human-cli. Both files are inside COPY_PATHS (packages/bus/src, packages/cli/src), and serve imports from both at start, so a missing one fails the serve test. The seat mutant below shows the test catches a package missing from the list.

Mutants (mutants.py, out/mutants.txt)

18 mutants, each applied alone to the candidate tree, all killed: failed counted as running; reloading not running; a failed state read ignored; install skipping the state read; uninstall skipping it under --no-reload; the uid header case-sensitive; upper-case hex accepted; allowed user free to start with -; no --checkout in the unit; canonical() always passing; install without --copy; a missing copy accepted; status drift always "the copy's commit"; packages/seat/src dropped from the list; files left writable; off-branch commits accepted; the queue reader on root; a relative --checkout accepted.

The last one first survived: the test's relative path didn't exist, so stat refused it either way. The test now uses an existing relative directory under the child's cwd, with a 10 s spawn timeout so a mutant that really serves fails instead of hanging the suite (it hung once; I killed my own fixture serve on port 0).

Byte check (byte-check.sh, out/byte-check.txt, out/byte-check/)

The base script, placed beside the candidate for the run and removed afterwards, and the candidate render eight flag sets without --copy, including both preview headers, --send, a refused --port 0 and a dotted business id. With comment lines dropped (the template's header changed by design), all 8 match, exit codes too, and the template's non-comment body is identical.

Gate (out/gate/, out/gate.sh)

Sequential, in the scratch worktree at 27a1a2bb with the candidate and node_modules linked, full output kept per suite, load average 20-33:

Suite Result
webui node (packages/webui/tests/*.test.mjs) 94 pass, 0 fail
test-auth 15 passed, 0 failed
test-conductor 17 passed, 0 failed
test-config 24 passed, 0 failed
test-discord 66 passed, 0 failed
test-extension-package 18 passed, 0 failed
test-foundation 44 passed, 0 failed
test-queue 27 passed, 0 failed (the canonical-root checks skip in a worktree, as always)
test-release 14 passed, 0 failed
test-task 98 passed, 0 failed

No ✖ or not ok in any output file. rocko-r77_default was removed after the Docker suites.

For the reviewers

  • The stale comment in status about --allow-user --send is updated: the allowed user can no longer be --send.
  • The live unit is untouched. Mos's reinstall after the landing is copy at the landed commit, stop, install --copy ID with the uid, start (brief, "Gate").