feat(phase-29 Task-12): structured failure telemetry + durable permuter-winner save

The permuter-autopsy prerequisite (Drew-directed). Captures the WHAT/WHERE of every
non-match so the classifier/autopsy (Task-13) can mine it, not just a scalar closeness:
- masked_diff.structured_diff(): the per-instruction masked residual [(idx,mine,tgt),...]
- match_one --json: emits {status,closeness,nins,residual} (refactored to share the primitive)
- backlog FIELDS += residual, passes_tried
- gate_stage.match_one_closeness returns + logs the residual on every near/fail (verified
  end-to-end: a near-miss's structured residual now lands in backlog.jsonl)
- grinder: durably save the winning C to .run/permuter-winners/ BEFORE gate_stage banks
  (this session's lesson — 3 permuter wins were lost to a mid-flight revert)
This commit is contained in:
Drew T
2026-07-20 19:50:14 -06:00
parent 30fa4fb34d
commit eec96f76a4
5 changed files with 71 additions and 24 deletions
+8 -1
View File
@@ -37,7 +37,14 @@ DRAFTS = os.path.join(REPO, ".run/backlog_drafts")
SRC_GLOB = os.path.join(REPO, "src/ov_SC01_077/ov_SC01_077*.c")
STUB_RE = re.compile(r"INCLUDE_ASM\([^,]+,\s*(\w+)\)")
FIELDS = ("ts", "addr", "name", "reach", "klass", "nins", "status",
"closeness", "where_stuck", "best_draft", "binary", "source")
"closeness", "where_stuck", "best_draft", "binary", "source",
# Phase-29 Task-12: structured failure telemetry for the permuter-autopsy loop.
# residual = masked_diff.structured_diff()[:cap] — [(idx, mine, target), ...]; the WHAT/WHERE
# of the residual (reg-alloc swap / scheduled-order flip / extra-absent insn). passes_tried =
# the permuter perm_* passes exercised (from permuter_weights) before the plateau — so the
# autopsy can tell "the right lever was tried and failed" (wall) from "the lever was never
# tried" (missing-transform → extend the ILS). Both optional; absent on legacy records.
"residual", "passes_tried")
def append_record(rec):
+19 -9
View File
@@ -112,16 +112,25 @@ def match_one_closeness(fn, cpath, asm, binary=None):
if hit is not None:
asm = hit.asm_dir
if not asm:
return ("fail", None)
return ("fail", None, None)
try:
r = sh([PY, "tools/match_one.py", fn, "--c", cpath, "--asm-subdir", asm], timeout=180)
r = sh([PY, "tools/match_one.py", fn, "--c", cpath, "--asm-subdir", asm, "--json"], timeout=180)
except subprocess.TimeoutExpired:
return ("fail", None)
first = (r.stdout.strip().splitlines() or ["?"])[0]
if first.startswith("MATCH"):
return ("match", 0)
m = re.search(r"(\d+) mismatch", first)
return ("near", int(m.group(1))) if m else ("fail", None)
return ("fail", None, None)
# Task-12: parse the JSON result line (structured residual for the autopsy); text-fallback on any issue.
try:
j = json.loads([l for l in r.stdout.strip().splitlines() if l.strip()][-1])
if j.get("status") == "match":
return ("match", 0, [])
if j.get("status") == "near":
return ("near", int(j["closeness"]), j.get("residual"))
return ("fail", None, None)
except Exception:
first = (r.stdout.strip().splitlines() or ["?"])[0]
if first.startswith("MATCH"):
return ("match", 0, [])
m = re.search(r"(\d+) mismatch", first)
return ("near", int(m.group(1)), None) if m else ("fail", None, None)
def run_gate(drafts, binary=OV, src=None, asm=None, out=None, good_sha=None,
@@ -251,7 +260,7 @@ def _run_gate_locked(drafts, binary, src, asm, out, good_sha, propagate, source_
for fn in [f for f in draft_fns if f not in verified]:
cpath = os.path.join(REPO, d, fn + ".c")
body = open(cpath).read() if os.path.exists(cpath) else ""
kind, close = match_one_closeness(fn, cpath, asm, binary) if body else ("fail", None)
kind, close, resid = match_one_closeness(fn, cpath, asm, binary) if body else ("fail", None, None)
meta = _manifest_class(fn)
cm = re.search(r"//\s*@class:\s*(.+)", body)
sm = re.search(r"//\s*@stuck:\s*(.+)", body)
@@ -272,6 +281,7 @@ def _run_gate_locked(drafts, binary, src, asm, out, good_sha, propagate, source_
backlog.append_record({"addr": meta.get("addr"), "name": fn, "reach": meta.get("reach"),
"klass": rclass or meta.get("class"), "nins": meta.get("nins"), "status": status,
"closeness": close, "where_stuck": where, "best_draft": draft_path,
"residual": (resid[:24] if resid else None), # Task-12 structured residual telemetry
"binary": binary, "source": source_tag}) # binary: lets the grinder gate non-077 near-misses
backlog.render()
+8 -2
View File
@@ -187,8 +187,14 @@ def main():
continue
win = p16_permute.run_permuter(pd, a.permute_secs, a.j)
if win:
open(os.path.join(REPO, DRAFTS, fn + ".c"), "w").write(
p16_permute.winner_to_draft(open(win).read()))
_wtext = p16_permute.winner_to_draft(open(win).read())
open(os.path.join(REPO, DRAFTS, fn + ".c"), "w").write(_wtext)
# Task-12: durably persist the winning C BEFORE gate_stage banks/propagates/commits, so a
# mid-flight interrupt (kill or a revert of the uncommitted bank) never loses a hard-won
# permuter crack (this session's lesson: 3 wins lost to exactly that). permuter-winners/ is
# the recovery source — winner_to_draft(...) form, re-gateable any time.
_wdir = os.path.join(REPO, ".run", "permuter-winners"); os.makedirs(_wdir, exist_ok=True)
open(os.path.join(_wdir, fn + ".c"), "w").write(_wtext)
won.append((fn, binary)); log(f"permuter WON {fn} @ {binary} (close was {r.get('closeness')})")
except Exception as e:
log(f"{fn}: {e}")
+22
View File
@@ -166,6 +166,28 @@ def insns_from_s(s_path):
return insns
def structured_diff(mine, tgt):
"""The per-instruction relocation-masked mismatch list: [(idx, mine_str, tgt_str), ...] over
two insns_from_* streams. Each entry is a mismatching instruction position; mine_str/tgt_str are
'<word_hex> <mnemonic>' (or '--' past the end of the shorter stream). len() == the masked mismatch
count (the `closeness`). This is the STRUCTURED RESIDUAL telemetry the permuter-autopsy classifier
reads (Phase-29 Task-12): idx localizes the residual, the word+mnem pair says what codegen differs
(a reg-alloc swap, a scheduled-order flip, an extra/absent instruction). Shared by match_one +
gate_stage's near-record so the same residual an agent reads is the one the autopsy mines."""
n = max(len(mine), len(tgt))
diffs = []
for i in range(n):
mw = mine[i]['word'] if i < len(mine) else None
mask = mask_for(mine[i]['word'], mine[i]['reloc_kind']) if i < len(mine) else 0xFFFFFFFF
me = (mw & mask) if mw is not None else None
tg = (tgt[i]['word'] & mask) if i < len(tgt) else None
if me != tg:
diffs.append((i,
('%08x %s' % (mine[i]['word'], mine[i]['mnem'])) if i < len(mine) else '--',
('%08x %s' % (tgt[i]['word'], tgt[i]['mnem'])) if i < len(tgt) else '--'))
return diffs
def diff_object_object(cand, tgt):
"""masked mismatch count between two objdump'd objects (permuter scorer). Mask driven by the
TARGET's relocs; at masked reloc/jal positions also require reloc-operand (symbol+addend) equality.
+14 -12
View File
@@ -18,6 +18,7 @@ longer under-counted (func_80132784 now reads its true 400 ins, not 384).
"""
import subprocess, re, sys, os, argparse
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
import json
import masked_diff
ap = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter)
@@ -31,6 +32,9 @@ ap.add_argument('--work', default=None,
ap.add_argument('--o0', action='store_true',
help='compile at -O0 (for the _o0 split subsegments: ov_SC01_077_o0.c, whale _o0b — '
'their target bytes are -O0; an -O2 compile can never match them, Makefile:445)')
ap.add_argument('--json', action='store_true',
help='emit one JSON result line {status,closeness,nins,residual} (Task-12 structured '
'residual telemetry for the permuter-autopsy). Still exits 0 on MATCH, 1 otherwise.')
a = ap.parse_args()
# PRIVATE SCRATCH BY DEFAULT (Phase-28 T5). This tool's own docstring promises "Fully isolated (own
@@ -91,21 +95,19 @@ tgt = masked_diff.insns_from_s('%s/%s.s' % (a.asm_subdir, a.fn))
if not mine:
print('FAIL: my object has no function', a.fn, '(compile produced nothing?)'); sys.exit(1)
n = max(len(mine), len(tgt))
diffs = []
for i in range(n):
mw = mine[i]['word'] if i < len(mine) else None
mask = masked_diff.mask_for(mine[i]['word'], mine[i]['reloc_kind']) if i < len(mine) else 0xFFFFFFFF
me = (mw & mask) if mw is not None else None
tg = (tgt[i]['word'] & mask) if i < len(tgt) else None
if me != tg:
diffs.append((i,
('%08x %s' % (mine[i]['word'], mine[i]['mnem'])) if i < len(mine) else '--',
('%08x %s' % (tgt[i]['word'], tgt[i]['mnem'])) if i < len(tgt) else '--'))
# structured residual (shared with gate_stage's near-record + the Task-12 autopsy telemetry)
diffs = masked_diff.structured_diff(mine, tgt)
if not diffs and len(mine) == len(tgt):
print('MATCH (%d ins) %s' % (len(mine), a.fn))
if a.json:
print(json.dumps({"status": "match", "closeness": 0, "nins": len(mine), "residual": []}))
else:
print('MATCH (%d ins) %s' % (len(mine), a.fn))
sys.exit(0)
if a.json:
print(json.dumps({"status": "near" if diffs else "fail", "closeness": len(diffs),
"nins": len(mine), "residual": [[i, me, tg] for i, me, tg in diffs[:48]]}))
sys.exit(1)
print('DIFF %s mine=%d ins, target=%d ins, %d mismatched' % (a.fn, len(mine), len(tgt), len(diffs)))
print(' idx | MINE | TARGET')
for i, me, tg in diffs[:40]: