# Queue E (#1508, row 13), candidate for review, round 2 Darkwing, 2026-09-27. Piece E is the ledger's queue section, per plan section 8.10 (`agents/filbert/work/queue-as-data-plan-2026-09-26.md`) and the brief's "Piece E: ledger checks the queue". The ledger now checks `docs/plans/queue.json` against Gitea and the seat registrations and prints the result above the weekly table. Filbert reviews E. Sage commits. Nothing is committed, staged or pushed. Round 2 answers Filbert's round 1 review (`agents/filbert/work/queue-e-review-r1-2026-09-27.md`, sha256 81f26f2e…): C1, plus n1, n2 and n3. See "Round 2" below. The round 1 files are kept unchanged in `r1/` (patch 02a01c29…, manifest cad51929…, build.md d3bdb826…). Round 1 was amended before its verdict for lead decision 40 (origin 2333d837): a full open-issue page is undecided only while some issue a row closes has no known state. See "The full page" below. Base is f3f48cfd. Nothing under `packages/ledger`, `packages/queue/src` or `packages/seat/src` changed between it and origin 2333d837. In a fresh clone at 2333d837 the patch applies, the result matches the manifest 5/5, and `node --test packages/ledger/tests/` passes 78/78 in three runs. ## Files `build.patch` (sha256 `ab1f12cad711284f8a722ea51fa73cd8e344c703701f8b76957ae33de091ae84`) changes 3 files and adds 2. `build-manifest.sha256` (sha256 `0b20bbca6c9e0a00d39dc7aae0ca2e268b6908c4b24e9f9b401df8268d29c5fe`) pins all 5 after the patch. - `packages/ledger/src/queue-checks.mjs` (new): reads the queue, classifies owners, makes the issue calls, runs the checks, lists protected changes, formats the section. - `packages/ledger/src/cli.mjs`: `--no-queue`, `--unsupported-runtime SEAT` (repeatable), the queue read before any Gitea call, the section above the table and under `queue` in `--json`. - `packages/ledger/README.md`: a "Queue section" with the rules, the call budget, the result levels and the weekly routine. The heading "One Gitea call" becomes "Gitea calls", and the exit-code paragraph names the queue. - `packages/ledger/tests/queue-checks.test.mjs` (new, 20 tests). - `packages/ledger/tests/ledger.test.mjs`: the fixture copies `packages/queue/src` and `packages/seat/src`, which the ledger now imports, and its runs pass `--no-queue`. Those tests cover the weekly table; the new file covers the queue section. 58 tests before, 78 now. `docs/TOOLS.md` has no ledger entry today, so there is no TOOLS patch. The README is the reference, as it was for Piece 3. ## What a run prints ``` Queue checks: queue.json revision 19, as of 2026-09-27T16:09:10.883Z owner darkwing (row 13): exempt, declared with --unsupported-runtime ... liveness: 0 pid-present (unverified), 3 exempt, 0 pid-unknown, 0 missing, 0 invalid, 0 pid-gone declared unsupported runtime: darkwing, dewey, sage queue issue checks: open list (full page), 2 lookups protected changes in range: 0 (not checks; confirm the actors) queue: 0 violations; result reduced pass ``` Each finding is a line `violation|undecided|disposition <#issue>: `, so its identity (check, row, issue) can be read off the line for the same-day remediation run. The JSON carries the same objects. ## Choices and where they differ from the plan 1. **A new module.** The plan's file list puts `queueChecks` in `ledger.mjs`. I put the section in `queue-checks.mjs`, as `t3.mjs` did for the T3 source, so `ledger.mjs` stays the metric code. The CLI wires it in. 2. **Registrations through `readRegistration`**, not the control board's scan. It returns a record, null or a thrown error per seat, with no text to parse, and it keeps the board's import chain out of the ledger. Liveness is its own signal-0 probe; EPERM counts as present. 3. **Closure uses `closes`, not `issues`** (J6). Rows 9 to 12 name #1508 but close nothing, so they can be done while it is open. The brief's literal "a row naming an open issue" would flag them. 4. **A malformed or absent pid is `invalid`, not `pid-unknown`.** The plan's table puts it under `pid-unknown`. `validateRegistration` rejects a pid that is not a positive integer or null, so the record fails validation first. `invalid` is a violation where `pid-unknown` is undecided, so the difference fails closed. `pid-unknown` is a valid record with a null pid. 5. **A registration for another checkout is `missing`.** The plan defines missing as no registration for (canonical root, `repo`, seat). A `repo` record whose `sessionsDir` is not under the queue's canonical root matches the seat name but not the root. 6. **An unreadable config makes every non-exempt owner `invalid`**, and the report still runs. It does not refuse, because the other checks don't need the data root. 7. **Refusals.** A missing, symlinked or hand-edited `queue.json` exits 1 before any Gitea call; the message names `--no-queue`. A failed or malformed open-list call exits 2, like the metric call, and names `--no-issues`. A failed lookup, a 404, a pull request or a number that doesn't match leaves only that issue unknown. 8. **On by default.** `--no-queue` skips the section and prints `Queue: not checked (--no-queue)`. It can't be combined with `--unsupported-runtime`. `--unsupported-runtime` is the only repeatable flag; a repeated seat is refused. 9. **Age is as of the run**, not `--until`: the gate is about today's queue, and the table's range doesn't move it. "More than 14 days" is whole days, so a row required on 2026-09-13 turns on 2026-09-28. 10. **Exit 0 whenever a report was computed.** The result is in the text and the JSON, like every other number the ledger prints. 11. **Protected changes (added).** The 8.10 checks don't include it, but R4 and R10 ("E lists every protected change from [the journal]"), J2 and the queue README's trust boundary ("piece E lists changes for review") do. So the section lists every log entry dated inside `--since`/`--until` that changes a row which is required or parked before or after the entry, with rev, verb, claimed actor and rows. It replays the log up to the range with the queue's own `replay` and `applyEntry`. It is a list, not a check: it never changes the result. For 2026-09-20 to 2026-09-26 it lists nothing, because genesis was 2026-09-27; for a range that includes today it lists 18 entries at rev 19, on rows 8 to 13 and 26 to 30. ## Round 2 - **C1, an ISO `requiredSince` never aged.** `set required` and `add --required` write an ISO time, and round 1 appended `T00:00:00Z` to it, which parses to NaN. Now `Date.parse` reads the value as it is (a bare date parses as 00:00Z), and the result is floored to its UTC day. One deviation from the suggested fix: an ISO time counts from 00:00Z of its day, not from its hour, so both forms age in whole UTC days and a Monday run's result doesn't depend on the hour a row was made required. A row required at 23:59Z on 2026-09-13 turns on 2026-09-28, as a date-only row does. A value that doesn't parse is an `age-invalid` violation. I chose a violation over undecided because row 13's gate counts violations, and a bad value is a queue defect. The queue validator lets one through: `ISO_RE` checks the shape, so `2026-13-01T00:00:00.000Z` passes it. That gap is in `packages/queue/src/queue.mjs`, outside E; I'm raising it as a follow-up, not fixing it here. - **n1, the orphaned curl.** Each queue call now runs as `timeout -s KILL 60 gitea-api.sh GET ...`, as D's `callTool` does, so the kill takes the helper's process group. A kill reads `no answer within 60 s`, and exit 126 or 127 from `timeout` (no helper) reads `gitea-api.sh unavailable`. The metric call in `ledger.mjs` still uses execFileSync's timeout. It isn't in this patch, so that is a follow-up too. - **n2, a reopened issue.** Closed evidence from the metric page now needs `state: "closed"` and a `closed_at`. Either one alone leads to a lookup. - **n3.** The budget detail is `over the lookup budget`, so the message reads `unknown (over the lookup budget)`. Two tests are new. One covers an ISO `requiredSince` at 15 days (fails), at 14 (passes), at 23:59Z fifteen days back (fails), and one that doesn't parse (`age-invalid`, result fail). The other gives the calls a hanging helper that starts a hanging child and a 1-second deadline. Both calls return at the deadline and neither process survives. A missing helper reads `gitea-api.sh unavailable`. The metric fixture adds a reopened issue and a `state: "closed"` entry with no `closed_at`, and both are looked up. Round 2 mutants, all 12 killed: 1. The round 1 template restored. 2. No floor to the UTC day. 3. No NaN guard. 4. NaN as undecided. 5. Ceiling instead of floor. 6. The metric check on `closed_at` alone. 7. The metric check on `state` alone. 8. A Node timeout with SIGKILL in place of `timeout`, with ETIMEDOUT ignored. The orphan assertion kills it. 9. The kill flag always false. 10. The kill flag read from the exit status alone. 11. No 126/127 mapping. 12. The old budget detail. A first version kept the suggested `DATE_RE` branch, and dropping it was an equivalent mutant because `Date.parse` already reads a bare date as 00:00Z. I removed the branch. ## The full page (lead decision 40) mosaicstack/stack has 50 or more open issues, so the open list is always a full page. Plan 8.10 made a full page undecided on its own. But an issue missing from the page is looked up, so the page matters only when an issue is left without a known state, and that issue is already undecided. Sage approved the change. Now `open-list-full` is added only when the page is full and some issue a row closes is `unknown`, and its message names those issues. The text still prints `open list (full page)`, and the JSON keeps `openListFull`. Tests cover a full page with every issue resolved (no undecided item), an open issue off the page found by lookup (its done row fails), and a full page with an issue past the budget (still incomplete). They drive the fake helper end to end through `issueStates` and `queueChecks`. ## Live run (read-only) Round 2, in a fresh clone at 2333d837 with the patch applied, through the new `timeout` path: the same result as below with the exemptions, 0 violations, `reduced pass`, 2 lookups, exit 0. Round 1, at 2333d837 in the verify clone, with my own token file in `MOSAIC_GITEA_CREDENTIAL_FILE`, for 2026-09-20 to 2026-09-26: exit 0, the metric call, the open list and 2 lookups. That is GET only, and nothing was posted. - Plain run: 3 violations, result `fail`. Rows 13 (darkwing) and 5 (dewey) are `pid-gone`, from old pi launches; row 7 (sage) is `missing`. All three seats run in T3, which writes no registration. - With `--unsupported-runtime` for darkwing, dewey and sage: 0 violations, result `reduced pass`. Before the amendment the same run was `incomplete`, from the full page alone. - No issue violations: every issue in a done row's `closes` is closed. This is not the acceptance run. That is a dated run posted on #1508 after approval. ## Lead decision 40 Sage ruled on the five points I raised: 1. The full page: approved as amended above. 2. Row 7's brief pins `packages/ledger/README.md` at blob 3a2ce27c. Sage re-pins it with `set 7 brief` in a queue commit right after E lands. 3. Row 7 stays. Its gate is Jason's, so Q9 is amended. The weekly routine stays in the README. 4. The age rule stays. From 2026-09-28 the Monday run lists rows 9, 10, 11 and 13, which is accurate. 5. The T3 exemption is accepted as built. The weekly run declares every T3 seat that owns an active row. ## Tests `packages/ledger/tests/queue-checks.test.mjs`. In-process checks run on fixture rows with a fixed clock and an injected pid probe. CLI tests use a scratch repository whose `queue.json` the real queue CLI wrote, a temporary config and data root, and a fake `gitea-api.sh` that routes the open list, single issues and the metric page and logs every call. No test reads a real token, registration, config or `~/.t3`; HOME and MOSAIC_CONFIG are temporary. - Issues: done with the issue open (fail) or closed (pass); a multi-row issue open while one closer is pending, closed early (disposition), and open with both done; rows 9 to 12's shape (issues without closes); unknown and not run (incomplete); a full page is undecided only beside an unknown issue. - Issue calls: open list first, metric page next, then at most 10 lookups in order and `unknown (budget)` after; a pull request on the open list is not an issue; lookups of a pull request, a mismatched number and a 404 are unknown; a metric entry with no `closed_at` is not closed evidence; a full page. The open list refuses on exit 3, exit 1, bad JSON and bad or closed records, and never echoes the helper's stderr. - Owners: every class in one run, including another checkout and a broken record; two rows for one owner; briefed and done owners not checked; no config. - Age: 15 days fails, 14 doesn't; done and not-required rows are skipped; the legacy bound at 20 days (fail), exactly 14 and 3 (undecided); an ISO `requiredSince` by UTC day, and one that doesn't parse (round 2). - Deadline: a hanging helper and its child are both killed (round 2). - `pidAlive`: running, exited, and EPERM (pid 1, non-root). - `readQueue`: a real queue, a hand edit, a missing file, a symlink. - Protected changes: genesis, a note on a required row, a note on an ordinary row (not listed), an unpark by jason (listed from the row before), and range boundaries (start included, end excluded). - CLI: section above the table; `--json` key; a clean queue prints `queue: 0 violations; result reduced pass`; call counts (3 with issues, 0 with `--no-issues`, 1 with `--no-queue`); refusals cost no call; flag errors. Mutation testing, round 1: 37 hand-made mutants of `queue-checks.mjs` and the CLI wiring (boundaries, each class, each result level, budget, filters, refusals, the protected-change range and guard, the full-page rule). All 37 are killed. The first pass left three alive (the 14-day genesis edge, EPERM, a null `closed_at`) and a later one three more (the before-row guard and both range edges); the tests above were added for them. Round 2 adds the 12 listed above. Suites: `node --test packages/ledger/tests/` 78/78, three runs; `packages/queue/tests` and `packages/seat/tests` 161/161. ## Verify ```sh git clone -q /mnt/storage/src/mosaic-stack /tmp/e && cd /tmp/e git checkout -q 2333d837 git apply /mnt/storage/src/mosaic-stack/agents/darkwing/work/queue-e/build.patch sha256sum -c /mnt/storage/src/mosaic-stack/agents/darkwing/work/queue-e/build-manifest.sha256 ln -s /mnt/storage/src/mosaic-stack/node_modules node_modules node --test packages/ledger/tests/ node packages/ledger/src/cli.mjs --since 2026-09-20 --until 2026-09-26 --no-t3 --no-issues ```