Files
jason.woltje 362893d043 feat: integrate reviewed proxy page and managed model keys
Source-candidate: poe d9a564e6d20f5f0fd75ce45b12cbbf8bb4c9d568\nSource-candidate: magica38ba5762b06716db1dee00d0afec162656488f5c\nValidated321whole-file phase plus whole bus1349/model-proxy100/webui490; final gate tooling checks pending, not landed.
2026-10-26 12:04:21 -05:00

831 lines
58 KiB
Markdown

# Deferred list
One line per item, written from evidence, never from inertia. Add a line
when a gap is found and not fixed in the current piece; strike it (move to
Done at the bottom with the commit) when a piece closes it. Nothing here
starts on its own; a line becomes a piece only when Jason calls it. Check it
at every gate. Started 2026-09-12 during the control board MVP.
## Open
- **Inherited Discord engine test intermittency during #1512 review.** Frozen
serialized run recorded a tool-turn engine assertion failure and then timed
out at 120s. A later full TAP run with a 15s test deadline passed 351/351.
Rocko isolated the busy/settled-event test race and missing failure cleanup;
Darkwing independently reproduced the event-split assertion. Rocko also reported
a timeout-log test race and a separate engine followUp-after-timeout defect.
No live incident established. Row21 owner orch-01 notified; no Discord source
takeover. Not assumed identical to earlier concurrent hangs.
Integration held pending owner disposition; logs and scope in
`agents/darkwing/work/relaunch-activity/r1-review.md`. (2026-09-15)
Outcome 2026-09-26: 1685deb4 fixed the busy/settled race and the engine no
longer sends a follow-up (held prompts). A re-run of the frozen candidate
against 21e3e908 passed the serial acceptance command 397/397 three times.
Two engine failures remain when tests run concurrently, and they reproduce on
clean HEAD without the #1512 files, so they are #1509's (entry below). Sage
ruled that #1512 integrates on the serial evidence; it landed in af4203ca.
Record: `agents/darkwing/work/relaunch-activity/rerun-2026-09-26.md`.
- **Registration has no "set by" field.** `mosaic seat task` records the
task and `updatedAt` but not who set it, so a human session and the
orchestrator look the same on the board. One-line record change; rides
with the orchestrator assignment piece. (2026-09-12, #1504)
- **Usage-limit notice was missed.** Jason reports Rocko exhausted usage without
Jason seeing the notice, and is now available again. Investigate where the
notice appeared and what operator notification is missing as part of Claude
console integration. User-reported, not independently reproduced; no root
cause established. Keep outside #1512's relaunch-display scope. (2026-09-15)
- **Claude Code seats register but get no board row.** Rocko has no Pi
session log, and rows come only from Pi logs. Needs a second row source
or a Claude-side log reader. (2026-09-12, darkwing's #1504 report)
2026-09-15 #1511 live-test preflight reconfirmed no Rocko row: expected
`.pi/state/rocko/sessions` discovery directory absent. Its existing repo
registration names dead PID 185602, while the verified native lock owner is
3707667. No registration repair, discovery expansion or relaunch performed;
test write refused before mutation. Evidence:
`agents/darkwing/work/task-attribution/backend-live-verification.json`.
- **Fleet seats (`~/.mosaic`) stay on the old launchers.** No registration,
no board reply, no `seat task`. Moving them is the stress test and comes
after the WebUI and fleet launch work. (Jason, 2026-09-12)
- **Comms skill has no rule for senders without a pane.** The board trailer
covers it today; if another pane-less sender appears, put the rule in
`skills/ms-communications` instead of a second trailer. (2026-09-12, #1505)
- **Board: multiple sessions per seat, stopping seats, auth beyond
loopback.** Out of scope for the MVP by the plan; listed so they are not
forgotten. (plan page, Boundaries)
- **`MOSAIC-STACK-D-001` is Answered, not Completed.** Mark `--completed` in
jarvis-brain once Jason says the board answers "who is waiting on me".
(2026-09-12)
- **Uncommitted working tree.** `AGENTS.md` autonomy section,
`agents/README.md` Sage row, Dewey's `work/wui` docs and evidence,
`docs/plans/reviews/2026-09-08..10_*`, `skills/ms-{arena,blast-radius,
board,executive-update,git-operations,technical-writing}`. Jason's edits
from before 2026-09-12; they ride along in the next commit that touches
them unless staged on purpose. (2026-09-12)
- **Fleet debt (orch-01, `~/.mosaic`):** `sweep-commit.sh` deny-list regex
skips `launch.env`; needs the fix plus a regression test. (2026-09-12)
- **Parked, not cancelled:** Dewey D05 dashboard mockup work beyond design 3,
registry increment 3 (headless identity-env leak), new seat directories.
(plan page, 2026-09-12 parking note)
- **Gitea helper response-file hardening.** Predictable `/tmp/gitea-api-response.$$`
has no explicit 0600 mode; use `mktemp` with cleanup in a separately reviewed
pass. Credential validation and later reread also have a race window. Other
non-blocking ledger review notes remain in
`reviews/2026-09-12_ledger-verdict.md`. No change beyond the reviewed GET
cleanup fix in this piece. Filbert, LEDGER-1506-R1, 2026-09-12, #1506.
- **One shared checkout for every seat and the coordinator.** Any `git add`
of a shared file (CURRENT.md, SESSIONS.md, the plan page) commits another
seat's in-progress edit under the wrong commit; it happened at f80404f5
(dewey's CURRENT.md status note went in with the piece 5 brief, harmless
but unowned). Fix candidates: per-seat worktrees, or shared files edited
only by the seat named in CURRENT.md. (2026-09-12)
- **Console mockup fields absent from the board API.** Controller, generation,
config fingerprint, turn count, goal/report and activity stream are omitted.
Workspace paths are shown as board data, not invented registered workspace
nodes. Launch, observe, stop, full transcript navigation, command palette and
other screens are absent. Existing palette/appearance switches remain on the
single page. Revisit only with a new brief. (2026-09-13 UTC, #1507)
- **Board send can turn into a Pi slash command.** `tools/tmux`
`send-message.sh` pastes onto whatever is already in Pi's composer without
clearing it. On Jason's 2026-09-26 20:10:57Z send, a leftover "/" landed in
front of the agent-send header. Pi read it as plain text that time, but the
same path could run a board reply as a slash command. Belongs to CHAT-03I/B3.
No tools/tmux change until that piece. Found by Dewey in the CHAT-02 brief
evidence. (2026-09-26, #1507)
- **Discord wedge restarts have no cumulative stop.** Since 6b R2, a wedged
pi ends the connector with exit 1, and the unit restarts it. With
turnTimeoutSeconds 600, one cycle takes at least 645 s, longer than the
600 s StartLimit window. So a pi that wedges on every prompt restarts once
per prompt and never hits the limit. It only happens when someone sends a
message, and that person gets an error reply. Sage accepted it for now. The
fix is a separate policy (a wedge count kept across restarts). (2026-09-26,
#1509, Rocko 6b R2 review)
- **Connector does not journal the source it started from.** The unit runs
from the live checkout, so an automatic restart loads whatever Discord
source is in the tree, even uncommitted source. Rocko recommends a startup
journal line with HEAD, the dirty state of `packages/discord` and a digest
of the runtime files. Until then the rule is that unreviewed Discord source
stays uncommitted in the tree only as long as a review needs it.
(2026-09-26, #1509)
- **Index-export suite runs leave Docker networks behind.** Each export
directory becomes a compose project, and its `<dir>_default` network
stays after the run. On 2026-09-26 the host ran out of address pools. The
task and release suites then failed 31 and 3 tests with "all predefined
address pools have been fully subnetted". Sage removed its own five stale
networks. The dw-* ones are still there. Fix: the export recipe, or the
suites, removes the project network on exit. (2026-09-26)
2026-09-27: it happened again on the Piece D candidate (task 31 and
release 3 failures). Sage removed 12 leftover export networks from
other seats, each checked for zero attached containers first, and both
suites then passed. Still open.
- **Ledger T3 read: snapshot isolation has no test.** `readT3` runs every
query in one read transaction, but no test proves a write landing during
the read stays invisible. Filbert's note 4 on the Gate F build (e47ec6da).
Notes 1 to 3 go to Darkwing's follow-up. (2026-09-26, #1506)
- **A T3 sender role with spaces counts as human.** The SetSpark lead's
reply to Sage (21:41:24Z) used the role "main content-engine agent". The
6a rule requires a one-word role, so the ledger counts that message as
human in Sage's row. Gate F reads only Filbert's row, so it is not
affected. Filbert found it through the live diagnostic (14 went to 15).
Sage asked the SetSpark lead to use a one-word role. The rule stays as
it is. (2026-09-26, #1506)
- **Queue tests: the cold-start failure came back (reopened).** Row 33
closed this as not reproduced in 20 idle cold runs. On 2026-10-04 Sage's
index export of the row 33 candidate ran the nine suites, then
`node --test packages/queue/tests/`, and got 147 passed, 1 failed. Sage's
recipe threw the output away, so again nobody knows which test failed.
Ten more runs all passed 148/148: three reruns in the same export, four
under parallel suite load, and three fresh exports run straight after
`test-queue.sh`. It needs a first run on a fresh tree after a full
suite pass, and even then it's rare. Darkwing's n2 on row 33 (the cold
runs were idle) was right. Next time: Sage's export recipe keeps the
node test output in a file, so the first failure has a name. Then fix it
as its own row. (2026-10-04, #1508)
- **Queue: `add --issue N` also sets closes to N.** Follow-up rows under
an umbrella issue, like rows 31 and 33 under #1508, would close the issue
when they finish. Sage narrowed both by hand (revs 36 and 43). Either
`add` takes `--closes` or the lead checks closes on every add. Not
urgent while Sage adds the rows. (2026-10-04, #1508)
- **Ledger metric call can orphan curl.** The metric call in
`packages/ledger/src/ledger.mjs` still relies on execFileSync's
timeout, which kills the helper but can leave curl running. E's queue
calls already use `timeout -s KILL`; the metric call should match.
(2026-09-27, #1508)
- **Ledger: a missing `timeout` command reads as a Gitea failure.** With
no `timeout` on PATH the queue issue calls report "credential or Gitea
request failure". Optional test and clearer message. Filbert, E r2.
(2026-09-27, #1508)
- **`review record` doesn't check which issue the verdict comment is on.**
Row 5 round 1's request (26671) and Filbert's verdict (26683) are on
#1507. Darkwing's verdict 26681 went to #1508, and `review record`
accepted it, because only `review resolve` reads the comment back
(`checkComment` in `packages/queue/src/review.mjs`). The receipt
points at a real comment on the wrong issue. Sage posted a pointer on
#1507 (comment 26685). Fix: have `record` check the comment's issue and author the way
`resolve` does, or refuse a comment id it can't verify. Found by
Filbert. (2026-10-04, #1508)
- **Host seats launch Pi with `--approve`.** A future `.pi/SYSTEM.md` or
`.pi/settings.json` in the checkout would load in every host seat. This
is latent, since neither file exists today. Fix in
`scripts/agent-host-dev.sh`: pass `--no-approve` and an explicit system
prompt. Filbert, meta-harness survey section 8.4. (2026-10-04)
- **Workers can read provider keys.** The worker container passes
`ZAI_API_KEY` and `ANTHROPIC_API_KEY` in the environment, which Pi's
`bash` inherits, and Pi's `read` can open the mounted `auth.json`.
Slice 1 role tokens stay in the broker (lead decision 47). The workers'
own exposure needs a separate piece. Filbert, survey section 8.6.
(2026-10-04)
- **CHAT-03 I1 round 2 follow-ups, for the I3 brief.** Neither reviewer
held any of these as blocking (Darkwing comment 26690, Filbert comment
26694 on #1507). Four of the five are closed by slice 1 S5 (see Done);
this one stays open.
- No test proves that the processes are listed only after the cgroup
is frozen. The code waits for `frozen 1` in `cgroup.events`, and
both Filbert and Dewey confirmed it by reading it. Mutants r2-B5b and
C4 survive. Proposed: a test-only hold after `members`, or a FUSE or
privileged fixture. Filbert F1.
(2026-10-04, #1507)
- **CHAT-03 `engine.envKeys` accepts founder credential names.**
`CREDENTIAL_NAME` in `packages/conversation/src/pi-pin.mjs` matches any
`*_API_KEY` or `*_TOKEN`, including `GITEA_TOKEN` and `GH_TOKEN`, which
the S6 runner refuses as founder credentials (REQ-CRED-2). Nothing
builds a Controller outside the tests yet. Whoever wires the entry point
narrows the pattern to provider names or checks the founder list.
Filbert N2 on #1522. (2026-10-10)
- **`test-task.sh` makes a live Pi call without the Docker guard.** The
"user recall" block near line 478 runs `run-task.sh` with no
availability check, so a run with Docker unavailable fails "user recall
run succeeds" and "recalled user name" (26 pass, 2 fail). The other
live groups skip. Rocko hit it during the S2 suites. Wrap the block in
the same guard as the other live groups, so a seat without provider
access gets clean skips. (2026-10-04, #1519)
- **`node --test <dir>/` fails on Node 24.** Node 24 reads a directory
argument as a module, so every package's test command in that form
fails there, while Node 26 accepts it. The glob form
(`node --test 'packages/x/tests/*.test.mjs'`) works on both. Package
READMEs, `docs/TOOLS.md` and Sage's suite recipe should use the glob
form wherever Node 24 is a target. Darkwing, S1 packet. (2026-10-04,
#1518)
- **`stack-upgrade.sh` doesn't check `$REPO`.** Nothing confirms the
source checkout is root-owned or that its tree is clean before the
upgrade builds from it. Filbert N2 on row 233 (29922, #1735).
(2026-10-21)
- **`stack-upgrade.sh` has no signal handling between A2 and A5.** A HUP,
INT or TERM in that stretch leaves the run failed closed, but recovery
is by hand. A trap that writes the stop point and the recovery step to
the log would make it a sheet step. Filbert N3 on row 233 (29922,
#1735). (2026-10-21)
- **`checkCreative` doesn't refuse `credentials`.** Row 213's
`checkCreative` (packages/business/src/role.mjs:143 at 2d2ad2760)
limits a creative role's tools, authority and messaging, but a role
file that gave companion or research a credential would still load.
The business file could then map a credential to that instance
(business.mjs:157). Nobody has traced whether a mapped credential
reaches the run's environment. The decision paths stay closed (M6),
but a creative role with `web` holding a live token is a wider channel
than design 4 means. The fix: refuse any `credentials` in
`checkCreative`, with a role.test.mjs case. It goes after row 213
lands, as a follow-up row or in the next row that edits role.mjs.
Webby N1 on row 213 r3 (REVIEW-r3.md, 3ab3c010b), mos-1160.
(2026-10-21)
- **The boot host check orders only one business's bus.**
`mosaic-hostcheck.service` (row 240) has
`[email protected]`. `case2-unit.sh` enables the
bus as `mosaic-bus@<business>.service`, so on a host whose business id
isn't `mosaic-stack` nothing orders the two. The bus can start before
the boot stop runs, and after a boot-time drift role sessions can use
bwrap until it does. The fix: `After=mosaic-hostcheck.service` in
`packages/cli/systemd/mosaic-bus.service.in`, which orders every
instance and does nothing where the unit isn't installed. It goes in
before the first install whose business id isn't `mosaic-stack`.
Filbert N1 on row 240 (29945, #1746). (2026-10-21)
- **The run sheet writes the apt hook before the first `--check`.**
Section 4 installs `80mosaic-hostcheck`, then runs
`hostcheck.sh --check`. If that check fails, the next package run
closes bwrap before anyone has read the failure. It fails closed, but
it surprises an installer. The fix: run `--check` first, or say what a
failure at that step means. Doc-only, for the next row that edits
`docs/guides/host-run-sheet.md`. Filbert N2 on row 240 (29945, #1746).
Filbert's N3, that a closed bwrap appends to `DRIFT` on every later
apt run and boot, is by design; ops-01's STATE read should expect the
repeats. (2026-10-21)
- **The sso-config first-step hint has no business.** When the target
copy has no `console.sso`, `stack-release.sh` (`:938`) refuses with
"activate $TO without --sso first, then ... activate $TO --sso". The
usage line is `activate ID BUSINESS` (`:47`), so pasting either command
as printed gives a usage error, not the first step. It fails safe,
since the usage error changes nothing. The fix: add `$BUSINESS`
(set at `:2138`) to both commands, and the business (`acme`)
to the three expected strings in `stack-release.test.mjs`
(`:1538`, `:1689`, `:1691`). For a later row that edits
`stack-release.sh`. Webby N1 on row 238 r1 (REVIEW-r1.md, 433a95292,
#1744). (2026-10-21)
- **Lane image prep inspects a base that BuildKit never tagged.**
(N7 of the row 197 proof.) After the Compose build,
`scripts/testing/lane-image.mjs:41` (`prepareImage`) runs
`docker image inspect node:24-bookworm-slim` to put the base digest
in the receipt. BuildKit resolves the base from the registry
without tagging it in the local store, so on a host without the tag
the inspect throws "No such image" after a good build. That
happened in Sage's proof try 1 (2026-10-21 09:03Z, run cf48efccf083).
The throw fails closed: the run's terminal record is unclean, both
lanes quarantine, and `gate-lane.sh recover` clears them. Pulling the
base first fixes it, and the receipt digest then matches what the
build used (91acba91 both ways). The fix: before the build, inspect
the base and refuse with a named reason if it's missing, or pull it.
After the build, refuse if the base digest changed. Add a test with a
fake Docker that has no base tag. Kept out of row 197 r6 by Sage
(sage-1883). Mos asked for this record in mos-1162. (2026-10-21)
- **Case 1 acceptance script: root parses with the node it's checking, and the header says less than root does.**
(Duckworth N1-N3 on the row 195 manifest, REVIEW-m1 6afcee2a5.)
N1: F9 runs `$ROOT$NODE` as root (`scripts/case1-accept.sh:298`)
before F10 checks that `--node` is the `/opt/mosaic-stack/node-<pin>`
path (:303-304). On a host installed as designed, `case2-unit.sh:128-133`
refused a non-root-owned `--node` and the args file is root's, so no
escalation path exists today. Run F9's parse through `AS_USER`, or
apply F10's path check first. N2: the header (:10) and the TOOLS.md
entry say root only reads, but root also runs `curl` (F2), `sudo -l`
(P1, P3), the jarvis probe (P9), `hostcheck.sh --check` and
`systemctl`. Doc-only. N3: F6's prefix match (:261) accepts a
`--claude` with `..` after `claude-<pin>/`. That claude runs only as
`mosaic`, and F10 uses equality. Darkwing has a follow-up on
`darkwing/row195-n1` 1e3c4767c, not yet run; it gets its own row now
that 195 has landed (b65014647), Duckworth reviews. (2026-10-21)
- **Morgana's row 213 mutation set: the B3 pattern no longer matches `bundle.mjs`.**
(Webby N3 on row 221 round 1, REVIEW-r1 a0a00630e.) Row 221's
candidate changes `packages/harness/src/bundle.mjs`, and the B3
anchor at Morgana's 213 `mutants.py:48` stops matching on that
base. This matters only if someone reruns that set. Fix the anchor
in that tooling before any rerun, and check that a stale anchor
refuses instead of skipping the mutant (sage-1941). (2026-10-21)
- **Grant executor test X5 starts a `sleep 30` that can outlive its test.**
(Webby N1 on row 248 r1, webby-0080; deferred by Duckworth in the row
249 review, REVIEW-r1 28174fe08, #1751 comment 30070.) The test at
`packages/bus/tests/grant-executor.test.mjs:989` (:934 before row
249) runs `sleep 30` on the sshd side, where an abort doesn't reach it. Fix it with a stub
that exits once its test dir is gone, as row 248 did for the hold
stubs, or with a shorter sleep. This binds the next row that touches
that test file. Until then, a lane cleanup that runs after this file
waits 30 s before it calls a lone `sleep 30` a leak. The row 197
lanes scan only at lane end, about 15 minutes after pkg-bus, so they
can't see it (mos-1169). (2026-10-21)
Closing note, row 280 (2026-10-22): both scratch sshd fixtures install a
bounded 30/60-second sleep stub that exits when its test directory is gone;
other sleep arguments exec /usr/bin/sleep unchanged. Evidence is recorded
in agents/rocko/work/queue-280/BUILD.md.
- **Row 66 live checks: the unslop notice on a live managed session, both harnesses.**
(mos-1186, Vikunja #454.) Row 66's gate asked for one landing session per
harness with a prompt that draws a known tell. Mos changed that gate. The
Claude Code session ran before landing on a scratch root from the
candidate tree (run r4ab24630d323, landed 2c38325ec). Pi ran launch only there (run r1dd94171bede):
this host has no credential pi reads until P19 #383 settles. Two live
checks remain. Claude Code: in the first re-pin whose tree includes row 66,
backup first (Q173). Pi: one full session with a tell after #383. Each
attaches the operator note on the RESULT, the next-turn correction and the
`unslop.jsonl` counts. (2026-10-21)
- **Console tester listener: `testerNoSignIn` is a dead parameter.**
(Duckworth N2 on the row 196 manifest, REVIEW-m1; carried as C1 in
REVIEW-m2 cdbf7b64e, #1694 comment 30156.) Row 196 landed 7247d80c7
with the parameter and its comment still at `serve.mjs:472-475` and
passed from `webui/src/cli.mjs:109` and `sso.test.mjs:392`. Nothing
in `serve.mjs` reads it after the destructure. Remove it, or wire it
with a test. This binds the next row that touches
`packages/webui/src/serve.mjs`. (2026-10-21)
- **Console library routes: the tester listener and `library.sync`.**
(Duckworth N5 on row 196 round 2, REVIEW-m2 cdbf7b64e.) At 7247d80c7
`startServer` (`serve.mjs:475`) takes none of `libraryFile`,
`libraryStatus` or `librarySync`, so the callbacks `cli.mjs` passes
do nothing yet. The row that adds the Console library routes decides
whether the tester listener gets `library.sync`. Duckworth's view is
that it shouldn't, because sync is a human write. (2026-10-21)
- **Console library routes: four library codes still map to 502.**
(Dewey N2 on the row 254 manifest, #1759 comment 30173.) Row 254
gave six library codes a STATUS entry in `packages/webui/src/bus.mjs`.
`library-mapping-invalid`, `library-remote-invalid`,
`library-state-invalid` and `library-git` still fall through
`busStatus` to 502. The row that adds the Console library routes
decides their statuses and gives its `humanCall` for `library.sync`
a timeout over 130 seconds. (2026-10-21)
- **T27 sweep test leaves two `sleep 60` children running.**
(Darkwing row 251 slot, darkwing-0113 and darkwing-0117; Dewey on
r2, comment 30168.) The sweep test spawns two detached shells,
`planted` at `host-run.test.mjs:528` and `decoy` at `:536`, each
running `sleep 60; :` with the run dir as `$0`. `sweepRunDir` kills
the planted shell by pid (`host-run.mjs:238`), and `t.after` kills
only the decoy shell. Neither kill reaches the `sleep` child, so each
one lives for up to 60 s after the test and trips any busy check that
follows. The fix is to kill the process group in both `t.after`
hooks: `process.kill(-pid, 'SIGKILL')`. Each shell is spawned
detached, so it leads its own group. `exec sleep 60` doesn't work.
The sweep matches the run dir in `/proc/<pid>/cmdline`
(`host-run.mjs:236`), and exec drops the `$0` that carries it, so the
planted process would survive and the "planted process killed" assert
would fail. This binds the next row that touches
`packages/bus/tests/host-run.test.mjs`. (2026-10-21)
- **Real-model suite cases run outside the gate.** Row 244 moved
`test-task.sh` fork recall (M11) and user recall (M16) behind
`MOSAIC_LIVE_PROVIDER=1`, and `test-release.sh` activations to the mock
adapter. Before any re-pin whose tree changes `packages/harness` or the
session fork code (`scripts/mosaic-task.mjs` session and
`sessionForkFrom` handling, the pi adapter), an operator runs
`MOSAIC_LIVE_PROVIDER=1 bash scripts/test-task.sh` with the real pi
credential and a real `release.sh activate`, and records both results on
the re-pin row. Morgana's wording (row 244 BUILD.md), Webby N2 on row 244
r1 (#1757). (2026-10-21)
- **Creative instances: the human can't delete one.** (Sage's ruling
sage-2164 on the row 194 b4 rebase, #1692.) Row 213 put the creative
definitions in END_PROTECTED, and row 194's `deleteRefusal`
(`instance-settings.mjs`) refuses END_PROTECTED for every actor, so
the human can't delete a companion instance and fire refuses one too.
The Agents page hides Delete on the listing's `endProtected`. The fix
threads the actor through `deleteRefusal` and the launcher's delete
pass, so the human may delete one and the tester may not
(`creativeHumanOnly`). Until then, the human can delist
(instances.set launch: false) but neither fire nor delete removes a
creative entry (sage-2166). The row covers fire and delete together;
Sage files it. (2026-10-21)
Closed for delete by row 256 (#1761, REQ-COS-3): `deleteRefusal` takes
the actor, so the human deletes a creative instance and any other actor
is refused `organization-creative-human-only`, and the listing's
endProtected is the same check for its reader. Fire still refuses every
END_PROTECTED instance, by the row 256 brief's ruling, not by a code
change. (2026-10-22)
- **A fire stuck between step A and step B has no human escape.** (Webby
N4 on row 194 b5, webby-0094; Sage's ruling sage-2241, #1692.) The
broker's chief fire records `fire.stopped` at step A, and `#firing`
refuses every launch and take of the instance until a `fire.done`. The
events are in the store, so a bus restart doesn't clear them. Row 194
b6 runs the writer's checks against the file before step A
(`instance.fireCheck`), so a keep the bus hasn't loaded no longer
strands an instance. A step B that keeps refusing still does: a
credential revoke that fails every time, or a file a human changed
between the check and the write. Each repeat fire runs step A again
and refuses at B. The fix is a human verb, never an agent one, that
records the end of the firing (a `fire.done`, or a new
`fire.abandoned` that `#firing` also counts) with a reason. Its test:
a fire whose step B refuses leaves the instance `instance-firing`, the
verb as the chief or the tester refuses, and the verb as the human
clears it so the instance launches again. (2026-10-21)
- **A fire's done with more than 64 stops refuses, and nothing flags a
run left alive.** (Webby N2 on row 194 b8 r2, webby-0100; Sage's ruling
sage-2290, #1692.) `lifecycleDone` caps `stops` at 64 and refuses
`invalid-request` above it (`broker.mjs`). The launcher's fireStop has
already signalled every run through stopRun by then, so the killed runs
are dead. But the refused done writes no lifecycle.end.killed or
lifecycle.end.alive event and no lifecycle.done, and the end request
stays taken. A run the stop left alive, or couldn't confirm (unknown),
gets no Console flag. The fire itself is unaffected, because
`fire.stopped` and `fire.done` are written at the verb. No MVP instance
comes near 64 runs. The follow-up row either raises the cap or splits
the done. Its test: a fire's done over the cap refuses, and the runs it
left alive still get the alive flag. (2026-10-22)
- **The runs suite doesn't gate a change to `scripts/mosaic-task.mjs`.**
(Dewey N7 on row 191, repeated on the Vikunja 502 review.)
`packages/runs/tests/task-cli.test.mjs` spawns `scripts/mosaic-task.mjs`,
but row 191's gate didn't run the runs suite. test-task.sh covers the same
list and show paths, so nothing is known broken. Binds the next row that
changes `scripts/mosaic-task.mjs`: its gate runs the runs suite whole.
(2026-10-22)
- **Row 277's M8 is killed by a rethrown error, not an assertion.**
(Duckworth N1 on the row 277 manifest round, duckworth-0127.) M8 makes
the thread.claims catch in `packages/cli/src/cli.mjs` rethrow. The stubbed
`processing-halted` error then escapes `main` and fails
`packages/cli/tests/tester.test.mjs:149`. That counts as a kill, but no
test pins that `main` resolves on that path. Binds the next row that
changes `packages/cli/tests/tester.test.mjs`: add the resolve pin so M8
dies on an assertion. (2026-10-22)
- **Row 274's user reply check is scoped to the thread, not the session.**
(Row 264a r6, sage-2591.) In `packages/bus/src/broker.mjs`, the check that
a run answers a user only in reply to that user's message in its thread
joins `message_threads` by thread. That holds while a user's thread has
only its default session: a user can't call `session.create`, and
`#thread` refuses anyone but the owner. Binds row 272 (264e, #1777), or
any row that lets a user add sessions: make the check session-scoped and
test it with two sessions. (2026-10-22)
- **Row 281's refusal texts point the wrong way.** (Dewey N1 and N2 on
the row 281 manifest round, dewey-0842/0843 and dewey-0846; Sage
sage-2653.) N1: when a host decision is still open and the host is
removed, step G's fire or delete job refuses `host-unknown`, and the
inbox hint names a verb that can't help. The way out is to decline or
withdraw the decision (`packages/bus/src/broker.mjs`, the closed check
near :1684 and `decision.withdraw` near :5015). T14 now pins that path.
N2: the chief's fire tool text in `packages/harness/src/tools.mjs`
(near :186) doesn't say the fire's job can come back for an answer.
Neither changes behaviour. Binds the next row that changes the step G
hint text in `broker.mjs` or the fire tool text in `tools.mjs`: fix
both wording gaps and pin the hint in a test. (2026-10-22)
- **Row 264a's refusal check in sessions.test doesn't pin the actor.**
(Duckworth N1 on the row 264a r12 delta, duckworth-0138; Sage sage-2695.)
`refusedOnce` in `packages/bus/tests/sessions.test.mjs` checks that each
refused call adds one `action.refused` event with code `not-holder`, but
not that the event's `actor_run` is the refused run. Binds row 269 (264b,
#1774), or the next row that changes `sessions.test.mjs`: pin `actor_run`
in `refusedOnce`. (2026-10-22)
Closed by row 269 (264b, #1774): `refusedOnce` selects `actor_run` and
expects the refused run's name (mos-1292). (2026-10-22)
- **editor-provenance skips its rebuild check on esbuild 0.28.1.** (Duckworth
N2 on the row 264a manifest round, duckworth-0146.) The webui test
"rebuilding from the source gives the same bytes" needs esbuild 0.28.2,
the checkout has 0.28.1, and the gate logs a skip (webui.log:99). The file
is at base and was not in 264a's manifest. Binds the next gate that
touches the webui build: install the pinned esbuild in the gate checkout
so the check runs, or record why it can't. (2026-10-22)
- **The case supervisor reports a fast child as an inner-session failure.**
(Row 253 N1, mos-1297 item 5.) A child that exits before the supervisor
verifies its session is reaped first, so the supervisor exits 85
(inner-session) instead of the child's own code. Mos measured rc85 in 39
of 40 fast-child bash runs. It fails closed, so it is not a 10-27 defect;
row 253 fixed only its own fixture (PYTHON_STUB execs python3). The
product fix is Vikunja 532, after 10-27. (2026-10-22)
- **boardSend /api/seen returns 400 for a scan failure after writeMark.**
(Webby N2 on row 198, ruled sage-2867.) The mark is written, then the
rescan fails, and the client gets 400 as if its request were bad. Webby
suggests 500. It does not hold the 198 gate; binds the next row that
touches serve.mjs. (2026-10-22)
- **Stale "row 203" text in the access command.** (Quackerjack, row 203
BUILD.md follow-up.) packages/cli/src/access.mjs lines 26-27, its help
string at line 36 and line 44, and packages/cli/README.md line 1107 say
peer homes stay unread "until row 203" adds the federation key. Row 203
changes only scripts/mosaic-config.mjs and test-config.sh, so once it
lands those lines point at a row that did not do the work. Binds the next
row that edits access.mjs. (2026-10-22)
- **A remote outage can make the first library lookup outlast the tool
timeout.** (Row 224 residual, sage-2819.) In each 60 s window the first
lookup still runs an inline fetch, and that fetch can take longer than the
60 s tool timeout. Bound the inline fetch in a later row. (2026-10-22)
- **A sharee's session.list stays refused.** (Row 206, sage-2818, Q7.)
Whichever of row 193's session wiring or the share wiring lands second
wires it. (2026-10-22)
## Queue
Moved to `docs/plans/QUEUE.md` on 2026-09-13. This file holds only gaps.
## Done
- Ledger (piece 3, #1506): shipped cd0aa5fb, Gate D written 2026-09-12 (18.9 human messages per closed issue).
- Relaunched seat shows the old last message (darkwing, Step 3 log, 2026-09-12): closed by #1512 relaunch activity, af4203ca. Filbert authored it; Darkwing and Dewey reviewed. The live board restart and Gate F remain separate.
- Sage launcher hook uncommitted (2026-09-12): closed by the agents/sage launch files commit, Dewey review R1 revise, R2 approve (`agents/dewey/work/sage-launch-review-2026-09-26.md`), launcher test now covers sage.
- Ledger counts T3 agent messages as human (2026-09-26, #1506): fixed in ef0020ad (Darkwing 6a, Filbert reviewed). `messageKind` now counts a T3 header as agent and the control-board sender as board. It changes no current count, because no source the ledger reads has a T3 header yet. That waits for the T3 thread source (Gate F).
- Control board checked the bind address but not Host or Origin (2026-09-26, #1507, found by Dewey): closed by the board guard commit. `foreignRequest` refuses a non-loopback Host, a wrong port, userinfo or a path in Host, and any foreign Origin on every route. Dewey authored it, and Rocko approved it (`agents/rocko/work/board-guard-review-2026-09-26.md`, 5a12f08e). Rocko's one low finding: URL normalization accepts a root slash, empty userinfo and an empty query in Host, so the notes' wording overstates what the raw-syntax check refuses. No bypass was found.
- Discord engine: leaked fake pi and the timeout gap in `busy` (2026-09-26, #1509): fixed in 3edb15eb (Darkwing 6b R2, Rocko approved, review ed5510a0). Tests stop the engine in `finally`. A turn pi never started now stops pi with exit 1 instead of guessing, and the unit restarts it. Known limit: see the open entry on wedge restarts.
- Combined concurrent test hangs (2026-09-14, #1509): closed on evidence from 3edb15eb. At default concurrency the union passed 406/406 three times on the R2 snapshot and 408/408 on the commit's index, where clean HEAD had hung four runs out of four. The inherited-intermittency entry from 2026-09-15 stays open until a later run shows it is the same cause.
- A nested `node --test` passes even when its tests fail (2026-09-26, #1508): closed. Queue A1 (34a72af9) clears NODE_TEST_CONTEXT on its own path. N13 fixes `test-foundation.sh` and `test-discord.sh`, each with a check that fails the suite if the clearing is lost. Darkwing wrote it (n13.patch 00868b2f) and Filbert approved it (e464be6c). Sage's grep on 2026-09-27 finds no other nested `node --test`: in scripts, only the two suites, `test-queue.sh` and `queue-commit.sh`, and in JS, only the queue test that covers the fix.
- SetSpark service accepted any string as a decision approver (2026-09-26, #1509): closed. Fixed in shared-signals cc74d92 (Rocko approved R2, lead decision 24). The operations seat (T3 12fe8cda) deployed it to VM 1022 at 00:23Z on 2026-09-27. The running `service.py` hash matches cc74d92, and `/healthz` returns 200 inside the network and through the public tunnel. The read-only README survey found one row breaking the rule (DEC-009, Proposed, version 1), one open request that isn't ready (DEC-009's), and no Accepted decision with more than 16 approvers. No rows changed. DEC-009 stays as stored (lead decisions 21 and 28). The service now refuses approvals on it, so nothing can go wrong silently. Side effect: `up -d` also recreated caddy, about 20 s of edge downtime for all hosts. That is for the shared-signals deploy procedure to fix, not Mosaic.
- Queue suite failed its live checks outside the canonical checkout
(2026-09-27, #1508): closed by Piece D, f539466f. `test-queue.sh` now
skips `verify` and `render --check` with a printed reason when the
toplevel isn't the recorded `canonicalRoot`. An index export passed
27/0, and the canonical checkout 29/0.
- Queue: an owner listed as a reviewer blocked the row (2026-09-27,
#1508): closed by row 31, bb2fa970. `add`, `set reviewers` and `assign`
now refuse the owner as a reviewer with exit 2 and write nothing. Rocko
built it in the Gate G run, and Filbert approved round 1 (#1508 comment
26629, 8 of 8 mutants caught).
- Queue follow-ups from rows 12, 13 and 31 (2026-10-04, #1508): closed by
row 33, 6fb50cc0. Filbert built it, and Darkwing approved round 1
(#1508 comment 26651).
- `genesis` refuses a map row whose owner is one of its reviewers (exit
2, nothing written). The check is on the map, not in replay, so a log
from before the rule still loads.
- The `assign` refusal now reads "can't assign row N to SEAT: SEAT is
one of its reviewers".
- `queue-commit.sh` compares HEAD with H before each canary and again if
the clean run fails. A moved HEAD exits 2 with "HEAD moved since H was
recorded"; the guard message is kept for a real guard failure.
- Every time and date the validator accepts must format back to itself,
so month 13, day 32, 2026-02-30, 2027-02-29 and 24:00 are refused.
The live log at rev 45 still replays.
- The cold-start failure (2026-09-27): not reproduced in 20 cold runs.
Each ran `node --test packages/queue/tests/` in a fresh tree, 10
shared clones and 10 exports, one after another. All 20 passed
148/148 (2960 of 2960). The 300 ms deadline test is unchanged. Counts:
`agents/filbert/work/queue-33/cold-runs.txt`.
- CHAT-03 I1 round 2 follow-ups (2026-10-04, #1507): four of five closed
by slice 1 S5 (row 40, #1522; Dewey authored, Darkwing and Filbert
reviewing). The commit is named in the row's review record when it
lands.
- Engine command and `preArgs`: `engine` takes only `extraArgs`, `cwd`
and `envKeys`. The controller always launches the pinned Pi and
checks that at construction and at bind. The fake engine comes in
through a symbol key that JSON config can't carry (N24b).
- Engine environment: an explicit list (`ENGINE_ENV` in `pi-pin.mjs`)
plus `*_API_KEY` and `*_TOKEN` names from `engine.envKeys`. Nothing
else is inherited. `HOME` passes, so Pi reads `~/.pi/agent` unless
`PI_CODING_AGENT_DIR` is set (Darkwing F3).
- `escalating` is cleared when the force-stop fence throws (Darkwing F2,
races.test.mjs).
- Takeover then Enter in one chunk: input after Ctrl-T or Ctrl-O waits
for that action, in the same chunk or a later one, and also while an
earlier action is still pending, so it is judged as if typed one key
at a time (Filbert F2; Darkwing and Filbert T1 on #1522;
flows.test.mjs).
The frozen-cgroup test (Filbert F1) stays open above.
- Gate runs mount the host's pi auth file (Webby N4 on the row 197 proof
driver, 059577886): closed by row 244 (#1757), landing e69260a3c. The
suites and the lane's `cleanEnv` export a 0600 fixture `PI_AUTH_FILE`,
`guardDocker` refuses a Compose run without one, and
`packages/cli/tests/gate-lane-auth.test.mjs` proves no gate step mounts
the host default. The real-model cases this moved off the gate are open
above.
- A fired or deleted instance kept its grants (2026-10-22, Dewey on the
row 236 rebase look, mos-1271): closed by row 281 (#1786), landing
e479521d3. A fire or a delete now runs a step G job that revokes every
host and Gitea grant the instance holds, fail closed, and tells the
human in the inbox when it can't. Megavolt built it; Dewey approved the
design (d6) and the manifest (c30614). The text gaps it left are open
above.
## Row253 F5: legacy Case AS_USER supervision
Tracked as row 276, Gitea #1781 (sage-2331), owner Gander, reviewer Duckworth, after row253.
Brief: docs/plans/2026-10-22_case-sudo-legacy-followup.md, “Brief: supervised legacy AS_USER calls”.
case1-accept.sh's legacy timeout330 around AS_USER talk and its AS_USER precheck, Claude version, PM launches and Node version diagnostics predate the new socket metadata/probe helpers. sudo's pty monitor can move them to a separate session; the old outer timeout does not prove their descendants are killed. The C4 repair in row253 covers only its new config query and peer probe. These legacy paths need the reviewed supervisor or a header explicitly explaining why each remains unsupervised. The follow-up must preserve the 300-second talk/reply contract, concurrent F5 scan, diagnostics and secret-output refusals, with fake-only session-hop tests and Sage's slot. No host or live unit change is authorized.
Row 276 / #1781 also owns the case1 root-side capture/tools mutant counterparts (duckworth-0112 N1) and documenting the setuid-member cleanup refusal in the headers (N2). A member that changes uid and cannot be killed causes inner-cleanup refusal, never successful observation. N4 is addressed in row253 by allowing SID confirmation until the existing inner watchdog expires, instead of a separate 200 ms scheduling margin.
## Row276 candidate closure of Row253 F5
Effective when row276 / #1781 lands after Duckworth review and Sage's gate: all five legacy Case1 calls (F3 talk, F4 precheck, F6 Claude version, F7 PM launches, F10 Node version) use row253's byte-identical after-uid supervisor and their own independent outer bound. No named AS_USER exception remains. Talk retains --wait300 and concurrent F5 observation; stdout and stderr remain separate, with root retaining only stderr's last65536bytes. A trailer can choose only a failure display number, never success. Supervisor0 alone permits pass.
Row253 N1 closure: row276's M10 checks case1's root-owned/non-writable tool fence through both capture and legacy paths; M11 checks case1's root-side capture byte limit. Each must be killed in the accepted gate. Row253 N2 closure: both case-script headers explicitly state that a uid-changing member which cannot be killed causes inner-cleanup refusal, never successful observation.
The hostile same-uid setsid-away or killed/stalled-supervisor residual remains as row253 R3. This closes none of that boundary. Instant command exit before SID confirmation remains the fail-closed residual tracked as Vikunja532 (mos-1297); row276 does not change the pinned payload.
## Row276 mos-1337 A5: Case1 cmdline redirection ordering
Open follow-up: scripts/case1-accept.sh:413 (at candidate3c4c463da) uses `tr < "$ROOT/proc/$c/cmdline" 2>/dev/null` outside the supervisor. A vanished entry can print an open diagnostic before stderr is redirected. mos-1337 explicitly excludes this path from row276; it remains unchanged. The row276 supervisor repair closes only row253 N5 inside stat_fields.
Row276 mos-1345 R3 residual remains open: a same-uid process that escapes the session, or a stalled supervisor in that session, can retain descriptors to the private stdout/stderr spool after root unlinks it and keep growing the unlinked file. The outer bound releases the caller because no residual holds its capture pipe. Row276 adds no disk cap and no cross-session kill; this is not a cleanup or closure claim.
## Row207 N1: "refuses at any business" has no test
Open follow-up (duckworth-0230 N1, sage-3043). Row 207 (#1705) lands on 9bb406c6c without a test that `mosaic access user remove` refuses while the user holds a grant at any business, not only the named one. A mutant adding `&& g.business === business` survives T4: checkAccess still refuses at exit 3, so nothing is written, but the reason and exit code are wrong. The next row that touches packages/cli/tests/access.test.mjs adds one T4 case with the grant at a second business and a mutant that the case kills. N2 (parse ignores stray flags) is pre-existing in grant and revoke and stays out of scope.
## Row270 N6: ledger growth and the whole-file read on session.list
Open follow-up (duckworth-0228 N6), bound to row 272 (264e, #1777). Row 270 caps what the bus reads per compaction collection (compactionReadBytes, default 64 MiB) but an agent can still grow a session's compaction ledger by about 231 KB per collection, and session.list reads the whole file. Row 272 bounds the ledger or the read on session.list, with a test and a mutant, or records why the cap is enough.
## Row219 N1: the thread list and header don't show the persona label (U6)
Open follow-up (dewey-0913 N1). Row 219 (#1720, C2-09a) lands on 5fdfd7275 with UI items U1-U5. The Create picker labels each instance with its persona name and instance key. U6 is deferred: the thread list and the thread header still show only the instance key (agents/rocko/work/queue-219/BUILD.md:81). The next row that touches the Console thread list (packages/webui/src/public/chat.js) shows the persona label there and in the header, with a chat-browser test and a mutant that the test kills.
## Row295 N1: a revoked run's late bus call exits 22, not stopped
Open follow-up (morgana-0167, sage-3318, sage-3360), to become a row. Row 295 (#1801) lands on 2efd127ce. Revocation ends a user's thread claim in one transaction, and the launcher then sends SIGTERM. A run that calls the bus between the claim end and the SIGTERM gets not-holder and exits 22, so its record reads error rather than stopped. Row 302's launcher-released answer covers only threadIdle and landRelease. The row gives a revoked claim the same shape: calls that need the claim answer thread-revoked until the run ends, and the runner maps that answer to stopped. It needs a forcing test that holds the window open and a mutant the test kills. Brief: docs/plans/2026-10-23_gate-env-rows.md; owner Morgana, reviewer Dewey, after row 272.
## Row272 N1: users can't reach session verbs
Open follow-up (Huey's BUILD.md, sage-3357), to become a row. Row 272 (#1777) lands the session CLI and the Console chooser for owners. USER_VERBS still leaves out the session verbs, and no transport carries a user's session operation. A granted user's Console therefore falls back to the thread default when session.list answers access-refused on a route with no session named. A session link with an explicit session still refuses, and the session controls stay hidden. The row that adds user session grants (193/T5) adds the verbs to USER_VERBS under a grant check, and adds a test that a granted user lists, opens and clears only sessions on threads the grant covers. It removes the fallback once the grant exists.
## Row270 N6 rebound: the ledger and the session.list read are still unbounded after 272
Row 272 (#1777) lands without bounding the compaction ledger or the whole-file read on session.list, and it records no reason the 64 MiB collection cap is enough. N6 above stays open. It moves to the row after 307 (#1814, Vikunja 598), because 307 reshapes the compaction reading in packages/seat/src/compactions.mjs and both rows would edit readLedger. That row bounds the ledger or the read, with a test and a mutant, or records why the cap is enough.
## Row307 N1: row 270's store-unreadable doesn't stick
Duckworth, row 307 build round 1 (duckworth-0285, note d43690d0b). When a collection finds an unreadable
session store header, it writes store-unreadable. The cursor then moves past the bad header, so the next
collection never checks it again and the count reads as known with 0. The row 307 ledger test pins this
behavior as it stands, so the move didn't change it. Bound to a follow-up row on row 270's
store-unreadable path. The fix keeps the unreadable state until the header reads clean, or re-checks the
header on every collection. Either way it's an old-test change and goes to Mos.
## Row306 N1: the revoke Set doesn't survive a broker restart
Morgana, row 306 BUILD.md (#1813, landed 2d0318a44; Dewey agrees, dewey-0918). `#accessRevoked` lives in
memory. After a broker restart, recover() rebinds an earlier host's run, and if that run makes a bus call
before the launcher's SIGTERM, it answers `not-holder` and the run exits 22, as every revoked run did before
306. Seeding the Set at a rebind needs a mark only `#revokeLost` writes; `reason` and `by` on the ended
claim aren't one (design D1's alternative). Bound to the next row that changes the broker's recover() path.
## Row306 N2: a pending limit stop answers before the revoke
Morgana, row 306 BUILD.md (#1813, landed 2d0318a44). A revoked run that also has a pending limit stop gets
`#limitStop`'s `not-holder` on `message.receive` and `state`, because that check runs before `#agent`. It's
rare and the run still stops. Bound to the next row that changes `#limitStop` or the order of those checks.
## Row226 N1: the install id regex is narrower than ID_PATTERN
Webby, row 226 round 1 (webby-0225, #1727, landed 661890e90). case2-install.sh:56 refuses a business id
with `.` or `_`, which ID_PATTERN (vocabulary.mjs:199) and case2-precheck.sh:67 accept. Gosalyn confirmed
it isn't deliberate (gosalyn-0208). It fails closed, so a real id with either character is refused, not
mishandled. Bound to the next row that changes case2-install.sh: take the ID_PATTERN set, and add a test.
## Row226 N2: a first-run validate refusal leaves personas and the slot dir
Webby, row 226 round 1 (webby-0225). After a validate refusal on a first run, the staged personas and the
empty token slot dir stay behind, and a re-run counts them as present. Bound to the same follow-up as N1:
remove what this run created, as the business file path already does (install.sh:198/:202/:218).
## Row226 N3: the run sheet doesn't pin the clone to the copy's sha
Webby, row 226 round 1 (webby-0225). install.sh:217 validates with the checkout's scripts/mosaic, not the
copy's, and host-run-sheet.md:237 never says the clone must be at `<sha>`. Bound to the same follow-up:
a HEAD check in the install or one clause on the run sheet.
## Row226 N4: --library-status refuses after a 5 s lock wait
Webby, row 226 round 1 (webby-0225). If the bus holds the library lock for more than 5 s,
`case2-precheck.sh --library-status` refuses with `ERROR library-sync-busy`. A re-run clears it. Recorded
as a known operator step, no fix bound.
## Row283 N1: SURVEY-RUN.md shows the scopes line without supporting
Webby, row 283 round 1 (webby-0267, #1788, review note queue-283/REVIEW-manifest-b2a1f4092.md on
webby/reviews c6ea27aab). docs/case2/SURVEY-RUN.md:96 documents the `--mapping` line as
`scopes canon .., playground .., unmapped ..; indexed ..`, which predates the supporting scope 283 adds.
Nothing reads the file (decision 137). Bound to the next docs commit that touches SURVEY-RUN.md: add the
supporting count to that line.
## ops-0275 O2 and O3: triage, no row
Sage, for Mos's mos-1549 (433 factory on 1013, ops-0275). Neither is a defect, so neither gets a row.
O2, `bus status` shows writer.lock pid 4174 while the unit's host pid is 4078. By design. `scripts/mosaic`
execs node (scripts/mosaic:44), so the unit's MainPID is the bus host. The host forks the broker
(packages/cli/src/host.mjs:1, :215), and the broker's Store writes its own `process.pid` into
`<dataRoot>/bus/writer.lock` (packages/bus/src/store.mjs). The lock holder is the host's child. The
status line's liveness check (packages/cli/src/cli.mjs:612) reported it running, so it isn't stale.
If an operator note is wanted, the next row that edits `bus status` output can name the broker pid as the
host's child; nothing binds it now.
O3, the copy's npm step warns that @google/genai, esbuild and protobufjs have install scripts outside
allowScripts. scripts/stack-copy.sh:74 runs `npm ci --no-audit --no-fund`. On npm 12.0.2 (dragon-lin)
dependency install scripts are blocked by default: npm skips any dependency script with no allowScripts
entry and lists the skipped packages at the end (`npm help install-scripts`). So the copy skips all three.
The 433 factory passed on that copy, and the landing gates (webui included) pass with the same skip.
Recommendation: refuse, not allow. An explicit `false` entry for each in package.json allowScripts makes
the tested behavior the written policy and drops the warning (`npm help config`, strict-allow-scripts:
explicit denials are always skipped silently). That edits package.json, so it waits for the next reviewed
row that touches package.json; bound there.
## ops-0282 B2-2: the run sheet doesn't state the Console proof file format
Sage, for Mos's mos-1557 (433 Run B P1 at 3569bf54f). docs/guides/host-run-sheet.md:321, the .54 library
table, gives the Console proof file's mode (0600 or 0400) and owner but not its format. The Console reads
one base64url line of at least 32 characters, `[A-Za-z0-9_-]` only (packages/webui/src/serve.mjs:264), and
exits 2 at start otherwise (:267). case2-unit.sh doesn't open the file, so a short or malformed proof fails
only at Console start. That's what stopped Run B P1 (B2-1, ops-01's 24-byte fixture). Fix: add the format to
the File cell of that row. Doc only, no test reads it; bound to the next doc row that edits the run sheet.
## Row 323: revoke-window hides a socket-path refusal as a 20-second "no RESULT"
Sage, for Mos's mos-1560/mos-1561. packages/cli/tests/revoke-window.test.mjs failed on every commit in
Goldie's environment (base dd6cb5872 included) and passed in Sage's. The cause is TMPDIR length, not Codex.
The test's root is `$TMPDIR/mosaic-revoke-window-XXXXXX`, and a run's `tools.sock` sits at
`<root>/data/launches/acme/r<12 hex>/tools.sock`. newRun refuses past 107 bytes
(packages/seat/src/session.mjs:278). Measured lengths: `.../tmp/sageXXXXXX` (35 bytes) gives 107 and passes,
`.../tmp/goldieXXXXXX` (37 bytes) gives 109 and fails. Sage reproduced it with a 37-byte TMPDIR: 0/1 after
20235 ms, the same "no RESULT". The test passes `log: () => {}` to the launcher, so the refusal never shows.
The guard is right. The test is the weak part: its prefix leaves no margin, and a launch refusal reads as a
timeout. Fix: shorten the prefix and fail on a launch-failed log line instead of waiting 20 s. That's an
old-test change, so it needs Mos's go (mos-1272). Bound to the next row that edits revoke-window.test.mjs.
Until then, docs/plans/BRIEF-TEMPLATE.md's Gate section sets TMPDIR to `mktemp -d /mnt/storage/scratch/tmp/XXXXXX`.
## ops-0282 B2-2 closed by row 326
Sage. Row 326 (#1822) landed in f58049de0 (33f49f7d5). docs/guides/host-run-sheet.md now gives the Console
proof file format in the .54 library table, so the B2-2 entry above is closed. Row226 N1-N4 stay open.
## Row 208 N1: user.js re-renders every 5 seconds
Sage, for Duckworth's N1 on row 208 (c31496, landed f58049de0). packages/webui/src/public/user.js:16
re-renders the user view on a 5 s timer while the tab is visible, whether or not anything changed. That's a
steady client and server load per open tab, not a correctness or guard issue. Fix: render on change, or poll the bus only for a change
marker. Bound to the next row that edits user.js.
## Row 329 N2: `mosaic proxy fetch` has no agent or human guard
Sage, for Dewey's round 1 N2 on row 329 (c31639, landing deferral per c31643). `mosaic proxy fetch` downloads
the pinned CLIProxyAPI archive from GitHub and writes `<dataRoot>/opt/cliproxyapi/` for any caller that has
the instance config. P1's brief didn't ask for a guard. The fetch is pinned and sha256-checked against
`packages/model-proxy/PIN`, so the risk is an unasked network fetch and a disk write, not a wrong binary.
Bound to row 331 (P2): P2 decides whether fetch needs the human or agent guard its key verbs get, and
records the choice in its BUILD.md. If P2 leaves it, P7 decides before cutover.
## Row 329 R2-N1: the management-key rule lives in three places
Sage, for Dewey's round 2 R2-N1 on row 329 (c31643). Round 2 tightened the key rule to
`/^[\x21-\x7e]{1,4096}$/` in `packages/model-proxy/src/common.mjs`:33, which `initializeProxy` enforces, but
`packages/bus/src/broker.mjs`:4827 and `packages/cli/src/model-proxy.mjs`:57 still use the round 1 predicate.
A Latin-1 or edge-space key gets past both and is refused at init with `proxy-key-invalid`, before any write,
but only after both prompts and a bus round trip. Fix: export one predicate from the model-proxy package
and use it in all three places, with a test at each caller. Bound to row 331 (P2), which edits the broker's
proxy verbs.
## Row 329 N1: the 127.0.0.1 bind isn't observed on the real binary
Sage, for Dewey's round 1 N1 on row 329 (c31639). The acceptance netns had only `lo`, so P1 writes
`server.host: 127.0.0.1` and `management.allow-remote: false`
but never saw the real binary refuse a non-loopback client.
Bound to P7: at cutover, with Jason present, confirm the listener is on 127.0.0.1 only (`ss -ltnp`), and
record it in P7's receipt.
## Row 284 N1: library.get lists the whole served tree per read
Sage, for Webby's round 1 N1 on row 284 (c31654 carries it forward). Each library get runs `listTree`
(`git ls-tree -r -l`) over the whole served tree to find one blob oid. On a large library that is one full
tree listing per read. `git rev-parse <commit>:<path>`, or a per-commit cache, would avoid it. Not a
correctness issue. Bound to the next row that edits the library read path; that row measures a get on a
library of realistic size and records the choice in its BUILD.md.
## Row 284 N1: Vikunja task
Sage, for mos-1647 item 3 (Q109). The row 284 N1 entry above is tracked as Vikunja 634. No open task
covered the library read path.
## Row 332 finding: CLIProxyAPI startup version refreshes reach outside the host
Sage, for Fethry's row 332 isolated runs (2026-10-24, pinned 8.0.42, `unshare -rn` with only `lo`). At
startup the binary tried `https://registry.npmjs.org/@xai-official/grok/latest` (xai_version.go:96, blocked
by the namespace) and then started a 3-hour Grok CLI version refresh and a 3-hour antigravity version
refresh. The proxy kept serving with its fallback version. Evidence: origin/fethry/row332-p1
`agents/fethry/work/queue-332/BUILD.md`:251-252 and `evidence/offline.txt`. Bound to P7: before or at
cutover, find a config switch that turns these off and set it in P1's initial config with a test, or
decide on purpose to allow or block that egress and record it in P7's receipt. Tracked as Vikunja 633
(umbrella 237).
## Row 331: human-only per-agent model rotation override — P7
Sage ruling sage-4388 (2026-10-24) limits P2 to the 30-day model-key rotation default,
with no setter and no `proxy.rotate --agent --rotate-by` verb. A human-only
per-agent rotation override is bound to P7, with Jason's cutover/settings review.
It must preserve D20/Q222: no agent or tester can change the period. Row 331 ships
only the default and the approved proxy.json grace/first-output settings.