feat(board): FLY column and free-capacity lines, no process inspection

ORDRE counted pending orders only, so a repo with one order in flight and a
repo with no orders at all both printed 0 - the same digit for two opposite
facts. Measured 2026-08-23: two panes stood open and idle for 45 hours holding
finished orders, with full quota authorised, and no column on the board
reported it.

FLY counts orders/claimed/ - the same queue in its other state, never summed
with ORDRE and never a fourth axis. It does NOT mean a session is alive:
nothing un-claims an order when the claiming session dies, and one order on
the live mailbox had been claimed for 117 hours. The legend denies the
liveness reading in those words, pinned as a check on the legend text.

--plan now names free capacity as ledig_antall=N plus one ledig=<repo>
(<status>) line per repo with nothing owed, no pending order, nothing in
flight, a clean tree, at done or deferred. All four conditions are required:
measured on the real tree, 4 of 17 done/deferred repos were not free. Lines,
never blocks - the plan's second consumer discards a block with no tab=, so a
block would be visible to the operator and invisible to the driver.

Process inspection was considered and refused; the selftest asserts the
absence of pgrep/pkill/lsof structurally with a known-positive control. The
full argument, and the gap left open, are in
docs/2026-08-23-free-capacity-investigation.md.

board-selftest: 259 -> 281 checks, all green. npm test 11/11.

Order 20260823T162951Z-941745020-from-.claude

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Hfbfr8ERC63kuAfWeHYHQb
This commit is contained in:
Kjell Tore Guttormsen 2026-08-23 19:07:00 +02:00
commit 3beef2a603
12 changed files with 532 additions and 25 deletions

View file

@ -347,7 +347,7 @@ marketplace plugin. Three components, one boundary:
- **Board (`scripts/board.sh`):** cross-repo attention board. Reads STATE.md
next-step blocks + board lines, `git status`, and mailbox pending counts, and
prints one line per repo. Read-only by construction: it writes to no repo, no
STATE.md and no mailbox. Pinned by `board-selftest.sh` (259 checks).
STATE.md and no mailbox. Pinned by `board-selftest.sh` (281 checks).
**It lives here because the mailbox is one of its three inputs, and it carries
the same axis distinction the mailbox does.** A pending count means *others
@ -805,12 +805,77 @@ marketplace plugin. Three components, one boundary:
- **Board's ORDRE column** counts PENDING orders per repo with the identical
idiom as INN, and the two are never summed: INN is "others are waiting on
YOU" (outgoing obligation), ORDRE is "authorized work is waiting on this
REPO" (incoming). Claimed orders are excluded — the column answers what a
session can pick up, and one in flight cannot be. Bounded gap, stated rather
REPO" (incoming). Claimed orders are excluded from THIS column — it answers
what a session can pick up, and one in flight cannot be; since 0.29.0 they are
counted in FLY beside it (below). Bounded gap, stated rather
than closed: an order addressed to a mailbox with no matching directory in
the scanned roots is invisible here, exactly as mail to such a name is
invisible in INN. There is deliberately no join built for it; `coord-count.sh`
is the cross-check for the mail half only.
**Order 20260823T162951Z (2026-08-23): ORDRE alone printed the SAME DIGIT for
two opposite facts, and the fix is a second column, not a fourth axis.** A
repo holding one order in flight and a repo holding no orders at all both
read `ORDRE 0` — "work is happening here" and "nothing is waiting here" were
byte-identical rows. Measured the day the order was written: two panes stood
open and idle for 45 hours holding finished orders, during a window with full
quota authorised, and no column on the board reported it. **FLY** counts
`orders/claimed/` — the same queue in its other state, which is why it is a
second reading of a source the board already had rather than a new source,
and why it is never summed with ORDRE. Verified live: one repo moved from
`ORDRE 0` to `ORDRE 0 / FLY 1` beside another still reading `ORDRE 0 / FLY 0`
and genuinely holding nothing.
**FLY never claims a session is ALIVE, and the legend denies it in those
words.** A claim is a `mv` a session performed once; nothing un-claims it when
that session dies, which is precisely why `coord-order-inbox.sh` already shows
claimed orders with their in-flight age. Measured on the live mailbox the same
day: one order had been claimed for **117 hours**. Rewording the column into
"a session is running here" would be the process axis smuggled in as a file
count. Selftest section 24 pins the denial as a check on the legend TEXT, so
the wording is engine behavior and not prose.
**There is deliberately NO process inspection, and the selftest asserts it
structurally** (no live `pgrep`/`pkill`/`lsof` in `board.sh`, with a
known-positive control proving the grep can find a planted call). Four
compounding reasons, argued in full in
`docs/2026-08-23-free-capacity-investigation.md`: every other column is a
durable filesystem fact reproducible in the fixture tree, while a process
column measures one instant on one machine and could not be pinned here at
all; mapping a process to a repo needs its cwd, hence `lsof`, against the
zero-dependency convention; the ELAPSED-vs-CPU discriminator is a THRESHOLD,
so encoding it makes the board decide a session is dead — the identical thing
the order queue is already forbidden from doing ("nothing here expires
anything, and building expiry would make the engine decide that a session is
dead, which it cannot know"); and the file-based signal is no substitute
either, which the 117-hour claim proves. The board reports the claim and
refuses the inference.
- **`--plan` NAMES free capacity as lines, never as blocks (0.29.0).**
`ledig_antall=N` plus one `ledig=<repo> (<status>)` line per repo with nothing
OWED (not merely nothing pending — a notice is not an obligation), no pending
order, nothing in flight, a clean tree, at `done` or `deferred`. Such a repo
has no next step to open a tab for and used to simply fall OUT of the plan —
correctly, but silently, which is where new capacity went unseen. All four
conditions are load-bearing and `status=done` alone is not the same set:
measured 2026-08-23, 13 of 17 done/deferred repos were free and 4 were not
(two held a pending order, one owed a reply, one had an uncommitted tree).
The LINE form is the design, not a formatting choice: `morning`'s `plan_parse`
discards a block with no `tab=`, so a free repo emitted as a block would be
visible to the operator and invisible to the driver, while a line is visible
to both and can never be opened as a tab. The count prints at 0 too, so
"none found" and "not computed" cannot render the same. A tab block whose repo
holds a claimed order carries `fly=N` beside it — the driver types into live
panes.
**The gap this leaves is stated rather than closed:** an open, idle pane in a
`planned` repo with no claimed order and no queued work is still invisible,
and cannot be seen from the filesystem at all — nothing there distinguishes
it from a `planned` repo nobody has opened. Closing it needs a terminal-side
measurement supplied INWARD by the driver that already probes panes, the same
way `--dispatch` requires `--target-pane` to be measured by the caller and
passed in. Not built: not ordered, and keeping it out keeps the dependency
running one way.
- **Skills (`skills/coord-send/`, `skills/board/`, `skills/route/`, `skills/dispatch/`):** natural-language front
doors mapping user intent to engine invocations. No mailbox logic lives here
either. `board` additionally owns the *ranking* — which repo wins and why —
@ -859,7 +924,7 @@ obligations in another repo.
builtins only in hook and tests.
- TDD: no behavior change without a failing selftest check first.
`bash scripts/coord-selftest.sh` must exit 0 (230/230),
`bash scripts/board-selftest.sh` must exit 0 (259/259),
`bash scripts/board-selftest.sh` must exit 0 (281/281),
`bash scripts/route-selftest.sh` must exit 0 (69/69),
`bash scripts/orders-selftest.sh` must exit 0 (110/110) and
`bash scripts/state-line-guard-selftest.sh` must exit 0 (40/40).