fix(board): plan/brief use actual debt, not raw pending mail

board.sh --plan group 2 and --brief conflated every unhandled inbox
message with an obligation to reply, including ones the sender
declared reply-expected: no (a notice, not a request). Reported by
morning-driver (2026-08-11), independently reproduced against the live
mailbox on 2026-08-13: 27 of 72 pending messages were notices. Both
paths now join against coord-count.sh's owed column instead, so a
done/deferred/blocked repo whose only mail is FYI no longer gets a
plan tab, and --brief no longer counts a notice as an obligation. The
table's raw INN column is unchanged by design.

While extending that join with a second lookup file, found and fixed
a more severe, independent defect: the existing $UNBLOCKS/$RECORDS
join used the NR==FNR awk idiom, which silently empties the entire
plan whenever the first file is empty -- i.e. whenever the repo tree
has zero blocked repos, a common, ordinary state, not an edge case.
Verified against the shipped 0.21.0 script. Fixed by matching on
FILENAME instead of cumulative line counts, for both lookup files.

Also repoints the README's governance link at repo-standard's
canonical GOVERNANCE.md (was pointing at the marketplace's copy),
per org-ops D11.

board-selftest.sh: 142 -> 150 checks. Version 0.21.0 -> 0.22.0.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WGWMPskXBsTjMrrQ2GofFx
This commit is contained in:
Kjell Tore Guttormsen 2026-08-13 20:54:55 +02:00
commit 03e712a423
10 changed files with 263 additions and 27 deletions

View file

@ -54,7 +54,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` (142 checks).
STATE.md and no mailbox. Pinned by `board-selftest.sh` (150 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
@ -118,6 +118,53 @@ marketplace plugin. Three components, one boundary:
section 12 pins both halves - the root outranking four owed messages, and the
debtor keeping everything it had.
**"Debt" means OWED, never raw pending, since 0.22.0 - and this NARROWS what
counts as debt, it does not reopen the paragraph above.** The paragraph above
settles a different question: once a repo has debt, is it ever excluded or
capped (no). This one settles what counts as debt in the first place. Through
0.21.0, group 2's `keep`/`mag`/`why=inbox:N` and `--brief`'s whole "repo som
skylder et svar" listing were computed from the raw pending-file count - every
unhandled message in the inbox, including ones the sender declared
`reply-expected: no`. That is a notice, not a request, and 0.11.0 gave
`coord-count.sh` a second column (`owed`) for exactly this distinction - but
`board.sh` never read it. Reported by morning-driver (2026-08-11) and
independently reproduced against the live mailbox 2026-08-13: 27 of 72
pending messages (37.5%) were notices. The fix joins `--plan` and `--brief`
against `coord-count.sh`'s `owed` column by repo name (same technique as the
chain-root `$UNBLOCKS` join below), so a `done`/`deferred`/`blocked` repo
whose only mail is FYI no longer gets a tab, and `--brief` no longer counts a
notice as an obligation. This reverses a decision from session 41
(2026-08-10) that declined to build this filter, on the premise that "the
arrival of the request IS the admission signal" - a premise that assumed
group 2 already meant requests. It didn't; the code computed pending, the
comments already said "owed" throughout, and the plan's own printed header
("Utelatt naar repoet verken skylder svar...") already claimed the exclusion
was debt-based. The fix makes the code match what its own comments and
header already promised. Pinned by board-selftest.sh section 8/12 fixtures
`repo-done-fyi` (pending 2, owed 0 - excluded) and `repo-blocked-mixed`
(pending 3, owed 2 - planned on 2, not 3). The TABLE's `INN` column and the
raw scan (`RECORDS` field 6) are UNCHANGED - they answer "what is the state
of every repo," not "who is waiting on you," and stay on raw pending by
design.
**The `$UNBLOCKS`/`$RECORDS` join used `NR==FNR` through 0.21.0, and that
idiom silently drops the entire plan whenever the FIRST file is empty - fixed
to `FILENAME==` comparison in 0.22.0, found while adding the `$OWED` join
above.** Verified against the shipped 0.21.0 script: one in-progress repo
with an unhandled inbox message, zero blocked repos anywhere in the tree
(so `$UNBLOCKS` is empty, which is a common, ordinary tree state, not an
edge case) - `--plan` printed "0 tabber". `NR==FNR` is only true for the
FIRST file's own lines; when that file is empty, `FNR` and `NR` stay equal
for the ENTIRE next file too (not just its first line - verified with a
minimal awk reproduction), so every record in it is misrouted into the
`ub[]` branch and dropped via `next`. This was invisible to
board-selftest.sh because the fixture tree has carried at least one
`blocked` repo since the chain-root feature shipped, and it was invisible
on the real tree because `~/repos` currently always has one too - neither
is a guarantee. `FILENAME==UBF`/`FILENAME==OWF` compares the exact path,
never line counts, so an empty lookup file degrades to "nothing matched,"
never to "everything after it is misrouted."
**Chain-root credit lands on the ROOT and nowhere else.** For every `blocked`
repo the `blocked-on` edge is followed transitively to the first repo that is
not itself blocked. Crediting a blocked repo would open a tab that cannot move;
@ -300,7 +347,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 (191/191),
`bash scripts/board-selftest.sh` must exit 0 (142/142) and
`bash scripts/board-selftest.sh` must exit 0 (150/150) and
`bash scripts/route-selftest.sh` must exit 0 (69/69).
- English for all code, docs, and commit messages (public repo). Norwegian
trigger aliases in the skill description are deliberate.