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

204 lines
11 KiB
Markdown

# 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").