feat(TASK-019): code-reviewer fail-closed review pipeline

Every llm/agent stage that can die persistently now falls back to a
machine-readable PIPELINE-FAULT sentinel instead of killing the graph:

- review_domain -> branch-local domain_fault marker (banner-prefixed
  domain_report the map collects; completeness_gate precedent)
- verify -> verdict (verifier fault renders findings unverified + a
  PIPELINE-FAULT attention line)
- parse -> parse_fault -> render (complete NEEDS-HUMAN verdict_out;
  render's no-changes stub is bypassed for fault verdicts)
- refine_groups -> cover_gate (deterministic-proposal fallback absorbs
  the failure; degradation noted in groups_note)
- aux_lanes -> aux_fault -> synthesize (aux_note fault marker; visible,
  never blocking - aux is enrichment)
- synthesize -> verdict (synth_failure marker; keyed off the failure
  prefix + non-empty diff, never findings==[], so clean zero-finding
  reviews stay MERGE-READY)

verdict.py gains unified fault accounting (R2): any PIPELINE-FAULT in
domain_reports / verifier_output / state fault keys forces NEEDS-HUMAN,
prefix-anchored so a report merely quoting the marker mid-text never
trips it; old 'PIPELINE FAULT' spelling migrated. Faults are prepended
to attention so the [:5] cap can never hide one. Dedicated *_failure
state keys capture engine failure text on every fallback+output_schema
node, keeping the bundled warning baseline at zero.

15 python3-guarded R5 tests: fault paths, quoted-marker precedence,
happy-path byte-compat regression, marker normalization, crash-path
fail-closed (verdict/cover_gate/domain_fault), verifier-fault-on-empty-
diff render, and the no-changes stub regression.
This commit is contained in:
Dark-Alex-17 committed 2026-09-14 10:50:33 -06:00
1 parent ebb9e9af62
commit 4e72c6f5e6
8 files changed
+687 -4

No files matched your search

+41
View File
@@ -91,6 +91,10 @@ initial_state:
final_report: ''
gate_feedback: ''
gate_attempts: 0
parse_failure: ''
refine_failure: ''
aux_failure: ''
synth_failure: ''
start: parse
@@ -139,8 +143,19 @@ nodes:
required: [diff_spec, intent, rigor_in, surfaces_in, project_dir_in, extra_context]
max_iterations: 3
max_attempts: 2
state_updates:
parse_failure: '{{output}}'
fallback: parse_fault
next: facts
parse_fault:
id: parse_fault
type: script
description: Fail-closed marker for a dead parse stage (reached only via parse's fallback) — emits a complete PIPELINE-FAULT verdict_out and jumps straight to render; a review that could not parse its request never reads as MERGE-READY.
script: scripts/parse_fault.py
timeout: 30
next: render
facts:
id: facts
type: script
@@ -209,6 +224,9 @@ nodes:
required: [groups]
max_iterations: 3
max_attempts: 2
state_updates:
refine_failure: '{{output}}'
fallback: cover_gate
next: cover_gate
cover_gate:
@@ -275,6 +293,7 @@ nodes:
max_attempts: 2
state_updates:
domain_report: '{{output}}'
fallback: domain_fault
next: completeness_gate
completeness_gate:
@@ -284,6 +303,13 @@ nodes:
script: scripts/completeness_gate.py
timeout: 30
domain_fault:
id: domain_fault
type: script
description: Fail-closed domain lane fault marker (reached only via review_domain's fallback route inside the map branch). Normalizes the engine's "Agent node failed" text in domain_report into a > ⚠️ PIPELINE-FAULT banner the map collects; verdict.py counts any faulted lane and forces NEEDS-HUMAN — a dead domain lane never reads as a clean slice.
script: scripts/domain_fault.py
timeout: 30
aux_lanes:
id: aux_lanes
type: llm
@@ -339,6 +365,17 @@ nodes:
max_iterations: 10
max_attempts: 2
timeout: 3600
state_updates:
aux_failure: '{{output}}'
fallback: aux_fault
next: synthesize
aux_fault:
id: aux_fault
type: script
description: Fail-closed marker for dead aux context lanes (reached only via aux_lanes' fallback) — records the failure as a PIPELINE-FAULT in aux_note, which synthesize surfaces as a lane skip. Aux lanes are enrichment; the degradation is visible but never blocks.
script: scripts/aux_fault.py
timeout: 30
next: synthesize
downstream_sweep:
@@ -457,6 +494,9 @@ nodes:
required: [walkthrough, changes_rows, findings, cross_file, operational_history, org_context_section, attention_flags, placement_notes]
max_iterations: 6
max_attempts: 2
state_updates:
synth_failure: '{{output}}'
fallback: verdict
next: verify
verify:
@@ -476,6 +516,7 @@ nodes:
max_attempts: 2
state_updates:
verifier_output: '{{output}}'
fallback: verdict
next: verdict
verdict: