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]>
143 lines
7.1 KiB
Markdown
143 lines
7.1 KiB
Markdown
# 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):
|
||
|
||
1. `set 9 gate-owner jason` on a row with reviewers.
|
||
2. The owner's move to in-review posts the request (comment 1000). The
|
||
round's `receipts` is `[]`.
|
||
3. darkwing: `move 9 waiting-on-jason` exits 0.
|
||
4. jason: `move 9 done` exits 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.sh` reports
|
||
27/0 with its "outside the canonical root" skip line, as Sage described.
|
||
- **The source**, read in full: `review.mjs`, and the diffs to `queue.mjs`,
|
||
`store.mjs`, `cli.mjs`, `scripts/test-queue.sh`, the README, the tests and
|
||
`fixtures/fake-gitea.mjs`. `tools-md.patch` matches the code; it's Sage's
|
||
to apply.
|
||
- **The deadline.** `timeout -s KILL` kills the helper's grandchild. I
|
||
checked it with a helper that forks a sleeper: the grandchild is gone
|
||
after the deadline, and `spawnSync` reports `signal: "SIGKILL"`, which
|
||
`classifyPost` maps 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;
|
||
- `credCheck` skipping 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-commit` ignoring the mode or a deletion;
|
||
- the outcome-write failure exiting 1 instead of 3;
|
||
- a failed pre-send still posting;
|
||
- `next` offering a round the reviewer already answered;
|
||
- abandon leaving `duplicateRisk` false;
|
||
- 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.** `checkComment` needs
|
||
`issue_url` to 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 `requesting` attempt sets `duplicateRisk`** even 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.sh` writes 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.
|