feat(route)!: Fable rows are a hand-written override, never a route.sh outcome
The operator removed the "Fable can only be suggested after a failed Opus 5/xhigh session" policy on 2026-08-06 - it stood in the way too often. Until now that policy was implemented as code: --opus-xhigh-failed was the ONLY way route.sh's SELECTION chain could reach rows 5-6 (Fable), so the rubric still enforced a policy the operator had already dropped. Given a choice between (A) deleting the branches and the flag outright, so the calculator's output range closes at row 4, or (C) keeping a path that renders a Fable row on explicit instruction with rule=operator-override, the operator chose A (AskUserQuestion) - CLAUDE.md is explicit that the rubric stays the only deterministic lookup and a Fable choice is now always a deviation from it, recorded in STATE as an override rather than produced here. board.sh is untouched and still parses "Fable 5/high"/"Fable 5/xhigh" written by hand into a board line (route-selftest.sh section 6 now pins that half directly, since route.sh can no longer produce the strings itself). TDD: every affected selftest check was rewritten to fail against the unmodified route.sh first (72->68/69, confirmed red), then route.sh was edited to match. --opus-xhigh-failed is gone outright - passing it now exits 2 like any other unknown argument, not silently accepted as a no-op. route-selftest.sh: 73 -> 69 checks (three checks tested command shapes row_advisor() can no longer produce; the two row-5/row-6 reachability checks in section 1 collapsed into one "the flag is gone" check). coord 191, board 142 unaffected. Suite total 406 -> 402. Also folds in a standalone fix already pushed this session: section 14 was gating the wrong CLI fact (whether "claude --advisor fable" is rejected, which row_advisor() never depends on) instead of the one it actually rests on (whether opus/sonnet can advise a FABLE main model). Re-pointed and verified against the installed CC 2.1.226. Verified before committing: grepped every repo under ~/repos for a route line carrying --opus-xhigh-failed (none - one repo has it in prose only, not in its <!-- route: --> comment) and ran board.sh --plan/--brief over the real tree to confirm no repo's command line broke. Sent a follow-up coord-send to catalog superseding an earlier now-stale "406" stat-line correction with the current 402. skills/route/SKILL.md: usage block, "last-session record" framing, and the closing --opus-xhigh-failed paragraph rewritten to match. CLAUDE.md, README and CHANGELOG updated (checks 73->69, badge 406->402, new 0.21.0 entry). Version bumped 0.20.3 -> 0.21.0 across plugin.json, package.json and all three skill frontmatters (breaking CLI removal at 0.x -> minor, per v0.20.0 precedent). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Ap1WKHCDPcSfpjo4ds2dQX
This commit is contained in:
parent
4821ba8d4b
commit
dd8f3ce042
10 changed files with 172 additions and 124 deletions
|
|
@ -33,8 +33,9 @@ field() {
|
|||
|
||||
echo "route-selftest"
|
||||
|
||||
# --- 1. Every rubric row is reachable -------------------------------------
|
||||
# A row that no trait combination can produce is dead policy. All six must fire.
|
||||
# --- 1. Every calculator row is reachable -----------------------------------
|
||||
# A row that no trait combination can produce is dead policy. All four rows
|
||||
# this calculator can output must fire from traits alone.
|
||||
|
||||
got="$(field known strong cheap local next-cost)"
|
||||
[ "$got" = "Sonnet 5/high" ]; check "row 1: known/strong/cheap/local -> Sonnet 5/high" $?
|
||||
|
|
@ -48,13 +49,14 @@ got="$(field partial strong cheap local next-cost)"
|
|||
got="$(field known strong cheap cross-cutting next-cost)"
|
||||
[ "$got" = "Opus 5/xhigh" ]; check "row 4: scope=cross-cutting -> Opus 5/xhigh" $?
|
||||
|
||||
got="$("$R" --path known --verification strong --reversibility cheap --scope local \
|
||||
--rationale x --opus-xhigh-failed 2>/dev/null | sed -n 's/^next-cost=//p')"
|
||||
[ "$got" = "Fable 5/high" ]; check "row 5: opus-xhigh-failed -> Fable 5/high" $?
|
||||
|
||||
got="$("$R" --path undetermined --verification strong --reversibility cheap --scope local \
|
||||
--rationale x --opus-xhigh-failed 2>/dev/null | sed -n 's/^next-cost=//p')"
|
||||
[ "$got" = "Fable 5/xhigh" ]; check "row 6: failed + undetermined -> Fable 5/xhigh" $?
|
||||
# Rows 5-6 (Fable) are the operator's hand-written override (policy decision
|
||||
# 2026-08-06), never a rubric outcome - the calculator's output range is
|
||||
# closed at row 4. The flag that used to gate them into reach is gone
|
||||
# outright, not merely disarmed: a caller passing it gets the same "unknown
|
||||
# argument" as any other typo.
|
||||
"$R" --path known --verification strong --reversibility cheap --scope local \
|
||||
--rationale x --opus-xhigh-failed >/dev/null 2>&1
|
||||
[ $? -eq 2 ]; check "--opus-xhigh-failed is gone: unknown argument, not a route to Fable" $?
|
||||
|
||||
# --- 2. Escalation is asymmetric ------------------------------------------
|
||||
# One trait escalates; a downgrade needs ALL of them. Underkill costs one
|
||||
|
|
@ -77,29 +79,28 @@ got="$(field undetermined strong cheap local next-cost)"
|
|||
[ "$got" = "Opus 5/high" ]; check "path=undetermined stops at row 3, not row 4" $?
|
||||
|
||||
# --- 3. The emitted vocabulary is CLOSED ----------------------------------
|
||||
# Every one of the 3*3*3*3 trait combinations, in both history states, must
|
||||
# emit one of exactly six strings. This is what structurally prevents a
|
||||
# seventh spelling from ever entering circulation: not a convention, an
|
||||
# enumeration. The board line's drift was possible only because the field had
|
||||
# no writer with a closed range.
|
||||
VOCAB="|Sonnet 5/high|Sonnet 5/xhigh|Opus 5/high|Opus 5/xhigh|Fable 5/high|Fable 5/xhigh|"
|
||||
# Every one of the 3*3*3*3 trait combinations must emit one of exactly FOUR
|
||||
# strings - the calculator's whole output range now that Fable is reached
|
||||
# only by a hand-written operator override, never by this script. This is
|
||||
# what structurally prevents a fifth spelling from ever entering circulation:
|
||||
# not a convention, an enumeration. The board line's drift was possible only
|
||||
# because the field had no writer with a closed range.
|
||||
VOCAB="|Sonnet 5/high|Sonnet 5/xhigh|Opus 5/high|Opus 5/xhigh|"
|
||||
bad=0; n=0
|
||||
for p in known partial undetermined; do
|
||||
for v in strong weak none; do
|
||||
for r in cheap costly one-way; do
|
||||
for s in local multi-file cross-cutting; do
|
||||
for h in "" "--opus-xhigh-failed"; do
|
||||
n=$((n+1))
|
||||
out="$("$R" --path "$p" --verification "$v" --reversibility "$r" --scope "$s" \
|
||||
--rationale x $h 2>/dev/null | sed -n 's/^next-cost=//p')"
|
||||
case "$VOCAB" in *"|$out|"*) ;; *) bad=$((bad+1)); echo " out-of-vocab: $p/$v/$r/$s [$out]" ;; esac
|
||||
done
|
||||
n=$((n+1))
|
||||
out="$("$R" --path "$p" --verification "$v" --reversibility "$r" --scope "$s" \
|
||||
--rationale x 2>/dev/null | sed -n 's/^next-cost=//p')"
|
||||
case "$VOCAB" in *"|$out|"*) ;; *) bad=$((bad+1)); echo " out-of-vocab: $p/$v/$r/$s [$out]" ;; esac
|
||||
done
|
||||
done
|
||||
done
|
||||
done
|
||||
[ "$n" -eq 162 ] && [ "$bad" -eq 0 ]
|
||||
check "all 162 trait combinations emit one of the 6 rubric rows" $?
|
||||
[ "$n" -eq 81 ] && [ "$bad" -eq 0 ]
|
||||
check "all 81 trait combinations emit one of the 4 calculator rows" $?
|
||||
|
||||
# --- 4. Every trait is REQUIRED -------------------------------------------
|
||||
# verification carries the most signal and is the one most often left out, so
|
||||
|
|
@ -161,12 +162,13 @@ for combo in "known strong cheap local" "known weak cheap local" \
|
|||
cost="$(field "$1" "$2" "$3" "$4" next-cost)"
|
||||
rt_case "rt-$i" "$cost"
|
||||
done
|
||||
for h in 5 6; do
|
||||
if [ "$h" = "5" ]; then pp=known; else pp=undetermined; fi
|
||||
cost="$("$R" --path "$pp" --verification strong --reversibility cheap --scope local \
|
||||
--rationale x --opus-xhigh-failed 2>/dev/null | sed -n 's/^next-cost=//p')"
|
||||
rt_case "rt-$h" "$cost"
|
||||
done
|
||||
# Rows 5-6 are never emitted by route.sh any more (policy decision
|
||||
# 2026-08-06), but board.sh must still parse them back when the operator
|
||||
# hand-writes a Fable board line - that is exactly the path that replaces the
|
||||
# removed rubric outcome, so the literal strings are spliced in directly here
|
||||
# rather than produced by "$R".
|
||||
rt_case "rt-5" "Fable 5/high"
|
||||
rt_case "rt-6" "Fable 5/xhigh"
|
||||
|
||||
OUT="$(CLAUDE_COORD_DIR="$MBOX" "$BOARD" --roots "$ROOT" 2>/dev/null)"
|
||||
for want in "Sonnet 5/high" "Sonnet 5/xhigh" "Opus 5/high" "Opus 5/xhigh" \
|
||||
|
|
@ -225,12 +227,13 @@ LINE3="$("$R" --path known --verification strong --reversibility cheap --scope l
|
|||
check "'-->' in rationale cannot close the comment early" $?
|
||||
|
||||
# --- 8b. The last-session record ------------------------------------------
|
||||
# Rows 5 and 6 are history-dependent, so without a record of how the previous
|
||||
# session went they are dead policy. The record is also the cheap proxy for
|
||||
# whether the routing itself is any good: systematically high corrections on
|
||||
# row 1 means the cheap row is too easy to reach, systematically zero on row 4
|
||||
# means escalation fires too readily. It gets a WRITER here for the same
|
||||
# reason next-cost needed one - a format with only a reader drifts.
|
||||
# The record is the cheap proxy for whether the routing itself is any good:
|
||||
# systematically high corrections on row 1 means the cheap row is too easy to
|
||||
# reach, systematically zero on row 4 means escalation fires too readily. It
|
||||
# gets a WRITER here for the same reason next-cost needed one - a format with
|
||||
# only a reader drifts. It is pure telemetry - it never changes what the
|
||||
# calculator outputs, Fable rows included, which are unreachable through it
|
||||
# entirely now.
|
||||
LAST="$("$R" --path known --verification strong --reversibility cheap --scope local \
|
||||
--rationale x --last-model "Opus 5" --last-effort xhigh \
|
||||
--last-completed no --last-corrections 3 2>/dev/null | sed -n 's/^route-last=//p')"
|
||||
|
|
@ -251,7 +254,7 @@ check "no route-last line when the record is omitted" "$rc"
|
|||
--rationale x --last-corrections three >/dev/null 2>&1
|
||||
[ $? -eq 2 ]; check "non-numeric --last-corrections exits 2" $?
|
||||
|
||||
# The record is read back by the NEXT session to decide --opus-xhigh-failed, so
|
||||
# The record is read back by the NEXT session as evidence months from now, so
|
||||
# its model and effort are compared, not just displayed. Leaving them as free
|
||||
# text would rebuild the exact reader-versus-writer drift this script exists to
|
||||
# kill, one field over. Both are closed sets: the row table's three model names
|
||||
|
|
@ -269,15 +272,16 @@ check "no route-last line when the record is omitted" "$rc"
|
|||
--last-completed yes --last-corrections 0 >/dev/null 2>&1
|
||||
[ $? -eq 0 ]; check "--last-model/-effort accept every legal value" $?
|
||||
|
||||
# The record is telemetry and must NOT silently fire the Fable rows. Only the
|
||||
# explicit flag does, because "an opus/xhigh session did not finish" also
|
||||
# covers context exhaustion, an operator interrupt and a block on another repo
|
||||
# - none of which mean the MODEL failed at this step. Fable runs without an
|
||||
# advisor, so this auto-escalation has to stay a judgement, not an inference.
|
||||
# The record is telemetry and must NOT silently change what the calculator
|
||||
# outputs - a "completed=no" record describes what happened, and covers
|
||||
# context exhaustion, an operator interrupt and a block on another repo just
|
||||
# as much as an actual model failure. Reading it as an inference would revive
|
||||
# exactly the escalation path the removed --opus-xhigh-failed flag used to
|
||||
# gate deliberately.
|
||||
got="$("$R" --path known --verification strong --reversibility cheap --scope local \
|
||||
--rationale x --last-model "Opus 5" --last-effort xhigh --last-completed no \
|
||||
--last-corrections 4 2>/dev/null | sed -n 's/^next-cost=//p')"
|
||||
[ "$got" = "Sonnet 5/high" ]; check "a failed opus/xhigh record alone does NOT reach Fable" $?
|
||||
[ "$got" = "Sonnet 5/high" ]; check "a failed-session record alone does not change the routing outcome" $?
|
||||
|
||||
# A record is all four fields or none. A partial one emits `corrections=` with
|
||||
# nothing after it, which reads back later exactly like a measured zero.
|
||||
|
|
@ -469,26 +473,12 @@ 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"
|
||||
|
||||
# Fable rejects every advisor (measured against 2.1.220, gated below), so both
|
||||
# triggers must yield to the row. Rows 5 and 6 fire on the failure flag
|
||||
# regardless of reversibility, so this combination is reachable.
|
||||
adv="$("$R" --path known --verification strong --reversibility one-way --scope local \
|
||||
--rationale x --opus-xhigh-failed 2>/dev/null | sed -n 's/^command=//p')"
|
||||
if printf '%s' "$adv" | grep -q -- '--advisor'; then rc=1; else rc=0; fi
|
||||
check "row 5 (Fable) takes no advisor even at one-way stakes" "$rc"
|
||||
|
||||
adv="$("$R" --path undetermined --verification strong --reversibility one-way --scope local \
|
||||
--rationale x --opus-xhigh-failed 2>/dev/null | sed -n 's/^command=//p')"
|
||||
if printf '%s' "$adv" | grep -q -- '--advisor'; then rc=1; else rc=0; fi
|
||||
check "row 6 (Fable) takes no advisor even at one-way stakes" "$rc"
|
||||
|
||||
# The fallback is a real command the operator pastes under quota pressure, so
|
||||
# the advisor decision has to be made per ROW, not once for the winning row.
|
||||
# Row 5 falls back to row 4, which is Opus and CAN advise.
|
||||
adv="$("$R" --path known --verification strong --reversibility one-way --scope local \
|
||||
--rationale x --opus-xhigh-failed 2>/dev/null | sed -n 's/^fallback-command=//p')"
|
||||
printf '%s' "$adv" | grep -q -- '--advisor opus'
|
||||
check "Fable row falling back to an Opus row regains the advisor" $?
|
||||
# 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.
|
||||
|
||||
adv="$(field known strong one-way local fallback-command)"
|
||||
printf '%s' "$adv" | grep -q -- '--advisor opus'
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue