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