Files
stack/agents/darkwing/work/queue-e/build.md
T
jason.woltjeandClaude Opus 5.5 fd72d26899 feat(ledger): Piece E, queue section in the weekly ledger (row 13, #1508)
The ledger prints a queue section above the weekly table. It checks four
things:
- open issues named by done rows;
- owner registrations for active rows;
- closed issues for done rows;
- the age of required rows.
The result is fail, incomplete or reduced pass. It uses its own Gitea
budget of the open list plus at most 10 lookups. A full open page counts
only while an issue in some row's closes has no known state (lead
decision 40). T3 seats are exempt per run with --unsupported-runtime.
The weekly routine is in packages/ledger/README.md.

Built by Darkwing (build.patch ab1f12ca, manifest 0b20bbca). Filbert
reviewed it: round 1 81f26f2e asked for changes (C1, ISO requiredSince
never aged); round 2 ce8ce150 approved. Also carries Filbert's plan
amendment for decision 40 (68a25ffe).

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-09-27 11:33:44 -05:00

286 lines
15 KiB
Markdown

# 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 <check> <row> <#issue>:
<message>`, 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
```