feat(board)!: rank --plan on five ordered groups, planned above in-progress

Replaces the weighted score shipped in 0.19.0 with five lookups: chain-root
credit, unhandled inbox, planned, in-progress, undeclared status. Within a
group: that group's own quantity, then a Sonnet next-cost, then oldest plan.

The score's objection is accepted, not forgotten, and is written into board.sh
and CLAUDE.md so a later session reads it as decided rather than as an unfixed
defect: a group order cannot express "owes one message AND releases two others"
as one quantity. What the score could not do was hold still for the format's
second consumer - re-tuning one weight against another silently reorders a
parser in another repo, and no test here can catch that.

planned now ranks above in-progress, inverted by the same decision: converting a
decision into motion is the slow step; live work is already moving.

Debt stays uncapped and never excluded. One group below chain-root credit is not
the cap declined at 0.19.0 - the debtor keeps its tab, its most-owed-first
position, and its why=inbox:N. Pinned by a discriminating fixture the score
would fail: a root releasing one repo outranks a repo owing four.

board-selftest 134 -> 138. Suite 183 + 138 + 73 = 394.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Y6ULuFCPMNYAPNN3pAjsXQ
This commit is contained in:
Kjell Tore Guttormsen 2026-08-03 06:55:21 +02:00
commit d0a5ffe515
10 changed files with 287 additions and 118 deletions

View file

