mirror of
https://github.com/Druthulu/BFM-decomp
synced 2026-09-26 05:32:17 -04:00
1dd15eda32
tools/triage_ladder.py — the zero-token pre-agent pass, split PRE (target-side: BANKED/WALL-332/PARKED, no build) from POST (residual_rules_b, needs a draft). --escalate refuses a walled or banked target; --acceptance is the R39/R32 harness. Refuses on a non-quiescent tree: a merging gate makes the stub oracle wrong in both directions (measured, ov_SC01_004:func_8017EB30). Acceptance, on 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). The first wall control asked for evidence that CANNOT exist — it scanned banked functions' .s, which splat never writes — and printed '0 scanned / 0 tripped', indistinguishable from a pass. The R32 empty-denominator assertion caught it on its first run; replaced with a two-sided sweep over all open stubs. tools/cast_self_callers.py — the §378 lever + --sync-decls for the narrow-param case C89 forbids no-proto from reaching (§378a). Wiring: wave_args drops walled/parked targets at draw time via pre_classify (one implementation, R33); escalate_fable.js refuses any target without triage:'DRAFT'. Tool fixes found by measurement: * fix_arity_callers was blind to main entirely (globbed src/main/main*.c; main is src/*.c) — reported success over an empty file set through three gates. Now refuses when --binary selects no files. * parallel_gate records each worker's 'failed by class' line (was truncated out of the 200-char tail); gater_lane retries in-tree ONLY on the diagnostic-free blind-worktree signature — S69 ran 22 serial retries against real cc1 errors. docs: cookbook §376/§377/§378 (index 1033), SETUP.md, wave-playbook §4b.
139 lines
7.2 KiB
Python
139 lines
7.2 KiB
Python
#!/usr/bin/env python3
|
|
"""tools/wave_args.py — emit the EXACT Workflow args for a drafting wave, asserted (P31 S66).
|
|
|
|
Usage: wave_args.py .run/<wave> [--out <wave>/wf_args.json]
|
|
|
|
WHY THIS EXISTS (measured, P31 S66). `tools/workflows/claude_wave_draft.js` takes its targets as
|
|
an INLINE args object, so the orchestrator has to reproduce `<wave>/targets.json` by hand into the
|
|
tool call. I did exactly that for wave r1 and hand-typed `sub` as `ov_SC03_112/jr_80181D08` when
|
|
the truth is `asm/ov_SC03_112/nonmatchings/ov_SC03_112_jr_801817E0` — a DIFFERENT subdirectory,
|
|
not merely a shorter spelling (a stub's asm dir is named for its jr-carve block, not for itself).
|
|
Every agent's `match_one --asm-subdir` would have pointed at a path that does not exist, so all 19
|
|
oracles would have failed identically and the wave would have read as a model failure (R40:
|
|
exonerate the instrument). Caught before launch only because the .s paths were re-read.
|
|
|
|
So: never retype a wave's targets. Run this, paste its output. It ASSERTS, per target (R32/R43):
|
|
* targets.json carries every field the workflow prompt interpolates
|
|
* the .s file exists on disk, and `sub` is exactly its parent directory
|
|
* the pack the agent is told to read in full exists
|
|
and REFUSES to emit anything if a single target fails — a partly-correct args blob is the failure
|
|
mode this tool exists to delete.
|
|
"""
|
|
import argparse, json, os, sys
|
|
sys.path.insert(0, os.path.join(os.path.dirname(os.path.abspath(__file__))))
|
|
|
|
REPO = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
|
FIELDS = ('name', 'binary', 'nins', 'sub', 'arm')
|
|
|
|
|
|
def main():
|
|
ap = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter)
|
|
ap.add_argument('wave', help='wave dir, e.g. .run/r1')
|
|
ap.add_argument('--out', default=None, help='also write the args JSON here (default <wave>/wf_args.json)')
|
|
a = ap.parse_args()
|
|
wave = a.wave.rstrip('/')
|
|
tgt_path = os.path.join(REPO, wave, 'targets.json')
|
|
if not os.path.exists(tgt_path):
|
|
sys.exit('REFUSED: no %s' % tgt_path)
|
|
targets = json.load(open(tgt_path))
|
|
|
|
# OPENNESS IS AN ASSERTION, NOT A CONVENTION (P31 S66, measured — see F14).
|
|
# A wave drawn at T and launched at T+2h is stale by construction: gates land continuously, and
|
|
# sibling propagation banks targets nobody drafted. Measured on this session's own waves,
|
|
# against corpus.stubs at gate time: w2 80 drawn / 12 still open (85% wasted), w3 80/39 (51%),
|
|
# x1 80/71 (11%). ~109 of 240 agents in w2+w3 re-derived already-banked functions; they detected
|
|
# it themselves only AFTER reading the pack and burning their budget. So refuse a closed target
|
|
# here, where it costs nothing.
|
|
import importlib
|
|
corpus = importlib.import_module('corpus')
|
|
closed, open_cache = [], {}
|
|
for t in targets:
|
|
b = t.get('binary')
|
|
if not b:
|
|
continue
|
|
if b not in open_cache:
|
|
try:
|
|
open_cache[b] = {v.symbol for v in corpus.stubs(b).values()}
|
|
except Exception as e: # a refusing oracle is reported, never silently trusted
|
|
print('WARN: stub oracle refused %s (%s) — openness unchecked for it'
|
|
% (b, str(e)[:80]), file=sys.stderr)
|
|
open_cache[b] = None
|
|
live = open_cache[b]
|
|
if live is not None and t['name'] not in live:
|
|
closed.append('%s:%s' % (b, t['name']))
|
|
if closed:
|
|
print('SKIPPING %d of %d target(s) already banked since the draw: %s%s'
|
|
% (len(closed), len(targets), ', '.join(closed[:8]),
|
|
' …' if len(closed) > 8 else ''), file=sys.stderr)
|
|
targets = [t for t in targets
|
|
if open_cache.get(t.get('binary')) is None
|
|
or t['name'] in open_cache[t['binary']]]
|
|
if not targets:
|
|
sys.exit('REFUSED: every target in %s is already banked — nothing to draft.' % wave)
|
|
|
|
# THE TRIAGE LADDER'S TARGET-SIDE TIERS (P31 S69). The openness check above is one tier of a
|
|
# ladder; the others are just as draft-independent and just as cheap. A §332 delay-slot wall
|
|
# cannot be produced from C by the pinned triple, so an agent spending its whole budget on one
|
|
# is pure waste — S68 paid 92,684 tokens to rediscover that, then escalated a SECOND wall to a
|
|
# frontier model at closeness 8. `triage_ladder.pre_classify` is the single implementation of
|
|
# those tiers (R33); this is its draw-time consumer. Quiescence is NOT asserted here: the wall
|
|
# and parked tiers read only the .s and a ledger, and the banked tier is handled above.
|
|
ladder = importlib.import_module('triage_ladder')
|
|
parked = ladder.load_parked()
|
|
refused = []
|
|
for t in list(targets):
|
|
b = t.get('binary')
|
|
if not b:
|
|
continue
|
|
try:
|
|
v = ladder.pre_classify(b, t['name'], parked, t.get('asm'))
|
|
except Exception as e: # a refusing tier is reported, never silently trusted
|
|
print('WARN: triage tier refused %s:%s (%s) — target kept'
|
|
% (b, t['name'], str(e)[:80]), file=sys.stderr)
|
|
continue
|
|
if v['verdict'] in ('WALL-332', 'PARKED'):
|
|
refused.append((b, t['name'], v['verdict'], v['evidence']))
|
|
targets.remove(t)
|
|
if refused:
|
|
print('TRIAGE dropped %d target(s) no agent can bank:' % len(refused), file=sys.stderr)
|
|
for b, fn, verdict, ev in refused:
|
|
print(' %s:%s %s — %s' % (b, fn, verdict, ev), file=sys.stderr)
|
|
if not targets:
|
|
sys.exit('REFUSED: every target in %s is banked, walled or parked — nothing to draft.' % wave)
|
|
|
|
bad = []
|
|
for t in targets:
|
|
miss = [f for f in FIELDS if not t.get(f) and t.get(f) != 0]
|
|
if miss:
|
|
bad.append('%s: missing field(s) %s' % (t.get('name', '?'), ','.join(miss))); continue
|
|
asm = t.get('asm') or os.path.join(t['sub'], t['name'] + '.s')
|
|
if not os.path.exists(os.path.join(REPO, asm)):
|
|
bad.append('%s: asm does not exist: %s' % (t['name'], asm)); continue
|
|
if os.path.dirname(asm) != t['sub'].rstrip('/'):
|
|
bad.append('%s: sub (%s) is not the .s parent (%s)' % (t['name'], t['sub'], os.path.dirname(asm)))
|
|
pack = os.path.join(REPO, wave, 'packs', t['name'] + '.md')
|
|
if not os.path.exists(pack):
|
|
bad.append('%s: no pack at %s' % (t['name'], os.path.relpath(pack, REPO)))
|
|
if bad:
|
|
print('REFUSED: %d of %d target(s) failed the assertion:' % (len(bad), len(targets)), file=sys.stderr)
|
|
for b in bad:
|
|
print(' ' + b, file=sys.stderr)
|
|
sys.exit(2)
|
|
|
|
args = {'wave': wave,
|
|
'targets': [{k: t[k] for k in FIELDS} for t in targets]}
|
|
blob = json.dumps(args, separators=(',', ':'))
|
|
out = a.out or os.path.join(REPO, wave, 'wf_args.json')
|
|
open(out, 'w').write(blob + '\n')
|
|
arms = {}
|
|
for t in args['targets']:
|
|
arms[t['arm']] = arms.get(t['arm'], 0) + 1
|
|
print('OK: %d target(s) asserted (%s); args written to %s' %
|
|
(len(targets), ', '.join('%s %d' % kv for kv in sorted(arms.items())), os.path.relpath(out, REPO)),
|
|
file=sys.stderr)
|
|
print(blob)
|
|
|
|
|
|
if __name__ == '__main__':
|
|
main()
|