From ff99f4acec303697eda7621e4dd4011a5e57eac7 Mon Sep 17 00:00:00 2001 From: Drew T <50529377+Druthulu@users.noreply.github.com> Date: Mon, 31 Aug 2026 16:45:58 -0600 Subject: [PATCH] fix(gater): never gate a draft whose workflow has not returned a verdict A draft file appears at //.c long before its agent is finished -- agents iterate in place and the wave brief tells them to write the file, not to write it last. Gating one mid-flight spends a build on unfinished work, records an honest-looking rejection, and then LEDGERS it, so the FINISHED draft is skipped as 'already-gated' when it lands. That is a silent loss of the whole draft. Measured this session: ov_SC01_000:func_8017E594 was gated at 0 banked while its workflow was still running, and its ledger entry had to be cleared by hand. Completion is now an explicit signal -- .run/gate_lane/verdicts.jsonl, one object per RETURNED verdict, appended by the orchestrator. A quiet file mtime is deliberately NOT accepted as one: an agent thinking for four minutes between edits looks identical to a finished agent. --any-draft opts out, and says what it costs. [gater] skipped 1 (IN-FLIGHT (no verdict yet)): ov_SC01_000:func_8017E594 --- tools/gater_lane.py | 46 ++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 43 insertions(+), 3 deletions(-) diff --git a/tools/gater_lane.py b/tools/gater_lane.py index 7890148ea..642b1b4c2 100644 --- a/tools/gater_lane.py +++ b/tools/gater_lane.py @@ -51,9 +51,41 @@ sys.path.insert(0, os.path.join(REPO, "tools")) import corpus # noqa: E402 LEDGER = os.path.join(REPO, ".run/gate_lane/ledger.json") +VERDICTS = os.path.join(REPO, ".run/gate_lane/verdicts.jsonl") STAGE = os.path.join(REPO, ".run/gate_lane") +def completed(): + """{(binary, fn)} whose drafting workflow has RETURNED A VERDICT. + + A draft file appears in //.c long before its agent is finished — agents iterate + in place, and the wave brief tells them to write the file, not to write it last. Gating one + mid-flight spends a build on an unfinished draft, records an honest-looking rejection, and then + LEDGERS it, so the finished draft is skipped as "already-gated" when it lands. Measured S68 on + ov_SC01_000:func_8017E594 — gated at 0 banked while its workflow was still running. + + The orchestrator appends one JSON object per returned verdict; nothing else is a completion + signal (a quiet file mtime is not one — an agent that thinks for four minutes between edits looks + identical to a finished one). + """ + out = set() + try: + with open(VERDICTS) as fh: + for ln in fh: + ln = ln.strip() + if not ln: + continue + try: + r = json.loads(ln) + except ValueError: + continue + if r.get("binary") and r.get("fn"): + out.add((r["binary"], r["fn"])) + except OSError: + pass + return out + + def load_ledger(): try: with open(LEDGER) as fh: @@ -93,11 +125,13 @@ def open_stub(binary, fn, cache={}): return None if known is None else (fn in known) -def collect(waves): +def collect(waves, require_verdict=True): """[(binary, fn, path)] for every draft file not yet gated. Refuses unresolvable drafts.""" led = load_ledger() - out, skipped = [], {"already-gated": [], "already-banked": [], "UNRESOLVED": [], "oracle": []} + out, skipped = [], {"already-gated": [], "already-banked": [], "UNRESOLVED": [], "oracle": [], + "IN-FLIGHT (no verdict yet)": []} best, seen_arms = {}, {} + done = completed() for wave in waves: tgts = wave_targets(wave) wdir = os.path.join(REPO, wave) @@ -125,6 +159,9 @@ def collect(waves): skipped["already-banked"].append(key) led[key] = "banked-elsewhere" continue + if require_verdict and (binary, fn) not in done: + skipped["IN-FLIGHT (no verdict yet)"].append(key) + continue cand = (ARM_RANK.get(arm, 0), arm, binary, fn, os.path.join(adir, name)) cur = best.get(key) if cur is None or cand[0] > cur[0]: @@ -179,6 +216,9 @@ def main(): ap.add_argument("--min-drafts", type=int, default=3, help="do nothing unless at least this many ungated drafts exist (default 3)") ap.add_argument("--drain", action="store_true", help="gate whatever is there, ignoring --min-drafts") + ap.add_argument("--any-draft", action="store_true", + help="gate drafts that have no recorded verdict yet (see completed(); this gates " + "work an agent may still be iterating on and will ledger the result)") ap.add_argument("--workers", type=int, default=12) ap.add_argument("--no-r22", action="store_true", help="skip the post-merge clean-fleet verify (see docstring)") ap.add_argument("--dry", action="store_true", help="print the plan, stage nothing, gate nothing") @@ -187,7 +227,7 @@ def main(): waves = [w.strip() for w in a.waves.split(",") if w.strip()] if not waves and not a.extra: sys.exit("[gater] nothing to do — give --waves and/or --extra") - ready, skipped = collect(waves) + ready, skipped = collect(waves, require_verdict=not a.any_draft) ready.extend(collect_extra(a.extra, skipped)) for why, items in skipped.items(): if items: