feat(route,board): strike the advisor rule, add board.sh --row <repo>

Order 20260912T202210Z-7588027378-from-.claude, operator decision
2026-09-12 (helhetlig vurdering av arbeidssystemet, cut row 3 and the
board.sh --row improvement row). One order, two parts, one version bump.

THE ADVISOR RULE IS STRUCK. route.sh and board.sh --dispatch emit no
--advisor at all. The rule fired per ROW on a need - always on the Sonnet
rows (a capability lift, which is what made the quota fallback safe to
take), and on the Opus rows at reversibility=costly|one-way - and it read
well. It was killed by a MEASUREMENT, not by taste: of 54 dispatches the
PM issued 08.-12.09, ZERO carried the flag, because sessions are started
by hand from the model and effort rather than from the whole emitted
line. A rule nothing honours is not a policy, and an emitted value nobody
acts on is decoration in a field whose only job is to be evidence. The
advisor is now what it already was in practice: an operator decision per
session, said in one sentence in route.sh --help.

The comments that rested on the rule were REWRITTEN, not left standing.
board.sh --dispatch still refuses a --model/--effort pair, but the reason
is no longer "the advisor is a property of the ROW": it is that the rubric
has exactly one copy, and a dispatch taking the model directly would be a
second, unscored way to reach the same decision - recording no traits, no
rationale and no next-cost, so nothing afterwards could say whether the
routing or the scoring was wrong. A comment defending a removed mechanism
is how the next session restores it. Both skills carry the correction.

Pinned as an ABSENCE over the whole trait space - 81 combinations, every
line of output, with a known-positive control proving the sweep's grep
can find a planted advisor - rather than on four sampled rows, because
the claim is that no path emits it. board.sh --dispatch at
reversibility=costly is pinned separately: that is the exact input a
reintroduced rule would fire on. The literal string is absent from
route.sh entirely, including the paragraph recording what was struck (it
says "an opus advisor flag" in words), because a blunt grep cannot tell a
description from a specification. Backward compatibility is pinned rather
than assumed: a route line carrying a legacy advisor= field still parses
and still yields a command - measured, 0 of 48 route lines in ~/repos
carry one, but a reader that broke on an unknown field would turn last
month's STATE.md into "that repo has no route line". The three CLI gates
section 14 carried went with the rule; the suite no longer depends on the
installed claude at all.

board.sh --row <repo> IS THE SEVENTH RENDERING of the same scan, never a
second scan, read-only like every other one. (The order calls it the
sixth; by this file's own numbering --inbox-plan is the fourth and
--dispatch the fifth. Corrected rather than carried wrong.) It exists
because the columns WERE misread: on 11.09 the PM read FLY off the table
by eye and got it wrong, while every other rendering a program consumes
is already key=value. inn, ordre and fly are three separate fields
because they are three separate facts; status is the bare token, never
the table's blocked>target display, with blocked-on beside it; neste is
last and uncut. An unknown repo exits 2 and writes NOTHING to stdout - an
empty block would read as a repo whose every column is blank, which is a
real and different state.

upushet is the ONE field that is not a rendering of the scan, and it is
named rather than blended in: nothing in the scan measures it, so it is
read once, for the named repo only, and never enters the table, the plan
or the briefing. It reads the remote-TRACKING ref, not the remote, so
upushet=N honestly means "the local ref says N"; a repo with no upstream
reports ?, never 0.

The row fixture's three counts are three DIFFERENT integers (3/2/1), and
that is the finding worth recording. Built first with 2/1/1, it was
mutation-tested by making fly read the ORDRE field - the exact 11.09
misreading - and the check stayed GREEN, because the two fields held the
same digit. A fixture that cannot tell two columns apart is the defect
wearing a passing test, inside the section written to prevent it.

Suites under /bin/bash 3.2, before -> after: coord 257 -> 257, board
393 -> 427, route 73 -> 73 (13 advisor checks and 3 CLI gates out, 15
absence/legacy checks in, and it no longer varies with claude being on
PATH), orders 116 -> 116, state-line-guard 54 -> 54. Sum 893 -> 927,
README badge updated to the measured sum. npm test 12/12, fail 0.

Verified live against the real tree, not only fixtures: --row
repo-mailbox reports fly=1 beside ordre=0 (the distinction that was
misread), --row on the nested key from-ai-to-chitta/content-sadhguru
resolves, and an unknown repo exits 2.

No tag, no push, no catalog change - that is the operator's
release-plugin.mjs run.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Kjell Tore Guttormsen 2026-09-12 23:45:47 +02:00
commit 5e5bc4a66e
13 changed files with 689 additions and 271 deletions

View file

