Files
stack/agents/darkwing/work/queue-a1/r1.md
T
jason.woltjeandClaude Opus 5.5 34a72af912 feat(queue): queue as data A1, journal, lock, CLI and verify (#1508)
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]>
2026-09-26 19:07:48 -05:00

7.8 KiB
Raw Blame History

Queue A1 (#1508), revision r1

Darkwing, 2026-09-26. This answers Filbert's review (agents/filbert/work/queue-a1-review-2026-09-26.md, sha256 6933b885) and Sage's lead decision 23 (40a02d2b). Nothing is committed, staged or pushed.

Files

File sha256 What it is
delta-r1.patch b733b894 the change on top of build.patch
build-manifest-r1.sha256 85a8a453 all 20 files after the delta

The delta changes 11 of the 20 files, +410 −49 lines. It adds no file and changes no mode. In a fresh clone at 3a209eea, build.patch and then delta-r1.patch apply cleanly, and the result matches the new manifest 20/20.

Required changes

R1, the matrix. data.test.mjs has a new test, "matrix R1". Its oracle, specAllows, is written from 8.7's table, not from queue.mjs. The test replays a row into every reachable state and tries every target in STATES as five actors: darkwing (the row's owner), filbert, rocko, sage and jason. It does this with the gate owner as filbert, jason and the row's owner, and with the row required and not. Release gets the same treatment. That is 273 allowed moves and 2487 refusals, each compared with what applyOp does. Your R1 mutation and the agent's two survivors each fail it (R1a to R1c below).

R2, the review issue, as lead decision 23 rules. move in-review refuses a row with no issues. A row with one issue uses it. A row with several needs --issue N, and N must be one of them. A later round keeps the previous round's issue unless --issue names another. --issue is accepted only on in-progress→in-review, and giving it twice is a usage error (exit 4). The receipt now ends round N on #ISSUE.

The ruling didn't cover one case: a later round whose kept issue the row no longer lists, after a set issues. I refuse it until --issue names one of the row's issues. Falling back to the first issue would be the silent choice decision 23 replaced.

The row schema now refuses review.issue: null, so a hand-built file can't hold one either. set piece and set gate stay privileged only (decision 23, point 2); the code already did that, and nothing changed.

Tests: "review issue, lead decision 23" in data.test.mjs covers the four cases and a refused --issue that isn't in the row. "the review issue and the evidence round through the CLI" in store.test.mjs runs the same through the CLI. "the row schema refuses a review with a null issue" checks validateRow. Mutations D1 to D5.

R3, the evidence round. The format is now comment=<id>,round=<n>,candidate=<digest>. move done compares the round with the current one and refuses evidence names round 1; row 12 is in round 2. The J5 test refuses evidence with no round and with a wrong round, then sends a row back and re-requests it with the same candidate: round-1 evidence is refused and round-2 evidence closes it. Mutation E1.

Notes I took

  • N1. withLock appends the release warning to a refusal's message. unlock now does the same for the gate: a swapped gate is left in place and reported, on a refusal and on success. On success the CLI prints the warning on stderr and the result on stdout. Mutations W1, G1, G2, U1.
  • N2. In acquire, an error checking the gate releases the lock, then refuses with cannot check the unlock gate ...; lock released. In publish, the temp file's stat now runs before the link, so nothing that can fail runs between a successful link and the return. The test covers an unreadable gate, a stat that fails before the link, and a stat that fails from its second call on. The last case came late. L1 (a second stat after the link) survived my first mutation run, so I added it; L1 is now caught.
  • N3. write.test.mjs has three fault tests: the queue.json and docs/plans fsyncs in confirmTail (sync exits 1, nothing changes); the .git fsync after the witness rename (exit 3); the docs/plans fsync after the view rename (the op stands, a warning says the view isn't confirmed durable). Mutations F1 to F4.
  • N4. The foreign-host test adds a record with another boot id. It must still classify unknown. Mutation H1 swaps the two checks.
  • N6. The message now says durable, witness written, its directory fsync failed when the rename happened, and witness not updated only when it didn't.
  • N9. BRIEF-TEMPLATE.md: briefed when a privileged actor (jason or sage) accepts it; the owner can't.
  • N14. You were right. The paused-editor test is now pausedCommit(t, form), which checks whether index.lock exists at the pause and asserts on that: exit 3 and another git process holds .git/index.lock when held, exit 0 when free. Either way the paused commit then fails with cannot lock ref 'HEAD': is at C but expected H. Two forms run on git 2.55.0: plain commit -e (the lock was free, exit 0) and commit -e -- src.txt (the lock was held, exit 3). The test no longer pins a git version, and the stale comment is gone.

A correction to build.md

build.md says "unlock works on a missing or invalid lock file". That's wrong, as you said. It means a missing or invalid queue.json. unlock refuses an invalid lock. build.md stays as sent, since your review pins it.

Notes not taken

N5, N7, N8, N10, N11, N12, N15 and N16. They don't block, and none is in the files this round had to touch for a reason. N8 (the \ escape in cell()) and N11 (replay looser than the CLI on op ids) are cheapest before genesis. That's Sage's call; I can take them in A2.

Verification

At 3a209eea plus the candidate (/tmp/qa1-verify): test-queue.sh 19 checks, node --test 107/107 (data 22, lock 19, store 19, write 25, commit 22), verify skipped because HEAD has no queue.json.

At today's HEAD, 40a02d2b, plus the candidate and the N13 change (/tmp/n13-verify): config 24, task 90, foundation 44, conductor 17, release 14, auth 15, discord 64, extension-package 18, queue 19 with 107/107. Foundation and discord each gained one check from N13. I reran the queue suite there after the last change; the other suites can't reach packages/queue.

The canonical .git is unchanged: .git/hooks holds only samples, no mosaic-queue* file, and git config --show-scope --get-all core.hooksPath returns nothing (rc 1). Every run was in a --shared clone under /tmp.

Mutations

Each mutation went into the verify clone, the queue tests ran, and the file was restored from the candidate. All 20 files matched the candidate after each run. The number is how many tests failed.

Id Mutation Failing tests
R1a drop the owner check on unblock 1
R1b let the owner move waiting-on-jason→in-progress 1
R1c check the owner on block only when not queued 1
D1 allow review with no issues 1
D2 require --issue with one issue 9
D3a take the first of several issues 2
D3b accept an --issue the row doesn't list 2
D4a never keep the previous round's issue 2
D4b keep an issue the row no longer lists 1
D5 schema allows a null review issue 1
E1 ignore the evidence round 2
L1 stat the temp again after the link 1
L2 don't release the lock when the gate check fails 1
F1 drop confirmTail's queue.json fsync 1
F2 drop confirmTail's docs/plans fsync 1
F3 drop the .git fsync after the witness rename 1
F4 drop the docs/plans fsync after the view rename 1
W1 drop the release warning on a refusal 1
H1 check boot before host 1
G1 drop the gate warning on a refusal 1
G2 drop the gate warning on success 2
U1 print the gate warning nowhere in the CLI 1

R1a to E1 ran before the last lock and store changes, which touch neither queue.mjs nor the tests that caught them. L1 to U1 ran on the final candidate. L1 first survived with 0 failures, as noted under N2.