From ae7c68a89aafdc03cf08fcb71fcfe9c6bf53df17 Mon Sep 17 00:00:00 2001 From: Drew T <50529377+Druthulu@users.noreply.github.com> Date: Tue, 1 Sep 2026 00:05:25 -0600 Subject: [PATCH] fix(gate): carry the per-function verdicts out of the worktree, and gate the in-tree retry on them MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The first version of this parsed 'failed by class:' from the worker's stdout and was INERT: the worker is gate_stage, which never prints that line (harvest_verify does, one level down). classes came back empty for all 17 binaries of a batch and the retry gate that consumed it fired ZERO times — a field that is always empty makes its consumer a silent no-op (R54). Verified the claim only after re-reading the log; correcting it here. Now parallel_gate copies harvest_verify's .classified.txt out of the worktree before teardown (it lives in the worktree's own .run/, which is not symlinked and dies with it) and derives the class summary from those rows. That also PRESERVES the verdict layer, which until now survived only as a side effect of gater_lane re-running the whole binary in-tree afterwards (R47). gater_lane judges the retry on the rows: a class with no per-function diagnostic is the blind-worktree signature; anything cc1 named is a real compile error and the serial rebuild would only reproduce it. Verified live on ov_SC07_000: 'NOT retrying in-tree' fired, and the verdict row landed at .run/gate_lane/ov_SC07_000.pgate.classified.txt. --- tools/gater_lane.py | 9 +++++++-- tools/parallel_gate.py | 40 +++++++++++++++++++++++++++++++++++----- 2 files changed, 42 insertions(+), 7 deletions(-) diff --git a/tools/gater_lane.py b/tools/gater_lane.py index 1648dbac4..88cb42648 100644 --- a/tools/gater_lane.py +++ b/tools/gater_lane.py @@ -399,9 +399,14 @@ def main(): # worktree was not blind: the draft genuinely does not compile in its TU, and the in-tree # retry is a serial full-binary build that reproduces the same error. S69 ran 22 of those # back-to-back and banked 0 — ~20 minutes of a lane whose whole design goal is parallelism. + # Judge on the VERDICT ROWS the worker actually wrote, not on a summary string. A row reads + # `\t: :: `; the blind-worktree signature is a class with NO + # per-function diagnostic (`CC1-FAIL(no-diagnostic)`), which is precisely what a missing + # generated header or an unstageable link input produces. Anything cc1 named is real. + rows = r.get("verdicts") or [] + real = [row for row in rows if "no-diagnostic" not in row and ":" in row.split("\t", 1)[-1]] cls = (r.get("classes") or "") - real = [c for c in cls.split() if c and "no-diagnostic" not in c] - if real: + if rows and real: print("[gater] %s: worktree FAILED %d/%d with real cc1 diagnostics (%s) — NOT retrying " "in-tree; the worktree was not blind, the drafts do not compile in their TU" % (b, tail["failed"], tail.get("drafts", 0), cls), flush=True) diff --git a/tools/parallel_gate.py b/tools/parallel_gate.py index 68762314b..05b6e2912 100644 --- a/tools/parallel_gate.py +++ b/tools/parallel_gate.py @@ -318,13 +318,43 @@ def gate_one(idx, pin, job): # printed EARLIER by harvest_verify, never survived. gater_lane's in-tree retry therefore # could not tell "the worktree was blind" from "cc1 emitted a real diagnostic naming the # function", and retried all 22 binaries serially for nothing (measured S69: ~20 min). - cls = "" - for ln in (r.stdout or "").splitlines(): - if "failed by class:" in ln: - cls = ln.split("failed by class:", 1)[1].strip() + # THE VERDICT LAYER LIVES IN THE WORKTREE'S OWN .run/ AND DIES WITH IT (P31 S69, R47). + # `harvest_verify` writes `.classified.txt` — ONE ROW PER FUNCTION with the exact cc1 + # diagnostic. `.run/` is not symlinked into a worktree, so those rows were lost and survived + # only because gater_lane re-ran the whole binary IN-TREE afterwards, purely as a side effect. + # Copy them out, and derive the class summary FROM THEM. + # + # THE FIRST ATTEMPT AT THIS PARSED `failed by class:` OUT OF THE WORKER'S STDOUT AND WAS + # INERT: the worker is `gate_stage`, which does not print that line — harvest_verify does, + # one level down. `classes` came back empty for all 17 binaries of the batch and the retry + # gate that consumed it never fired once. A field that is always empty makes its consumer a + # no-op, silently (R54); reading the artifact the tool actually writes cannot drift that way. + verdicts, cls = [], "" + wrun = os.path.join(wt, ".run") + if os.path.isdir(wrun): + for name in sorted(os.listdir(wrun)): + if name.startswith("harvest_failed") and name.endswith(".classified.txt"): + try: + rows = [ln.rstrip("\n") for ln in open(os.path.join(wrun, name)) if ln.strip()] + except OSError: + continue + verdicts.extend(rows) + try: + shutil.copy(os.path.join(wrun, name), + os.path.join(REPO, ".run/gate_lane/%s.pgate.classified.txt" % binary)) + except OSError: + pass + # A row is `\t: `. The CLASS alone is not enough to decide whether the + # worktree was blind — `CC1-FAIL(no-diagnostic)` is the blind signature, `CC1-FAIL: :: + # ` is a real compile error — so keep the whole row and let the consumer judge. + classes = [] + for row in verdicts: + part = row.split("\t", 1)[-1] + classes.append(part.split(":", 1)[0].strip()) + cls = " ".join(sorted(set(classes))) return {"binary": binary, "banked": banked, "files": files, "ovl": ovl, "secs": round(time.time() - t0, 1), - "missing_generated": missing, "classes": cls, + "missing_generated": missing, "classes": cls, "verdicts": verdicts, "rc": r.returncode, "tail": (r.stdout or r.stderr)[-200:] if not banked else ""} except Exception as e: return {"binary": binary, "banked": [], "error": "%s: %s" % (type(e).__name__, str(e)[:160])}