Files
coyote/assets/agents/finding-verifier/README.md
T
Dark-Alex-17 156f3193a9 fix(review-suite): detect nested finding-verifier faults; harden render/fault markers
code-reviewer verdict.py: finding-verifier now completes "successfully"
with fault markers INSIDE its FINDING_VERIFIER_RESULTS payload
(parse_fault's `pipeline-fault` sentinel id, verify_fault / verdict_gate
crash-guard notes prefixed PIPELINE-FAULT:), which the old
"Agent node failed:" prefix check never saw — a dead parse degraded
silently to "unverified" with no fault recorded. Detect them, anchored on
the sentinel id / note prefix only (never a substring over the report),
counted over the raw entry list so duplicate crash-guard ids are not
collapsed. Faults lead the attention list uncapped; the display cap of 5
applies to non-fault entries; the "always-human trigger(s) fired" reason
fires only when non-fault triggers exist.

code-reviewer render.py: CONTRACT comments at the verdict and summary
lines review-gauntlet's gate parses; every LLM-influenced value rendered
after the summary line (dropped_titles, rigor, bar_provenance,
resolved_surfaces, reason) is newline-sanitised so none can forge a
second summary line.

finding-verifier verdict_gate.py: the outer crash-guard note carries the
PIPELINE-FAULT: prefix (the after-retry UNVERIFIABLE path stays
unprefixed — model misbehaviour, not infrastructure); README names all
three emitters.

deep-research: vet_fault's UNVETTED note tells the critic not to request
revision solely because the assessment is unavailable (it matched
critique's PREPRINT/UNVERIFIED REVISE trigger verbatim, burning the
revision budget on an infra fault); vet_fault and critique_fault dedupe
repeated faults and preserve accumulated faults on crash via
safe_faults(); question_fault keeps its plan-pinned prefix and appends the
lane's question identity so distinct dead lanes no longer collapse in
combine_findings' dedupe.
2026-09-14 10:50:34 -06:00

4.1 KiB
Raw Blame History

finding-verifier

A graph agent that independently verifies code-review findings against the working tree. It exists because false positives kill hands-off trust faster than missed bugs do — and because an orchestrator verifying its own workers' findings inline can skip, fudge, or confirmation-bias the check. A graph can't: the structure IS the discipline.

How it works

flowchart TD
    A["parse (llm, max_attempts 2)"] --> B["verify_each (map, max_concurrency 4): one branch per finding"]
    A -. "fallback" .-> PF["parse_fault (script): verdicts = one UNVERIFIABLE PIPELINE-FAULT entry"]
    PF --> D
    B -. "spawns × N" .-> C["verify_one (llm + fs tools, max_attempts 2)"]
    C --> G["verdict_gate (script): machine-validates the JSON contract"]
    G -. "reject-retry once" .-> C
    C -. "fallback" .-> VF["verify_fault (script): branch-local UNVERIFIABLE marker"]
    B --> D(["done (end): FINDING_VERIFIER_RESULTS"])
  1. parse — extracts every 🔴/🟡 finding from the spawn prompt into a structured list (id, severity verbatim, path, lines, self-contained claim). Extraction only — no judging. Gets two attempts (max_attempts: 2); if both fail, its fallback routes to parse_fault, which rewrites verdicts to a single UNVERIFIABLE entry naming the fault and jumps straight to done.

  2. verify_each — a map node fans out one verify_one branch per finding. Skipping a finding is structurally impossible; results collect in input order.

  3. verify_one — reads the cited code (greps for moved code before concluding anything) and answers ONE narrow question: does the cited code exist, and does it behave as the claim says? Verdicts:

    • VERIFIED — with the decisive line(s) quoted verbatim (path:line).
    • FALSE — the code doesn't exist anywhere or provably doesn't do what the claim says. Requires PROOF, not disagreement — doubt never lands here.
    • UNVERIFIABLE — couldn't establish either way (runtime behavior, external systems).

    Each branch is a chain: verify_one → verdict_gate. The gate machine-validates the JSON contract (verdict enum; evidence required for VERIFIED/FALSE; note for UNVERIFIABLE), stamps the authoritative id FROM the finding item (the model's echo is never trusted), rejects back to verify_one exactly once with the reason, and on a second failure records UNVERIFIABLE — a malformed branch never sinks the map. If verify_one itself dies (both max_attempts: 2 attempts fail), its fallback routes to verify_fault, which normalizes the engine's failure text into a branch-local UNVERIFIABLE verdict the map still collects.

  4. done — a deterministic end node emits the raw verdict list plus the caller contract (VERIFIED = keep + paste evidence; FALSE = drop + tally; UNVERIFIABLE = keep, marked). No final LLM step that could editorialize.

The dashed edges are fault paths. All three fault emitters (parse_fault, verify_fault, and verdict_gate's own crash guard) surface as PIPELINE-FAULT: text in a verdict's note, and a fault can only ever produce UNVERIFIABLE — degraded output is never a passing verdict. Every path ends at done, so the FINDING_VERIFIER_RESULTS sentinel is always emitted and the caller's verify lane degrades instead of dying.

What it is NOT

  • Not a second opinion. It never re-judges severity, style, or whether a finding "matters" — that authority stays with the reviewers and the orchestrator's rigor rules.
  • Not a reviewer. It reads only what findings cite (plus greps to locate moved code).

Caller

Spawned by code-reviewer during its Finding Verifier pass, ONCE per review, with the full 🔴/🟡 list and project_dir. If this agent isn't installed, code-reviewer runs the same contract inline as a fallback.

Files

  • graph.yaml — the agent (no tools.sh; the graph grants fs_read/fs_cat/fs_grep per node).
  • scripts/verdict_gate.py — the deterministic contract gate.
  • scripts/parse_fault.py, scripts/verify_fault.py — the fault markers behind the dashed fallback edges.