next-session-triage-ladder.md was still written as a to-build spec. It now leads with the shipped status, the acceptance numbers, and the three things the spec got wrong (the '32 free banks' were 0/28; the autodecl arm is worse in-tree than the raw draft; PRE and POST cannot be the same pass because residual_rules_b needs a draft), plus the one found by building it — never classify on a moving tree. accelerators #16: a 'verified, just bank it' claim must name the compilation it survived. Day-one kit material for a new decomp: any per-function oracle compiles in isolation, every real bank compiles in context.
6.7 KiB
THE TRIAGE LADDER — BUILT AND SHIPPED (P31 S69)
Status: DONE. The tool is
tools/triage_ladder.py. It is wired intowave_args.py(draw time) andtools/workflows/escalate_fable.js(escalation time), documented indocs/SETUP.mdanddocs/wave-playbook.md§4b, and its acceptance harness is green over the whole corpus: false-skip 0/1367 open stubs · recall 426/426 matched · wall tier fires on exactly the 10 enumerated walls (0 extra, 0 missing). Runtools/triage_ladder.py --acceptanceto re-verify.Three things in the spec below were WRONG, and the corrections are the valuable part:
- "32 FREE BANKS ARE WAITING" was not free. 4 had already banked; the other 28 gated 0/28. Every failure was a declaration conflict inside the real TU.
match_onecompiles the draft ALONE — a standalone closeness of 0 proves the BODY and says nothing about the TU accepting the SIGNATURE (cookbook §376). 8 of the 28 banked once the missing lever existed (§378, the self-caller cast,tools/cast_self_callers.py); the remaining 20 are on a named ledger at.run/S69_class376_ledger.json.- The autodecl arm is WORSE in-tree than the raw draft — the
externit adds to satisfy the standalone probe is a second conflicting declaration. Gate the raw draft.- The spec conflated PRE and POST.
residual_rules_bneeds a draft and runsmatch_one, so it cannot run at DRAW time. Only the target-side tiers (BANKED · WALL-332 · PARKED) can — and those are the ones that save a whole agent rather than one iteration. The tool splits them.One more, added by building it: the ladder must not classify on a moving tree. A merging gate makes the stub oracle wrong in BOTH directions (§377), which manufactures the exact false skip the ladder exists to prevent. It refuses unless the tree is quiescent.
The original spec, kept as written (S68)
Written deliberately so a session with NONE of S68's context can build this. The companion tool,
tools/neighbor_ref.py(retrieval), is already BUILT and committed — this is the other half.
What it is
One deterministic, local, ZERO-TOKEN pass that sits between wave_args and the drafting agent
and answers "does this target need an agent at all?" before one is spent.
draw -> cards -> packs -> wave_args -> [TRIAGE LADDER] -> draft -> gate -> bank
|
banked / wall / already-matches / one-extern-away
-> skip the agent, go straight to the gate
It is ~70% already built
tools/residual_rules_b.py exists and works. Built by a Fable agent in S68 as a head-to-head
experiment against tools/residual_rules.py (mine). Scored on a HELD-OUT set of 113 drafts
(.run/S68_rules_eval_set.json), results in .run/rules_b/eval_results.jsonl:
| mine | residual_rules_b | |
|---|---|---|
| classified | 85/113 | 113/113 |
| errored | 28 | 0 |
| any rule fired | 18% | 88% |
| certain/high | 1% | 63% |
| pure residual-SHAPE rules only | ~1% | 1.8% |
Read that last row carefully — it is the most important number in the experiment. Two independent implementations converged at ~1-2% on pure cookbook-shape rules. The shape tier's ceiling is the POPULATION, not the implementation: surgical single-mechanism residuals live at the END of escalations, not in first-pass wave output. So the shape tier belongs in ESCALATION loops; the ladder's value is everything ABOVE it.
Its tiers, with S68 held-out firing counts:
ALREADY-BANKED 28 · INTEG-STANDALONE-MATCH 22 · NOCOMPILE-UNDECLARED-MEASURED 21 ·
NOCOMPILE-UNDECLARED-FIXED 10 · NOCOMPILE-UNDECLARED 9 · NOCOMPILE 5 ·
R372-COPY-CAPTURE 3 · REDRAFT-SIZE-MISMATCH 2 · RPOL-KLASS 1 · W332-MASPSX-WALL 1
THE FREE BANKS IT ALREADY FOUND — do this first, it is the immediate payoff
10 drafts are byte-MATCHES once a missing extern derived from the target's own .s is added.
I verified all 10 myself with match_one: 10 of 10 match, closeness 0. Patched drafts are at
.run/rules_b/<case>/autodecl.c:
ov_SC01_080:func_8017D72C ov_SC04_010:func_8017D6CC ov_SC07_010:func_80180E68
md_SC07_004:func_801A3D60 ov_SC01_077:func_80185E68 ov_SC03_105:func_801829CC
ov_SC03_105:func_80182BD8 ov_SC06_022:func_8017CDE4 ov_SC07_000:func_8017E1BC
ov_SC07_002:func_80185FB0
Plus 22 more classified INTEG-STANDALONE-MATCH (already match standalone, misfiled as failures)
— gate those too. Gate with tools/gater_lane.py --extra BINARY:PATH, and remember main routes
IN-TREE via harvest_verify (its worktree cannot link).
What is left to build
- Wire it into the wave pipeline between
wave_argsand the draft launch; emit a per-target verdict the card renderer consumes. - Put it in front of every ESCALATION. S68 escalated
main/func_8005D734to Fable at closeness 8 — it is a §332 toolchain wall and the escalation could never have succeeded. The wall tier refuses it in under a second. That was pure waste and it was a MISSING CHECK, not a judgement failure. - HARDEN — and this is the part that matters most. The failure modes are ASYMMETRIC: a false
"needs an agent" costs one draft; a false "skip — already banked" silently drops a bankable
function, possibly forever. That is the worst failure mode in this codebase and the same shape
as
seed_refoffering dead text as bankable earlier in S68. Acceptance test (R39), not optional polish: run the ladder over EVERY function that has ever banked and assert ZERO false skips. Also give it an R32 coverage assertion over its own denominator. - Re-run it — the S68 eval set is stale by construction (28 of its 113 were banked mid-session,
which is exactly why my version errored on 28:
corpus.stubs()no longer contained them and the resolver raised IndexError).
Predictions, so this can be judged rather than believed
- ~30% of a fresh wave skips the drafting agent (the 28 stale is an artifact of this set's age; the durable tiers are the 22 standalone + 10 autodecl + walls).
- ZERO functions falsely skipped. If any are, the ladder is worse than useless.
Related, same session
tools/neighbor_ref.py— BUILT. Ranks matched functions as worked examples; validated onmain/func_80024054, whose top 3 neighbours were all matched in S68 in the same TU.tools/wall_sweep.py— BUILT.--emit-excludefeedsdraw_waves --exclude. 10 fns / 1,027 ins.docs/accelerators.md#15 — the differential-oracle harness (the day-one tool).docs/generic-decomp-package.md— what a NEW decomp inherits.