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:
2026-09-27 11:33:44 -05:00
co-authored by Claude Opus 5.5
parent 2333d837e2
commit fd72d26899
9 changed files with 1421 additions and 20 deletions
+114 -10
View File
@@ -2,7 +2,9 @@
Read-only counts from local `refactor` commit subjects, one Gitea issue-list
request through `scripts/gitea-api.sh`, repo seats' Pi session logs, and T3's
thread messages in `~/.t3/userdata/state.sqlite`.
thread messages in `~/.t3/userdata/state.sqlite`. Above the counts, a queue
section checks `docs/plans/queue.json` against Gitea and the seat
registrations (see "Queue section").
No board changes, data-root writes, fleet reads, transcript output, or scheduler.
```sh
@@ -11,6 +13,8 @@ node packages/ledger/src/cli.mjs --since 2026-09-06 --until 2026-09-12 --json
node packages/ledger/src/cli.mjs --since 2026-09-06 --no-issues
node packages/ledger/src/cli.mjs --since 2026-09-06 --no-t3
node packages/ledger/src/cli.mjs --since 2026-09-06 --t3-db /tmp/fixture.sqlite
node packages/ledger/src/cli.mjs --since 2026-09-06 --no-queue
node packages/ledger/src/cli.mjs --since 2026-09-06 --unsupported-runtime dewey
node --test packages/ledger/tests/
```
@@ -125,27 +129,127 @@ directory means no Pi seats ran here; a missing T3 database means the path or
T3 changed, so it refuses instead of counting zero. Error messages name ids
and paths, never message text.
## One Gitea call and missing evidence
## Queue section
The client requests issues updated since the start date, all states, first page,
limit 50. This includes issues closed in range, even if later updated. Gitea caps
The rules come from plan 8.10 in
`agents/filbert/work/queue-as-data-plan-2026-09-26.md` (Piece E, #1508). The
section is on by default and prints above the weekly table; `--json` puts it
under `queue`. `--no-queue` skips it and prints `Queue: not checked
(--no-queue)`. The ledger reads `docs/plans/queue.json` through the queue's own
validator before any Gitea call, so a missing, symlinked or hand-edited file
exits 1 and costs no call. It writes nothing, takes no queue lock and never
changes a row.
Four checks, each finding named by (check, row, issue):
- **Issues.** An issue is expected closed once every row whose `closes`
includes it is done (J6). Then an open issue is `issue-open` on each of
those rows. A row that names an issue in `issues` but not in `closes` is
never checked against it, so rows 9 to 12 can be done while #1508 is open.
A closed issue with a row that closes it still pending is printed as a
`disposition` for the lead. It is not a violation.
- **Owners.** Each owner of an `in-progress` or `in-review` row gets one
liveness class from its `repo` registration in `<dataRoot>/seats`:
- `exempt`: declared on this run with `--unsupported-runtime SEAT`.
- `missing`: no registration, or one written for another checkout.
- `invalid`: the registration fails validation, or the config that names
the data root can't be read. A malformed or absent pid fails validation,
so it lands here, not in `pid-unknown`.
- `pid-unknown`: a valid registration with a null pid.
- `pid-gone`: the recorded pid is not running.
- `pid-present`: the pid is running. A registration holds no process-start
identity and a reused pid looks the same, so this reads "pid present
(identity not verified)".
The coverage line counts them: `liveness: N pid-present (unverified), N
exempt, N pid-unknown, N missing, N invalid, N pid-gone`.
- **Age.** A required row that is not done and whose `requiredSince` is more
than 14 whole days before the run is listed by name. `requiredSince` is a
date at genesis and an ISO time once `set required` or `add --required`
writes it; both count from 00:00Z of their UTC day. A value that doesn't
parse as a date is an `age-invalid` violation. A legacy row with
`requiredSince: "unknown"` was required no later than genesis, so once
genesis is more than 14 days old it is listed as `age ≥ N days (legacy lower
bound)`. Before that its age is undecided. Age runs to the time of the run,
not to `--until`.
- **Protected changes.** Every log entry dated inside the report range that
changes a required or parked row is listed with its revision, verb, claimed
actor and rows. The queue trusts `--by` (its README, "Trust boundary"), so
this list is how a wrong claim gets seen. It is not a check and never
changes the result. Jason confirms the actors weekly.
Every run ends with `queue: N violations; result R`, where R is one of three:
- `fail`: any violation. That is an open issue, a `missing`, `invalid` or
`pid-gone` owner, or an age.
- `incomplete`: no violation, but something undecided. That is an issue
whose state is unknown, a full open-issue page with some issue left
unknown, issue checks not run, a
`pid-unknown` owner, or a legacy age before genesis is 14 days old.
- `reduced pass`: nothing known and nothing undecided. There is no full pass,
because no owner's process identity is ever verified.
The exit code stays 0 whenever a report was computed; the result is in the
text and the JSON.
### Queue issue calls
The metric call can't answer "is this issue closed", because an issue nobody
touched this week isn't in it. So the queue checks have their own budget, per
plan 8.10:
| Calls | Purpose |
|---|---|
| 1 | `state=open`, issues only, limit 50, one page. A full page may be short. Issues missing from it are looked up, so it makes the run `incomplete` only while some issue a row closes has no known state (lead decision 40). |
| up to 10 | `GET issues/N` for each issue in some row's `closes` that is neither on the open list nor closed on the metric page. Beyond 10, the issue's state is unknown, "over the lookup budget". |
With the metric call that is at most 12 calls. `--no-issues` makes none and
prints `queue issue checks: not run`. Closed needs positive evidence: an
entry on the metric page with state `closed` and a `closed_at`, or a lookup
returning a closed issue that isn't a pull request. A failed lookup, a 404, a
pull request or a mismatched number leaves the issue unknown. A failed or
malformed open-list call exits 2, like the metric call. Each queue call runs
under `timeout -s KILL 60`, which kills the helper and its curl together.
### Weekly routine
Run the ledger each Monday for the week that ended on Saturday, Sunday
through Saturday as row 7 counts it, with the queue section on and no
`--no-issues`:
```sh
node packages/ledger/src/cli.mjs --since 2026-09-20 --until 2026-09-26
```
Post the dated run on #1508 with its coverage line and result. Any seat
whose runtime writes no registration (T3 seats today) is passed with
`--unsupported-runtime SEAT`, which the output prints. A violation is
remediated when a second dated run on the same UTC day no longer reports its
(check, row, issue), and both runs are posted on #1508. A row edit alone is
not remediation, and a `note` can't clear an owner finding, because that
check reads registrations. Row 13's gate is one Monday run with zero
violations, or every violation remediated that day.
## Gitea calls and missing evidence
The metric client requests issues updated since the start date, all states,
first page, limit 50. This includes issues closed in range, even if later updated. Gitea caps
responses at 50; a full page fails rather than silently reporting partial totals.
Use a narrower range or `--no-issues`, not hidden pagination. A commit-linked
issue not returned by the updated-since query still has a row, with unknown
metadata. This is the cost of the brief's one-call boundary.
Exit 0 means a report was computed. Exit 1 means bad arguments or unreadable git,
session or T3 evidence. Malformed JSONL, including a partially written last line,
Exit 0 means a report was computed. Exit 1 means bad arguments, a queue.json
the validator refuses, or unreadable git, session or T3 evidence. Malformed JSONL, including a partially written last line,
refuses the report; rerun after the seat finishes writing. Exit 2 means issue
credentials, API, payload, or completeness failure. The CLI never prints API
error bodies or reads authentication files itself. `--no-issues` makes no API
call, keeps commit-derived rows, and shows unknown issue metadata, closed counts,
error bodies or reads authentication files itself. The queue's open-list call
fails with exit 2 the same way. `--no-issues` makes no API call, keeps commit-derived rows, and shows unknown issue metadata, closed counts,
median duration, and human-per-closed ratio. It cannot invent close-only rows.
For fixtures, a fake `gitea-api.sh` can be placed first on PATH. Otherwise the
repository scripts directory is appended to PATH for the issue request.
Tests use only temporary repositories, logs, T3 databases and fake API tools,
with no real credentials or network. Every CLI run in the tests sets `HOME` to
Tests use only temporary repositories, logs, T3 databases, queue files, seat
registrations, configs and fake API tools, with no real credentials or network. Every CLI run in the tests sets `HOME` to
a temporary directory, so no test opens the real `~/.t3`. The helper regression stubs Node before any credential
read and checks successful GET, successful POST, and failed HTTP status.