queue move ID in-review posts the review request as a Gitea comment and review record reads verdicts back, so reviews stop being files in docs/plans/reviews/. On a comment round, in-review to waiting-on-jason now needs every listed reviewer's approval for the current round, the same as in-review to done (Filbert r1 C1). scripts/gitea-api.sh reads the raw per-seat token files (lead decisions 37 to 39): config built and checked before curl starts, export attribute cleared, fixed base URL. test-queue.sh skips its live checks outside the canonical root. Darkwing authored. Filbert approved D r2 (cf1d3fd0) after r1 (a2dc2302) and corrected the plan (293747cd). Rocko reviewed the helper (e896192f, 2096b0a3), and Sage's lead check passed under decision 38. Manifest b402fb38, 19 files. Co-Authored-By: Claude Opus 5.5 <[email protected]>
7.1 KiB
Queue Piece D review, round 1 (#1508, row 12)
Filbert, 2026-09-27. Plan: queue-as-data-plan-2026-09-26.md §8.9.
A2 round 1 review: queue-a2-review-r1-2026-09-27.md (sha256 f167b85e).
Verdict
Changes requested, one item (C1). The review covers build.patch
sha256 cb7a6c6649f88496887eacab52db77ef1cc62aaf59a48c3cadcd5f8e33ae1243
on base 8ffbd73b (it applies cleanly at 8efc0ff3), with
build-manifest.sha256 5646d938…. Everything else in the candidate is
ready. C1 is a gap in my own plan's transition table, and D implements that
table as written. Sage can rule it a plan question and approve without it,
but I recommend fixing it now: no semantics-2 entry is logged yet, so the
fix needs no semantics 3.
I re-hashed the inputs after the review. build.patch and
build-manifest.sha256 match the hashes Darkwing sent. build.md changed
from b079fbc1… to ced0946b… while I reviewed. The diff is two lines: the
helper.patch hash now names helper round 2, and a helper test count went
from 56 to 57. Both are in the helper, which is Rocko's scope, so the
change doesn't touch this review.
C1. A Jason-gated row can close without its reviewers' verdicts
Where: packages/queue/src/queue.mjs, applyMove, the branch
} else if (from === "in-review" && (to === "in-progress" || to === "waiting-on-jason")) {
(lines 766–767 in the patched file). It calls only ownerOrPriv.
What happens. On a semantics-2 comment round, in-review→waiting-on-jason checks neither the reviewers' approvals nor the unresolved attempts. The in-review→done path checks both, but a Jason-gated row never takes that path; it goes through waiting-on-jason. After that, waiting-on-jason→done checks only for unresolved attempts. So on a Jason-gated row with reviewers, the listed reviewers are advisory.
Reproduced with a temporary probe test on the review.test.mjs
harness (since deleted):
set 9 gate-owner jasonon a row with reviewers.- The owner's move to in-review posts the request (comment 1000). The
round's
receiptsis[]. - darkwing:
move 9 waiting-on-jasonexits 0. - jason:
move 9 doneexits 0.
No reviewer recorded a verdict at any point.
Why it matters now. Row 5 (CHAT-03 source) is next through this path. Its gate owner is jason and its reviewers are filbert and rocko. D exists so that the queue, not a seat's memory, holds "reviewed before Jason sees it".
Cause. My plan's table, line 1470, gives in-review→waiting-on-jason as "owner, or privileged | —". I wrote the approval condition only on in-review→done (line 1472). D follows the table.
Fix. When the current round is a comment round, in-review→waiting-on-jason
runs refuseUnresolved and requires an approving receipt from every listed
reviewer, the same checks as in-review→done (lines 779–787). Share them in
one helper. In-review→in-progress stays as it is: that's the changes path,
and the next round already refuses unresolved attempts.
- Scope it to
cur?.request === "comment". Only semantics-2 entries create such rounds, so v1 replay is unaffected. - Whether
by === "jason"is exempt is Sage's call. I lean against it: an exemption is one more path to test, and the owner can't use it. - Add a test: the steps above refuse at step 3, then pass once every reviewer has recorded an approval. Add a mutant that drops the check.
- Update the plan table line 1470 and the README's review section to match. I'll make the plan edit if Sage wants it in my file.
What I checked
All of this ran in a scratch clone, /tmp/fqd, with push disabled, on
frozen 0444 copies of the inputs in /tmp/fqd-in.
- Manifest and suites. The manifest checks 10/10 after
git apply build.patch.node --test packages/queue/tests/passes 141/141, and the ledger tests pass 51/51.scripts/test-queue.shreports 27/0 with its "outside the canonical root" skip line, as Sage described. - The source, read in full:
review.mjs, and the diffs toqueue.mjs,store.mjs,cli.mjs,scripts/test-queue.sh, the README, the tests andfixtures/fake-gitea.mjs.tools-md.patchmatches the code; it's Sage's to apply. - The deadline.
timeout -s KILLkills the helper's grandchild. I checked it with a helper that forks a sleeper: the grandchild is gone after the deadline, andspawnSyncreportssignal: "SIGKILL", whichclassifyPostmaps to uncertain. - Mutations. I wrote 24 mutants of my own, separate from Darkwing's 61,
and the suite kills all 24. They cover:
- a same-op retry falling through to a second POST;
- an abandoned attempt followed by a late post, which must be a conflict;
- resolve accepting a comment with another id, another author, or no round marker;
credCheckskipping the mode check or the realpath check;- a 201 with no comment id treated as posted;
- other HTTP codes treated as failed instead of uncertain;
- a killed call treated as failed;
- done without every approval;
- a new round, or waiting-on-jason→done, ignoring unresolved attempts;
- a new request while one is posted;
- resolve without the GET;
- the body limit removed;
- sage posting as sage instead of jarvis;
verify-commitignoring the mode or a deletion;- the outcome-write failure exiting 1 instead of 3;
- a failed pre-send still posting;
nextoffering a round the reviewer already answered;- abandon leaving
duplicateRiskfalse; - record accepting a different candidate digest.
Non-blocking
- n1. Done doesn't require a posted request. If every attempt failed or was abandoned, reviewers can still record approvals and the row can close. That's acceptable: the receipts are the evidence, and the comment is how reviewers are asked, not what they approve.
- n2. A review issue that is a pull request.
checkCommentneedsissue_urlto end in/issues/N. I haven't checked what Gitea returns for a comment on a pull request. If it's anything else, resolve refuses. That fails closed, and our review issues are issues, so no change now. - n3. Abandoning a
requestingattempt setsduplicateRiskeven when the kill came before the POST. That's per plan: the queue can't tell which side of the POST the kill landed on. - n4. The body travels on argv as a JSON argument. It's capped at 60,000 bytes, well under the per-argument limit. An E2BIG would classify as uncertain, which is the safe side.
- n5. For Rocko:
scripts/gitea-api.shwrites the response to the predictable path/tmp/gitea-api-response.$$. That's the helper, not D.
What I'd keep as it is:
- resolve checking the comment's author against the requester's login. That goes beyond the plan, and it's right;
- semantics recorded per entry, with review verbs refused on v1 entries;
- the late-outcome rules in
outcomeState; - fixed detail strings, so nothing from a response reaches the log.
For Darkwing and Sage
Fix C1 with its test and mutant, re-send the patch and manifest, and I'll review round 2 against this list only. If Sage rules C1 out of D's scope, the candidate is approved as it stands, and C1 has to land before row 5 moves to in-review.