@ -244,8 +244,8 @@ for n in 1 2 3; do echo "msg" > "$CLAUDE_COORD_DIR/repo-a/inbox/2026-msg$n-from-
echo "old" > "$CLAUDE_COORD_DIR/repo-a/archive/2026-old-from-x.md"
# repo-owes: unhandled inbox AND a route line, so the briefing can derive the
# EXACT startup command - advisor flag included - from the repo's own four
# traits instead of guessing from next-cost alone. Its NESTE runs deliberately
# EXACT startup command from the repo's own four traits instead of guessing
# from next-cost alone. Its NESTE runs deliberately
# past the table's 38-character column: carrying that line whole is the reason
# the briefing exists at all.
mkrepo "$ROOT/repo-owes"
@ -581,10 +581,15 @@ printf '%s' "$BRIEF" | grep -q 'check-versions'
check "brief prints the full NESTE line, not the 38-char table excerpt" $?
# Derived by CALLING route.sh with the repo's own four traits - deliberately
# not spelled out in board.sh - so the rubric keeps exactly one copy. Row 1
# carries the advisor, which is what makes the quota fallback safe to take.
printf '%s' "$BRIEF" | grep -q 'claude --model sonnet --effort high --advisor opus'
# not spelled out in board.sh - so the rubric keeps exactly one copy.
printf '%s' "$BRIEF" | grep -q 'claude --model sonnet --effort high'
check "brief derives the exact startup command from the repo's route line" $?
# Anchored to end-of-line, because the loose grep above would pass just as
# happily on a command that grew a flag back. The rubric emits no advisor
# since 2026-09-12 and the briefing is a rendering of the rubric, not a
# second policy.
printf '%s' "$BRIEF" | grep -q 'claude --model sonnet --effort high$'
check "brief's startup command ends at the effort - no advisor is appended" $?
# The command must start its OWN line. `fold` copies its input's missing
# trailing newline, which ran the command onto the tail of the NESTE prose and
@ -900,8 +905,8 @@ n_b="$(printf '%s\n' "$PLAN" | grep -n '^repo=repo-b$' | cut -d: -f1)"
check "uavklart repos rank below every repo that declared a status" $?
# The command comes from route.sh, same single copy of the rubric the briefing
# uses - advisor flag included, since that is a property of the ROW.
printf '%s' "$PLAN" | grep -q '^command=claude --model sonnet --effort high --advisor opus$'
# uses.
printf '%s' "$PLAN" | grep -q '^command=claude --model sonnet --effort high$'
check "plan derives the exact startup command from the repo's route line" $?
# Both no-command causes must degrade to a marker. A bare `command=` would be
@ -932,7 +937,7 @@ check "plan prints the full NESTE line, not the 38-char table excerpt" $?
# because they answer different questions: a driver cd's the pane itself and
# would choke on a compound line, while the operator needs ONE thing to copy.
# Assembling it by hand from two fields is where a tab lands in the wrong repo.
printf '%s' "$PLAN" | grep -q "^paste=cd $ROOT/repo-owes && claude --model sonnet --effort high --advisor opus\$"
printf '%s' "$PLAN" | grep -q "^paste=cd $ROOT/repo-owes && claude --model sonnet --effort high\$"
check "paste= is the whole line: cd into the repo, then the startup command" $?
printf '%s\n' "$PLAN" | grep -A7 '^repo=repo-typo$' | grep -q '^paste='; [ $? -ne 0 ]
@ -1530,7 +1535,7 @@ check "ip-repo-no-state reports 1 owed (no reply-expected: no declared)" $?
# class=repo still derives the exact startup command from the repo's own
# route line - same single copy of the rubric every other rendering uses.
printf '%s\n' "$IPLAN" | grep -A8 '^repo=ip-repo-done-fyi$' \
| grep -q '^command=claude --model sonnet --effort high --advisor opus$'
| grep -q '^command=claude --model sonnet --effort high$'
check "class=repo block derives its command from the repo's own route line" $?
# class=no-state and class=orphan-mailbox can never fabricate a command - ask
@ -1766,8 +1771,8 @@ d6="$("$BOARD" --roots "$ROOT" --dispatch --repo no-such-repo --prompt-file "$DS
[ "$rc" -eq 2 ]; check "dispatch: refuses a repo the scan does not know" $?
# Invalid traits must refuse, never degrade to a command without them: a
# command missing --advisor reads exactly like a row that legitimately has no
# advisor.
# command built on three scored traits and one silently dropped one reads
# exactly like a fully scored decision.
d7="$("$BOARD" --roots "$ROOT" --dispatch --repo repo-a --prompt-file "$DSP" --target-pane no \
--path bogus --verification strong --reversibility cheap --scope local --rationale t 2>&1)"; rc=$?
[ "$rc" -eq 2 ]; check "dispatch: refuses trait values route.sh rejects, rather than emitting a partial command" $?
@ -1789,18 +1794,27 @@ printf '%s\n' "$d8" | grep -q '^paste=cd '; check "dispatch: plan block carries
printf '%s\n' "$d8" | grep -q "command=.*\"\$(cat $DSP)\""; check "dispatch: the command carries the prompt in argv, never bare model+effort" $?
printf '%s\n' "$d8" | grep -q "^paste=cd $ROOT/repo-a && claude .*\"\$(cat $DSP)\""; check "dispatch: paste= is the complete one-string form, prompt included" $?
# The cost comes from route.sh's row table and nowhere else, so --advisor is a
# property of the ROW. These two differ ONLY in reversibility: both land on
# row 3 (Opus 5/high), and only the costly one carries the advisor. A dispatch
# that took --model/--effort directly would have no source for that flag at
# all, and forwarding it blindly is how a session ends up looking peer-reviewed
# without being.
# The cost comes from route.sh's row table and nowhere else. These two differ
# ONLY in reversibility, and both land on row 3 (Opus 5/high) - so since the
# advisor rule was struck (2026-09-12) they must produce the IDENTICAL command.
# costly is the case that used to add --advisor opus, which makes it the exact
# input a reintroduced rule would fire on: this is the order's named red test,
# not a sampled one.
d9="$("$BOARD" --roots "$ROOT" --dispatch --repo repo-a --prompt-file "$DSP" --target-pane no \
--path known --verification strong --reversibility cheap --scope multi-file --rationale t 2>/dev/null)"
printf '%s\n' "$d9" | grep -q '^command=claude --model opus --effort high "'; check "dispatch: model+effort come from the route row (row 3, no advisor at cheap)" $?
printf '%s\n' "$d9" | grep -q '^command=claude --model opus --effort high "'; check "dispatch: model+effort come from the route row (row 3)" $?
d10="$("$BOARD" --roots "$ROOT" --dispatch --repo repo-a --prompt-file "$DSP" --target-pane no \
--path known --verification strong --reversibility costly --scope local --rationale t 2>/dev/null)"
printf '%s\n' "$d10" | grep -q '^command=claude --model opus --effort high --advisor opus "'; check "dispatch: the advisor flag follows the ROW (costly -> row 3 with advisor)" $?
printf '%s\n' "$d10" | grep -q '^command=claude --model opus --effort high "'; check "dispatch: reversibility=costly emits NO advisor (rule struck 2026-09-12)" $?
if printf '%s\n' "$d10" | grep -q -- '--advisor'; then rc=1; else rc=0; fi
check "dispatch: no advisor anywhere in the costly dispatch block" "$rc"
if printf '%s\n' "$d10 claude --advisor opus" | grep -q -- '--advisor'; then rc=0; else rc=1; fi
check "control: that grep does find a planted advisor" "$rc"
# The paste line is assembled separately from command=, so it is pinned
# separately: a flag reintroduced in only one of the two is the shape that
# sends the operator and the driver down different paths.
if printf '%s\n' "$d10" | grep '^paste=' | grep -q -- '--advisor'; then rc=1; else rc=0; fi
check "dispatch: the paste line carries no advisor either" "$rc"
printf '%s\n' "$d8" | grep -q '^next-cost=Sonnet 5/high$'; check "dispatch: the row's next-cost is reported alongside the command" $?
# --no-go stops the Go MESSAGE, not the work: the startup command - prompt in
@ -2133,7 +2147,7 @@ mkrepo "$RG_ROOT/repo-rationale-overrides"
} > "$RG_ROOT/repo-rationale-overrides/STATE.md"
RG_PLAN="$("$BOARD" --roots "$RG_ROOT" --plan 2>/dev/null)"
RG_CMD='^command=claude --model sonnet --effort high --advisor opus$'
RG_CMD='^command=claude --model sonnet --effort high$'
printf '%s\n' "$RG_PLAN" | grep -A6 '^repo=repo-greedy-rationale$' | grep -q "$RG_CMD"
check "route line whose rationale names all four traits still yields a command" $?
@ -3252,6 +3266,187 @@ check "denominator: a missing scan root adds nothing to the counts" $?
/bin/rm -rf "$DEN_ROOT" "$DEN_COORD" 2>/dev/null
# --- 33. --row <repo>: one machine-readable line per column -----------------
# Order 20260912T202210Z-7588027378 (.claude, operator decision 2026-09-12).
# The motivation is a measured misreading, not a convenience: on 11.09 the PM
# read the FLY column off the table BY EYE and got it wrong. Every other
# rendering that a program consumes emits `key=value` for exactly that reason -
# the table's fixed-width columns are for a human, and a 34-character repo key
# already shifts a row two characters right (a stated, unclosed gap). A repo
# holding a value nobody can grep is a repo whose state is read by counting
# spaces.
#
# It is a RENDERING of the scan every other view already ran, never a second
# scan - the same rule --brief has carried since it shipped. Two numbers under
# one name, computed twice, is the defect this file names most often.
#
# ONE DEPARTURE, stated rather than smuggled: `upushet=` is not a column the
# scan computes, and it is measured here, once, for the named repo only. It is
# in the order's field list, it is exactly the kind of fact that gets misread
# ("nothing unpushed" vs "not measured"), and measuring it for one repo in a
# one-repo rendering is not a scan. It never enters the table, the plan or the
# briefing.
ROW_ROOT="$(mktemp -d)"
ROW_COORD="$(mktemp -d)"
mkrepo "$ROW_ROOT/row-repo"
{
echo "# STATE - row-repo"
printf '## %s NESTE %s START HER\n' "$HAND" "$EMDASH"
echo "<!-- board: status=blocked; blocked-on=other-repo; next-cost=Opus 5/high -->"
echo "<!-- route: path=known; verification=strong; reversibility=cheap; scope=local; rationale=x -->"
echo "The whole next step, well past the 38 characters the table column cuts at."
} > "$ROW_ROOT/row-repo/STATE.md"
# Mailbox fixture: 3 pending messages, 2 pending orders, 1 claimed order. The
# three counts are deliberately three DIFFERENT integers, and that is the whole
# design of this fixture rather than an arbitrary choice. Built first with
# 2/1/1, it was mutation-tested by making `fly` read the ORDRE field - the
# exact 2026-09-11 misreading this rendering exists to close - and the check
# stayed GREEN, because the two fields held the same digit. A fixture that
# cannot tell two columns apart is the defect wearing a passing test, in the
# section written to prevent it. With 3/2/1 that mutation turns it red.
mkdir -p "$ROW_COORD/row-repo/inbox" "$ROW_COORD/row-repo/orders/claimed"
echo "m" > "$ROW_COORD/row-repo/inbox/20260901T101010Z-1-from-x.md"
echo "m" > "$ROW_COORD/row-repo/inbox/20260901T101011Z-2-from-x.md"
echo "m" > "$ROW_COORD/row-repo/inbox/20260901T101014Z-5-from-x.md"
echo "o" > "$ROW_COORD/row-repo/orders/20260901T101012Z-3-from-x.md"
echo "o" > "$ROW_COORD/row-repo/orders/20260901T101015Z-6-from-x.md"
echo "o" > "$ROW_COORD/row-repo/orders/claimed/20260901T101013Z-4-from-x.md"
ROW_OUT="$(CLAUDE_COORD_DIR="$ROW_COORD" "$BOARD" --roots "$ROW_ROOT" --row row-repo 2>/dev/null)"; rc=$?
[ "$rc" -eq 0 ]; check "row: a known repo exits 0" $?
printf '%s\n' "$ROW_OUT" | grep -q '^repo=row-repo$'; check "row: repo= is the board key" $?
printf '%s\n' "$ROW_OUT" | grep -q "^dir=$ROW_ROOT/row-repo\$"; check "row: dir= is the checkout path" $?
printf '%s\n' "$ROW_OUT" | grep -q '^status=blocked$'; check "row: status= is the bare token, not the blocked>target display" $?
printf '%s\n' "$ROW_OUT" | grep -q '^blocked-on=other-repo$';check "row: blocked-on= is its own field" $?
printf '%s\n' "$ROW_OUT" | grep -q '^next-cost=Opus 5/high$';check "row: next-cost= survives the space and the slash" $?
printf '%s\n' "$ROW_OUT" | grep -q '^inn=3$'; check "row: inn= counts pending messages" $?
printf '%s\n' "$ROW_OUT" | grep -q '^ordre=2$'; check "row: ordre= counts pending orders" $?
printf '%s\n' "$ROW_OUT" | grep -q '^fly=1$'; check "row: fly= counts claimed orders" $?
printf '%s\n' "$ROW_OUT" | grep -q '^siste='; check "row: siste= reports the last-commit reading" $?
printf '%s\n' "$ROW_OUT" | grep -q '^drt='; check "row: drt= reports the working-tree reading" $?
printf '%s\n' "$ROW_OUT" | grep -q '^upushet='; check "row: upushet= is present" $?
# The three counts are the whole point: FLY was misread as ORDRE, so a rendering
# that emitted the same digit under two names would reproduce the defect it
# exists to close.
[ "$(printf '%s\n' "$ROW_OUT" | grep -c '^\(inn\|ordre\|fly\)=')" -eq 3 ]
check "row: inn, ordre and fly are three separate fields" $?
# NESTE uncut, same argument --brief has: the 38-char cut is the TABLE column's
# property, and cutting here would make the cut string the only copy.
printf '%s\n' "$ROW_OUT" | grep -q '^neste=.*38 characters the table column cuts at'
check "row: neste= carries the whole line, not the table excerpt" $?
# `neste` is free prose and may contain anything the STATE.md author wrote, so
# it is LAST - the same rule the RECORDS line itself obeys. A consumer reading
# field N from the end depends on it.
[ "$(printf '%s\n' "$ROW_OUT" | tail -1 | cut -d= -f1)" = "neste" ]
check "row: neste= is the last line" $?
# One line per field, every line a field: a value that wrapped would be read as
# a field name by anything grepping ^name=.
ROW_BAD="$(printf '%s\n' "$ROW_OUT" | grep -v '^[a-z][a-z-]*=' || true)"
[ -z "$ROW_BAD" ]; check "row: every line is a field=value line:${ROW_BAD:- clean}" $?
# ASCII, because the row is consumed by awk/sed/grep under bash 3.2 - the same
# constraint every other emitted block in this file carries. Asserted on a
# fixture whose own prose is ASCII, so this measures the renderer, not the
# STATE.md it happened to read.
if printf '%s' "$ROW_OUT" | LC_ALL=C grep -q '[^ -~]'; then rc=1; else rc=0; fi
check "row: the output is ASCII" "$rc"
if printf '%s' "$ROW_OUT" | LC_ALL=C grep -q '[^ -~]x'; then rc=1; else rc=0; fi
check "control: that ASCII grep runs at all (no match on a clean string)" "$rc"
# A repo whose NESTE is NOT ASCII still emits one line per field. The renderer
# cannot make the operator's Norwegian prose ASCII and must never try - the
# field NAMES are the machine surface, the value is whatever was written.
mkrepo "$ROW_ROOT/row-utf8"
{
echo "# STATE - row-utf8"
printf '## %s NESTE %s START HER\n' "$HAND" "$EMDASH"
echo "<!-- board: status=planned; blocked-on=-; next-cost=Sonnet 5/high -->"
printf 'M\xc3\x85LT i dag: ingenting gjenst\xc3\xa5r.\n'
} > "$ROW_ROOT/row-utf8/STATE.md"
ROW_U="$(CLAUDE_COORD_DIR="$ROW_COORD" "$BOARD" --roots "$ROW_ROOT" --row row-utf8 2>/dev/null)"
ROW_UBAD="$(printf '%s\n' "$ROW_U" | grep -v '^[a-z][a-z-]*=' || true)"
[ -z "$ROW_UBAD" ]; check "row: non-ASCII NESTE still yields one field per line" $?
printf '%s\n' "$ROW_U" | grep -q '^neste=.*ingenting'
check "row: non-ASCII NESTE is carried through, never mangled or dropped" $?
# An unknown repo must REFUSE. Emitting an empty block would be the same defect
# as a bare `command=`: a consumer reading ^status= would get nothing back and
# read it as a repo with no status, which is a real and different state ("?").
ROW_ERR="$(CLAUDE_COORD_DIR="$ROW_COORD" "$BOARD" --roots "$ROW_ROOT" --row no-such-repo 2>&1)"; rc=$?
[ "$rc" -eq 2 ]; check "row: an unknown repo exits 2" $?
printf '%s\n' "$ROW_ERR" | grep -q 'no-such-repo'
check "row: the refusal names the repo it could not find" $?
ROW_ERR_OUT="$(CLAUDE_COORD_DIR="$ROW_COORD" "$BOARD" --roots "$ROW_ROOT" --row no-such-repo 2>/dev/null)"
[ -z "$ROW_ERR_OUT" ]; check "row: a refusal writes nothing at all to stdout" $?
# A missing value is a usage error, not a silently empty row.
CLAUDE_COORD_DIR="$ROW_COORD" "$BOARD" --roots "$ROW_ROOT" --row >/dev/null 2>&1
[ $? -eq 2 ]; check "row: --row with no value exits 2" $?
# Read-only, asserted rather than assumed: this is the one property every
# rendering in this file shares and the one a new rendering is most likely to
# break.
ROW_BEFORE="$(find "$ROW_ROOT" "$ROW_COORD" -type f | sort | while read -r f; do printf '%s %s\n' "$f" "$(stat -f %m "$f")"; done)"
CLAUDE_COORD_DIR="$ROW_COORD" "$BOARD" --roots "$ROW_ROOT" --row row-repo >/dev/null 2>&1
ROW_AFTER="$(find "$ROW_ROOT" "$ROW_COORD" -type f | sort | while read -r f; do printf '%s %s\n' "$f" "$(stat -f %m "$f")"; done)"
[ "$ROW_BEFORE" = "$ROW_AFTER" ]; check "row: writes nothing - no file added, removed or touched" $?
# upushet: the three states must stay distinguishable. This fixture has no
# upstream at all, so the honest answer is "not measured", never 0 - the same
# rule DRT's ? and coord-count's exit 3 carry. A 0 here would say "nothing is
# waiting to be pushed" about a repo that has never had a remote.
printf '%s\n' "$ROW_OUT" | grep -q '^upushet=?$'
check "row: a repo with no upstream reports upushet=?, never 0" $?
# Known-positive control, and the case the field exists for: a real upstream
# with a real unpushed commit must report the count. Without this the ? above
# would pass on a field that can only ever say ?.
ROW_UP="$ROW_ROOT/row-upstream"
mkrepo "$ROW_UP"
{
echo "# STATE - row-upstream"
printf '## %s NESTE %s START HER\n' "$HAND" "$EMDASH"
echo "<!-- board: status=planned; blocked-on=-; next-cost=Sonnet 5/high -->"
echo "Next step."
} > "$ROW_UP/STATE.md"
git -C "$ROW_UP" add -A >/dev/null 2>&1
git -C "$ROW_UP" commit -qm "state" >/dev/null 2>&1
ROW_BARE="$(mktemp -d)/origin.git"
git init -q --bare "$ROW_BARE" >/dev/null 2>&1
git -C "$ROW_UP" remote add origin "$ROW_BARE" >/dev/null 2>&1
git -C "$ROW_UP" push -q -u origin HEAD >/dev/null 2>&1
ROW_U0="$(CLAUDE_COORD_DIR="$ROW_COORD" "$BOARD" --roots "$ROW_ROOT" --row row-upstream 2>/dev/null)"
printf '%s\n' "$ROW_U0" | grep -q '^upushet=0$'
check "row: control - an upstream with everything pushed reports upushet=0" $?
echo "more" > "$ROW_UP/extra.txt"
git -C "$ROW_UP" add -A >/dev/null 2>&1
git -C "$ROW_UP" commit -qm "unpushed work" >/dev/null 2>&1
ROW_U1="$(CLAUDE_COORD_DIR="$ROW_COORD" "$BOARD" --roots "$ROW_ROOT" --row row-upstream 2>/dev/null)"
printf '%s\n' "$ROW_U1" | grep -q '^upushet=1$'
check "row: one unpushed commit reports upushet=1" $?
# The table is UNCHANGED by all of this. --row is a rendering, so a field that
# only exists there must not leak into the view a human reads.
ROW_TABLE="$(CLAUDE_COORD_DIR="$ROW_COORD" "$BOARD" --roots "$ROW_ROOT" 2>/dev/null)"
if printf '%s' "$ROW_TABLE" | grep -qi 'upushet'; then rc=1; else rc=0; fi
check "row: upushet does not leak into the table" "$rc"
# Structural, in the shape this file already uses for "no write path exists":
# --row must not run its own repo discovery. A second scan is the defect the
# rendering rule exists to prevent, and a behavioural test cannot see it.
grep -q -- '--row' "$BOARD"; check "row: board.sh actually carries the --row flag" $?
/bin/rm -rf "$ROW_ROOT" "$ROW_COORD" 2>/dev/null
echo ""
echo "board-selftest: $PASS passed, $FAIL failed"
[ "$FAIL" -eq 0 ] || exit 1

