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]>
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/.gitmust 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 withrev-parse --verify "$commit^{commit}"to the full id, andmerge-base --is-ancestoragainstrefs/heads/refactorrefuses anything off the branch.--copiesmust be absolute, in the unit's character set. - An existing
DIR/<id>printsexists:and exits 0, untouched. - Otherwise
git archive <id> -- COPY_PATHS | tar -xintomktemp -d DIR/.<id>.XXXXXX(removed by a trap on any failure),chmod a-won every file,chmod 0755on the temp directory, thenmv -TtoDIR/<id>. A rename that loses a race to anothercopyprintsexists:.
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
installandrendertake--copy ID(40 lower-case hex) and--copies DIR. The copy must holdpackages/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.installrefuses without--copy.renderwithout--copyis the base unit byte for byte (byte check below), andrendertakes no--allow-worktree(it only prints).statusreadsWorkingDirectory. If its last part is 40 hex it printscopy: <id>and one ofrefactor: <sha>, the copy's commit,refactor: <sha>, another commit; the copy is <id>, orrefactor: unknown; .... Otherwisecopy: none; the unit runs from <wd>.
N1 to N5 and the uid rule
- N1, broader than the brief.
running()readssystemctl --user list-units --no-legend --plain mosaic-webui.serviceand treats any ACTIVE state butinactiveorfailedas running, soreloading(and any state systemd adds) refuses too, not only the brief's three.installrefuses 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.
copyandinstallcallcanonical(). - N3, I chose "always checks".
uninstallreads the state with or without--no-reload;--no-reloadskips onlydaemon-reload.installdoes 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-hostand--auth-headeralready had leading-character checks that no test covered;--allow-usergains 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 --sendwithout sending: it is now refused, and the old test that installed it now asserts the refusal. - uid rule. With
--auth-headerequal tox-authentik-uidin any case (${AUTH_HEADER,,}),--allow-usermust 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
statusabout--allow-user --sendis updated: the allowed user can no longer be--send. - The live unit is untouched. Mos's reinstall after the landing is
copyat the landed commit, stop,install --copy IDwith the uid, start (brief, "Gate").