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]>
This commit is contained in:
2026-10-12 13:39:36 -05:00
co-authored by Claude Opus 5.5
parent feb4978cf0
commit 436d581118
41 changed files with 2386 additions and 0 deletions
+6
View File
@@ -4394,3 +4394,9 @@ After: Jason gave the go at 17:21:09Z. Mos reports that ops-01 switched Argo `mo
Before: the follow-ups from rows 76, 83, 67 and the I1 review existed only as lists in BUILD-LOG entries, the standup and decision 109. Mos had filed #146 (decision 109's pinned Console copy) and #147 (decision 101's uid reinstall) in Vikunja project 19, both due 2026-10-13.
After: Jason ruled "Always use Vikunja for task tracking" (Q109, relayed by Mos in sage-0081). At 17:37:55Z Sage filed nine tasks in project 19 on tasks.woltje.com with the pm token slot, each read back with the sync key: #153 to #155 (the I1, M1 and row 77 briefs, due 2026-10-13) and #156 to #161 (the open follow-ups). Evidence: `agents/sage/work/vikunja-setup/2026-10-12_followups.json` and `file-followups.mjs`. The follow-up lists in the row 83 and cutover entries above are now tracked as #159, #160 and #146. Later entries cite Vikunja ids instead of listing follow-ups.
### 2026-10-12 — Rocko, row 77 (#1571) candidate, round 1: Console pinned copy, update step and service hardening
Before: `mosaic-webui.service` ran from the canonical checkout, so every landing there changed the live Console (decision 109). `install` and `uninstall --no-reload` didn't check whether the unit was running, a failed state read wasn't reported, `--allow-user` could start with `-`, and the uid header took any user name (Vikunja #146, #147, #155; brief `docs/plans/2026-10-12_console-pinned-copy.md`, decision 112).
After (candidate, uncommitted, base 27a1a2bb, packet `agents/rocko/work/queue-77/`, candidate manifest sha256 4927de66): `scripts/webui-service.sh copy COMMIT` writes a read-only `git archive` of the Console's paths to `~/.local/share/mosaic-console/<full id>/` (only from the canonical checkout and only for commits on refactor). `install --copy ID` points the unit at that copy, and serve's new `--checkout` keeps the queue read in the checkout. `status` shows the copy id and whether refactor has moved past it. `install` and `uninstall` refuse while the unit is in any state but inactive or failed, with or without `--no-reload`; a failed state read refuses with one line. Preview values must start with a letter or digit, and the uid header takes only `^[0-9a-f]{64}$`. The template carries decision 102's wording. Two choices are stricter than the brief: files are made read-only before the rename, and the running check covers every state but inactive or failed. Evidence: 28 service and pinned-copy tests; 18 mutants, all killed; a byte check showing `render` without `--copy` matches the base 8 of 8; and the gate (webui node 94/0 and all nine `scripts/test-*.sh` green, kept in `out/gate/`). Moved to in-review at rev 676; the review request failed at rev 677, as expected without a credential, so Sage sends it as jarvis. Nothing touched the live unit.
+203
View File
@@ -0,0 +1,203 @@
# 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").
+1
View File
@@ -0,0 +1 @@
27a1a2bb162a96116e85c881b0061433741353fe
File diff suppressed because it is too large Load Diff
+36
View File
@@ -0,0 +1,36 @@
#!/bin/bash
# Row 77 byte check: render without --copy, base script against candidate,
# same tree, same flags. Usage: byte-check.sh <tree> <base-commit> <out-dir>
# The base script is placed beside the candidate in <tree>/scripts for the
# run (so both resolve the same REPO and template) and removed afterwards.
# Only render runs; nothing installs, reads systemd or starts anything.
set -u
tree=$1 base=$2 out=$3
mkdir -p "$out"
b="$tree/scripts/.webui-service.base.sh"
git -C "$tree" show "$base:scripts/webui-service.sh" > "$b" && chmod +x "$b" || exit 1
trap 'rm -f "$b"' EXIT
# The base renders its own template; check the template's body is unchanged
# apart from the header comment, so the comparison is fair.
diff <(git -C "$tree" show "$base:packages/webui/systemd/mosaic-webui.service.in" | grep -v '^#') <(grep -v '^#' "$tree/packages/webui/systemd/mosaic-webui.service.in") > "$out/template-body.diff" && echo "template body: same" || echo "template body: DIFFERS"
uid=6f1c0e2a9b8d7c4e5f3a2b1c0d9e8f7a6b5c4d3e2f1a0b9c8d7e6f5a4b3c2d1e
n=0 same=0
while IFS= read -r flags; do
n=$((n+1))
# shellcheck disable=SC2086
"$b" render $flags 2>&1 | grep -v '^#' > "$out/base-$n.txt"; bs=${PIPESTATUS[0]}
# shellcheck disable=SC2086
"$tree/scripts/webui-service.sh" render $flags 2>&1 | grep -v '^#' > "$out/cand-$n.txt"; cs=${PIPESTATUS[0]}
if [ "$bs" = "$cs" ] && cmp -s "$out/base-$n.txt" "$out/cand-$n.txt"; then same=$((same+1)); r=same; else r=DIFFERS; fi
echo "$n exit $bs/$cs $r: render $flags"
done <<FLAGS
--port 7444
--business mosaic --board http://127.0.0.1:7331
--public-host console.example.com --auth-header X-authentik-uid --allow-user $uid
--public-host console.example.com --auth-header X-authentik-uid --allow-user $uid --send
--public-host console.example.com --auth-header X-authentik-username --allow-user jason
--port 0
--business a.b
FLAGS
echo "$same of $n same"
@@ -0,0 +1,10 @@
28c4708c062b752832576eb42aa1f1c9cede16f940d6d8929a4f47957c2f2b4d agents/rocko/work/queue-77/byte-check.sh
b917b2607ae5f27a95ac09d29b8a2fdedf80069eda0beab9943e22a167250188 agents/rocko/work/queue-77/mutants.py
ff08f52d09ab3f1f9254e7afed418469fb4744a8fee3971129b62b4e2664ec5b docs/TOOLS.md
769e8358fee021cf1a1d6cdce712549220e07491743829e657fe1ec905df86de packages/webui/README.md
fdd202e6faa08f3b6e1784b805c6e4db5464126b8d7ed54f471cc38a524597e6 packages/webui/src/cli.mjs
5692e797e1a1b561a218f49229c8ff297e00ea83a246ac6f828dff7c6e89afed packages/webui/src/reads.mjs
7a449ef91310ab6b0185ab97f1720e0790adbd9a8d1d83c3c716e36da976acb5 packages/webui/systemd/mosaic-webui.service.in
1282f274af3a1d956f4411699f884390493e0a067f3674daba86c1fad8bdce39 packages/webui/tests/pinned-copy.test.mjs
7a22650b68a8a0502ac79b47f3d5c725c3014a78da15b2f12a979e6b6346eed6 packages/webui/tests/service.test.mjs
c8e43013ef9b7bba28a712ba09b86903ed7a0e49906f4998ccb4fda8099043a4 scripts/webui-service.sh
+10
View File
@@ -0,0 +1,10 @@
agents/rocko/work/queue-77/byte-check.sh
agents/rocko/work/queue-77/mutants.py
docs/TOOLS.md
packages/webui/README.md
packages/webui/src/cli.mjs
packages/webui/src/reads.mjs
packages/webui/systemd/mosaic-webui.service.in
packages/webui/tests/pinned-copy.test.mjs
packages/webui/tests/service.test.mjs
scripts/webui-service.sh
+37
View File
@@ -0,0 +1,37 @@
# Row 77 mutants: each edit applied alone to <tree>, the service and pinned-copy
# tests run, the file restored. Usage: python3 mutants.py <tree>
import subprocess, shutil, sys
W=sys.argv[1] # the tree with the candidate applied
S='scripts/webui-service.sh'
M=[
('failed counts as running', S, '&& $3 != "failed" {', '{'),
('reloading not running', S, '$3 != "failed" {', '$3 != "failed" && $3 != "reloading" {'),
('list-units failure ignored', S, '2>/dev/null)" || die "refusing: systemctl --user list-units', '2>/dev/null)" || true; : "refusing: systemctl --user list-units'),
('install skips state read', S, ' state="$(running)" || exit 1\n [ -z "$state" ] || die "refusing: $UNIT_NAME is running', ' state=""\n [ -z "$state" ] || die "refusing: $UNIT_NAME is running'),
('uninstall skips state read with --no-reload', S, '[ -z "$state" ] || die "refusing: $UNIT_NAME is still running', '[ -z "$state" ] || [ "$RELOAD" = 0 ] || die "refusing: $UNIT_NAME is still running'),
('uid header case-sensitive', S, '"${AUTH_HEADER,,}" = x-authentik-uid', '"$AUTH_HEADER" = X-authentik-uid'),
('uid allows upper hex', S, '^[0-9a-f]{64}$ ]]; then', '^[0-9a-fA-F]{64}$ ]]; then'),
('allowed user may start with -', S, 'case "$ALLOW_USER" in [A-Za-z0-9]*) ;;', 'case "$ALLOW_USER" in *) ;;'),
('no --checkout in unit', S, 'flags="--checkout $REPO $flags"', 'flags="$flags"'),
('canonical always passes', S, '[ -d "$REPO/.git" ] || [ "$ALLOW_WORKTREE" = 1 ] ||', 'true ||'),
('install without --copy', S, '[ -n "$COPY_ID" ] || die "install needs --copy ID', 'true || die "install needs --copy ID'),
('missing copy accepted', S, '[ -f "$COPIES/$COPY_ID/packages/webui/src/cli.mjs" ] ||', 'true ||'),
('status drift wrong', S, 'if [ "$head" = "$copy" ]; then', 'if true; then'),
('copy misses seat', S, ' packages/seat/src\n', ''),
('copy stays writable', S, '-exec chmod a-w', '-exec true'),
('copy off-branch accepted', S, 'merge-base --is-ancestor "$id" refs/heads/refactor ||', 'true ||'),
('queue reads root', 'packages/webui/src/reads.mjs', 'root: queueRoot', 'root'),
('--checkout relative ok', 'packages/webui/src/cli.mjs', 'isAbsolute(', '!!('),
]
res=[]
for name,f,a,b in M:
p=f'{W}/{f}'; s=open(p).read()
if s.count(a)<1: res.append(f'NOMATCH {name}'); continue
shutil.copy(p,p+'.bak'); open(p,'w').write(s.replace(a,b))
try:
r=subprocess.run(['node','--test','tests/service.test.mjs','tests/pinned-copy.test.mjs'],cwd=f'{W}/packages/webui',capture_output=True,text=True,timeout=300)
fails=[l for l in r.stdout.splitlines() if l.startswith('✖') and 'failing tests' not in l]
res.append(f"{'KILLED' if r.returncode else 'SURVIVED'} {name}: {len(set(fails))} failing")
finally:
shutil.move(p+'.bak',p)
print('\n'.join(res))
@@ -0,0 +1,10 @@
template body: same
1 exit 0/0 same: render
2 exit 0/0 same: render --port 7444
3 exit 0/0 same: render --business mosaic --board http://127.0.0.1:7331
4 exit 0/0 same: render --public-host console.example.com --auth-header X-authentik-uid --allow-user 6f1c0e2a9b8d7c4e5f3a2b1c0d9e8f7a6b5c4d3e2f1a0b9c8d7e6f5a4b3c2d1e
5 exit 0/0 same: render --public-host console.example.com --auth-header X-authentik-uid --allow-user 6f1c0e2a9b8d7c4e5f3a2b1c0d9e8f7a6b5c4d3e2f1a0b9c8d7e6f5a4b3c2d1e --send
6 exit 0/0 same: render --public-host console.example.com --auth-header X-authentik-username --allow-user jason
7 exit 1/1 same: render --port 0
8 exit 0/0 same: render --business a.b
8 of 8 same
@@ -0,0 +1,20 @@
[Unit]
Description=Mosaic Console
Documentation=file:///mnt/storage/scratch/rocko-r77/packages/webui/README.md
StartLimitIntervalSec=600
StartLimitBurst=5
[Service]
Type=simple
WorkingDirectory=/mnt/storage/scratch/rocko-r77
Environment=PATH=/usr/local/bin:/usr/bin:/bin
ExecStart=/usr/bin/node /mnt/storage/scratch/rocko-r77/packages/webui/src/cli.mjs serve --port 7332
Restart=on-failure
RestartSec=15
RestartPreventExitStatus=2
KillSignal=SIGTERM
TimeoutStopSec=30
NoNewPrivileges=yes
[Install]
WantedBy=default.target
@@ -0,0 +1,20 @@
[Unit]
Description=Mosaic Console
Documentation=file:///mnt/storage/scratch/rocko-r77/packages/webui/README.md
StartLimitIntervalSec=600
StartLimitBurst=5
[Service]
Type=simple
WorkingDirectory=/mnt/storage/scratch/rocko-r77
Environment=PATH=/usr/local/bin:/usr/bin:/bin
ExecStart=/usr/bin/node /mnt/storage/scratch/rocko-r77/packages/webui/src/cli.mjs serve --port 7444
Restart=on-failure
RestartSec=15
RestartPreventExitStatus=2
KillSignal=SIGTERM
TimeoutStopSec=30
NoNewPrivileges=yes
[Install]
WantedBy=default.target
@@ -0,0 +1,20 @@
[Unit]
Description=Mosaic Console
Documentation=file:///mnt/storage/scratch/rocko-r77/packages/webui/README.md
StartLimitIntervalSec=600
StartLimitBurst=5
[Service]
Type=simple
WorkingDirectory=/mnt/storage/scratch/rocko-r77
Environment=PATH=/usr/local/bin:/usr/bin:/bin
ExecStart=/usr/bin/node /mnt/storage/scratch/rocko-r77/packages/webui/src/cli.mjs serve --port 7332 --business mosaic --board http://127.0.0.1:7331
Restart=on-failure
RestartSec=15
RestartPreventExitStatus=2
KillSignal=SIGTERM
TimeoutStopSec=30
NoNewPrivileges=yes
[Install]
WantedBy=default.target
@@ -0,0 +1,20 @@
[Unit]
Description=Mosaic Console
Documentation=file:///mnt/storage/scratch/rocko-r77/packages/webui/README.md
StartLimitIntervalSec=600
StartLimitBurst=5
[Service]
Type=simple
WorkingDirectory=/mnt/storage/scratch/rocko-r77
Environment=PATH=/usr/local/bin:/usr/bin:/bin
ExecStart=/usr/bin/node /mnt/storage/scratch/rocko-r77/packages/webui/src/cli.mjs serve --port 7332 --public-host console.example.com --auth-header X-authentik-uid --allow-user 6f1c0e2a9b8d7c4e5f3a2b1c0d9e8f7a6b5c4d3e2f1a0b9c8d7e6f5a4b3c2d1e
Restart=on-failure
RestartSec=15
RestartPreventExitStatus=2
KillSignal=SIGTERM
TimeoutStopSec=30
NoNewPrivileges=yes
[Install]
WantedBy=default.target
@@ -0,0 +1,20 @@
[Unit]
Description=Mosaic Console
Documentation=file:///mnt/storage/scratch/rocko-r77/packages/webui/README.md
StartLimitIntervalSec=600
StartLimitBurst=5
[Service]
Type=simple
WorkingDirectory=/mnt/storage/scratch/rocko-r77
Environment=PATH=/usr/local/bin:/usr/bin:/bin
ExecStart=/usr/bin/node /mnt/storage/scratch/rocko-r77/packages/webui/src/cli.mjs serve --port 7332 --public-host console.example.com --auth-header X-authentik-uid --allow-user 6f1c0e2a9b8d7c4e5f3a2b1c0d9e8f7a6b5c4d3e2f1a0b9c8d7e6f5a4b3c2d1e --send
Restart=on-failure
RestartSec=15
RestartPreventExitStatus=2
KillSignal=SIGTERM
TimeoutStopSec=30
NoNewPrivileges=yes
[Install]
WantedBy=default.target
@@ -0,0 +1,20 @@
[Unit]
Description=Mosaic Console
Documentation=file:///mnt/storage/scratch/rocko-r77/packages/webui/README.md
StartLimitIntervalSec=600
StartLimitBurst=5
[Service]
Type=simple
WorkingDirectory=/mnt/storage/scratch/rocko-r77
Environment=PATH=/usr/local/bin:/usr/bin:/bin
ExecStart=/usr/bin/node /mnt/storage/scratch/rocko-r77/packages/webui/src/cli.mjs serve --port 7332 --public-host console.example.com --auth-header X-authentik-username --allow-user jason
Restart=on-failure
RestartSec=15
RestartPreventExitStatus=2
KillSignal=SIGTERM
TimeoutStopSec=30
NoNewPrivileges=yes
[Install]
WantedBy=default.target
@@ -0,0 +1 @@
webui-service: port must be 1..65535: 0
@@ -0,0 +1,20 @@
[Unit]
Description=Mosaic Console
Documentation=file:///mnt/storage/scratch/rocko-r77/packages/webui/README.md
StartLimitIntervalSec=600
StartLimitBurst=5
[Service]
Type=simple
WorkingDirectory=/mnt/storage/scratch/rocko-r77
Environment=PATH=/usr/local/bin:/usr/bin:/bin
ExecStart=/usr/bin/node /mnt/storage/scratch/rocko-r77/packages/webui/src/cli.mjs serve --port 7332 --business a.b
Restart=on-failure
RestartSec=15
RestartPreventExitStatus=2
KillSignal=SIGTERM
TimeoutStopSec=30
NoNewPrivileges=yes
[Install]
WantedBy=default.target
@@ -0,0 +1,20 @@
[Unit]
Description=Mosaic Console
Documentation=file:///mnt/storage/scratch/rocko-r77/packages/webui/README.md
StartLimitIntervalSec=600
StartLimitBurst=5
[Service]
Type=simple
WorkingDirectory=/mnt/storage/scratch/rocko-r77
Environment=PATH=/usr/local/bin:/usr/bin:/bin
ExecStart=/usr/bin/node /mnt/storage/scratch/rocko-r77/packages/webui/src/cli.mjs serve --port 7332
Restart=on-failure
RestartSec=15
RestartPreventExitStatus=2
KillSignal=SIGTERM
TimeoutStopSec=30
NoNewPrivileges=yes
[Install]
WantedBy=default.target
@@ -0,0 +1,20 @@
[Unit]
Description=Mosaic Console
Documentation=file:///mnt/storage/scratch/rocko-r77/packages/webui/README.md
StartLimitIntervalSec=600
StartLimitBurst=5
[Service]
Type=simple
WorkingDirectory=/mnt/storage/scratch/rocko-r77
Environment=PATH=/usr/local/bin:/usr/bin:/bin
ExecStart=/usr/bin/node /mnt/storage/scratch/rocko-r77/packages/webui/src/cli.mjs serve --port 7444
Restart=on-failure
RestartSec=15
RestartPreventExitStatus=2
KillSignal=SIGTERM
TimeoutStopSec=30
NoNewPrivileges=yes
[Install]
WantedBy=default.target
@@ -0,0 +1,20 @@
[Unit]
Description=Mosaic Console
Documentation=file:///mnt/storage/scratch/rocko-r77/packages/webui/README.md
StartLimitIntervalSec=600
StartLimitBurst=5
[Service]
Type=simple
WorkingDirectory=/mnt/storage/scratch/rocko-r77
Environment=PATH=/usr/local/bin:/usr/bin:/bin
ExecStart=/usr/bin/node /mnt/storage/scratch/rocko-r77/packages/webui/src/cli.mjs serve --port 7332 --business mosaic --board http://127.0.0.1:7331
Restart=on-failure
RestartSec=15
RestartPreventExitStatus=2
KillSignal=SIGTERM
TimeoutStopSec=30
NoNewPrivileges=yes
[Install]
WantedBy=default.target
@@ -0,0 +1,20 @@
[Unit]
Description=Mosaic Console
Documentation=file:///mnt/storage/scratch/rocko-r77/packages/webui/README.md
StartLimitIntervalSec=600
StartLimitBurst=5
[Service]
Type=simple
WorkingDirectory=/mnt/storage/scratch/rocko-r77
Environment=PATH=/usr/local/bin:/usr/bin:/bin
ExecStart=/usr/bin/node /mnt/storage/scratch/rocko-r77/packages/webui/src/cli.mjs serve --port 7332 --public-host console.example.com --auth-header X-authentik-uid --allow-user 6f1c0e2a9b8d7c4e5f3a2b1c0d9e8f7a6b5c4d3e2f1a0b9c8d7e6f5a4b3c2d1e
Restart=on-failure
RestartSec=15
RestartPreventExitStatus=2
KillSignal=SIGTERM
TimeoutStopSec=30
NoNewPrivileges=yes
[Install]
WantedBy=default.target
@@ -0,0 +1,20 @@
[Unit]
Description=Mosaic Console
Documentation=file:///mnt/storage/scratch/rocko-r77/packages/webui/README.md
StartLimitIntervalSec=600
StartLimitBurst=5
[Service]
Type=simple
WorkingDirectory=/mnt/storage/scratch/rocko-r77
Environment=PATH=/usr/local/bin:/usr/bin:/bin
ExecStart=/usr/bin/node /mnt/storage/scratch/rocko-r77/packages/webui/src/cli.mjs serve --port 7332 --public-host console.example.com --auth-header X-authentik-uid --allow-user 6f1c0e2a9b8d7c4e5f3a2b1c0d9e8f7a6b5c4d3e2f1a0b9c8d7e6f5a4b3c2d1e --send
Restart=on-failure
RestartSec=15
RestartPreventExitStatus=2
KillSignal=SIGTERM
TimeoutStopSec=30
NoNewPrivileges=yes
[Install]
WantedBy=default.target
@@ -0,0 +1,20 @@
[Unit]
Description=Mosaic Console
Documentation=file:///mnt/storage/scratch/rocko-r77/packages/webui/README.md
StartLimitIntervalSec=600
StartLimitBurst=5
[Service]
Type=simple
WorkingDirectory=/mnt/storage/scratch/rocko-r77
Environment=PATH=/usr/local/bin:/usr/bin:/bin
ExecStart=/usr/bin/node /mnt/storage/scratch/rocko-r77/packages/webui/src/cli.mjs serve --port 7332 --public-host console.example.com --auth-header X-authentik-username --allow-user jason
Restart=on-failure
RestartSec=15
RestartPreventExitStatus=2
KillSignal=SIGTERM
TimeoutStopSec=30
NoNewPrivileges=yes
[Install]
WantedBy=default.target
@@ -0,0 +1 @@
webui-service: port must be 1..65535: 0
@@ -0,0 +1,20 @@
[Unit]
Description=Mosaic Console
Documentation=file:///mnt/storage/scratch/rocko-r77/packages/webui/README.md
StartLimitIntervalSec=600
StartLimitBurst=5
[Service]
Type=simple
WorkingDirectory=/mnt/storage/scratch/rocko-r77
Environment=PATH=/usr/local/bin:/usr/bin:/bin
ExecStart=/usr/bin/node /mnt/storage/scratch/rocko-r77/packages/webui/src/cli.mjs serve --port 7332 --business a.b
Restart=on-failure
RestartSec=15
RestartPreventExitStatus=2
KillSignal=SIGTERM
TimeoutStopSec=30
NoNewPrivileges=yes
[Install]
WantedBy=default.target
+14
View File
@@ -0,0 +1,14 @@
#!/bin/bash
# Row 77 gate: webui node suite, then every scripts/test-*.sh, sequentially, full output kept.
W=/mnt/storage/scratch/rocko-r77
O=/mnt/storage/scratch/rocko-r77-out/gate
mkdir -p "$O"
cd "$W" || exit 1
node --test 'packages/webui/tests/*.test.mjs' > "$O/node-webui.txt" 2>&1; echo "node-webui exit $?" | tee -a "$O/summary.txt"
grep -E '^ℹ (pass|fail)' "$O/node-webui.txt" | tee -a "$O/summary.txt"
for s in scripts/test-*.sh; do
n=$(basename "$s" .sh)
bash "$s" > "$O/$n.txt" 2>&1; echo "$n exit $?" | tee -a "$O/summary.txt"
tail -3 "$O/$n.txt" | sed "s/^/ $n: /" | tee -a "$O/summary.txt"
done
grep -l -E '✖|^not ok' "$O"/*.txt | sed 's/^/failure markers in: /' | tee -a "$O/summary.txt"
@@ -0,0 +1,103 @@
✔ browser edge states: loading, empty, malformed, stale, hostile/long values, in-flight reply and appearance fallback (15601.557828ms)
Rendered contrast: {"failures":[],"count":330,"lowest":4.504658476260286}
✔ served Console browser: real board fixtures, keyboard, drafts, receipts, themes, 320px and failures (18032.513274ms)
✔ S5 pages in a browser: real broker rows, inert text, Copy, routes, failures, layout and no writes (22758.305545ms)
✔ bus routes serve the four reads from the reader view, unescaped JSON for the page to escape (700.409249ms)
✔ decisions route serves the roles and recent lists from the reader view (180.462266ms)
✔ credentials route serves the broker credential status from the reader view (35.303262ms)
✔ bus routes are GET-only, have no write verb, and keep the same-origin checks (525.954337ms)
✔ bus refusals map to statuses and never carry a path or a stack (350.69229ms)
✔ humanCall speaks the human transport protocol without blocking the server (6101.197852ms)
✔ busReader takes --business, else the live bus host, else refuses (2578.038579ms)
✔ humanCall kills a transport that runs past its timeout (588.320553ms)
✔ serve refuses a --business value that is not a business id (1939.668383ms)
✔ conversation view: full history, collapsed tools, hidden thinking, inert hostile content, malformed and reconcile markers (14055.749617ms)
✔ conversation view: a fork keeps the open branch, says so, and opens the new one on request (7310.541095ms)
✔ conversation view: a newer session with no readable history keeps the marker (3470.035276ms)
✔ conversation view: seats without history say so and offer no reply (2457.175278ms)
✔ Discord row through real board/WebUI: independent brake/liveness, no Reply, literal content (10599.42132ms)
✔ return flow through the conversation view: send, tool call, delayed result, peer message, exact long answers, relaunch (62887.54945ms)
✔ copy writes the Console paths at the full id, read-only, and leaves an existing copy alone (2576.537587ms)
✔ copy refuses a commit off refactor, an unknown one, a worktree and a bad copies directory (8654.610227ms)
✔ copy writes nothing when an archived path is missing at the commit (3140.32104ms)
✔ serve runs from a copy: /healthz 200, the config check, roles and RELEASE from the copy, the queue from --checkout (5845.492103ms)
✔ serve refuses a --checkout that is not an absolute directory path (1879.635451ms)
✔ consoleReads runs the queue read under queueRoot and reads RELEASE under root (586.476585ms)
✔ preview options are all three or none, and each is checked (61.258631ms)
✔ the CLI refuses a partial or invalid preview flag set at start (10230.408835ms)
✔ the CLI with all three flags serves the preview mode (2990.333677ms)
✔ a request for the public name needs the header equal to the user id, else 401 and nothing read (124.488485ms)
✔ the header name matches without regard to case, and the value compare is exact (15.238063ms)
✔ the public name with the header reads the bus views, and the Host and Origin are checked first (169.82948ms)
✔ in preview mode every write answers 403 read-only, from the public name and loopback, and reaches nothing (26.821294ms)
✔ loopback requests are unchanged in preview mode, and the bus views work with the board down (11.269594ms)
✔ a proxy that rewrites Host to the loopback port gets 403 on its first request, whatever the user (857.530788ms)
✔ a repeated Host is 400 before any other check, in preview mode and without it (18.09997ms)
✔ without the preview flags the public name is refused and writes still reach the board (12.972511ms)
✔ redactor: config, dataRoot, any absolute path, Discord ids and the JSON parser quote (4.93245ms)
✔ credential state from the date alone, as the bus states it (0.443466ms)
✔ queue: rows, one row, ids and methods, notes; no path or id from a row (11509.772275ms)
✔ queue: a refused read fails closed with the store's text, redacted (2381.40084ms)
✔ queue: a store that fails to load, or a child that dies, gives a fixed message with no path or stack (7251.422ms)
✔ business: names, vars on the allowlist, authority, credential metadata only (128.297462ms)
✔ business: missing or invalid file refuses with the module's text, redacted (66.712351ms)
✔ business without --business: the live bus host, else no-bus-host; no system config: not-configured (26.151662ms)
✔ settings: release, board, loopback address, notifier binding name only (887.802038ms)
✔ both presentations replace old activity with relaunch notice, label retained history, then resume after new activity (12778.075479ms)
✔ reported return flow and relative Age: reply sent from the inspector, then the new answer appears there without manual refresh (31777.273449ms)
✔ the send box: held roles, the draft and caret across a refresh, each receipt, one POST per Send, and the trail link; the board Reply on a 4xx and a 5xx (17479.194295ms)
✔ startServer takes send only as a function and only with the preview mode (154.231038ms)
✔ the CLI refuses --send without the preview flags, and without a configured bus (6282.066599ms)
✔ the CLI with --send and a configured bus sends through the human transport and logs one line (2812.495457ms)
✔ without --send the route is read-only like every other write, and config says no send (237.550692ms)
✔ with --send every other write is still 403 read-only, and nothing reaches the board or bus (317.206484ms)
✔ config says send only on the public name with --send on (36.559736ms)
✔ a send without the signed-in user is 401 and is neither sent nor logged, loopback included (329.90074ms)
✔ each request check refuses before anything is sent, and logs the refusal without the body (1039.266242ms)
✔ a send passes exactly to, body and in_reply_to, answers the id, and logs it (61.31387ms)
✔ a crafted role name cannot forge a log line (114.133966ms)
✔ a bus refusal is not sent, with its status; an outcome the bus could not confirm is unknown and never retried (326.701638ms)
✔ through the human transport the request is exactly the business, message.send and the keys (894.105727ms)
✔ against a real broker: the message is written once as a human send, and its refusals map (302.716121ms)
✔ loopback host and board origin fail closed (52.903382ms)
✔ real board fixture passes through WebUI; assets and isolated seen/reply work (1626.612392ms)
✔ proxy preserves exact request bytes, status and receipt, rejects forms and malformed JSON, never follows redirect (777.733655ms)
✔ unreachable board reports URL; CLI rejects unsupported options (8431.757782ms)
✔ render prints the unit without the preview flags (365.87324ms)
✔ render carries every serve flag, the preview set included (467.408924ms)
✔ a partial preview set is refused (369.42351ms)
✔ a value the unit cannot carry is refused, for every flag (6755.264513ms)
✔ the rendered unit names none of the human proof agent markers and sets no agent variable (272.774702ms)
✔ install into --dir with --no-reload writes the file whole, then reports unchanged (480.924142ms)
✔ install without --no-reload runs daemon-reload and nothing that starts the unit (137.136655ms)
✔ uninstall refuses while the unit is running, with or without --no-reload, and removes it once it is not (2078.794324ms)
✔ status prints the unit state and GET /healthz on the installed port (1481.358448ms)
✔ usage: an unknown verb or flag, and start or enable, exit 4 (395.422729ms)
✔ without --send, render is the template with its placeholders filled, byte for byte (106.775215ms)
✔ render --send with the preview set ends ExecStart with --send and changes nothing else (207.887044ms)
✔ --send without the whole preview set is refused and writes nothing (1005.576838ms)
✔ install --send into --dir with --no-reload writes the sending unit, and status says whether it sends (1821.277564ms)
✔ uninstall and status take no --send (283.979343ms)
✔ install refuses while the unit is running, in any state but inactive or failed, and changes nothing (2226.248786ms)
✔ when the state read fails, install and uninstall refuse with one line and change nothing (335.030766ms)
✔ install needs --copy naming a copy that exists, and refuses outside the canonical checkout without --allow-worktree (345.194863ms)
✔ render --copy runs the copy and reads the queue in the checkout; without --copy the unit is unchanged (228.923116ms)
✔ the uid header takes only an Authentik uid; the username header still takes a username (1816.904262ms)
✔ a preview value cannot start with - or . (144.651532ms)
✔ status names the copy and whether refactor has moved past it, and says when the unit runs from no copy (589.967005ms)
✔ tokens.css and icons.svg are byte copies of docs/design (83.129679ms)
✔ JetBrains Mono files match the digests in their sources file, with the OFL text (5.366978ms)
✔ shipped strings are unchanged, and the web keeps its write boundary (5.380723ms)
✔ shell in a browser: System default, seeded states, 400px, focus and the Ctrl+K palette (15371.351516ms)
✔ five states on Board, Inbox, Tasks, Agents and Trail (5964.003504ms)
✔ Queue, Business and Settings in their five states, with no move button (13122.544578ms)
✔ seeded secrets, Discord ids and paths reach no response and no page; business refusals in the module's words (4711.966993ms)
✔ the server log names no config path when the system config refuses (773.380807ms)
ℹ tests 94
ℹ suites 0
ℹ pass 94
ℹ fail 0
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 65140.492167
@@ -0,0 +1,39 @@
node-webui exit 0
ℹ pass 94
ℹ fail 0
test-auth exit 0
test-auth: OK auth.sh without valid config refuses
test-auth:
test-auth: selftest: 15 passed, 0 failed
test-conductor exit 0
test-conductor: OK invalid policy exits 2 (exit 2)
test-conductor:
test-conductor: selftest: 17 passed, 0 failed
test-config exit 0
test-config: OK failed validation modified nothing
test-config:
test-config: selftest: 24 passed, 0 failed
test-discord exit 0
test-discord: OK service install with USER unset finishes and names the account for lingering
test-discord:
test-discord: discord suite: 66 passed, 0 failed
test-extension-package exit 0
test-extension-package: OK nested second entrypoint fails closed
test-extension-package:
test-extension-package: extension package selftest: 18 passed, 0 failed
test-foundation exit 0
test-foundation: OK no field of the non-bundle file is echoed
test-foundation:
test-foundation: selftest: 44 passed, 0 failed
test-queue exit 0
test-queue: skip queue verify and render --check: this checkout (/mnt/storage/scratch/rocko-r77) is not the queue's canonical root (/mnt/storage/src/mosaic-stack)
test-queue:
test-queue: queue suite: 27 passed, 0 failed
test-release exit 0
test-release: OK rollback without previous refuses (exit 1)
test-release:
test-release: selftest: 14 passed, 0 failed
test-task exit 0
test-task: OK no agent identity on headless run
test-task:
test-task: selftest: 98 passed, 0 failed
@@ -0,0 +1,17 @@
OK status with missing harness credential exits 3 and still lists accounts
OK status reports harness credential (read-only) + mosaic accounts
OK api key material never reaches output
OK oauth token material never reaches output
OK unparseable credential file exits 2
OK symlinked credential file exits 4
OK env-side credential names reported
OK env var values never reach output
OK accounts without an accounts dir reports none and creates nothing
OK accounts lists files and marks the active one
OK loose account perms flagged in listing
OK agent --auth with missing account file refuses (exit 4)
OK agent --auth with non-0600 account file refuses
OK agent --auth with invalid account name refuses
OK auth.sh without valid config refuses
selftest: 15 passed, 0 failed
@@ -0,0 +1,55 @@
Note: switching to '27a1a2bb162a96116e85c881b0061433741353fe'.
You are in 'detached HEAD' state. You can look around, make experimental
changes and commit them, and you can discard any commits you make in this
state without impacting any branches by switching back to a branch.
If you want to create a new branch to retain commits you create, you may
do so (now or later) by using -c with the switch command. Example:
git switch -c <new-branch-name>
Or undo this operation with:
git switch -
Turn off this advice by setting config variable advice.detachedHead to false
Not currently on any branch.
nothing to commit, working tree clean
Note: switching to '27a1a2bb162a96116e85c881b0061433741353fe'.
You are in 'detached HEAD' state. You can look around, make experimental
changes and commit them, and you can discard any commits you make in this
state without impacting any branches by switching back to a branch.
If you want to create a new branch to retain commits you create, you may
do so (now or later) by using -c with the switch command. Example:
git switch -c <new-branch-name>
Or undo this operation with:
git switch -
Turn off this advice by setting config variable advice.detachedHead to false
OK dry-run: allowed change, exit 0, nothing committed (exit 0)
OK dry-run committed nothing
OK apply: allowed change exits 0 (exit 0)
OK apply: attribution in commit subject
OK apply: target tree clean after commit
OK disallowed path refused (exit 1)
OK disallowed path: target untouched
OK syntax gate refused broken .mjs (exit 1)
OK syntax gate: target untouched
OK suite failure refused (exit 1)
OK suite failure: target reverted to clean
OK disabled policy refused (exit 2)
OK disabled policy: target untouched
OK failed run refused (exit 1)
OK failed run: target untouched
OK missing run exits 4 (exit 4)
OK invalid policy exits 2 (exit 2)
selftest: 17 passed, 0 failed
@@ -0,0 +1,26 @@
OK absent adapter defaults to pi
OK adapter mock validates (exit 0)
OK unsupported adapter exits 2 (exit 2)
OK env exports adapter
OK bootstrap creates default when absent (exit 0)
OK bootstrap wrote config file
OK bootstrap is idempotent on existing config (exit 0)
OK bootstrap did not rewrite existing config
OK validate missing config exits 3 (exit 3)
OK malformed JSON exits 2 (exit 2)
OK unsupported configVersion exits 2 (exit 2)
OK unknown top-level key exits 2 (exit 2)
OK unknown execution key exits 2 (exit 2)
OK unsupported backend exits 2 (exit 2)
OK unsupported environment exits 2 (exit 2)
OK relative dataRoot exits 2 (exit 2)
OK non-canonical dataRoot exits 2 (exit 2)
OK filesystem root dataRoot exits 2 (exit 2)
OK home directory dataRoot exits 2 (exit 2)
OK dataRoot containing config dir exits 2 (exit 2)
OK control character in provider exits 2 (exit 2)
OK symlinked config file exits 2 (exit 2)
OK env exports resolve correctly
OK failed validation modified nothing
selftest: 24 passed, 0 failed
@@ -0,0 +1,70 @@
toolchain: node v26.8.1
OK syntax: packages/discord/src/approvals.mjs
OK syntax: packages/discord/src/authorize.mjs
OK syntax: packages/discord/src/binding.mjs
OK syntax: packages/discord/src/cli.mjs
OK syntax: packages/discord/src/connector.mjs
OK syntax: packages/discord/src/context.mjs
OK syntax: packages/discord/src/engine-pi.mjs
OK syntax: packages/discord/src/errors.mjs
OK syntax: packages/discord/src/gateway.mjs
OK syntax: packages/discord/src/git.mjs
OK syntax: packages/discord/src/journal.mjs
OK syntax: packages/discord/src/notify.mjs
OK syntax: packages/discord/src/rest.mjs
OK syntax: packages/discord/src/setspark.mjs
OK syntax: packages/discord/src/tools.mjs
OK syntax: packages/discord/src/web.mjs
OK syntax: packages/discord/bin/git-credential.mjs
OK syntax: packages/discord/extension/tools.mjs
OK syntax: packages/discord/tests/approvals.test.mjs
OK syntax: packages/discord/tests/authorize.test.mjs
OK syntax: packages/discord/tests/binding.test.mjs
OK syntax: packages/discord/tests/connector.test.mjs
OK syntax: packages/discord/tests/context.test.mjs
OK syntax: packages/discord/tests/engine.test.mjs
OK syntax: packages/discord/tests/fake-pi.mjs
OK syntax: packages/discord/tests/gateway.test.mjs
OK syntax: packages/discord/tests/git.test.mjs
OK syntax: packages/discord/tests/helpers.mjs
OK syntax: packages/discord/tests/journal.test.mjs
OK syntax: packages/discord/tests/notify.test.mjs
OK syntax: packages/discord/tests/recover.test.mjs
OK syntax: packages/discord/tests/rest.test.mjs
OK syntax: packages/discord/tests/setspark.test.mjs
OK syntax: packages/discord/tests/tools.test.mjs
OK syntax: packages/discord/tests/web.test.mjs
OK syntax: packages/discord/fixtures/claim-worker.mjs
OK syntax: packages/discord/fixtures/legacy-owner-worker.mjs
OK syntax: scripts/discord.sh
OK syntax: scripts/discord-service.sh
OK packages/discord declares no dependencies
OK no bot-token-shaped string in packages/discord
OK fixture binding uses placeholder ids only
OK fixture binding validates
OK real pi with the extension exposes exactly list_dir, read_file, search and no built-in tool
OK real pi with a writable root exposes exactly the three reads plus write_file and edit_file, and writes nothing at start
OK real pi with a web key exposes the three reads plus web_fetch and web_search, and no write tool without a writable root
OK real pi with a git root exposes the reads, writes and the four git verbs, commits nothing at start, and never shows the token
OK real pi with protocol vault adds reserve_id to the git verbs
OK real pi with a setspark key exposes the reads and the eight record verbs, no counters, and never shows the key
OK real pi refuses a git key on a read-only root (fail closed)
OK real pi with the pilot flags (--no-tools) exposes no tool at all
OK real pi exits non-zero without MOSAIC_DISCORD_TOOLS: no session, no tools (fail closed)
OK a failing nested test fails the run under a parent runner's NODE_TEST_CONTEXT
OK node --test packages/discord/tests/ (ℹ pass 178)
OK scripts/discord.sh --help exits 0
OK scripts/discord.sh check without a binding exits 4
OK scripts/discord.sh recover without a binding exits 4
OK scripts/discord.sh reload without a binding exits 4
OK scripts/discord-service.sh without a command exits 4
OK service unit renders with the repository path, a supervised run as the main process, exit 3 never retried, and reload as SIGHUP
OK service install writes the rendered unit (0644) and leaves no temp file
OK service install a second time reports unchanged
OK systemd-analyze verify accepts the rendered unit
OK service uninstall removes the unit file
OK service install with an unknown flag exits 4
OK service install with USER unset finishes and names the account for lingering
discord suite: 66 passed, 0 failed
@@ -0,0 +1,21 @@
OK initial ordinary-file install
OK installed tree matches canonical source
OK installed tree has no symlinks
OK check detects installation drift
OK sync refuses to overwrite installation drift
OK check detects an extra destination file
OK check detects an extra destination directory
OK check rejects a destination symlink
OK sync accepts a canonical source update
OK updated installation matches canonical source
scripts/test-extension-package.sh: line 14: 983424 Killed "$@" > /dev/null 2>&1
OK forced interruption kills the replacing process
OK next invocation recovers old consistent installation
OK interrupted replacement rolled back
OK sync succeeds after interruption recovery
OK unlocked stale lock file does not block
OK active lock refuses a concurrent sync
OK source symlink fails closed
OK nested second entrypoint fails closed
extension package selftest: 18 passed, 0 failed
@@ -0,0 +1,53 @@
toolchain: node v26.8.1, python 3.12.8, jsonschema 4.26.0
OK syntax: scripts/foundation-inspect.mjs
OK syntax: scripts/foundation/strict-json.mjs
OK syntax: scripts/foundation/canonical.mjs
OK syntax: scripts/foundation/resolve.mjs
OK syntax: scripts/foundation/validate-record.mjs
OK syntax: scripts/foundation/fixtures/build-fixtures.mjs
OK syntax: scripts/foundation/canonical.test.mjs
OK syntax: scripts/foundation/cli.test.mjs
OK syntax: scripts/foundation/fixtures.test.mjs
OK syntax: scripts/foundation/resolve.test.mjs
OK syntax: scripts/foundation/strict-json.test.mjs
OK syntax: scripts/foundation/verify-schema.py (ast only; no bytecode written)
OK fixture generator runs
OK checked-in fixtures/bundles equal a fresh generation
OK checked-in fixtures/raw equal a fresh generation
OK checked-in fixtures/index.json equal a fresh generation
OK checked-in demo bundles equal a fresh generation
OK a failing nested test fails the run under a parent runner's NODE_TEST_CONTEXT
OK node --test scripts/foundation/ (ℹ pass 80)
OK differential schema oracle: PASS: differential schema oracle (finite corpus; compatibility evidence, not equivalence proof)
platform witness: strftime('%Y') for year 999 -> '999' (pinned checker refuses years 0001..0999)
node v26.8.1; corpus 1568 records (38 pinned fixtures, 478 unique bundle records, 1052 typeCase/mutation/lexical cases)
schema column: agree-valid 540, agree-invalid 991, DISAGREEMENTS 0; strict-only (parser-bound) cases: 27; unsupported-kind records not schema-assessed by the inspector: 10
profile column (schema-valid records only): profile-valid 510, profile-invalid 30
profile refusals asserted: 30 schema-agreed-valid records refused only by the strict typed-string profile (rule profile-pattern-mismatch), 12 declared by name; 73 named probes verified against declared schema/profile columns
OK oracle: zero schema-column disagreements with the pinned checker
OK oracle: strict-only profile refusals are counted and asserted
OK demo: permitted read preview exits 0 (exit 0)
OK demo: permitted file.change preview exits 0 (exit 0)
OK demo: assignment.change proposal is unresolved (exit 3) (exit 3)
OK demo: revoked registration is refused (exit 3) (exit 3)
OK demo: message is not authority (exit 3) (exit 3)
OK usage: no arguments exits 2 (exit 2)
OK io: missing file exits 4 (exit 4)
OK io: directory exits 4 (exit 4)
OK io: symlink exits 4 (O_NOFOLLOW) (exit 4)
OK bound: oversize fixture exits 2 (exit 2)
OK profile: one final LF in a typed selection id is refused before admission (exit 2) (exit 2)
OK profile: two final LFs fail the schema pattern itself (exit 2) (exit 2)
OK profile: escaped newlines in free-form text stay allowed (exit 0) (exit 0)
OK profile refusal is invalid-request/profile-pattern-mismatch with selection and operation withheld, value not echoed
OK text output starts with the disclaimer
OK json output is valid JSON with result allowed and exactly the charter §7 fields
OK json golden matches byte-for-byte
OK sandboxed bundle run (env -i, PATH=/nonexistent) produced the unresolved proposal
OK sandbox inventory (path/type/size/mode/uid/gid/inode/mtime/sha256) unchanged by runs
OK canary never printed (bundle run and credential-file run)
OK a non-bundle JSON file is refused at the shape gate, not read into output
OK no field of the non-bundle file is echoed
selftest: 44 passed, 0 failed
@@ -0,0 +1,35 @@
toolchain: node v26.8.1, git version 2.55.0
OK syntax: packages/queue/src/cli.mjs
OK syntax: packages/queue/src/errors.mjs
OK syntax: packages/queue/src/io.mjs
OK syntax: packages/queue/src/lock.mjs
OK syntax: packages/queue/src/queue.mjs
OK syntax: packages/queue/src/review.mjs
OK syntax: packages/queue/src/store.mjs
OK syntax: packages/queue/tests/commit.test.mjs
OK syntax: packages/queue/tests/data.test.mjs
OK syntax: packages/queue/tests/dispatch.test.mjs
OK syntax: packages/queue/tests/helpers.mjs
OK syntax: packages/queue/tests/lock.test.mjs
OK syntax: packages/queue/tests/migration.test.mjs
OK syntax: packages/queue/tests/review.test.mjs
OK syntax: packages/queue/tests/store.test.mjs
OK syntax: packages/queue/tests/write.test.mjs
OK syntax: packages/queue/tests/fixtures/fake-gitea.mjs
OK syntax: packages/queue/tests/fixtures/kill-at.mjs
OK syntax: packages/queue/tests/fixtures/lock-child.mjs
OK syntax: packages/queue/tests/fixtures/mosaic-pre-a2.sh
OK syntax: scripts/queue-commit.sh
OK syntax: scripts/git-hooks/pre-commit
OK syntax: scripts/mosaic
OK queue-commit.sh, the guard and scripts/mosaic are executable
OK packages/queue declares no dependencies
ℹ tests 150
ℹ pass 150
ℹ fail 0
OK node --test packages/queue/tests/
OK scripts/mosaic queue help
skip queue verify and render --check: this checkout (/mnt/storage/scratch/rocko-r77) is not the queue's canonical root (/mnt/storage/src/mosaic-stack)
queue suite: 27 passed, 0 failed
@@ -0,0 +1,16 @@
OK valid RELEASE resolves (exit 0)
OK invalid RELEASE exits 1 (exit 1)
OK missing RELEASE exits 1 (exit 1)
OK valid RELEASE leaves image tag consistent with version
OK status safe on empty state (exit 0)
OK status created no pointer
OK fault-injected activation refuses (exit 1)
OK refused activation wrote no pointer
OK refusal logged exactly once with valid fields
OK healthy activation succeeds (exit 0)
OK pointer written with valid fields
OK repeat activation succeeds (log grows) (exit 0)
OK log is append-only across activations
OK rollback without previous refuses (exit 1)
selftest: 14 passed, 0 failed
@@ -0,0 +1,101 @@
OK valid task validates (exit 0)
OK unknown task key exits 2 (exit 2)
OK unsupported taskVersion exits 2 (exit 2)
OK invalid task id exits 2 (exit 2)
OK empty prompt exits 2 (exit 2)
OK NUL in expectExact exits 2 (exit 2)
OK out-of-range timeout exits 2 (exit 2)
OK missing mission file exits 4 (exit 4)
OK task with valid mission validates (exit 0)
OK invalid mission exits 2 (exit 2)
OK validate missing task exits 4 (exit 4)
OK validation does not modify the task file
OK prune dry-run exits 0 (exit 0)
OK dry-run deleted nothing
OK prune --keep=2 --yes removes oldest (exit 0)
OK kept exactly 2 newest runs
OK newest run kept, oldest pruned
OK append-only receipt written (3 entries)
OK sessions/workspaces untouched by prune
OK prune with invalid keep exits 4 (exit 4)
OK mock adapter: gate passes on matching mock response (exit 0)
OK mock adapter: expect-mismatch recorded (exit 1)
OK mismatch reason recorded
OK unknown adapter fails closed (exit 1)
OK mission task runs via mock adapter (exit 0)
OK mission section injected into generated prompt
OK retry of mission run succeeds (exit 0)
OK retriedFrom lineage recorded
OK mission section present after retry (relative path resolved)
OK retry of missing run exits 4 (exit 4)
OK skill install bundled ms-tools (exit 0)
OK installed to skills-available
OK double install refuses (exit 1)
OK activate enables skill (exit 0)
OK enabled dir populated
OK uninstall while enabled refuses (exit 1)
OK deactivate moves back to available (exit 0)
OK uninstall removes from available (exit 0)
OK seat with enabled skill launches
OK skill path delivered to adapter
OK shipped researcher contract resolves (ceiling + network)
OK resolve-role refuses a non-role document (conductor policy) (exit 2)
OK resolve-role refuses name/filename mismatch (exit 2)
OK resolve-role refuses unknown network declaration (exit 2)
OK resolve-role refuses duplicate role tool (exit 2)
OK resolve-role refuses unsupported tool (exit 2)
OK resolve-role refuses missing contract file (exit 4)
OK version 1 role prints no contract line
OK shipped version 2 roles resolve with their contracts (pm, cto, coder, reviewer)
OK resolve-role accepts a minimal version 2 role (exit 0)
OK resolve-role refuses an action outside the vocabulary (exit 2)
OK resolve-role refuses a gated-only action in a role file (exit 2)
OK resolve-role refuses a Vikunja verb no role may hold (exit 2)
OK resolve-role refuses a version 2 role whose contract is missing (exit 2)
OK resolve-role refuses a contract that is a symbolic link (exit 2)
OK seat launches under role ceiling
OK role ceiling narrows seat tools (read,write,bash -> read,bash)
OK narrowing recorded loudly
OK missing role contract refuses launch (exit 2)
OK missing-contract refusal names the role
OK empty ceiling intersection -> tool-free seat
OK tool-free outcome recorded loudly
OK overridden agents dir without seat definition refuses (exit 4)
OK seat-resolution refusal names the seat
OK policy: mission only -> mission tools
OK policy: task only -> task tools
OK policy: both -> intersection (task narrowed)
OK policy: empty intersection -> tool-free
OK invalid mission capabilities rejected (exit 2)
mosaic-task: unsupported mission tool: "sudo" (supported: read, write, edit, bash, grep, find, ls)
OK task run with user layer present (exit 0)
OK user context dispatched into generated prompt
OK workspace+tools task runs via mock (exit 0)
OK workspace path + tools delivered to adapter
OK persistent workspace created on host
OK plain task still runs (no workspace/tools) (exit 0)
OK workspace var present but empty when absent
OK tools empty when absent
OK unknown tool exits 2 (exit 2)
OK workspace traversal exits 2 (exit 2)
OK live hello task succeeds with exact marker
OK result.json written in run dir
OK result.json contents are correct
OK wrong expectExact fails with exit 1 (reason: expect-mismatch)
OK each run gets a distinct run dir (no clobber)
OK list shows both runs
OK fork base: teach succeeds (exit 0)
OK fork child recalls ancestor context (exit 0)
OK forked child recalled ancestor code word
OK ancestor session untouched by fork
OK child session dir has its own branch file
OK onboard without name exits 4 (non-interactive) (exit 4)
OK onboard --name renders profile (exit 0)
OK profile written
OK canon structure: required filled, optional placeholdered
OK canon sections present
OK user recall run succeeds
OK recalled user name (response: Jason)
OK no agent identity on headless run
selftest: 98 passed, 0 failed
@@ -0,0 +1,18 @@
KILLED failed counts as running: 2 failing
KILLED reloading not running: 2 failing
KILLED list-units failure ignored: 1 failing
KILLED install skips state read: 5 failing
KILLED uninstall skips state read with --no-reload: 1 failing
KILLED uid header case-sensitive: 1 failing
KILLED uid allows upper hex: 1 failing
KILLED allowed user may start with -: 2 failing
KILLED no --checkout in unit: 4 failing
KILLED canonical always passes: 2 failing
KILLED install without --copy: 1 failing
KILLED missing copy accepted: 2 failing
KILLED status drift wrong: 1 failing
KILLED copy misses seat: 1 failing
KILLED copy stays writable: 1 failing
KILLED copy off-branch accepted: 4 failing
KILLED queue reads root: 2 failing
KILLED --checkout relative ok: 1 failing
@@ -0,0 +1,38 @@
0902ba62a53866f6bc35fba858f39bc50e23843b650f049cd613862625006a4c ./base.txt
669eabbd80559ab1e1bdf649a75b1ebcdf94bc0fdcc0c5312717b24a80005d20 ./BUILD.md
e852a913b48444a9043b48104939ebcde1bd4bae5bd6a30332d0d3e46326fbf3 ./build.patch
28c4708c062b752832576eb42aa1f1c9cede16f940d6d8929a4f47957c2f2b4d ./byte-check.sh
4927de6625a0159eb9633d679a348a77a98978cc403bd9eee78fff8c1d6482a0 ./candidate-manifest.sha256
0fbda19895fc2cb9cff06a0d766e4bc5a509030405f3ae851c8cb1fe59437c5b ./files.txt
b917b2607ae5f27a95ac09d29b8a2fdedf80069eda0beab9943e22a167250188 ./mutants.py
7236b1386f47d3e88f44c1baeb3cdc3ba4e0d72df6cf57b3ecd7adf24f56dfb4 ./out/byte-check/base-1.txt
619a486a2828f6eab280f8df4dd82735464c02a5aded055137374f6f3c17f9b0 ./out/byte-check/base-2.txt
64d6be0adfe28df72a3b79ede380561ddf18a93afccacc27ed8314d05ce18c7d ./out/byte-check/base-3.txt
3d5a4aac2243cc40acb90f8917372cedc222e354ea09bce939856568735da229 ./out/byte-check/base-4.txt
ffbfc5ee5865a1fd4705f1ae32b1e802c7b68789955a5e3b381fa242781ea63d ./out/byte-check/base-5.txt
ce08c820abdee674b08f09768e6fc56b8a17cfe082829d8c26daa1e456ec1a6f ./out/byte-check/base-6.txt
d38e7cfe67f8045602923579a9087779cc05cb433490ea606132be49ac53a897 ./out/byte-check/base-7.txt
af9d8b17ad1c42d0c881ff9a72673044b0ebc52eb78cf453ee76614b2cc0be72 ./out/byte-check/base-8.txt
7236b1386f47d3e88f44c1baeb3cdc3ba4e0d72df6cf57b3ecd7adf24f56dfb4 ./out/byte-check/cand-1.txt
619a486a2828f6eab280f8df4dd82735464c02a5aded055137374f6f3c17f9b0 ./out/byte-check/cand-2.txt
64d6be0adfe28df72a3b79ede380561ddf18a93afccacc27ed8314d05ce18c7d ./out/byte-check/cand-3.txt
3d5a4aac2243cc40acb90f8917372cedc222e354ea09bce939856568735da229 ./out/byte-check/cand-4.txt
ffbfc5ee5865a1fd4705f1ae32b1e802c7b68789955a5e3b381fa242781ea63d ./out/byte-check/cand-5.txt
ce08c820abdee674b08f09768e6fc56b8a17cfe082829d8c26daa1e456ec1a6f ./out/byte-check/cand-6.txt
d38e7cfe67f8045602923579a9087779cc05cb433490ea606132be49ac53a897 ./out/byte-check/cand-7.txt
af9d8b17ad1c42d0c881ff9a72673044b0ebc52eb78cf453ee76614b2cc0be72 ./out/byte-check/cand-8.txt
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 ./out/byte-check/template-body.diff
c8865434415cf50faef608ff185e4390ef068b1239c1bdfa8195f7b7ce98f872 ./out/byte-check.txt
b511d20155fab3e2f60f323ceda693e04dba9e1c5cfb336ddf3fda21b5987469 ./out/gate/node-webui.txt
5bcf23a19a8970a3378478c2180f5cc203f5544c7a78cbc31b3c7ad6d827acbb ./out/gate.sh
5bb4a55294a8a7e9a872e383f5326d41066ef6b7629f5ce8897bb15995362859 ./out/gate/summary.txt
f91fc95a173339137f981e24fbf630acfde957e32c3cd71e28304532083c6a77 ./out/gate/test-auth.txt
111546d97a763f32573dc2f0c5c44e83f4a136ef9d301fb1a6b7b17227d3aa03 ./out/gate/test-conductor.txt
52d211444489ea729dafc3c9745d4474d9dfd50f41055cbad792424197f04073 ./out/gate/test-config.txt
7d5016ded1994048642f8b9ae845e52e8af4a069ba05c056c8f580a4589bacbe ./out/gate/test-discord.txt
814f454e3476f9f81245b8eba3011e5af1a108e9b84ad7530f24ff7e27a0ea4f ./out/gate/test-extension-package.txt
83dfaa21c4e4ad941088efab3f69e87ebd2f6bd3cc72ede938e21dc06da8a34f ./out/gate/test-foundation.txt
0cd57fe625b02fdc477f1aea2c54a9cabcc8af10eb2b53fd6dbbb1eab3b5c3fd ./out/gate/test-queue.txt
fa832794ee35025fb4559d58d01a270f6bff2eb65e863e9075b797f47fa68545 ./out/gate/test-release.txt
ccea37f4ffde1f160dcd1a1864988083a645d974e892065c956b338d0f8c853a ./out/gate/test-task.txt
0edfc91fb9d610eda6914e6e4517cce5588e0e25db5a1cd63cce77a4f0fda191 ./out/mutants.txt
+1
View File
@@ -762,3 +762,4 @@ are never rewritten or removed; corrections are new entries.
2026-10-12T18:18Z | sage | M1 brief (Vikunja #154), rows 80/81 briefed, decision 113 | M1 brief ff61135e; row 84 (#1578, rocko after 77) briefed rev 671 (95cb51c3); #1577 misfiled as jason.woltje, closed for #1578; rows 80, 81 queued→briefed revs 667-668 (Dewey's bbb9cc40); decision 113 rules row 61 field table follows the code, edited in the row (30adbd3e, rev 672 73ef8ba7); Vikunja #154 comment 31, #218 filed
2026-10-12T18:33Z | sage | #162 round 1 (decisions 114, 115), S7 restart | decisions 114-115 and P19 #219 Q115, #220 Q114, #221 Q116 ops (ops-01 via Mos), #222 Q116 stack in 681de778; row 42 briefed→in-progress rev 673 (c5885469) on the Q118 channel; S7 run log agents/sage/work/slice1-acceptance/run-log.md
2026-10-12T18:38Z | dewey | row 81 (#1575) in review, round 1 | packet 5afa8b0f, candidate manifest 2263ff5f (12/12 on a fresh worktree of ff11aa4b), my gate green 18:29-18:35Z, 17/17 mutants killed; in-progress→in-review revs 674-675, request comment 27763 (ed5d7d35); Darkwing reviews, Sage notifies Mos before landing (decision 109)
2026-10-12T18:38:55Z | rocko | row 77 (#1571) candidate round 1 | packet agents/rocko/work/queue-77/ (candidate manifest 4927de66, base 27a1a2bb); in-progress→in-review rev 676, request rocko-77-review-1 failed rev 677 (no credential; Sage sends as jarvis); gate webui 94/0 + 9 scripts green; 18/18 mutants killed; live unit untouched