View file

@ -80,8 +80,9 @@
# the exact command to start a session there. The 38-char cut is the table
# column's property, not the record's, so the briefing prints NESTE uncut. Each
# command is derived by CALLING route.sh with that repo's own four traits -
# next-cost alone cannot yield it, since the advisor flag is a property of the
# ROW. A repo with no route line is told so rather than handed a guess.
# next-cost alone cannot yield it, since it names the row rather than spelling
# the command, and there must be exactly one spelling in circulation. A repo
# with no route line is told so rather than handed a guess.
#
# --brief is still read-only: it writes nothing. The file write lives in
# brief-nightly.sh, which renders to a temp file and renames it into place, and
@ -200,14 +201,24 @@
# --path P --verification V --reversibility R --scope S
# --rationale "why"
#
# Model, effort and --advisor come from route.sh's row table, never from a
# --model/--effort pair: the advisor flag is a property of the ROW, and a
# dispatch that took the model directly would have no honest source for it.
# Model and effort come from route.sh's row table, never from a --model/--effort
# pair: the rubric has exactly one copy, and a dispatch that took the model
# directly would be a second, unscored way to reach the same decision.
# --target-pane is a MEASUREMENT (morning --probe-panes) and has no default -
# see the dispatch() comment for what happens when it is guessed.
#
# --row <repo> is a SEVENTH rendering: the table's row for ONE repo, as
# `field=value` lines a program can grep instead of columns a human counts
# spaces in. It exists because the columns WERE misread - FLY read as ORDRE on
# 2026-09-11 - and because every other rendering a program consumes is already
# key=value. It takes no position: every field is a value the scan already
# holds, under the name its column carries, with `neste` last and uncut.
# One exception, stated rather than blended in: `upushet` is not a column, and
# is measured once for the named repo only. See row_one() for what it can and
# cannot claim.
#
# Usage: board.sh [--roots <dir>[,<dir>...]] [--plain]
# [--brief|--plan|--inbox-plan|--dispatch|--voyage ...]
# [--brief|--plan|--inbox-plan|--dispatch|--voyage|--row <repo>]
# [--focus "<prose>"]
# Env: CLAUDE_COORD_DIR overrides the mailbox root.
# BOARD_ROOTS overrides the default scan roots.
@ -224,6 +235,7 @@ INBOX_PLAN=0
FOCUS=""
DISPATCH=0
VOYAGE=0
ROW=0; ROW_REPO=""
D_REPO=""; D_PROMPT=""; D_PANE=""; D_ORDER=""
D_PATH=""; D_VER=""; D_REV=""; D_SCOPE=""; D_RAT=""
@ -238,10 +250,16 @@ while [ $# -gt 0 ]; do
# bash 3.2: `shift 2` past the end of $# is a no-op -> would loop forever.
--roots) [ $# -ge 2 ] || { echo "board: --roots requires a value" >&2; exit 2; }
ROOTS="$2"; shift 2 ;;
# Four renderings of one scan, so exactly one may be selected: last wins.
--brief) BRIEF=1; PLAN=0; INBOX_PLAN=0; VOYAGE=0; shift ;;
--plan) PLAN=1; BRIEF=0; INBOX_PLAN=0; VOYAGE=0; shift ;;
--inbox-plan) INBOX_PLAN=1; BRIEF=0; PLAN=0; VOYAGE=0; shift ;;
# Renderings of one scan, so exactly one may be selected: last wins.
--brief) BRIEF=1; PLAN=0; INBOX_PLAN=0; VOYAGE=0; ROW=0; shift ;;
--plan) PLAN=1; BRIEF=0; INBOX_PLAN=0; VOYAGE=0; ROW=0; shift ;;
--inbox-plan) INBOX_PLAN=1; BRIEF=0; PLAN=0; VOYAGE=0; ROW=0; shift ;;
# One repo, one field=value line per column. Same `shift 2` guard as every
# other value flag, for the same bash 3.2 reason - and a MISSING value is a
# usage error rather than an empty row, since an empty row reads exactly
# like a repo whose every column is blank.
--row) [ $# -ge 2 ] || { echo "board: --row requires a repo name" >&2; exit 2; }
ROW=1; ROW_REPO="$2"; BRIEF=0; PLAN=0; INBOX_PLAN=0; VOYAGE=0; DISPATCH=0; shift 2 ;;
# Raw operator prose, forwarded verbatim by the driver: it does not
# tokenize, match or normalize, so every bit of that work is here. Same
# `shift 2` guard as --roots, for the same bash 3.2 reason.
@ -249,8 +267,8 @@ while [ $# -gt 0 ]; do
FOCUS="$2"; shift 2 ;;
# Dispatch rendering: one repo, one prompt file, one startup command. Same
# `shift 2` guard as every other value flag, for the same bash 3.2 reason.
--dispatch) DISPATCH=1; BRIEF=0; PLAN=0; INBOX_PLAN=0; VOYAGE=0; shift ;;
--voyage) VOYAGE=1; BRIEF=0; PLAN=0; INBOX_PLAN=0; DISPATCH=0; shift ;;
--dispatch) DISPATCH=1; BRIEF=0; PLAN=0; INBOX_PLAN=0; VOYAGE=0; ROW=0; shift ;;
--voyage) VOYAGE=1; BRIEF=0; PLAN=0; INBOX_PLAN=0; DISPATCH=0; ROW=0; shift ;;
--repo) [ $# -ge 2 ] || { echo "board: --repo requires a value" >&2; exit 2; }
D_REPO="$2"; shift 2 ;;
--prompt-file) [ $# -ge 2 ] || { echo "board: --prompt-file requires a value" >&2; exit 2; }
@ -1079,9 +1097,9 @@ rows() {
# --- Briefing rendering (--brief) ------------------------------------------
# The startup command for one repo, derived from that repo's OWN route line by
# calling route.sh. Deriving it from next-cost instead would not work even in
# principle: the advisor flag is a property of the ROW, and two rows can share
# a model/effort pair while differing on it. A repo with no route line gets its
# calling route.sh. Deriving it from next-cost instead would mean a second
# writer for the same command string, spelled from a rubric NAME rather than
# from the row table itself. A repo with no route line gets its
# next-cost printed and is told where the command comes from - a guessed
# command would read as authoritative while being a guess, which is worse than
# no command at all.
@ -1303,13 +1321,18 @@ fi
# brief-nightly.sh already owns the briefing's write.
#
# THE COST COMES FROM route.sh's ROW TABLE, never from a --model/--effort pair
# handed in. That is not tidiness: `--advisor opus` is a property of the ROW,
# two rows share a model/effort pair while differing on it, and the CLI accepts
# a wrong advisor silently (it drops it for a Fable main model without failing).
# A dispatch that took the model directly would have no source for that flag,
# and both available guesses are wrong in the same direction - a session that
# looks peer-reviewed without being. Scoring the four traits is the caller's
# judgement; turning them into a command is this lookup.
# handed in. That is not tidiness: the rubric is a policy with exactly one copy,
# and a dispatch accepting the model directly would be a second way to reach the
# same decision - one that records no traits, no rationale and no next-cost, so
# nothing afterwards can say whether the ROUTING was wrong or the SCORING was.
# Scoring the four traits is the caller's judgement; turning them into a command
# is this lookup.
#
# Until 2026-09-12 this paragraph rested on `--advisor opus` instead: the flag
# was a property of the ROW, so a model/effort pair could not yield it. That
# rule is struck (see route.sh), and the argument is rewritten rather than
# quietly left standing on a premise the code no longer has - a comment
# defending a mechanism that was removed is how the next session "restores" it.
dispatch() {
# --target-pane is REQUIRED and has no default, the same rule route.sh's
# --last-effort carries: it is a MEASUREMENT of the world (does the target
@ -1387,8 +1410,8 @@ dispatch() {
[ -d "$d_dir" ] || { echo "board: the directory for $D_REPO does not exist: $d_dir" >&2; exit 2; }
# Rejected traits REFUSE. Degrading to a command without them is the worst
# available outcome: a command missing --advisor is indistinguishable from a
# row that legitimately has none.
# available outcome: a command built on three scored traits and one silently
# dropped one is indistinguishable from a fully scored decision.
d_route="$(bash "$ROUTE" --path "$D_PATH" --verification "$D_VER" \
--reversibility "$D_REV" --scope "$D_SCOPE" --rationale "$D_RAT" 2>&1)" || {
echo "board: route.sh rejected the traits, so there is no command to give:" >&2
@ -2097,6 +2120,114 @@ if [ "$VOYAGE" -eq 1 ]; then
exit 0
fi
# --- Row rendering (--row <repo>) -------------------------------------------
# A SEVENTH rendering of the same scan, never a second scan, and read-only like
# every other one. (Order 20260912T202210Z-7588027378 calls it the sixth; by
# this file's own numbering --inbox-plan is the fourth and --dispatch the fifth,
# so it is the seventh. The count is corrected here rather than carried wrong,
# for the same reason every other number in this file is measured.)
#
# WHAT IT ANSWERS: the table's row for ONE repo, as `field=value` lines a
# program can grep. The table is fixed-width and is for a human; reading a
# column off it by eye is how FLY was misread as ORDRE on 2026-09-11, and a
# 34-character repo key already shifts a row two characters right - a stated,
# unclosed gap that makes counting spaces strictly worse than it looks. Every
# other rendering a program consumes (--plan, --inbox-plan, --dispatch) is
# key=value for exactly this reason; the default view was the one left out.
#
# It takes NO position and adds NO judgement: every field is a value $RECORDS
# already holds, printed under the name its column carries. The three mailbox
# counts are three separate fields because they are three separate facts - INN
# is "others wait on you", ORDRE is "work waits on this repo", FLY is "someone
# took an order" - and this rendering exists because they were confused.
#
# ONE FIELD IS NOT A RENDERING, and it is named rather than blended in:
# `upushet` is not a column the scan computes. It is measured here, once, for
# the named repo only, because the order asks for it and because "nothing is
# waiting to be pushed" and "nobody looked" are exactly the pair this repo
# refuses to render alike. One reading for one repo in a one-repo view is not a
# scan; it never enters the table, the plan or the briefing.
#
# It reads the remote-TRACKING ref, not the remote. That is a real bound, not a
# hedge: a stale ref under-reports after someone else pushed, and over-reports
# after a push this checkout has not fetched. The honest reading of `upushet=N`
# is "the local ref says N", and `git ls-remote` is still what a claim ABOUT
# THE WORLD costs.
row_unpushed() {
ru_up="$(git -C "$1" rev-parse --abbrev-ref --symbolic-full-name '@{u}' 2>/dev/null)" || return 1
[ -n "$ru_up" ] || return 1
ru_n="$(git -C "$1" rev-list --count "$ru_up..HEAD" 2>/dev/null)" || return 1
case "$ru_n" in ''|*[!0-9]*) return 1 ;; esac
printf '%s' "$ru_n"
}
row_one() {
r_rec="$(awk -F'|' -v n="$ROW_REPO" '$3==n {print; exit}' "$RECORDS")"
# REFUSE rather than emit an empty block. A consumer reading ^status= off an
# empty output gets nothing back and reads it as a repo with no board line -
# which is a real, different state this very rendering spells as `?`.
if [ -z "$r_rec" ]; then
echo "board: no repo named '$ROW_REPO' in the scanned roots" >&2
return 2
fi
r_disp="$(printf '%s' "$r_rec" | cut -d'|' -f4)"
r_cost="$(printf '%s' "$r_rec" | cut -d'|' -f5)"
r_inn="$(printf '%s' "$r_rec" | cut -d'|' -f6)"
r_drt="$(printf '%s' "$r_rec" | cut -d'|' -f7)"
r_alder="$(printf '%s' "$r_rec" | cut -d'|' -f8)"
r_siste="$(printf '%s' "$r_rec" | cut -d'|' -f9)"
r_dir="$(printf '%s' "$r_rec" | cut -d'|' -f10)"
r_blocked="$(printf '%s' "$r_rec" | cut -d'|' -f11)"
r_ordre="$(printf '%s' "$r_rec" | cut -d'|' -f12)"
r_ordreage="$(printf '%s' "$r_rec" | cut -d'|' -f13)"
r_fly="$(printf '%s' "$r_rec" | cut -d'|' -f14)"
r_flyage="$(printf '%s' "$r_rec" | cut -d'|' -f15)"
r_voy="$(printf '%s' "$r_rec" | cut -d'|' -f16)"
# `neste` is free prose and may contain a literal '|', which is why it is the
# last field of the record - so it is taken as "everything from 17 on", never
# as field 17.
r_neste="$(printf '%s' "$r_rec" | cut -d'|' -f17-)"
# The TOKEN, not the display string: the table renders a blocked repo as
# `blocked>target` because one column has to carry both, and this rendering
# has two fields and no such constraint. A consumer matching status=blocked
# must not have to know about the arrow.
case "$r_disp" in
blocked\>*) r_status=blocked ;;
*) r_status="$r_disp" ;;
esac
r_unpushed="$(row_unpushed "$r_dir")" || r_unpushed="?"
echo "repo=$ROW_REPO"
echo "dir=$r_dir"
echo "status=$r_status"
echo "blocked-on=$r_blocked"
echo "next-cost=$r_cost"
echo "inn=$r_inn"
echo "ordre=$r_ordre"
echo "ordre-alder=$r_ordreage"
echo "fly=$r_fly"
echo "fly-alder=$r_flyage"
echo "voy=$r_voy"
echo "drt=$r_drt"
echo "alder=$r_alder"
echo "siste=$r_siste"
echo "upushet=$r_unpushed"
# LAST, and for the same reason it is last in the record: it is free prose
# lifted out of a STATE.md, so anything placed after it would be unreachable
# for a consumer reading from the end. Printed WHOLE - the 38-character cut
# belongs to the table column, and cutting here would make the cut string the
# only copy, which is the defect --brief was built to stop.
echo "neste=$r_neste"
return 0
}
if [ "$ROW" -eq 1 ]; then
row_one
exit $?
fi
count() { awk -F'|' -v b="$1" '$1==b' "$RECORDS" | wc -l | tr -d ' '; }
echo "BOARD - tverr-repo oppmerksomhetstavle ($(awk 'END{print NR}' "$RECORDS") repo)"

View file

@ -141,7 +141,8 @@ check "all 81 trait combinations emit one of the 4 calculator rows" $?
# to show the same string back. Runs for all six rows.
ROOT="$(mktemp -d)"
MBOX="$(mktemp -d)"
cleanup() { /bin/rm -rf "$ROOT" "$MBOX" 2>/dev/null; }
RL_ROOT=""
cleanup() { /bin/rm -rf "$ROOT" "$MBOX" ${RL_ROOT:+"$RL_ROOT"} 2>/dev/null; }
trap cleanup EXIT
rt_bad=0
@ -359,13 +360,14 @@ check "board + route + route-last stacked still yield prose in NESTE" $?
# --- 9. Startup command and fallback --------------------------------------
# Two spellings of ONE decision: the rubric name for the board line, the CLI
# alias for the command the operator pastes. They must never disagree.
# These assert the WHOLE string, advisor included, so section 14's rule cannot
# be widened without a deliberate edit here - the exact-match is the tripwire.
# These assert the WHOLE string, so no flag can be added back to the emitted
# command without a deliberate edit here - the exact-match is the tripwire that
# keeps section 14's absence rule from being widened around.
cmd="$(field partial strong cheap local command)"
[ "$cmd" = "claude --model opus --effort high" ]; check "command mirrors the row (opus/high)" $?
cmd="$(field known strong cheap local command)"
[ "$cmd" = "claude --model sonnet --effort high --advisor opus" ]; check "command mirrors the row (sonnet/high)" $?
[ "$cmd" = "claude --model sonnet --effort high" ]; check "command mirrors the row (sonnet/high)" $?
# The rubric requires ALWAYS naming one row cheaper as the quota fallback.
fb="$(field partial strong cheap local fallback)"
@ -377,7 +379,7 @@ fb="$(field known strong cheap local fallback)"
# The fallback needs its own pasteable command or the operator translates by
# hand at exactly the moment they are under quota pressure.
fbc="$(field partial strong cheap local fallback-command)"
[ "$fbc" = "claude --model sonnet --effort xhigh --advisor opus" ]; check "fallback ships its own command" $?
[ "$fbc" = "claude --model sonnet --effort xhigh" ]; check "fallback ships its own command" $?
# --- 10. The command carries no 'cd' --------------------------------------
# One repo per terminal tab: a startup command prefixed with cd is wrong by
@ -393,9 +395,9 @@ check "no emitted command contains a cd prefix" "$rc"
# Model aliases are whatever the INSTALLED claude accepts - never hardcoded
# without a gate, because an alias that stops resolving turns every emitted
# command into a paste that fails.
# Capture the effort TOKEN only. Anything may legitimately follow it on the
# command line (--advisor does, since section 14), and a match that swallowed
# the tail would report a valid effort as invalid.
# Capture the effort TOKEN only. The command ends at the effort today, but the
# pattern stays tail-tolerant: a match that swallowed whatever a later flag
# added would report a valid effort as invalid.
efforts="$(printf '%s' "$out" | sed -n 's/^command=claude --model [a-z]* --effort \([a-z]*\).*/\1/p')"
case "|low|medium|high|xhigh|max|" in *"|$efforts|"*) rc=0 ;; *) rc=1 ;; esac
check "emitted effort is in the verified effort set" "$rc"
@ -467,117 +469,129 @@ check "route.sh no longer claims effort is unobservable from inside" "$rc"
grep -q 'CLAUDE_EFFORT' "$R"
check "route.sh names the source the caller should measure from" $?
# --- 14. The advisor: capability gap first, stakes second -----------------
# The advisor is a second, stronger model consulted mid-task, so a session
# carrying one costs more than a session without. It must therefore fire where
# there is a NEED and nowhere else, or it decays into the always-on
# advisorModel setting it exists to replace. Two independent needs qualify,
# and they turn out to be almost disjoint.
# --- 14. The advisor is NOT the rubric's to emit --------------------------
# Struck by operator decision 2026-09-12
# (~/.claude/docs/2026-09-12-helhetlig-vurdering-arbeidssystemet.md, cut row 3).
# The rule it replaces fired on two needs - Sonnet rows always, Opus rows at
# costly|one-way stakes - and read well. What killed it was a measurement, not
# a change of taste: of 54 dispatches the PM issued 08.-12.09, ZERO carried
# --advisor opus, because the operator starts sessions by hand and pastes the
# model and effort, not the whole line. A rule nothing honours is not a policy,
# it is a claim about the world that the world disagrees with - and this repo's
# own standing rule is that an emitted value must be evidence, never decoration.
#
# 1. THE MAIN MODEL IS SONNET (rows 1-2). Here opus is a genuine capability
# lift, not a peer: opus judgement at sonnet cost. This is the load-bearing
# half, because every fallback-command is one row cheaper and the cheapest
# rows are Sonnet - so this is what makes the QUOTA FALLBACK safe to take.
# Pinned below by the row-3-falls-back-to-row-2 case.
# So the calculator emits no advisor at all, and the advisor becomes what it
# already was in practice: an operator decision per session. That is a strictly
# SAFER direction for the one thing the old rule protected - the quota fallback
# is still one row cheaper, it just no longer implies a capability lift nobody
# was taking.
#
# 2. A MISTAKE IS EXPENSIVE TO UNDO (reversibility costly|one-way). On an Opus
# row the advisor is a peer review rather than a lift, which is worth
# paying for when being wrong is not cheap to reverse.
#
# The two barely overlap: costly forces row 3 and one-way forces row 4, so a
# Sonnet row ALWAYS has reversibility=cheap and trigger 2 can never reach it.
# Rule 1 covers the Sonnet rows, rule 2 covers the Opus rows.
#
# What is deliberately NOT a trigger: verification=none on its own. The set it
# would add beyond rule 2 is exactly {verification=none AND
# reversibility=cheap} - mistakes that are cheap to reverse. It survives on the
# Sonnet rows only via rule 1, which is about the model, not the trait.
# Pinned as an ABSENCE over the whole trait space rather than on four sampled
# rows, because the claim is that no path emits it - the same "no write path
# exists" argument the channel split uses. An absence check is worthless
# without a known-positive control, so the sweep's own grep is proven able to
# find a planted advisor before it is trusted to report none.
adv="$(field known strong cheap local command)"
printf '%s' "$adv" | grep -q -- '--advisor opus'
check "row 1 (Sonnet/high) always carries an advisor" $?
if printf '%s' "$adv" | grep -q -- '--advisor'; then rc=1; else rc=0; fi
check "row 1 (Sonnet/high) carries no advisor" "$rc"
adv="$(field known weak cheap local command)"
printf '%s' "$adv" | grep -q -- '--advisor opus'
check "row 2 (Sonnet/xhigh) always carries an advisor" $?
if printf '%s' "$adv" | grep -q -- '--advisor'; then rc=1; else rc=0; fi
check "row 2 (Sonnet/xhigh) carries no advisor" "$rc"
adv="$(field known strong costly local command)"
printf '%s' "$adv" | grep -q -- '--advisor opus'
check "reversibility=costly carries an advisor onto an Opus row" $?
if printf '%s' "$adv" | grep -q -- '--advisor'; then rc=1; else rc=0; fi
check "reversibility=costly no longer pulls an advisor onto an Opus row" "$rc"
adv="$(field known strong one-way local command)"
printf '%s' "$adv" | grep -q -- '--advisor opus'
check "reversibility=one-way carries an advisor onto an Opus row" $?
# The Opus rows reached WITHOUT expensive stakes are the whole no-advisor set.
adv="$(field partial strong cheap local command)"
if printf '%s' "$adv" | grep -q -- '--advisor'; then rc=1; else rc=0; fi
check "row 3 via path=partial at cheap stakes takes NO advisor" "$rc"
adv="$(field known strong cheap multi-file command)"
if printf '%s' "$adv" | grep -q -- '--advisor'; then rc=1; else rc=0; fi
check "row 3 via scope=multi-file at cheap stakes takes NO advisor" "$rc"
adv="$(field known strong cheap cross-cutting command)"
if printf '%s' "$adv" | grep -q -- '--advisor'; then rc=1; else rc=0; fi
check "row 4 via scope=cross-cutting at cheap stakes takes NO advisor" "$rc"
# Rows 5-6 (Fable) are unreachable through this calculator now - section 1
# pins that directly, and section 3's closed vocabulary covers every command
# this script can ever emit. There is nothing left here for "Fable takes no
# advisor" to test: that fact still holds (gated against the installed claude
# by the CLI check below), but row_advisor()'s branch for it can no longer be
# exercised through the public interface.
check "reversibility=one-way no longer pulls an advisor onto an Opus row" "$rc"
# The fallback is the half the old rule called load-bearing, so it is pinned
# in its own right: dropping a row under quota pressure must not reintroduce
# the flag by the back door.
adv="$(field known strong one-way local fallback-command)"
printf '%s' "$adv" | grep -q -- '--advisor opus'
check "row 4 fallback to row 3 keeps the advisor at one-way stakes" $?
# THE POINT OF RULE 1, pinned. Row 3 at cheap stakes takes no advisor, but its
# quota fallback is row 2 - a Sonnet row - which does. Dropping a row under
# quota pressure must not silently drop the safety net with it.
adv="$(field partial strong cheap local fallback-command)"
printf '%s' "$adv" | grep -q -- '--advisor opus'
check "an Opus row with no advisor still falls back to an advised Sonnet row" $?
adv="$(field known strong cheap cross-cutting fallback-command)"
if printf '%s' "$adv" | grep -q -- '--advisor'; then rc=1; else rc=0; fi
check "row 4 falling back to row 3 at cheap stakes stays unadvised" "$rc"
check "the row-4 fallback carries no advisor either" "$rc"
# Closed vocabulary, same reason as the row table: one spelling in circulation.
# fable is not merely weaker here, it is REJECTED as an advisor outright.
allout="$("$R" --path known --verification strong --reversibility one-way \
--scope cross-cutting --rationale x 2>/dev/null)"
bad="$(printf '%s' "$allout" | grep -o -- '--advisor [a-z0-9-]*' | grep -v -- '--advisor opus')"
[ -z "$bad" ]; check "the only advisor value ever emitted is opus:${bad:- none other}" $?
adv="$(field partial strong cheap local fallback-command)"
if printf '%s' "$adv" | grep -q -- '--advisor'; then rc=1; else rc=0; fi
check "an Opus row falling back to a Sonnet row carries no advisor" "$rc"
if command -v claude >/dev/null 2>&1; then
# A real gate that spends NO tokens. Advisor validation runs BEFORE the
# empty-prompt check, so `-p ""` reaches the validator and then exits: a
# valid advisor fails on the missing prompt, an invalid one fails on itself.
# --help cannot gate this - it short-circuits before option validation, so
# even an unknown flag exits 0 and the gate would pass without testing.
# Row 1-2's trigger is "sonnet main + opus advisor" specifically, so the
# main model is pinned rather than left to the ambient default.
ADVOUT="$(claude --model sonnet --advisor opus -p "" 2>&1 | head -3)"
if printf '%s' "$ADVOUT" | grep -q 'unknown option'; then rc=1; else rc=0; fi
check "installed claude accepts the --advisor flag at all" "$rc"
# THE SWEEP: every trait combination the calculator accepts, every line of
# output. 81 combinations, so a rule surviving on one unsampled corner cannot
# hide. Accumulated into one string and grepped once - a per-combination check
# would add 81 lines to the summary and drown the rest of the suite.
sweep=""
for s_path in known partial undetermined; do
for s_ver in strong weak none; do
for s_rev in cheap costly one-way; do
for s_sc in local multi-file cross-cutting; do
sweep="$sweep
$("$R" --path "$s_path" --verification "$s_ver" --reversibility "$s_rev" \
--scope "$s_sc" --rationale x 2>/dev/null)"
done
done
done
done
if printf '%s' "$sweep" | grep -q -- '--advisor'; then rc=1; else rc=0; fi
check "no advisor in any output over all 81 trait combinations" "$rc"
if printf '%s' "$ADVOUT" | grep -q 'cannot advise'; then rc=1; else rc=0; fi
check "installed claude accepts opus advising a sonnet main" "$rc"
# Known-positive control: the grep above reports an absence, so it must be
# shown able to report a presence. Without this the sweep would pass just as
# happily against an empty string.
if printf '%s' "$sweep claude --advisor opus" | grep -q -- '--advisor'; then rc=0; else rc=1; fi
check "control: the sweep's grep does find a planted advisor" "$rc"
# The help text is the rubric's published form, so the rule has to leave there
# too - a doc line nothing tests is a doc line that drifts, and a struck rule
# still written down is worse than one never removed: it reads as current.
# The literal flag string is absent from route.sh ENTIRELY, including the
# paragraph that records what was struck - that paragraph names "an opus
# advisor flag" in words on purpose. A blunt grep cannot tell a description
# from a specification (the same reason the cache assertion in coord-selftest
# runs on an extracted path rather than the whole file), and keeping the
# string out is cheaper than teaching the check to read prose. Do not
# "restore the quote" here.
HELPOUT="$("$R" --help 2>/dev/null)"
if printf '%s' "$HELPOUT" | grep -q -- '--advisor opus'; then rc=1; else rc=0; fi
check "--help no longer documents emitting --advisor opus" "$rc"
if printf '%s' "$HELPOUT" | grep -q 'THE ADVISOR is emitted'; then rc=1; else rc=0; fi
check "--help no longer carries the advisor rule block" "$rc"
# Removing the rule silently would leave a reader guessing whether the advisor
# is forbidden, forgotten, or someone else's. It is the third, and the help
# says which.
printf '%s' "$HELPOUT" | grep -q 'advisor is an operator decision per session'
check "--help states whose decision the advisor is instead" $?
grep -q 'advisor is an operator decision per session' "$R"
check "route.sh itself carries that sentence, not just its help output" $?
# --- 14b. Old route lines still parse ------------------------------------
# Backward compatibility, pinned rather than assumed. Measured on the real
# tree the day the rule was struck: 0 of 48 `<!-- route:` lines anywhere in
# ~/repos carry an advisor field at all, so nothing in circulation depends on
# it - but board.sh's route_cmd_for() reads these lines by trait NAME, and a
# reader that broke on an unknown field would turn a STATE.md written last
# month into "that repo has no route line", which is the misreading this whole
# file exists to prevent.
RL_ROOT="$(mktemp -d)"; mkdir -p "$RL_ROOT/repo-legacy"
( cd "$RL_ROOT/repo-legacy" && git init -q . 2>/dev/null ) || true
{
printf '# STATE\n\n## NESTE - START HER\n'
printf '<!-- board: status=planned; blocked-on=-; next-cost=Sonnet 5/high -->\n'
printf '<!-- route: path=known; verification=strong; reversibility=cheap; scope=local; advisor=opus; rationale=legacy line written before the rule was struck -->\n'
printf 'legacy next step\n'
} > "$RL_ROOT/repo-legacy/STATE.md"
RL_OUT="$("$BOARD" --roots "$RL_ROOT" --plan 2>/dev/null)"
printf '%s' "$RL_OUT" | grep -q '^command=claude --model sonnet --effort high$'
check "a route line carrying a legacy advisor= field still yields a command" $?
if printf '%s' "$RL_OUT" | grep -q -- '--advisor'; then rc=1; else rc=0; fi
check "and the command derived from it carries no advisor" "$rc"
# row_advisor() never emits '--advisor fable' - it depends on the opposite
# direction: whether opus can advise a FABLE main model. That is the fact
# rows 5-6's suppression rests on, so that is what must be gated. (Measured
# at CC 2.1.226: fable is now itself accepted as an advisor for opus/sonnet
# mains - a capability-ordering change worth flagging to the operator, but
# not what this check exists to pin.)
FABOUT="$(claude --model fable --advisor opus -p "" 2>&1 | head -3)"
printf '%s' "$FABOUT" | grep -q 'cannot advise'
check "installed claude still rejects opus advising a fable main" $?
else
echo " skip - claude not on PATH, advisor flag gate not run"
fi
echo ""
echo "route-selftest: $PASS passed, $FAIL failed"

