fix(review-gauntlet): summary-line critical count, nested-fault hook, fail-closed lane fallback

verdict_gate.py: the code-review critical count is read from render.py's
deterministic summary line (full-shape regex, LAST match wins — a 🔴
quoted inside a finding body or Changes-table row is not a finding); the
raw 🔴 count remains the fail-closed fallback when the line is absent. A
code-review lane whose own rendered verdict line records a pipeline
fault (degraded nested run) now BLOCKS instead of passing as GREEN
(attention), and fault + reds append both blockers. A non-string lane
result item is BLOCKED as malformed, never routed to GREEN.

default_lanes.py: the crash handler now emits the WIDEST safe selection
(code-review ∪ adversary ∪ security) and records a PIPELINE-FAULT in
signals_error so the gate blocks — previously it repaired malformed state
into a NARROWER passable selection with no fault. Probe is added only
when consumer_surface AND a probe_context (local-run recipe) are present,
mirroring build_items' guard, so degraded selection cannot spawn an
INCONCLUSIVE-by-construction probe.

graph.yaml: re-derive the timeout envelope. Agent-node retries each get a
fresh per-attempt budget (agent.rs retry_transient/bounded_attempt) and
the child's "Graph … timed out" matches the transient anchor, so lanes
exceed their child graph's settings.timeout (code-review 11400 > 10800,
adversary 9600 > 9000) and the gauntlet's own timeout covers
max_attempts × lane timeout + margin (2 × 11400 + 1200 = 24000). The
child graphs' internal retry envelopes are deliberately not derived here
(tracked as a follow-up). README/descriptions updated and pinned.

Tests: summary-count/quoted-🔴/fallback/last-match/nested-fault/both-
blockers/malformed-lane, crash-widens-and-records-fault, probe guard,
ALIASES parity with build_items, and e2e render.py → verdict_gate runs.
This commit is contained in:
Dark-Alex-17 committed 2026-09-14 10:50:34 -06:00
1 parent ea76331c49
commit 4555af8299
6 files changed
+564 -18

No files matched your search

@@ -3,13 +3,19 @@
Reached ONLY via select_lanes' `fallback:` route, when the additive LLM
judgment dies after retries. Emits `forced_lanes` — code-review + adversary,
plus probe when the deterministic signals flagged a consumer-facing surface,
plus probe when the deterministic signals flagged a consumer-facing surface
AND a local-run recipe exists (build_items gates probe the same way — a
probe without a recipe is INCONCLUSIVE by construction),
plus security when the attack-surface signals (auth/deps/exec) or a hardened
posture demand it, unioned with any lanes the caller forced (canonicalized
with the same aliases build_items uses) — which build_items honors exactly,
so selection degrades WIDER, never narrower. The degradation note goes into
`lanes_degraded`; build_items folds it into lanes_summary so the final
report names the degradation.
Fail-closed: if the fallback itself crashes it emits the widest safe
selection (defaults ∪ security) plus a PIPELINE-FAULT in signals_error so
verdict_gate blocks — a crashed fallback never narrows selection silently.
"""
import json
@@ -49,7 +55,12 @@ def load_state():
def main():
state = load_state()
lanes = {"code-review", "adversary"}
if state.get("consumer_surface"):
probe_context = state.get("probe_context")
if (
state.get("consumer_surface")
and isinstance(probe_context, str)
and probe_context.strip()
):
lanes.add("probe")
# The deterministic security signals are already in state when this
# fallback runs; a degraded selection must never drop the security lane
@@ -82,9 +93,13 @@ except Exception as e: # noqa: BLE001 — the fallback itself must never crash
print(
json.dumps(
{
"forced_lanes": ["code-review", "adversary"],
"forced_lanes": ["code-review", "adversary", "security"],
"lanes_degraded": "lane selection degraded to deterministic "
f"defaults (fallback script error: {e})",
"signals_error": (
f"PIPELINE-FAULT: lane-selection fallback crashed — {e}; "
"lanes widened to defaults ∪ security"
),
}
)
)