mirror of
https://github.com/Druthulu/BFM-decomp
synced 2026-09-26 13:33:34 -04:00
1a8cda6c54
- tools/plumbing_groups.py: derives the honest still-open pool from the classified ledgers (R38) — '1,217 PLUMBING' collapsed to 237 (SELF 109 / CALLEE 48 / OTHER 48 / DATA 32) - recover_integration: PER-GROUP ISOLATION (git-checkout binary TUs between groups — one TU-stage edit was poisoning every other group's whole-binary gate with a phantom shared error; per-group banked_from_source capture) + new stages 'macro-externs' (§121 draft-tier, via family_sweep.macro_def_sig_map, R33) and 'tu-scope' (§103 STU binary-tier, the sweep-only lever) - the probe (ov_SC03_107): raw 0/14 -> root-caused (poisoning + stale seed symbols; rtu_match MATCHes them — blind to reloc names, R34) -> symfix-first -> 9/14 BANKED (64%) - sweep finding (Law 3): the no-draft majority (ov_SC02_037 44/44, most of ov_MAIN_012) had verdicts from transient sweep remaps never persisted — family- lane fuel, not recovery fuel; the stored-draft class is consumed - cookbook §173 (symfix-first / per-group isolation / verdicts-without-drafts); index 518 green; R22 clean fleet 213/213; phase total 17 banked @ 0 agent tokens
95 lines
3.9 KiB
Python
95 lines
3.9 KiB
Python
#!/usr/bin/env python3
|
|
"""P31 T6 — group the recorded PLUMBING failures into sweepable campaign groups (R38: the ledgers
|
|
already name every conflict; this derives the work queue from them instead of re-diagnosing).
|
|
|
|
Reads every classified failure ledger (newest verdict per (binary, fn)), keeps the still-open
|
|
PLUMBING rows, parses the conflict out of the recorded detail, and groups:
|
|
|
|
SELF — `conflicting types for 'FN'` where FN is the function itself
|
|
-> recover_integration --stages demacroize,tu-scope (the measured self_decl class)
|
|
CALLEE — conflicting types for another function -> --stages tu-scope (+ the
|
|
gate ladder's cast_call_sites, already standing)
|
|
DATA — conflicting types for / undefined reference to a D_* symbol
|
|
-> tu-scope where decl-shaped; the §171b-1 data-definition carry where undefined
|
|
OTHER — unparsed detail (counted, printed — R32: never silently dropped)
|
|
|
|
Output: .run/plumbing_groups.json — rows {binary, fn, sym, klass, detail}; groups keyed
|
|
(klass, binary) with per-symbol clusters, sized for the sweep order (biggest first).
|
|
"""
|
|
import collections, glob, json, os, re, sys
|
|
|
|
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
|
|
import corpus
|
|
|
|
LEDGER_RE = re.compile(r"failed\.(?P<bin>[A-Za-z0-9_]+?)(?:\.\d+)?\.classified\.txt$")
|
|
CONF_RE = re.compile(r"conflicting types for [`']([A-Za-z_]\w*)")
|
|
UNDEF_RE = re.compile(r"undefined reference to [`']([A-Za-z_]\w*)")
|
|
|
|
_stub_cache = {}
|
|
|
|
|
|
def is_stub(b, fn):
|
|
if b not in _stub_cache:
|
|
try:
|
|
_stub_cache[b] = {s.symbol for s in corpus.stubs(b).values()}
|
|
except Exception:
|
|
_stub_cache[b] = set()
|
|
return fn in _stub_cache[b]
|
|
|
|
|
|
def main():
|
|
newest = {}
|
|
files = glob.glob(".run/*failed*.classified.txt") + glob.glob(".run/c294/*failed*.classified.txt")
|
|
for f in files:
|
|
m = LEDGER_RE.search(os.path.basename(f))
|
|
if not m:
|
|
continue
|
|
b = m.group("bin")
|
|
mt = os.path.getmtime(f)
|
|
for ln in open(f, errors="replace"):
|
|
if "\t" not in ln:
|
|
continue
|
|
fn, verdict = ln.rstrip("\n").split("\t", 1)
|
|
k = (b, fn)
|
|
if k not in newest or newest[k][0] < mt:
|
|
newest[k] = (mt, verdict)
|
|
|
|
rows = []
|
|
other = 0
|
|
for (b, fn), (mt, verdict) in sorted(newest.items()):
|
|
if not verdict.startswith("PLUMBING"):
|
|
continue
|
|
if not is_stub(b, fn):
|
|
continue
|
|
mc, mu = CONF_RE.search(verdict), UNDEF_RE.search(verdict)
|
|
sym = (mc or mu).group(1) if (mc or mu) else None
|
|
if sym is None:
|
|
other += 1
|
|
klass, sym = "OTHER", "?"
|
|
elif sym == fn:
|
|
klass = "SELF"
|
|
elif sym.startswith(("D_", "jtbl_")):
|
|
klass = "DATA-UNDEF" if mu else "DATA-DECL"
|
|
else:
|
|
klass = "CALLEE"
|
|
rows.append({"binary": b, "fn": fn, "sym": sym, "klass": klass, "detail": verdict[:160]})
|
|
|
|
by_kb = collections.Counter((r["klass"], r["binary"]) for r in rows)
|
|
by_sym = collections.Counter((r["klass"], r["sym"]) for r in rows if r["sym"] != "?")
|
|
out = {"n_open_plumbing": len(rows),
|
|
"by_class": dict(collections.Counter(r["klass"] for r in rows)),
|
|
"top_groups_by_binary": [{"klass": k, "binary": b, "n": n}
|
|
for (k, b), n in by_kb.most_common(30)],
|
|
"top_symbols": [{"klass": k, "sym": s, "n": n} for (k, s), n in by_sym.most_common(20)],
|
|
"rows": rows}
|
|
json.dump(out, open(".run/plumbing_groups.json", "w"), indent=1)
|
|
print(f"plumbing_groups: {len(rows)} still-open PLUMBING rows from {len(files)} ledgers")
|
|
print(f" by class: {out['by_class']} (OTHER unparsed: {other})")
|
|
for g in out["top_groups_by_binary"][:12]:
|
|
print(f" {g['klass']:10} {g['binary']:14} n={g['n']}")
|
|
print("wrote .run/plumbing_groups.json")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|