fix(board): --brief's zero-debt branch no longer claims zero pending mail

Found in review of the previous commit, before catalog tags 0.22.0: once
n_owe counts OWED repos rather than raw pending, its ==0 branch could fire
while a repo still held FYI-only mail, making "Ingen repo har uhaandtert
innboks" false at the exact moment it printed. Fixed to state only the
debt claim, and to name any FYI-only mailboxes found instead of letting
their existence go unmentioned. No fixture in the shared test tree ever
reached n_owe==0 (it always carries a debtor), so this needed its own
isolated-root fixture to pin.

Still part of the 0.22.0 release -- amends that changelog entry rather
than bumping again, since no tag exists yet.

board-selftest.sh: 150 -> 152 checks.

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 21:07:39 +02:00
commit a56478bcfa
5 changed files with 80 additions and 10 deletions

View file

@ -30,9 +30,17 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
hit `--brief` too, whenever there was neither chain-root credit nor debt on hit `--brief` too, whenever there was neither chain-root credit nor debt on
a given day). Verified against the shipped 0.21.0 script with a minimal a given day). Verified against the shipped 0.21.0 script with a minimal
reproduction. Fixed by matching on `FILENAME` instead of line counts. reproduction. Fixed by matching on `FILENAME` instead of line counts.
- **`--brief`'s zero-debt branch claimed no mail was pending, not just that
none was owed.** A side effect of the first fix above: once `n_owe` counts
OWED repos, its `== 0` branch can fire while a repo still holds FYI-only
mail, making the branch's first sentence ("Ingen repo har uhaandtert
innboks") false the moment that happens. Fixed to state only the debt
claim, and to name any FYI-only mailboxes found instead of letting their
existence go unmentioned in that branch.
`board-selftest.sh`: 142 -> 150 checks (fixtures for FYI-only mail, `board-selftest.sh`: 142 -> 152 checks (fixtures for FYI-only mail, mixed
mixed owed/pending mail, and the zero-blocked-repos case). owed/pending mail, the zero-blocked-repos case, and the zero-debt
briefing's own false claim).
## [0.21.0] - 2026-08-09 ## [0.21.0] - 2026-08-09

View file

@ -54,7 +54,7 @@ marketplace plugin. Three components, one boundary:
- **Board (`scripts/board.sh`):** cross-repo attention board. Reads STATE.md - **Board (`scripts/board.sh`):** cross-repo attention board. Reads STATE.md
next-step blocks + board lines, `git status`, and mailbox pending counts, and 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 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` (150 checks). STATE.md and no mailbox. Pinned by `board-selftest.sh` (152 checks).
**It lives here because the mailbox is one of its three inputs, and it carries **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 the same axis distinction the mailbox does.** A pending count means *others
@ -147,6 +147,20 @@ marketplace plugin. Three components, one boundary:
of every repo," not "who is waiting on you," and stay on raw pending by of every repo," not "who is waiting on you," and stay on raw pending by
design. design.
**The same 0.22.0 patch that switched `n_owe` to OWED also had to fix what
`n_owe == 0` claims.** `--brief`'s empty-debt branch said "Ingen repo har
uhaandtert innboks. Ingen skylder noen et svar i dag." (no repo has
unhandled inbox; nobody owes a reply) - two claims in one branch, and only
the second is what `n_owe == 0` actually proves once `n_owe` means OWED. A
repo can hold FYI-only mail with zero debt, which makes the first sentence
false while it fires - caught in review before release, not by any fixture
(the shared test tree never reaches `n_owe == 0`, since it always carries a
debtor). Fixed to state only the debt claim, and to name any FYI-only
mailboxes found rather than let their existence become invisible again -
the same "labelled, not silently dropped" principle `--plan` already
applies to unknown-status repos. Pinned by board-selftest.sh section 14
with its own isolated root (debt-free, one FYI-only repo).
**The `$UNBLOCKS`/`$RECORDS` join used `NR==FNR` through 0.21.0, and that **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 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 to `FILENAME==` comparison in 0.22.0, found while adding the `$OWED` join
@ -347,7 +361,7 @@ obligations in another repo.
builtins only in hook and tests. builtins only in hook and tests.
- TDD: no behavior change without a failing selftest check first. - TDD: no behavior change without a failing selftest check first.
`bash scripts/coord-selftest.sh` must exit 0 (191/191), `bash scripts/coord-selftest.sh` must exit 0 (191/191),
`bash scripts/board-selftest.sh` must exit 0 (150/150) and `bash scripts/board-selftest.sh` must exit 0 (152/152) and
`bash scripts/route-selftest.sh` must exit 0 (69/69). `bash scripts/route-selftest.sh` must exit 0 (69/69).
- English for all code, docs, and commit messages (public repo). Norwegian - English for all code, docs, and commit messages (public repo). Norwegian
trigger aliases in the skill description are deliberate. trigger aliases in the skill description are deliberate.

View file

@ -14,7 +14,7 @@ Session A in repo X leaves a message for repo Y; the next session in repo Y gets
![Hooks](https://img.shields.io/badge/hooks-1-green) ![Hooks](https://img.shields.io/badge/hooks-1-green)
![Skills](https://img.shields.io/badge/skills-3-orange) ![Skills](https://img.shields.io/badge/skills-3-orange)
![CLI scripts](https://img.shields.io/badge/CLI_scripts-8-blue) ![CLI scripts](https://img.shields.io/badge/CLI_scripts-8-blue)
![Selftest checks](https://img.shields.io/badge/selftest_checks-410-blue) ![Selftest checks](https://img.shields.io/badge/selftest_checks-412-blue)
--- ---
@ -162,7 +162,7 @@ Note that raising the inbox's priority (Rule 7) deliberately does **not** widen
## Development ## Development
bash scripts/coord-selftest.sh # 191 checks against a throwaway mailbox bash scripts/coord-selftest.sh # 191 checks against a throwaway mailbox
bash scripts/board-selftest.sh # 150 checks against a throwaway repo tree bash scripts/board-selftest.sh # 152 checks against a throwaway repo tree
bash scripts/route-selftest.sh # 69 checks, incl. the route->board round trip bash scripts/route-selftest.sh # 69 checks, incl. the route->board round trip
npm test # all three selftests via node --test npm test # all three selftests via node --test

View file

@ -1250,6 +1250,42 @@ check "the surviving repo still ranks correctly with an empty chain-root lookup"
/bin/rm -rf "$NOBLOCK_ROOT" "$NOBLOCK_COORD" 2>/dev/null /bin/rm -rf "$NOBLOCK_ROOT" "$NOBLOCK_COORD" 2>/dev/null
# --- 14. Zero-debt briefing must not claim zero pending mail ---------------
# n_owe now counts OWED repos (section 8's fix), so its ==0 branch can fire
# while a repo still holds FYI-only mail - a real, re-injected message the
# operator has not seen answered here. The branch's own first sentence used
# to say "Ingen repo har uhaandtert innboks" (no repo has unhandled inbox),
# which becomes FALSE the moment that happens; its second sentence ("ingen
# skylder svar") stays true. Isolated root: the shared fixture tree never
# reaches n_owe==0 (repo-a and friends always owe something), so this branch
# is otherwise never exercised by this suite.
NODEBT_ROOT="$(mktemp -d)"
NODEBT_COORD="$(mktemp -d)"
mkrepo "$NODEBT_ROOT/fyi-only-repo"
{
echo "# STATE - fyi-only-repo"
printf '## %s NESTE %s START HER\n' "$HAND" "$EMDASH"
echo "<!-- board: status=done; blocked-on=-; next-cost=Sonnet 5/high -->"
echo "Ferdig, ett varsel ligger i innboksen."
} > "$NODEBT_ROOT/fyi-only-repo/STATE.md"
mkdir -p "$NODEBT_COORD/fyi-only-repo/inbox"
{
echo "---"
echo "from: w"
echo "to: fyi-only-repo"
echo "reply-expected: no"
echo "---"
echo "fyi only"
} > "$NODEBT_COORD/fyi-only-repo/inbox/2026-msg1-from-w.md"
NODEBT_BRIEF="$(CLAUDE_COORD_DIR="$NODEBT_COORD" "$BOARD" --roots "$NODEBT_ROOT" --brief 2>/dev/null)"
printf '%s' "$NODEBT_BRIEF" | grep -qi 'ingen.*uhaandtert innboks'; [ $? -ne 0 ]
check "zero-debt briefing never claims no mail is pending when FYI mail exists" $?
printf '%s' "$NODEBT_BRIEF" | grep -q 'Ingen repo skylder'
check "zero-debt briefing still states the true claim: nobody owes a reply" $?
/bin/rm -rf "$NODEBT_ROOT" "$NODEBT_COORD" 2>/dev/null
echo "" echo ""
echo "board-selftest: $PASS passed, $FAIL failed" echo "board-selftest: $PASS passed, $FAIL failed"
[ "$FAIL" -eq 0 ] || exit 1 [ "$FAIL" -eq 0 ] || exit 1

View file

@ -495,9 +495,21 @@ brief() {
echo "" echo ""
if [ "${n_owe:-0}" -eq 0 ]; then if [ "${n_owe:-0}" -eq 0 ]; then
echo "Ingen repo har uhaandtert innboks. Ingen skylder noen et svar i dag." # Only the debt claim, never "no repo has unhandled inbox": n_owe counts
# Still checked: "no repo owes" and "no mailbox is pending" are different # OWED repos since 0.22.0, so this branch can fire while a repo still
# claims, and only the second one is the good news it reads as. # holds FYI-only mail - real, re-injected, just not an obligation. The
# comment this replaces already named the two claims as different; the
# code used to make both anyway.
echo "Ingen repo skylder noen et svar i dag."
fyi_only="$(awk -F'|' -v OWF="$OWED" '
FILENAME==OWF { ow[$1] = $2 + 0; next }
{ name = $3; inbox = $6 + 0; owed = (name in ow) ? ow[name] : 0
if (inbox > 0 && owed == 0) print name }
' "$OWED" "$RECORDS")"
if [ -n "$fyi_only" ]; then
echo "Disse har bare FYI-post (ingen skylder svar, men posten er reell):"
printf '%s\n' "$fyi_only" | sed 's/^/ /'
fi
brief_orphans brief_orphans
echo "" echo ""
echo "MERK: INN teller hva ANDRE venter paa fra deg. Hva et repo venter PAA" echo "MERK: INN teller hva ANDRE venter paa fra deg. Hva et repo venter PAA"