From 8f89cbcccdec6bdf62dbc919221193c3f7b12869 Mon Sep 17 00:00:00 2001 From: Drew T <50529377+Druthulu@users.noreply.github.com> Date: Mon, 31 Aug 2026 16:29:45 -0600 Subject: [PATCH] =?UTF-8?q?fix(pgate):=20main=20could=20never=20bank=20in?= =?UTF-8?q?=20a=20worktree=20=E2=80=94=20derive=20link=20inputs=20from=20t?= =?UTF-8?q?he=20Makefile,=20and=20REFUSE=20when=20they=20are=20absent?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit stage_generated hard-coded build//{.ld,undefined_*_auto.txt}. That is the OVERLAY convention. main's Makefile variables put its linker script at build/us/SLUS_007.26.ld and BOTH undefined_*_auto.txt at the REPO ROOT, so a worktree got none of them, could not link, and every main draft came back rejected -- indistinguishable from a wave of bad drafts. Measured this session: main banked 0 of 3 while the same drafts were match_one MATCH. The tell was already being recorded and thrown away: the results JSON carried missing_generated: [main.ld, undefined_syms_auto.txt, undefined_funcs_auto.txt] and nothing consumed it -- R32's corrected form, a loud failure nobody counts is exactly as invisible as a silent one. Same shape as R43's 'sweep_parallel accepted main and banked 0/105'. Now: paths come from the Makefile's own _LD_SCRIPT / _UNDEF_SYMS / _UNDEF_FUNCS (R33 -- derive from the invariant), are mirrored at the same repo-relative location in the worktree, and a missing one REFUSES the binary with the reason instead of gating it anyway (R43). Negative control (R39): resolved and existence-checked across all 213 binaries -- 0 would be refused, so the previously-succeeding population is untouched. --- tools/parallel_gate.py | 56 ++++++++++++++++++++++++++++++++++-------- 1 file changed, 46 insertions(+), 10 deletions(-) diff --git a/tools/parallel_gate.py b/tools/parallel_gate.py index a812b8343..f1eadd0bd 100644 --- a/tools/parallel_gate.py +++ b/tools/parallel_gate.py @@ -46,13 +46,36 @@ to prevent). Adopting is per-BINARY and per-FILE, never a blanket add. parallel_gate.py --plan plan.json [--workers 8] [--commit] [--r22] [--keep] plan.json: [{"binary": "ov_SC03_099", "drafts": "/abs/path/to/dir"}, ...] """ -import argparse, json, os, re, shutil, subprocess, sys, time +import argparse, functools, json, os, re, shutil, subprocess, sys, time from concurrent.futures import ThreadPoolExecutor, as_completed REPO = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) PY = os.path.join(REPO, ".venv/bin/python") WT_ROOT = os.path.join(REPO, ".run/pgate") -GEN = ("{b}.ld", "undefined_syms_auto.txt", "undefined_funcs_auto.txt") +# The per-binary generated link inputs. DO NOT hard-code `build//.ld` — that is the +# OVERLAY convention and it is wrong for `main`, whose Makefile variables put the linker script at +# `build/us/SLUS_007.26.ld` and its two undefined_*_auto.txt at the REPO ROOT. Hard-coding it made +# every main draft fail in a worktree and report as an honest gate rejection: measured S68, main +# banked 0 of 3 with `missing_generated: [main.ld, undefined_syms_auto.txt, +# undefined_funcs_auto.txt]` recorded in the results JSON and acted on by nobody — R32's corrected +# form, a loud failure that nobody counts is exactly as invisible as a silent one, and the same +# shape as R43's `sweep_parallel accepted main and banked 0/105`. +# Ask the Makefile, which already declares _LD_SCRIPT / _UNDEF_SYMS / _UNDEF_FUNCS per +# binary (R33: derive from the invariant, do not re-derive it). +GEN_VARS = ("LD_SCRIPT", "UNDEF_SYMS", "UNDEF_FUNCS") + + +@functools.lru_cache(maxsize=None) +def generated_paths(binary): + """(repo-relative path, ...) of 's generated link inputs, per the Makefile itself.""" + r = sh(["make", "--no-print-directory", "--eval=__pgate-%: ; @echo \"$($*)\""] + + ["__pgate-" + v for v in GEN_VARS] + ["BINARY=" + binary]) + out = [ln.strip() for ln in r.stdout.splitlines() if ln.strip()] + if r.returncode or len(out) != len(GEN_VARS): + raise SystemExit("[pgate] cannot resolve generated paths for %r from the Makefile " + "(rc=%d, got %r) — refusing to gate a binary whose link inputs are " + "unknown (R43)" % (binary, r.returncode, out)) + return tuple(out) def sh(cmd, cwd=REPO, timeout=None): @@ -117,17 +140,20 @@ def link_missing(s_dir, d_dir): def stage_generated(wt, binary): - """Copy splat's untracked per-binary outputs so the worker can LINK without re-extracting.""" - src, dst = os.path.join(REPO, "build", binary), os.path.join(wt, "build", binary) - os.makedirs(dst, exist_ok=True) + """Copy splat's untracked per-binary outputs so the worker can LINK without re-extracting. + + Paths come from the Makefile (see generated_paths) and are mirrored at the SAME repo-relative + location inside the worktree, so main's root-level undefined_*_auto.txt land at the root and an + overlay's land under build// — without this function needing to know which is which.""" missing = [] - for pat in GEN: - f = pat.format(b=binary) - s = os.path.join(src, f) + for rel in generated_paths(binary): + s = os.path.join(REPO, rel) + d = os.path.join(wt, rel) if os.path.exists(s): - shutil.copy2(s, os.path.join(dst, f)) + os.makedirs(os.path.dirname(d) or wt, exist_ok=True) + shutil.copy2(s, d) else: - missing.append(f) + missing.append(rel) # EXTRACTED ASSET OBJECTS. The link line pulls build/assets//*.o (splat's binary-data # objects, e.g. trailing.o). Without them the compile succeeds and the LINK dies with # "cannot find build/assets//trailing.o" — a failure that arrives late and, again, reads @@ -245,6 +271,16 @@ def gate_one(idx, pin, job): wt = job.get("_wt") try: missing = stage_generated(wt, binary) + if missing: + # REFUSE, never gate anyway (R43). A worker missing its linker script or its + # undefined_*_auto.txt cannot LINK, so every draft comes back rejected and the batch + # reads as an honest wave of bad drafts. Measured S68: main banked 0 of 3 this exact + # way, and the `missing_generated` field that recorded it was consumed by nobody. + return {"binary": binary, "banked": [], "files": {}, "ovl": None, + "secs": round(time.time() - t0, 1), "missing_generated": missing, "rc": None, + "error": "REFUSED — generated link inputs absent from the worktree: %s " + "(run `make extract BINARY=%s` in the main tree first)" + % (", ".join(missing), binary)} # A CARVING JOB NEEDS ITS OWN asm/ (see isolate_asm). Without this the job is unsafe in a # worktree and had to run serially; with it, jtbl parallelises like everything else. if _drafts_carry_jtbl(binary, drafts):