From fb4c1f03a1b5dab773d1466dbfd39c7438f9e4ec Mon Sep 17 00:00:00 2001 From: Drew T <50529377+Druthulu@users.noreply.github.com> Date: Sat, 15 Aug 2026 09:31:10 -0600 Subject: [PATCH] fix(phase-31): wave-selector coverage + gate_lane crash-vs-empty (S52-1, S52-8) build_wave_atlas.py: - taken-set was a hardcoded 'abcdefghijkl' wave-letter literal -> glob('.run/wave_*_cards.json') (R33 derive, don't re-derive). NC: 634 -> 726 taken, strict superset, +92 cards from waves m/n that the literal would have silently re-issued. - --exclude-bins defaulted to 'main' on the REFUTED link-defect rationale; default now empty and the help states the real reason (main cannot be gated incrementally; use tools/gate_main.py). - new --only-bins allow-list: main waves need it, since gate_main rebuilds once per SLATE. gate_lane.py: an unhandled gate_stage exception produced no JSON line -> v={} -> '0 banked', indistinguishable from an honest empty gate (cost 2 cycles in P31). Now a non-zero rc or a missing JSON line is labelled CRASH with the stderr tail, recorded in the results JSON, listed in the summary as NEVER GATED, and exits non-zero. R39 NC both directions. --- phase-ends/CURRENT_PHASE.md | 13 +++++++++++++ tools/build_wave_atlas.py | 28 +++++++++++++++++++++------- tools/gate_lane.py | 24 ++++++++++++++++++++---- 3 files changed, 54 insertions(+), 11 deletions(-) diff --git a/phase-ends/CURRENT_PHASE.md b/phase-ends/CURRENT_PHASE.md index 3b4fe6ead..a3baa2136 100644 --- a/phase-ends/CURRENT_PHASE.md +++ b/phase-ends/CURRENT_PHASE.md @@ -57,6 +57,19 @@ R22 clean-fleet **213/213** after every banked batch · tools-health green · 0 - 2026-08-14 — **T9 COMPLETE.** `tools/warmstart.py` (the permuter/grinder FEEDER): `--from-banked` walks a banked exemplar's h_seq family's still-open members, builds remapped proven-body drafts (`symbol_map` + `aprop_autodraft.build_draft`, refusing on reloc-count mismatch), **stream-classifies member-vs-seed with ZERO compiles** (masked_diff-shaped dicts from ground-truth bytes → `residual_class.classify_streams`), enqueues ONLY permuter-shaped (bucket==permuter or LENGTH-DRIFT |Δ|≤2) as backlog near-records; `--lenmiss` ingests T8's 49-route. **Grinder patch NOT needed** (its candidates() deliberately keeps unclassified records — "unknown is not a reason to skip" — so pre-filtered enqueues flow as-is; documented in the feeder's docstring). Armed live: **49 + 10 enqueued, 120 refused** by the stream filter (the anti-92%-wasted-CPU discipline working). `family_cousins --weak-cards`: **954 units** (the 0.70–0.85 annotate-only band, never before consumed) as seeded-crack cards, ins-ranked, §168 laws embedded, model-routed **haiku 804 / v3 43 / sonnet 86 / opus 21** (cheap tiers dominate — the token-efficiency shape), 0 unresolved `.s`. +## SESSION S52 TASK LIST (2026-08-15, ultracode) — the monitorable view (no TaskCreate tool in this harness build) + +- [x] **S52-1 — Preflight + wave-selector repair.** Tree clean @ `commit:2390`, no gate/grinder in flight. Fixed `build_wave_atlas.py`: (a) the already-waved set was a hardcoded `'abcdefghijkl'` wave-letter literal → now a `glob('.run/wave_*_cards.json')` derivation (R33); **NC: old 634 → new 726 taken, strict superset, +92 previously-missable cards from waves m/n**; (b) `--exclude-bins` defaulted to `main` carrying the REFUTED link-defect rationale → default now empty, help corrected to the real (gate-path) reason; (c) new `--only-bins` allow-list (main waves need it — `gate_main` rebuilds once per SLATE, so main has no per-TU gate cost). +- [x] **S52-2 — Atlas regen** (`make atlas`, $0) — the selector was stale by ~448 banks (last regen predates waves J–N). +- [x] **S52-8 — `gate_lane` crash-vs-empty fixed.** A non-zero rc or a missing JSON line is now labelled `‼ CRASH`, prints the stderr tail, records `{'CRASH':True,…}` in the results JSON, lists the never-gated groups in the summary, and exits non-zero. R39 NC both ways: crash→CRASH, honest-empty→not-CRASH. +- [~] **S52-3 — Wave O: MAIN + the UNKNOWN probe** — LAUNCHED (`wf_278b05de-bae`, 49 cards / **6,266 ins**, 2 gate groups). **The selection finding that reshaped this wave: main's agent-lane pool at ≥20 ins is nearly SPENT (21 left after 113 already waved) — main's remaining 52,714 ins are overwhelmingly UNKNOWN-lever, as are 138k fleet-wide.** So wave O is deliberately three arms: 21 main head-crack (avg 145 ins, opus) + 18 main UNKNOWN (avg 122) + 10 overlay UNKNOWN (`ov_SC04_011`, avg 103, sonnet). The UNKNOWN arms are an R37 probe of the biggest unclaimed block on the atlas — 1,239 candidates at 60–120 ins alone. Wave prompt gained §175/§176A–C (statement-order-around-a-call first; pin the interloper; **a pin cannot schedule across a call and can silently DELETE an instruction**). +- [ ] **S52-4 — Independent re-verify (R14) + `gate_main.py --apply`** (one clean rebuild per slate). +- [ ] **S52-5 — R22 clean fleet 213/213 + commit** (task + this log together). +- [ ] **S52-6 — Wave P: overlay TU-packed**, drafted concurrently with the main gate (drafting is tree-free). +- [ ] **S52-7 — Recover the ~10 conflict-dropped main drafts** (waves J/K/L; verified-correct, need cast-at-use). +- [ ] **S52-8 — Tooling debt: `gate_lane` swallows `gate_stage` stderr** (reports a crash as `0/0/0`). +- [ ] **S52-9 — Bank idioms into the cookbook + refresh this checkpoint** BEFORE any pause (memory `bank-idioms-before-checkpoint`). + ## Campaign velocity ledger (T10+) | wave | lane | cards | standalone MATCH | banked | tokens | tok/bank | lesson banked | diff --git a/tools/build_wave_atlas.py b/tools/build_wave_atlas.py index c7f87e57e..eb98b73ef 100644 --- a/tools/build_wave_atlas.py +++ b/tools/build_wave_atlas.py @@ -20,7 +20,7 @@ MUST NOT run while a gate is in flight (R35 — corpus.stubs() misreports substi Usage: build_wave_atlas.py [N] [--max-bins K] [--min-ins M] [--levers a,b,c] """ -import json, sys, collections, subprocess, argparse +import json, sys, collections, subprocess, argparse, glob sys.path.insert(0, 'tools') import corpus @@ -33,25 +33,38 @@ ap.add_argument('--max-ins', type=int, default=120, help='above this the bulk la ap.add_argument('--levers', default='head-crack,seeded-crack,redraft,len-vein,integration,family-sweep,tiny-direct', help='agent-draftable levers; UNKNOWN/tell/jtbl/o0/cc1 need their own lanes') ap.add_argument('--atlas', default='.run/atlas.json') -ap.add_argument('--exclude-bins', default='main', - help='comma-separated binaries to skip. main is excluded BY DEFAULT: it is blocked ' - 'on a link-resolution defect (a byte-correct C fn retargets a jal between game ' - 'code and a linked PsyQ archive object), not on matching — see CURRENT_PHASE.md') +ap.add_argument('--exclude-bins', default='', + help='comma-separated binaries to skip. NOTHING is excluded by default. ' + '(History: main used to be excluded on a "link-resolution defect" — that ' + 'diagnosis was REFUTED 2026-08-15 by a null-draft control: the false diff ' + 'reproduces with ZERO drafts substituted. main simply cannot be gated ' + 'INCREMENTALLY, because its extract runs psyq_integrate/ld_interleave and ' + 'rewrites the .ld. Draft main like any binary; gate it with ' + 'tools/gate_main.py, never gate_lane/gate_stage.)') +ap.add_argument('--only-bins', default='', + help='comma-separated allow-list; if set, ONLY these binaries are eligible. ' + 'Use --only-bins main for a main wave: gate_main.py rebuilds the whole EXE ' + 'once per SLATE, so main has no per-TU gate cost and --max-bins can be large.') a = ap.parse_args() EXCLUDE = {b for b in a.exclude_bins.split(',') if b} +ONLY = {b for b in a.only_bins.split(',') if b} busy = subprocess.run(['pgrep', '-f', 'tools/(gate_stage|dedup_propagate|gate_lane)'], capture_output=True, text=True) if busy.returncode == 0 and busy.stdout.strip(): sys.exit(f"REFUSING: gate in flight (pids {busy.stdout.split()}) — corpus.stubs() would misreport (R35).") -PRIORS = ['.run/wave_p31%s_cards.json' % c for c in 'abcdefghijkl'] +# R32/R33: derive the already-waved set from what is ON DISK, never from a hardcoded wave-letter +# list (the literal 'a'..'l' silently missed waves m and n and would have re-issued their cards). +PRIORS = sorted(glob.glob('.run/wave_*_cards.json')) taken = set() for p in PRIORS: try: taken |= {c.get('fn') or c.get('name') for c in json.load(open(p))} - except (FileNotFoundError, json.JSONDecodeError): + except (FileNotFoundError, json.JSONDecodeError, TypeError): pass +if not PRIORS: + print('NOTE: no prior wave card files found — nothing filtered as already-waved', file=sys.stderr) levers = set(a.levers.split(',')) atlas = json.load(open(a.atlas)) @@ -86,6 +99,7 @@ for g in atlas['groups']: fn, b, nins = m.get('name'), m.get('b'), m.get('nins') or 0 if not fn or not b: skipped['no-name'] += 1; continue if b in EXCLUDE: skipped['excluded-binary'] += 1; continue + if ONLY and b not in ONLY: skipped['not-in-only-bins'] += 1; continue if fn in taken: skipped['already-waved'] += 1; continue if not (a.min_ins <= nins <= a.max_ins): skipped['out-of-band'] += 1; continue if not is_open(b, fn): skipped['already-banked'] += 1; continue diff --git a/tools/gate_lane.py b/tools/gate_lane.py index 66b6e564a..76bca2474 100644 --- a/tools/gate_lane.py +++ b/tools/gate_lane.py @@ -44,7 +44,7 @@ if n_g != len(cards): # R32: a silent skip is a DEFECT, not a no-op print(f"R32 COVERAGE: {len(cards)} records -> {n_g} gateable; {len(missing)} missing drafts: {missing[:8]}", flush=True) if n_g == 0: sys.exit("R32: nothing gateable — refusing to report 0 as a result") print(f"gating {n_g} drafts in {len(groups)} groups", flush=True) -banked=[]; res={} +banked=[]; res={}; crashed=[] for (b,src),items in sorted(groups.items()): if dirty(): subprocess.run("git checkout -- src/ config/",shell=True); print(" (reverted residue)",flush=True) @@ -57,13 +57,28 @@ for (b,src),items in sorted(groups.items()): if sub: cmd+=["--asm-subdir",sub] r=subprocess.run(cmd,capture_output=True,text=True) # NO outer timeout (§169 law 3) js=[l for l in (r.stdout+r.stderr).splitlines() if l.startswith('{')] - v=json.loads(js[-1]) if js else {} - got=v.get('verified',[]); banked+=got - print(f" {b:14} {os.path.basename(src or ''):34} {len(items)}d -> {len(got)} banked", flush=True) + # A CRASH IS NOT A RESULT. Without this, an unhandled gate_stage exception (e.g. + # corpus.CorpusError) left js empty -> v={} -> "0 banked", INDISTINGUISHABLE from an honest + # empty gate. That cost two cycles in P31. Surface stderr and label the group CRASH. + if r.returncode != 0 or not js: + tail="\n".join((r.stderr or r.stdout or "").strip().splitlines()[-12:]) + v={'CRASH':True,'returncode':r.returncode,'stderr_tail':tail,'verified':[]} + crashed.append(f"{b}:{src}") + print(f" {b:14} {os.path.basename(src or ''):34} {len(items)}d -> ‼ CRASH rc={r.returncode} " + f"(NOT an empty result)\n{tail}", flush=True) + else: + v=json.loads(js[-1]) + got=v.get('verified',[]) + print(f" {b:14} {os.path.basename(src or ''):34} {len(items)}d -> {len(got)} banked", flush=True) + banked+=v.get('verified',[]) res[f"{b}:{src}"]=v json.dump(res,open(outp,'w'),indent=1) if dirty(): subprocess.run("git checkout -- src/ config/",shell=True) print(f"\nBANKED {len(banked)}: {banked}", flush=True) +if crashed: + print(f"‼ {len(crashed)} GATE GROUP(S) CRASHED and were NEVER GATED: {crashed}\n" + f" Their drafts are unjudged — re-run those groups after fixing the cause " + f"(run tools/gate_stage.py directly for the full traceback).", flush=True) json.dump(banked,open(outp.replace('.json','_banked.json'),'w')) # propagate per function for fn in banked: @@ -76,3 +91,4 @@ for fn in banked: subprocess.run(f'git commit -q -m "feat({tag}): propagate {fn} (gate lane)"',shell=True) print(f" prop {fn}: committed", flush=True) print("DONE", flush=True) +if crashed: sys.exit(f"EXIT NON-ZERO: {len(crashed)} gate group(s) crashed — output above is INCOMPLETE.")