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]>
This commit is contained in:
@@ -0,0 +1,285 @@
|
||||
# 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
|
||||
```
|
||||
@@ -137,6 +137,10 @@ Changed:
|
||||
`packages/ledger`, so A leaves a one-line hand-written pointer to the
|
||||
weekly routine below the markers. E moves the routine into
|
||||
`packages/ledger/README.md` and removes the pointer.
|
||||
*Amended by lead decision 40 (2026-09-27):* row 7 stays in the queue
|
||||
until Jason closes it, because its gate is his. E still writes the
|
||||
weekly routine into the ledger README. A left no pointer below the
|
||||
markers, so E has none to remove.
|
||||
- `docs/TOOLS.md`: usage lines. This file carries other owners' uncommitted
|
||||
changes; add a scoped patch the way #1511 did.
|
||||
|
||||
@@ -167,6 +171,8 @@ Changed:
|
||||
that was row 7).
|
||||
- One more Gitea call, `state=open`, one page; a full page fails as today
|
||||
(Q7). Referenced issues absent from that list count as closed.
|
||||
*Superseded by 8.10:* absent issues are looked up, and a full page is
|
||||
handled as lead decision 40 rules.
|
||||
- Reads `docs/plans/queue.json` through the queue validator by relative
|
||||
import (`../../queue/src/queue.mjs`). There are no workspaces, so a bare
|
||||
`@mosaic/queue` import would not resolve (R14).
|
||||
@@ -371,6 +377,10 @@ items have no owner or issue.
|
||||
*Decided:* row 7 moves to `packages/ledger/README.md` as the weekly
|
||||
routine (sequenced across A and E; see 2.A). The parked items become rows
|
||||
with state `parked`, owner `unassigned` and issue null.
|
||||
*Amended by lead decision 40 (2026-09-27):* row 7 stays until Jason
|
||||
closes it. Its gate is Jason's, and closing a Jason-gated row needs his
|
||||
cited approval, so the lead can't retire it alone. E writes the weekly
|
||||
routine into `packages/ledger/README.md` anyway.
|
||||
|
||||
## 5. Where the brief contradicts the code or itself
|
||||
|
||||
@@ -1741,7 +1751,7 @@ source is unchanged.
|
||||
| Calls | Purpose |
|
||||
|---|---|
|
||||
| 1 | Metrics, unchanged. |
|
||||
| 1 | Queue checks: `state=open`, limit 50. A full page makes the queue issue checks "incomplete". |
|
||||
| 1 | Queue checks: `state=open`, limit 50. A full page makes the queue issue checks "incomplete" only when some issue in a row's `closes` is left without a known state (lead decision 40). |
|
||||
| up to 10 | Queue checks: `GET issues/N` for issues in some row's `closes` that are neither in the open list nor shown closed in the metric page. Issues beyond 10 are "unknown (budget)", and the run is incomplete. |
|
||||
|
||||
That is at most 12 calls. `--no-issues` makes 0 calls and prints `queue
|
||||
@@ -1784,8 +1794,9 @@ days (legacy lower bound)". Until then its age is undecidable.
|
||||
`missing`, `invalid` or `pid-gone` owner, and any issue or age violation.
|
||||
- `incomplete`: no known violation, but something couldn't be decided. That
|
||||
covers:
|
||||
- an issue check that was `unknown (budget)`, hit a full page, or was not
|
||||
run;
|
||||
- an issue check that was `unknown (budget)`, or was not run;
|
||||
- a full open page while some issue in a row's `closes` has no known
|
||||
state;
|
||||
- a `pid-unknown` owner;
|
||||
- an undecidable legacy age.
|
||||
- `reduced pass`: nothing known and nothing undecided. The liveness evidence
|
||||
@@ -2411,3 +2422,10 @@ The round-2 modifications:
|
||||
with no reviewer approvals, so a Jason-gated row could close without its
|
||||
reviewers. That move now carries the in-review→done checks on a comment
|
||||
round; the table and 8.9's receipts paragraph say so.
|
||||
- 2026-09-27: amended for lead decision 40 (Piece E questions). A full
|
||||
`state=open` page makes the queue issue checks incomplete only when some
|
||||
issue in a row's `closes` is left without a known state. An issue off
|
||||
the page is looked up or left `unknown (budget)`, so truncation can't
|
||||
hide a violation. 8.10's budget table and result list say so. Row 7
|
||||
stays in the queue until Jason closes it; Q9 and 2.A say so. Section 2's
|
||||
E text points to 8.10.
|
||||
|
||||
@@ -0,0 +1,168 @@
|
||||
# Queue Piece E review, round 1 (#1508, row 13)
|
||||
|
||||
Filbert, 2026-09-27. Plan: `queue-as-data-plan-2026-09-26.md` §8.10,
|
||||
amended for lead decision 40 (plan sha256 68a25ffe…, uncommitted).
|
||||
|
||||
## Verdict
|
||||
|
||||
**Changes requested, one item (C1).** The review covers the amended
|
||||
round-1 candidate: `build.patch` sha256
|
||||
02a01c291890626f58ba103d1c65e853b042b609887868bf59dea7f9b50b227d at
|
||||
2333d837, with `build-manifest.sha256` cad51929… and `build.md` d3bdb826….
|
||||
I had also reviewed the first version (patch 78445322…, manifest
|
||||
8f0e4fea…) in full. The amendment changes only the full-page rule, its
|
||||
tests and the README, so everything I checked on the first version still
|
||||
holds.
|
||||
|
||||
C1 is a real bug in the age check, and it's small. Everything else is
|
||||
ready, including the decision-40 change.
|
||||
|
||||
## C1. The age check never flags a row made required after genesis
|
||||
|
||||
**Where:** `packages/ledger/src/queue-checks.mjs`, line 200 in the amended
|
||||
file:
|
||||
|
||||
```js
|
||||
const days = ageDays(Date.parse(`${r.requiredSince}T00:00:00Z`), now);
|
||||
if (days > AGE_LIMIT_DAYS) add(violations, 'age', ...
|
||||
```
|
||||
|
||||
**What happens.** The code assumes `requiredSince` is a date. Genesis
|
||||
rows carry dates (rows 9, 10, 11 and 13 have `2026-09-13`). But the queue
|
||||
writes a full ISO time for any row made required later:
|
||||
`set <id> required true` sets `requiredSince = entry.at`
|
||||
(`packages/queue/src/queue.mjs:860`), and `add --required` does the same
|
||||
(line 904). The validator accepts both forms (line 322, `checkTime(…,
|
||||
{ date: true })`). For an ISO time the template gives
|
||||
`2026-09-01T12:00:00.000ZT00:00:00Z`, `Date.parse` returns NaN, `days`
|
||||
is NaN, and `NaN > 14` is false. The row is never an age violation, and
|
||||
nothing is reported as undecided either.
|
||||
|
||||
**Reproduced** in-process at 2026-09-27T12:00Z on one required,
|
||||
in-progress row:
|
||||
- `requiredSince: "2026-09-01"` → violations `owner-invalid, age`, result
|
||||
`fail`;
|
||||
- `requiredSince: "2026-09-01T12:00:00.000Z"` → violations `owner-invalid`
|
||||
only. The age violation is missing.
|
||||
|
||||
(`owner-invalid` comes from passing no seats; it doesn't matter here.)
|
||||
|
||||
**Why it matters.** Today's rows all have dates, so Monday's run is right.
|
||||
The first row made required through the queue CLI would slip past the
|
||||
14-day gate without a word. A check that goes quiet is the failure the
|
||||
ledger's result levels exist to prevent.
|
||||
|
||||
**Fix.**
|
||||
- A `DATE_RE` value parses as `T00:00:00Z`; anything else goes to
|
||||
`Date.parse` as it stands.
|
||||
- If the result is NaN, fail closed: an `age` undecided item naming the
|
||||
row, or a violation. Never silence. (The validator should make this
|
||||
unreachable, but the check shouldn't depend on that.)
|
||||
- Tests: an ISO `requiredSince` 15 days old fails and one 14 days old
|
||||
doesn't, next to the existing date cases. Your fixtures build
|
||||
`requiredSince` with `since(n)`, so an ISO variant fits in the same test.
|
||||
- Mutant: restore the old template. The new test should kill it.
|
||||
- The README's age paragraph can say that `requiredSince` is a date for
|
||||
genesis rows and an ISO time for later ones.
|
||||
|
||||
## The full page (lead decision 40)
|
||||
|
||||
I agree with the ruling, and the amendment implements it correctly. An
|
||||
issue is never treated as closed because it's missing from the open list.
|
||||
It's looked up or left `unknown (budget)`, and the unknown state already
|
||||
makes the run incomplete. A server that caps pages below 50 would make
|
||||
`full` meaningless anyway, so dropping it as a standalone signal costs
|
||||
nothing.
|
||||
|
||||
What I checked on the delta:
|
||||
- `open-list-full` is added only when the page is full and some issue in
|
||||
`wanted` is `unknown`. `wanted` is the union of every row's `closes`,
|
||||
including rows that aren't done. An unknown issue on a pending row can
|
||||
only affect a disposition, but it still keeps the page undecided. That
|
||||
errs toward `incomplete`, and it matches the decision's wording ("some
|
||||
wanted issue"). Keep it.
|
||||
- The new end-to-end test drives the fake helper through `issueStates` and
|
||||
`queueChecks` for all three cases: resolved, open off the page, and past
|
||||
the budget.
|
||||
- The README's result list and call table match the code.
|
||||
- My plan now says the same thing: 8.10's budget table and result list,
|
||||
a pointer from section 2's E text, and Q9 and 2.A for row 7.
|
||||
|
||||
## What I checked
|
||||
|
||||
All of this ran in scratch clones with push disabled: `/tmp/fqe` at
|
||||
f304eaa5 for the first version, and `/tmp/fqe2` at 2333d837 for the
|
||||
amendment. The inputs were frozen as 0444 copies.
|
||||
|
||||
- **Manifest and suites.** The amended manifest checks 5/5. `node --test
|
||||
packages/ledger/tests/` passes 76/76, and `packages/queue/tests` with
|
||||
`packages/seat/tests` passes 161/161. The first version had passed
|
||||
75/75 and 161/161.
|
||||
- **Source.** I read `queue-checks.mjs` in full, and the diffs to
|
||||
`cli.mjs`, the README and both test files. I compared the amendment with
|
||||
the first version file by file. Only `queue-checks.mjs` (the rule and its
|
||||
comment), the README (two passages) and `queue-checks.test.mjs` changed.
|
||||
- **Local run** on the first version, for 2026-09-27 with `--no-issues
|
||||
--no-t3` and three seats declared: 0 violations, result `incomplete`
|
||||
(issues not run), 18 protected changes.
|
||||
- **Mutations.** I wrote 21 mutants of my own against the first version
|
||||
(E1–E21), apart from Darkwing's 37. The suite kills 19. The two
|
||||
survivors are equivalent:
|
||||
- E2 checks the metric page before the open list. Both are current
|
||||
sources, so they can't disagree about an issue.
|
||||
- E8 applies the age check to rows that aren't required. The validator
|
||||
refuses `requiredSince` on such a row, so the mutant changes nothing.
|
||||
|
||||
I wrote five more against the amendment (F1–F5), and the suite kills
|
||||
all five:
|
||||
- F1: a full page is always undecided;
|
||||
- F2: an unknown issue makes `open-list-full` without a full page;
|
||||
- F3: any state other than open counts as unresolved;
|
||||
- F4: only `unknown (budget)` counts, so a failed lookup doesn't;
|
||||
- F5: it takes two unknown issues.
|
||||
|
||||
None of my 26 mutants touches the age parsing. The C1 bug is in an input
|
||||
shape the fixtures don't use.
|
||||
|
||||
## Darkwing's choices
|
||||
|
||||
I agree with choices 1 to 11 in `build.md`. Two of them depart from the
|
||||
plan's table, and in both cases the change fails closed:
|
||||
- 4: a malformed pid is `invalid`, not `pid-unknown`, so it's a violation
|
||||
rather than undecided;
|
||||
- 5: a registration for another checkout is `missing`, because it matches
|
||||
the seat name but not the root.
|
||||
|
||||
Choice 11 (protected changes listed, never checked) is what R4, R10 and J2
|
||||
asked for.
|
||||
|
||||
## Non-blocking
|
||||
|
||||
- **n1. The call deadline kills the helper, not curl.** `execFileSync`
|
||||
with a 60-second `timeout` sends SIGTERM to `gitea-api.sh` only. I
|
||||
checked the behaviour with a script that runs a child `sleep`:
|
||||
`execFileSync` throws `ETIMEDOUT` at the deadline, so the ledger's wall
|
||||
time stays bounded, but the child keeps running as an orphan. For the
|
||||
ledger that means a stuck curl can outlive the run, and the helper's
|
||||
response file may be left in `/tmp`. It's a GET, so nothing is written
|
||||
to Gitea. D wrapped its helper call in `timeout -s KILL` for this.
|
||||
Doing the same here is a small change. It can go in this round or
|
||||
later.
|
||||
- **n2. `closedInMetric` could also check `state === 'closed'`.** It uses
|
||||
`closed_at` alone. I haven't checked whether Gitea clears `closed_at`
|
||||
when an issue is reopened. If it doesn't, a reopened issue that is off
|
||||
the open page but on the metric page would count as closed. The open
|
||||
page is always full now, so that case can happen. Checking `state` as
|
||||
well settles it either way.
|
||||
- **n3. The unknown-budget message doubles a word.** The test pins
|
||||
`unknown (unknown (budget))`. It would read better as `unknown (budget)`
|
||||
or `unknown (lookup budget)`. Cosmetic.
|
||||
- **n4. `--unsupported-runtime` is self-declared.** The flag is a
|
||||
statement from whoever runs the ledger, not evidence. It's printed on
|
||||
every run, which is what 8.10 asks for. Nothing to change.
|
||||
|
||||
## For Darkwing and Sage
|
||||
|
||||
Fix C1 with its test and mutant, then send the patch and manifest again.
|
||||
I'll review round 2 against C1 only, plus n1 if it's in. My plan
|
||||
amendment (68a25ffe…) goes into E's commit with this review.
|
||||
@@ -0,0 +1,101 @@
|
||||
# Queue Piece E review, round 2 (#1508, row 13)
|
||||
|
||||
Filbert, 2026-09-27. Round 1: `queue-e-review-r1-2026-09-27.md`
|
||||
(sha256 81f26f2e…). This round checks C1, n1, n2 and n3.
|
||||
|
||||
## Verdict
|
||||
|
||||
**Approved.** The review covers `build.patch` sha256
|
||||
ab1f12cad711284f8a722ea51fa73cd8e344c703701f8b76957ae33de091ae84 at
|
||||
2333d837, with `build-manifest.sha256` 0b20bbca… and `build.md`
|
||||
75571f0b…. C1 is fixed, and so are n1, n2 and n3. Two of my mutants
|
||||
survive. Neither hides a defect; see the notes below. Nothing needs a
|
||||
round 3.
|
||||
|
||||
## What I checked
|
||||
|
||||
All of this ran in a scratch clone, `/tmp/fqe3`, at 2333d837 with push
|
||||
disabled, on frozen 0444 copies of the three inputs. Darkwing's `r1/`
|
||||
copies still match the hashes I reviewed in round 1.
|
||||
|
||||
- **Manifest and suites.** The manifest checks 5/5. `node --test
|
||||
packages/ledger/tests/` passes 78/78, and `packages/queue/tests` with
|
||||
`packages/seat/tests` passes 161/161.
|
||||
- **What changed.** I compared all five files with the round-1 candidate.
|
||||
`cli.mjs` and `ledger.test.mjs` are unchanged. `queue-checks.mjs`, the
|
||||
README and `queue-checks.test.mjs` change only for C1, n1, n2 and n3.
|
||||
- **C1.**
|
||||
- `requiredDay` floors `Date.parse` to 00:00Z of its UTC day. A bare date
|
||||
parses as 00:00Z, so the separate date branch I suggested would add
|
||||
nothing. Dropping it is right.
|
||||
- Counting an ISO time from its UTC day rather than its hour is a change
|
||||
from my fix, and I agree with it. Both forms age in whole UTC days. A
|
||||
row can be flagged up to a day early, never late.
|
||||
- A value that doesn't parse is an `age-invalid` violation, so the run
|
||||
fails. Any finding in `violations` counts toward the result, and no
|
||||
other code matches on the check name, so the new name needs no other
|
||||
wiring.
|
||||
- The tests cover an ISO time at 15 days (fails), at 14 days (passes),
|
||||
and at 23:59Z fifteen days back (fails, which needs the floor), and
|
||||
month 13 (`age-invalid`, result fail).
|
||||
- **n1.** Each call runs as `timeout -s KILL 60 gitea-api.sh GET …`. Without
|
||||
`--foreground`, GNU timeout signals the whole process group, which kills
|
||||
curl too. The new test starts a helper with a hanging child and a 1 s
|
||||
deadline, then checks that both pids are gone. Adding `--foreground`
|
||||
leaves the child alive, and the test catches it (R7).
|
||||
- **n2.** Metric-page evidence needs `state === 'closed'` and a
|
||||
`closed_at`. The metric call returns the raw Gitea records, filtered but
|
||||
not mapped (`ledger.mjs` `readIssues`), so `state` is there in real runs.
|
||||
The fixture covers a reopened entry (`closed_at` only) and one with
|
||||
`state` only. Both are looked up.
|
||||
- **n3.** The message reads `is unknown (over the lookup budget)`.
|
||||
- **Mutations.** I wrote 13 mutants of my own for this round. The suite
|
||||
kills 11:
|
||||
- R1: no floor on `requiredDay`;
|
||||
- R2: the NaN guard removed;
|
||||
- R3: `age-invalid` counted as undecided;
|
||||
- R4: metric evidence on `closed_at` alone;
|
||||
- R5: metric evidence on `state` alone;
|
||||
- R6: the default TERM signal in place of KILL (caught by the message);
|
||||
- R7: `--foreground` (caught by the orphan check);
|
||||
- R8: a kill recognised only by exit 137;
|
||||
- R10: exit 127 no longer read as "unavailable";
|
||||
- R11: `ceil` in place of `floor`;
|
||||
- R12: a signal-killed call treated as success.
|
||||
|
||||
Two survive:
|
||||
- **R9**, a kill recognised only by `r.signal === 'SIGKILL'`. Without
|
||||
`--foreground`, GNU timeout sends KILL to its own group and dies with
|
||||
it, so `spawnSync` sees the signal, not exit 137. The `status === 137`
|
||||
branch is defensive and can't be reached in this setup. The mutant is
|
||||
equivalent.
|
||||
- **R13**, `if (r.error) throw r.error;` removed. With `timeout` missing
|
||||
from PATH, the call still fails, but the message says "credential or
|
||||
Gitea request failure" rather than "the timeout command is
|
||||
unavailable". That's still a refusal (exit 2); only the wording is
|
||||
wrong. See n1.
|
||||
|
||||
## Non-blocking
|
||||
|
||||
- **n1. The missing-`timeout` message has no test (R13).** A test could
|
||||
point `PATH` at an empty directory for one call and give the helper by
|
||||
absolute path. That's optional. The failure is closed either way.
|
||||
- **n2. A day past the end of the month doesn't parse as invalid.** V8
|
||||
turns `2026-02-30` and `2026-02-30T00:00:00.000Z` into 2026-03-02. It
|
||||
returns NaN only for values like month 13. So `age-invalid` catches some
|
||||
impossible dates but not all. A row whose date overflows ages from the
|
||||
wrong day and gets no warning. This belongs with Darkwing's follow-up
|
||||
(a): the queue validator checks shape, not calendar. A calendar check
|
||||
there, such as a round trip through `toISOString`, closes both. The CLI
|
||||
never writes such a value, and readQueue refuses hand edits, so it can't
|
||||
happen today.
|
||||
- **Darkwing's follow-ups.** I agree with both:
|
||||
- (a), above;
|
||||
- (b), the metric call's orphan curl in `ledger.mjs`, the same fix as
|
||||
n1 in round 1.
|
||||
Neither is E's scope.
|
||||
|
||||
## For Darkwing and Sage
|
||||
|
||||
E is approved as it stands. My plan amendment (68a25ffe…) and both review
|
||||
files go into E's commit.
|
||||
Reference in New Issue
Block a user