control board: brief for piece 3, the ledger, with Gate D (#1503)

Co-Authored-By: Claude Fable 5.1 <[email protected]>
This commit is contained in:
2026-09-12 11:42:28 -05:00
co-authored by Claude Fable 5.1
parent c90ce3a836
commit 0334026e13
@@ -165,6 +165,79 @@ message reflects the reply. One real exchange is the pass.
finding (ms-communications rule), reported on the page and in the issue. finding (ms-communications rule), reported on the page and in the issue.
- Fleet seats are out: they are not registered (Jason, 2026-09-12). - Fleet seats are out: they are not registered (Jason, 2026-09-12).
## Piece 3: ledger (numbers for the rails)
Approved by Jason 2026-09-12 ("go for it") after Gate C. Purpose: replace
"it feels like the rails work" with numbers read from evidence that already
exists. Same shape: one package, one gate, tests, small commits on `refactor`.
Darkwing opens the issue and updates `CURRENT.md` when he picks it up.
### What it is
`node packages/ledger/src/cli.mjs --since YYYY-MM-DD [--until YYYY-MM-DD]
[--json] [--no-issues]`. Read-only. Prints two tables and a totals line to
stdout; `--json` prints the same as one object. Sources, in this order:
1. **Local git log on `refactor`** (no network): commits in range whose
subject carries `#N`. Nothing else in the message is parsed.
2. **Gitea issues** through `scripts/gitea-api.sh` (`repos/mosaicstack/stack`):
number, title, `created_at`, `closed_at`. Missing or unreadable
credentials: exit 2 with the reason, unless `--no-issues`, which prints the
issue table with hours-open as "unknown".
3. **Session logs** under `.pi/state/<seat>/sessions/*.jsonl` for the seats
in `agents/` (repo layout only, no fleet sources): entries with
`type: "message"` and `message.role: "user"` whose timestamp falls in range.
Table 1, one row per issue touched in range (a commit or a close in range):
issue, title (truncated), opened, hours open (or "open"), commits,
follow-ups (commits after the first one for that issue), seats (from the
session logs that mention `#N` in a user message; blank when none).
Table 2, one row per seat: user messages in range split three ways by the
first line of the text: **board** (preamble source session `control-board`),
**agent** (any other `[a:b -> c:d]` preamble), **human** (no preamble).
Totals line: issues closed, median hours open, commits, follow-ups per issue
(one decimal), human messages per closed issue (one decimal).
### What it does not claim
- Agent versus human authorship. Every commit and every issue carries
Jason's identity; the ledger does not guess.
- PR metrics. This work is issue-tagged commits on `refactor`; a PR count
would read zero and mean nothing.
- Gate results. Those are written by hand in this log.
- Quality. A follow-up commit is a count, not a verdict; the reader decides.
### Gate D (yes/no)
Jason runs the ledger for 2026-09-06 to 2026-09-12, picks the one number he
wants to move next week, and writes that sentence with the number into
`CURRENT.md`. Pass: the sentence exists, and its number came from the
ledger's output, not from memory.
### Tests
- Fixture git repository built in a temp dir with commits tagged `#1`,
`#2` (two commits, so one follow-up) and one untagged commit; table 1
counts match.
- Fixture session logs with one message of each kind (board, agent, human)
and one out of range; table 2 counts match.
- Fake `gitea-api.sh` on PATH returning fixture JSON; hours open computed
from `created_at`/`closed_at`; an open issue prints "open".
- Missing credentials: exit 2 with a message; with `--no-issues`, exit 0 and
"unknown" in the hours column.
- Empty range: both tables with zero rows, totals of zero, exit 0.
- `--json` output parses and carries the same numbers as the tables.
### Boundaries
- Read-only. No writes under the data root, no board changes, no new root
files, no schedule, no charts, no `packages/mosaic` changes.
- No fleet sources (`~/.mosaic`) and no network beyond the one Gitea call.
- If a number cannot be computed from evidence it prints "unknown", never a
guess.
## How the scanner decides state ## How the scanner decides state
The scanner reads the newest pi session log for each agent, plus whether The scanner reads the newest pi session log for each agent, plus whether