feat(TASK-018): review-gauntlet lane degradation + verification passthrough

- lane_fault.py + fallback on all four run_* lane nodes: an agent-node
  failure is normalized into a PIPELINE-FAULT lane result instead of
  killing the graph
- verdict_gate.py: PIPELINE-FAULT lane results block distinctly from
  SKIPPED and missing-sentinel; a PIPELINE-FAULT in signals_error blocks
  at the gauntlet level
- build_items.py: restructured into main() + fail-closed guard (crash
  emits empty lanes + PIPELINE-FAULT) and folds lanes_degraded into
  lanes_summary
- parse: state_updates/fallback -> parse_fault.py, which records the
  fault in signals_error and jumps to the verdict gate
- select_lanes: state_updates/fallback -> default_lanes.py, a
  deterministic wider-never-narrower lane fallback
- verification_commands threaded through parse into run_adversary's
  prompt (declared-only, never invented)
- run_probe prompt gains the aborted-attempt reconcile line
- 8 gauntlet_* R5 tests in src/config/request_context.rs (script harness
  + graph interpolation)
This commit is contained in:
Dark-Alex-17 committed 2026-09-14 10:50:33 -06:00
1 parent 1e46743420
commit 8407729d5a
7 files changed
+603 -80

No files matched your search

@@ -5,6 +5,9 @@ Parses each configured lane's verdict SENTINEL with a regex — no LLM
re-reads, no judgment. The gate's hard rules:
- a missing sentinel is a LANE FAILURE and blocks — never a pass;
- a PIPELINE-FAULT lane result (the lane's agent died after retries) or a
PIPELINE-FAULT in signals_error (builder/parse fault) blocks — a
degraded pipeline is never a pass;
- any 🔴 finding in the code-review report blocks regardless of the
lane's own verdict line;
- NEEDS-HUMAN without 🔴 passes but is surfaced as attention required;
@@ -40,10 +43,26 @@ def main():
def record(lane, status, detail=""):
lane_lines.append(f"| {lane} | {status} | {detail} |")
def fault_excerpt(report):
line = next(
(ln for ln in report.splitlines() if "PIPELINE-FAULT:" in ln), report
)
return line.strip()[:200]
def lane_fault_blocker(lane, report):
blockers.append(
f"{lane}: PIPELINE-FAULT — the lane failed after retries and produced "
f"no verdict; a degraded lane is never a pass ({fault_excerpt(report)})"
)
record(lane, "BLOCKED", "PIPELINE-FAULT (lane failed)")
# --- code-review ------------------------------------------------------
cr = lane_report("code_review_results")
if cr is None:
record("code-review", "SKIPPED", "not selected")
elif "PIPELINE-FAULT:" in cr:
lane_fault_blocker("code-review", cr)
reports.append(("code-review", cr))
else:
reports.append(("code-review", cr))
m = re.search(r"Verdict:\**\s*\**\s*(MERGE-READY|NEEDS-HUMAN)", cr)
@@ -72,6 +91,9 @@ def main():
adv = lane_report("adversary_results")
if adv is None:
record("adversary", "SKIPPED", "not selected")
elif "PIPELINE-FAULT:" in adv:
lane_fault_blocker("adversary", adv)
reports.append(("adversary", adv))
else:
reports.append(("adversary", adv))
m = re.search(r"ADVERSARIAL_REVIEW:\s*(CONFORMS|DIVERGES)", adv)
@@ -92,6 +114,9 @@ def main():
sec = lane_report("security_results")
if sec is None:
record("security", "SKIPPED", "not selected")
elif "PIPELINE-FAULT:" in sec:
lane_fault_blocker("security", sec)
reports.append(("security", sec))
else:
reports.append(("security", sec))
m = re.search(r"SECURITY_REVIEW:\s*(PASS|FAIL)", sec)
@@ -110,6 +135,9 @@ def main():
pb = lane_report("probe_results")
if pb is None:
record("probe", "SKIPPED", "not selected")
elif "PIPELINE-FAULT:" in pb:
lane_fault_blocker("probe", pb)
reports.append(("probe", pb))
else:
reports.append(("probe", pb))
m = re.search(r"USAGE_PROBE:\s*(PASS|FAIL|INCONCLUSIVE)", pb)
@@ -131,6 +159,12 @@ def main():
)
record("probe", "BLOCKED", "missing verdict sentinel")
# A gauntlet-level fault (build_items crash, dead parse stage) means the
# lanes above were never built — SKIPPED rows alone must not read as PASS.
signals_error = state.get("signals_error")
if isinstance(signals_error, str) and "PIPELINE-FAULT:" in signals_error:
blockers.append(f"pipeline: {signals_error}")
verdict = "BLOCKED" if blockers else "PASS"
md = "\n".join(