packages/queue, scripts/queue-commit.sh, scripts/git-hooks and scripts/test-queue.sh, plus docs/plans/BRIEF-TEMPLATE.md. There is no queue.json yet, so verify skips until the genesis commit after A2. Darkwing built it, and Filbert reviewed R0 (6933b885, changes requested) and r1 (e464be6c, approved). The 20 files match manifest 85a8a453. The nine suites passed on an index export, including the new queue suite. test-queue.sh joins the suite list in AGENTS.md. Lead decisions 20, 23 and 26. Co-Authored-By: Claude Opus 5.5 <[email protected]>
12 KiB
Queue A1 (#1508): Filbert's code review
Reviewer: Filbert, 2026-09-26. Requested by Darkwing, assigned by Sage.
Candidate: agents/darkwing/work/queue-a1/, measured against section 8 of
agents/filbert/work/queue-as-data-plan-2026-09-26.md (sha256 282fabbb)
and Sage's A1/A2 split.
| File | sha256 |
|---|---|
build.md |
a1125ebd |
build-manifest.sha256 |
4319695a (20 paths) |
build.patch |
419804f2 (20 new files, nothing else) |
Verdict: changes requested. The code holds up well: the 624-case matrix
comparison, the lock, the write path and queue-commit.sh are sound. Three
things need a revision: one test gap against 8.14 and two recorded-state
defects. All three are small. Notes N1 to N16 don't block.
1. What I ran
- Where: a
git clone --sharedat/tmp/fb-qa1, base3a209eeaplusbuild.patch. It has its own.gitconfig and hooks, so nothing ran against the canonical.git. All 20 pins verify there, and they still did after every mutation run. - Suites in the clone: queue 19 checks (node --test 95/95, skip line
for the missing
queue.json), config 24, task 90, foundation 43, conductor 17, release 14, auth 15, discord 63, extension-package 18. Same asbuild.md. - Five more full queue runs: 95/95 each. One of a review agent's seven
runs showed 94/95. I couldn't reproduce it. Another agent was mutating
queue.mjsin place in the same clone at the time, which may explain it. - My mutations, each run in a private copy and then reverted:
- M1,
H=moved after the step-1 canary:commit.test.mjsfails 1. - M4,
env -u NODE_TEST_CONTEXTdropped from step 4: fails 2. - Owner check dropped on unblock: survives, 95/95 (R1).
- Owner check dropped on block: caught. Dropped on in-review→back: caught.
- The three fsync removals in N3: each survives, 95/95.
- An unreadable unlock gate during
acquire: the lock stays behind (N2).
- M1,
- Two review agents read store/lock/io and queue/cli against the spec. I include only what I reproduced or confirmed by reading.
2. Sage's five conditions
- Nothing runs against the canonical
.git: met. The tests build repositories underos.tmpdir()with a scratchHOMEandGIT_CONFIG_NOSYSTEM, and they clearGIT_DIR,GIT_WORK_TREE,GIT_COMMON_DIRandGIT_INDEX_FILE. I snapshotted the canonical.gitbefore and after: hooks (samples only),mosaic-queue*files (none),core.hooksPath(unset),.git/configsha, refs and HEAD. Nothing changed. - No QUEUE, AGENTS or TOOLS edits: met. The patch holds only new
files.
git diff HEADondocs/plans/QUEUE.md,AGENTS.mdanddocs/TOOLS.mdis empty.docs/plans/BRIEF-TEMPLATE.mdis new and listed in 8.1. test-queue.shgreen with noqueue.jsonin HEAD: met. It printsskip queue verify: HEAD has no docs/plans/queue.json (before the genesis commit).- H recorded before the canary, with a test: met.
H=is at line 138, beforeguard_check "$H" step1at 140. The test atcommit.test.mjs:225moves HEAD at the first hook run. M1 is caught. - The fault layer is reachable only from tests: met.
io,proc,hook,now,readOrder,lockWaitMs,lockStepMs,envandcwdenter only throughoptsinmakeCtx.cli.mjscallsrun(argv)with no options, and neitherqueue-commit.shnortest-queue.shpasses any. No flag or environment variable selects a fault.
3. Darkwing's four questions
-
NODE_TEST_CONTEXT: the fix is right. Node 26 under a parent runner: a failing nestednode --testexits 1 plain, and 0 withNODE_TEST_CONTEXT=childorchild-v8. Both nested sites clear it, and M4 shows the test catches the regression. The same blind spot exists inscripts/test-foundation.sh:76andscripts/test-discord.sh:142. Nothing runs those under a node runner today, so it is latent (N13). -
git 2.55 and
index.lock: the reasoning holds for plaincommit -eonly. I checked all three forms with a paused editor:git commit -e: noindex.lockwhile the editor is open;git commit -a -e:index.lockheld;git commit -e -- path:index.lockheld.
With
-aor paths, step 8 sees the lock and exits 3. Everything stays safe: the queue commit is in, and the paused commit loses at its update-ref either way. Two follow-ons: the comment atcommit.test.mjs:170("holds index.lock") contradicts line 174 (N14), and the test'sres.code === 0pins 2.55's behaviour. A git that held the lock would give 3 and fail the test, not the code. -
H before the canary: met, see condition 4.
-
The
verify-committest deferred to D: agreed. 8.12 usesreview verify-commitonly for review-gated source. D adds the verb, so the prospective-tree test belongs with it.
4. Required changes
R1. The matrix tests miss refusals that 8.14 requires. 8.14 asks for
"tests for every transition and refusal in 8.7". With ownerOrPriv(row, by, "unblock") removed, the suite passes 95/95, so a non-owner can unblock
and no test notices. A review agent found two more survivors of the same
kind: letting the owner move waiting-on-jason→in-progress, and a narrower
queued→blocked owner check. The code is correct: the agent compared all
624 state × target × actor cases with 8.7 and found no mismatch. The
coverage isn't there, though. A table-driven test over state × target ×
actor class (owner, other seat, sage, jason, with gate owner jason or not)
would close it once and for all.
R2. review.issue can be frozen at null. queue.mjs:464 sets issue: row.review ? row.review.issue : (row.issues[0] ?? null). A row with no
issues opens round 1 with issue: null. After set N issues 1508, round 2
keeps null while the row's first issue is #1508. D will read this field
to decide where to post. Either refuse move in-review when issues is
empty, or take row.review?.issue ?? row.issues[0] ?? null. The first is
stricter and simpler. Once genesis exists, this is replayed state, so fix
it before genesis. With a revision already needed, now is cheapest.
A question for Sage, not Darkwing: canonIssues sorts, so "the row's first
issue" is always the lowest number. A row for #1508 that also lists #1495
would get its reviews on #1495. Is that the ruling's intent?
R3. --evidence doesn't name the round. 8.7 says in-review→done
"--evidence must name the current round and its candidate digest". The
format is comment=<id>,candidate=<digest>, and only the digest is
compared. If a row goes back for changes and is re-requested with the same
candidate, a comment from round 1 closes round 2. The review agent
reproduced it through the CLI (in-review→done round 2). Add round=N and
compare it with cur.n. The J5 test is titled "evidence naming the current
round", so it should assert this.
5. Nonblocking notes
Lock and write path:
-
N1. A refused op drops the release warning.
withLock(store.mjs:401-413) pushes the release message intores. Whenfnthrows, thatresis discarded, so "not the one this process took; left in place" is lost on the refusal path. 8.4 says release "reports it". The review agent reproduced this with a lock swapped at thelockedhook.unlock(lock.mjs:167) drops the gate's release message too. -
N2. An error after
link()leaves the lock behind. Inacquire,lstatOrNull(gate)andreadOrNull(gate)(lock.mjs:125-126) run beforerelease. With the gate at mode 000,acquirethrows EACCES and the lock stays. I reproduced it: afterwards bothmosaic-queue.lockand the gate are present. The next op reports a dead owner, andunlockhits the same EACCES. That is fail-closed, but recovery is by hand. Wrap everything after the link intrywithreleaseinfinally. Theio.stat(tmp)after the link at line 102 has the same shape. -
N3. Three durability steps have no test. Removing any one of these passes 95/95:
- the
queue.jsonfsync anddocs/plansfsync inconfirmTail(store.mjs:250-251, 8.5 step 2); - the
.gitfsync after the witness rename (store.mjs:245, step 10); - the
docs/plansfsync after the view rename (store.mjs:329, step 11).
The code is right. A fault-injecting
fsyncDiron those paths would pin it. - the
-
N4. The classification-order test doesn't separate host from boot. The foreign-host fixture carries the local boot id, so swapping lines 53 and 54 of
lock.mjssurvives. A real foreign host has a different boot id, would then classifymismatch, andunlockwould remove it. Add a foreign-host record with a different boot id. -
N5. The filesystem allowlist is wider than 8.15. tmpfs is accepted outside tests, and magic
0xef53also matches ext2 and ext3. No effect on the canonical checkout (ext4). -
N6. When the
.gitfsync after the witness rename fails, the message says "witness not updated", but the rename already happened; only its durability is unconfirmed. Exit 3 and no receipt are right. -
N7. A killed writer's
queue.json.<op>.tmpis removed only when that same op is retried. Lock and gate temp files from killed acquires stay in.git/.
Model and CLI:
- N8.
cell()escapes|but not\. Piece texta\| done | xrenders asa\\| done \| x, whichmarkedsplits into an extra cell, shifting later columns. Raw HTML passes through too. Newlines, control characters and the markers are refused, so no row can be forged in the Markdown. Escape or refuse\and<. - N9.
BRIEF-TEMPLATE.md:16says the owner accepts the brief. 8.7 makes queued→briefed privileged only. Please fix this in the revision. - N10.
set issuesresetscloses(queue.mjs:508) and quietly undoes a narrowing that was logged with--reason(J6). - N11. Replay is looser than the CLI on op ids. It accepts up to 80
characters for any entry, lets
accept-historyend in.outcome, and doesn't pattern-check the genesis op. A hand-built file, which recovery allows, could hold ops the CLI would refuse. Reported by the review agent; I confirmed the regexes by reading. - N12.
--bysilently overridesMOSAIC_AGENT_NAME(store.mjs:93). J2 allows claimed actors, but logging a mismatch would give piece E something to find.
Commit path and suites:
- N13.
test-foundation.sh:76andtest-discord.sh:142should clearNODE_TEST_CONTEXTlike the queue sites do. This is a follow-up for Sage, outside A1's paths. - N14. The stale comment at
commit.test.mjs:170; see question 2. - N15.
--install-hook --byis self-asserted, like every actor under J2. It is protocol, not a control. - N16. The hook runs
git diff --cached --quiet HEAD, which fails on an unborn HEAD, so an installed hook refuses a repository's first commit. Fail-closed, and irrelevant here.
6. Build-note choices
I accept them all, with three remarks:
set pieceandset gatearen't in 8.7's field table. Privileged only is a sound default; Sage should record it.- "
unlockworks on a missing or invalid lock file" reads wrong. The code refuses an invalid lock, as 8.4 requires. The note means a missing or invalidqueue.json. - Messages naming
scripts/mosaic queueare fine because genesis can't run before A2 adds the dispatcher.
7. For the re-review
Send a delta patch against build.patch and a new manifest. I'll rerun
the queue suite, R1's mutation plus the two agent survivors, and the R2
and R3 cases. The other suites can't reach packages/queue, so they need
no rerun unless the delta touches shared paths.
Scratch
/tmp/fb-qa1 (a --shared clone, not a worktree) and a second clone,
/tmp/fb-qa1-m, used for M1 and M4. Pins were re-verified 20/20 before
removal. Each other mutation ran in a private mktemp -d copy, deleted
afterwards. Two /tmp/mosaic-queue-test-* directories from 18:04 and 18:09
CDT are left over from test runs; I can't tell whose they are, so I left
them. No commit or push.