View file

@ -29,10 +29,7 @@
# departure from it is recorded in STATE as an OVERRIDE, never produced here.
# Write "Fable 5/high" or "Fable 5/xhigh" into the board line by hand when
# that is the right call; board.sh still parses both (route-selftest.sh
# section 6 pins that half). The one fact worth carrying: a Fable session
# runs without an advisor (row_advisor() below, and gated against the
# installed claude by route-selftest.sh section 14) - informational, never a
# gate on reaching the row, since there is no longer a gate to reach.
# section 6 pins that half).
#
# Cheapest first, so the rubric's "always name one row cheaper as the quota
# fallback" is row minus one, floored at row 1, correct by construction.
@ -66,32 +63,14 @@
# overkill costs quota every session - but a wrong architecture decision in a
# published plugin costs more than either.
#
# THE ADVISOR is emitted into the command as '--advisor opus' - a second,
# stronger model consulted at key moments during the session. It is added on a
# NEED, never unconditionally: an always-on advisor is the global advisorModel
# setting, which burns quota on every session in every repo and is the thing
# this rule exists to replace. Two independent needs qualify:
#
# rows 1-2 ALWAYS. The main model is Sonnet, so opus is a capability LIFT
# rather than a peer - opus judgement at sonnet cost. This is what
# makes the FALLBACK safe to take: every fallback is one row
# cheaper, and the cheapest rows are the Sonnet ones.
# rows 3-4 only at reversibility=costly|one-way. The main model is already
# Opus, so the advisor buys peer review, worth paying for when a
# mistake is not cheap to undo.
#
# Rows 5-6 never reach this logic at all - the calculator cannot select them
# (see above). Informational only: were the operator to hand-write a Fable
# command, it would carry no advisor either way, since the CLI rejects every
# advisor for a Fable main model.
#
# The two triggers barely overlap: costly forces row 3 and one-way forces row
# 4, so a Sonnet row always has reversibility=cheap. verification=none is
# deliberately not a third trigger - beyond the stakes rule it would only add
# mistakes that are cheap to reverse, docs sessions among them.
#
# Applied per ROW, so 'fallback-command' carries its own correct answer rather
# than the winning row's.
# THE ADVISOR IS NOT EMITTED, and that is a decision rather than an omission:
# the advisor is an operator decision per session, never the rubric's. Until
# 2026-09-12 this calculator appended an opus advisor flag on a NEED - always on
# the Sonnet rows, and on the Opus rows at costly|one-way stakes. It was struck
# on a measurement: of 54 dispatches issued 08.-12.09 not one carried the flag,
# because sessions are started by hand from the model and effort, not from the
# whole line. A rule nothing honours is not policy, and an emitted value that
# nobody acts on is decoration in a field whose only job is to be evidence.
#
# WHERE IT DISAGREES WITH THE RUBRIC'S EXAMPLES. The rows are task-type labels;
# the traits are a different classification over the same six outcomes. They
@ -288,41 +267,14 @@ row_base_cmd() {
esac
}
# THE ADVISOR is a second, stronger model consulted mid-task. It costs real
# tokens per session, so it fires on a NEED and nowhere else - an unconditional
# advisor is just the global advisorModel setting, which is the thing this
# replaces. Two independent needs qualify, and they are almost disjoint:
#
# rows 1-2 (Sonnet) ALWAYS. opus is a capability LIFT here, not a peer:
# opus judgement at sonnet cost. This half is what makes
# the fallback-command safe, since every fallback is one
# row cheaper and the cheapest rows are Sonnet.
# rows 3-4 (Opus) only at costly|one-way stakes, where the advisor is a
# peer review and being wrong is not cheap to undo.
#
# Rows 5-6 (Fable) never reach this function - $ROW can only be 1-4 (see
# SELECTION above). Informational only: the only advisor this script ever
# emits is opus (pinned by selftest 14's "the only advisor value ever emitted
# is opus"), and opus is refused as under-capable for a fable main model -
# measured against the installed claude, still true at CC 2.1.226 - so a
# hand-written Fable command carries no advisor either way.
#
# costly forces row 3 and one-way forces row 4, so a Sonnet row always has
# reversibility=cheap - the stakes rule can never reach rows 1-2, and the model
# rule never reaches rows 3-4. verification=none is deliberately NOT a trigger:
# beyond the stakes rule it would only add cheap-to-reverse mistakes, and it
# would put an advisor on every docs session (known/none/cheap/local).
#
# Applied per ROW rather than once, because the fallback is a real command the
# operator pastes under quota pressure and must carry its own correct answer.
row_advisor() {
case "$1" in
1|2) echo " --advisor opus" ;;
3|4) case "$2" in costly|one-way) echo " --advisor opus" ;; *) echo "" ;; esac ;;
*) echo "" ;;
esac
}
row_cmd() { printf '%s%s\n' "$(row_base_cmd "$1")" "$(row_advisor "$1" "$REVERS")"; }
# No advisor is appended here or anywhere else - the advisor is an operator
# decision per session, not a property this rubric computes (struck
# 2026-09-12, see the header). row_cmd() is therefore the row's base command
# and nothing more; it stays a function rather than collapsing into
# row_base_cmd() because the emitted command and the row table are two
# separate things that happened to converge, and a later flag would attach
# here, to one place, for both the winning row and its fallback.
row_cmd() { row_base_cmd "$1"; }
FB=$((ROW - 1)); [ "$FB" -ge 1 ] || FB=1