@ -591,10 +591,18 @@ check "a BLOCKED repo that owes mail is still planned (debt is the other axis)"
printf '%s\n' "$PLAN" | grep -A4 '^repo=repo-blocked-owes$' | grep -q '^why=inbox:2$'
check "a blocked repo in the plan says debt, not status, is why it is there" $?
# Debt before live work: repo-h is in-progress and owes nothing.
# Debt before live work: repo-h is in-progress and owes nothing. Stated as
# group 2 over group 4, NOT as "before any that owes none" - since 0.20.0 the
# chain-root group sits above debt, so a repo owing nothing CAN outrank one that
# does. Section 12 pins that half on the fixture that shows it cleanly.
n_h="$(printf '%s\n' "$PLAN" | grep -n '^repo=repo-h$' | cut -d: -f1)"
[ -n "$n_h" ] && [ "$n_ow" -lt "$n_h" ]
check "every repo owing mail is planned before any that owes none" $?
check "a repo owing mail is planned before live work that owes and releases nothing" $?
# Group 2 over group 3: repo-g is planned and owes nothing.
n_g="$(printf '%s\n' "$PLAN" | grep -n '^repo=repo-g$' | cut -d: -f1)"
[ -n "$n_g" ] && [ "$n_ow" -lt "$n_g" ]
check "a repo owing mail is planned before a repo that merely has a plan" $?
# repo-a is BOTH in-progress and owing. It must appear exactly once - a repo
# listed twice is two tabs for one repo, which is the plan failing at its job.
@ -602,17 +610,28 @@ check "every repo owing mail is planned before any that owes none" $?
check "a repo qualifying under two rules is planned exactly once" $?
# repo-a owes 3 messages AND is the blocker plug-x and repo-blocked-owes both
# name. why= reports the DOMINANT term, so it says what a session there would
# release rather than what it owes - the whole reason this field was changed.
# Before chain-root credit existed this read `inbox:3`, and that was the line
# eleven blocks repeated verbatim.
# name. Under the group form why= names the GROUP that put the repo in the plan,
# and the chain-root group is the first one tested - so it reports what a session
# there would release rather than what it owes. Between 0.19.0 and 0.20.0 the
# same assertion held for a different reason (the dominant term of a score);
# the reason is written down because the check passes either way and only the
# reason says which form is running.
printf '%s\n' "$PLAN" | grep -A3 '^repo=repo-a$' | grep -q '^why=unblocks:2$'
check "why= names the dominant reason, releasing over owing when it dominates" $?
check "why= names the group that planned the repo, releasing over owing" $?
# in-progress outranks planned among the repos owing nothing.
n_g="$(printf '%s\n' "$PLAN" | grep -n '^repo=repo-g$' | cut -d: -f1)"
[ -n "$n_g" ] && [ "$n_h" -lt "$n_g" ]
check "live work (in-progress) is planned before merely planned work" $?
# planned outranks in-progress among the repos that neither owe nor release.
# INVERTED at 0.20.0 by operator decision: a decided-but-unstarted next step is
# the step that converts a decision into motion, and live work is already moving.
# This is a policy call, not a preference - it is recorded here and in board.sh
# so a later session cannot read it as a sort that drifted.
[ -n "$n_g" ] && [ "$n_g" -lt "$n_h" ]
check "planned work is planned before live in-progress work (operator inversion)" $?
# Group 4 over group 5: repo-e declares an out-of-set status token and lands in
# the undeclared group, which is planned LAST and never dropped.
n_e="$(printf '%s\n' "$PLAN" | grep -n '^repo=repo-e$' | cut -d: -f1)"
[ -n "$n_e" ] && [ "$n_h" -lt "$n_e" ]
check "a repo with no declared status is planned after live work, not dropped" $?
# Excluded classes. A tab for a done or deferred repo is noise; a tab for a
# blocked repo that owes nothing cannot be moved at all.
@ -896,9 +915,9 @@ mkrepo "$ROOT/chain-root"
{
echo "# STATE - chain-root"
printf '## %s NESTE %s START HER\n' "$HAND" "$EMDASH"
# Deliberately `planned`, not `in-progress`: under the old four-bucket order
# that puts the root BELOW every live repo, so the ranking check below fails
# for the right reason instead of passing on fixture order.
# Deliberately `planned`, not `in-progress`: the root must earn its position
# from the chain-root group alone, never from the status group, so the ranking
# checks below fail for the right reason instead of passing on fixture order.
echo "<!-- board: status=planned; blocked-on=-; next-cost=Opus 5/high -->"
printf 'Roten %s ingen gjeld, men holder to repo oppe.\n' "$EMDASH"
} > "$ROOT/chain-root/STATE.md"
@ -948,6 +967,43 @@ mkrepo "$ROOT/dangle"
echo "Venter paa noe som ikke finnes."
} > "$ROOT/dangle/STATE.md"
# THE DISCRIMINATING PAIR for the 0.20.0 group form. Every other ordering check
# in this section passes under BOTH the 0.19.0 score and the group form, because
# chain-root releases two repos and 40*2 beats any debt in this tree - it would
# have gone green while the operator's decision quietly failed to ship.
#
# These two are built so the forms DISAGREE. solo-root releases exactly one repo
# and owes nothing (score: 40 + 5 planned = 45). heavy-debtor owes four (score:
# 60 + 10 in-progress = 70). The score ranks the debtor first; the group form
# ranks the root first, because releasing is group 1 and debt is group 2.
mkrepo "$ROOT/solo-root"
{
echo "# STATE - solo-root"
printf '## %s NESTE %s START HER\n' "$HAND" "$EMDASH"
echo "<!-- board: status=planned; blocked-on=-; next-cost=Opus 5/high -->"
printf 'Losner ett repo, skylder ingenting.\n'
} > "$ROOT/solo-root/STATE.md"
mkrepo "$ROOT/solo-leaf"
{
echo "# STATE - solo-leaf"
printf '## %s NESTE %s START HER\n' "$HAND" "$EMDASH"
echo "<!-- board: status=blocked; blocked-on=solo-root; next-cost=Opus 5/high -->"
printf 'Venter p%s solo-root.\n' "$EMDASH"
} > "$ROOT/solo-leaf/STATE.md"
mkrepo "$ROOT/heavy-debtor"
{
echo "# STATE - heavy-debtor"
printf '## %s NESTE %s START HER\n' "$HAND" "$EMDASH"
echo "<!-- board: status=in-progress; blocked-on=-; next-cost=Opus 5/high -->"
printf 'Skylder fire svar, losner ingen.\n'
} > "$ROOT/heavy-debtor/STATE.md"
mkdir -p "$CLAUDE_COORD_DIR/heavy-debtor/inbox"
for n in 1 2 3 4; do
echo "msg" > "$CLAUDE_COORD_DIR/heavy-debtor/inbox/2026-msg$n-from-v.md"
done
P2="$("$BOARD" --roots "$ROOT" --plan 2>/dev/null)"; rc=$?
check "--plan still exits 0 with chain, cycle and dangling fixtures present" "$rc"
@ -973,6 +1029,22 @@ n2_h="$(printf '%s\n' "$P2" | grep -n '^repo=repo-h$' | cut -d: -f1)"
[ -n "$n2_cr" ] && [ -n "$n2_h" ] && [ "$n2_cr" -lt "$n2_h" ]
check "a chain root ranks above live work that releases nothing" $?
# THE decisive check for the 0.20.0 group form - the discriminating pair built
# above. solo-root releases ONE and owes nothing; heavy-debtor owes FOUR. This is
# the only ordering assertion in the file that the 0.19.0 score would fail, so it
# is the one that proves which form is actually running. Debt is still neither
# capped nor excluded here - it is one group lower, not down-weighted.
n2_sr="$(printf '%s\n' "$P2" | grep -n '^repo=solo-root$' | cut -d: -f1)"
n2_hd="$(printf '%s\n' "$P2" | grep -n '^repo=heavy-debtor$' | cut -d: -f1)"
[ -n "$n2_sr" ] && [ -n "$n2_hd" ] && [ "$n2_sr" -lt "$n2_hd" ]
check "releasing ONE repo outranks owing FOUR (group form, not the score)" $?
# The other half of the same rule: the debtor is still IN the plan, and still
# says debt is why. A group form that ranked debt lower by dropping it would be
# the excluded-debt defect wearing the operator's decision as a disguise.
printf '%s\n' "$P2" | grep -A3 '^repo=heavy-debtor$' | grep -q '^why=inbox:4$'
check "a repo outranked by a chain root still keeps its tab and its why=" $?
# A cycle terminates and credits nobody. Both halves matter: hanging is the
# failure everyone imagines, but silently inventing a root is the one that would
# ship - it looks like a working plan.
@ -989,9 +1061,9 @@ check "a dangling blocked-on target is never planned into existence" $?
printf '%s' "$P2" | grep -q '^repo=dangle$'; [ $? -ne 0 ]
check "the repo carrying a dangling blocked-on is itself still excluded" $?
# The format is the API. Adding a scoring rule must not move, rename or drop a
# single field - the driver parses these keys positionally-independently, but it
# parses THESE keys.
# The format is the API. Changing the ranking form must not move, rename or drop
# a single field - the driver parses these keys positionally-independently, but
# it parses THESE keys.
n2_tab="$(printf '%s\n' "$P2" | grep -c '^tab=')"
n2_rep="$(printf '%s\n' "$P2" | grep -c '^repo=')"
n2_dir="$(printf '%s\n' "$P2" | grep -c '^dir=')"
@ -1003,10 +1075,10 @@ n2_cms="$(printf '%s\n' "$P2" | grep -c '^command_missing=')"
[ "$n2_tab" -gt 0 ] && [ "$n2_rep" -eq "$n2_tab" ] && [ "$n2_dir" -eq "$n2_tab" ] \
&& [ "$n2_why" -eq "$n2_tab" ] && [ "$n2_sta" -eq "$n2_tab" ] \
&& [ "$n2_nes" -eq "$n2_tab" ] && [ $(( n2_cmd + n2_cms )) -eq "$n2_tab" ]
check "scored plan still carries every documented field exactly once per block" $?
check "ranked plan still carries every documented field exactly once per block" $?
printf '%s' "$P2" | grep -q '^command=$'; [ $? -ne 0 ]
check "scored plan never emits an empty command= line" $?
check "ranked plan never emits an empty command= line" $?
# Debt still puts a repo in the plan and still names itself when it is the
# dominant reason. Rejecting the proposed cap on debt was a deliberate call:
@ -1016,12 +1088,12 @@ check "debt still names itself in why= when it is the dominant reason" $?
# --brief is a second RENDERING of the same scan and must survive the change.
"$BOARD" --roots "$ROOT" --brief >/dev/null 2>&1
check "--brief still exits 0 after the scoring change" $?
check "--brief still exits 0 after the ranking change" $?
# The plan must still print the scan whole. There is no cutoff by design, so a
# consumer's own tab cap is the only thing that ever hides a repo.
[ "$n2_tab" -ge "$n_tab" ]
check "the scored plan hides nothing the bucketed plan showed" $?
check "the grouped plan hides nothing an earlier ordering showed" $?
echo ""
echo "board-selftest: $PASS passed, $FAIL failed"