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.
This commit is contained in:
8 files changed
+570
-50
No files matched your search
@@ -33,6 +33,14 @@ def load_state():
|
||||
return json.loads(os.environ.get("GRAPH_STATE", "{}"))
|
||||
|
||||
|
||||
def safe_faults():
|
||||
try:
|
||||
state = load_state()
|
||||
return [f for f in (state.get("pipeline_faults") or []) if isinstance(f, str)]
|
||||
except Exception: # noqa: BLE001 — best-effort preservation only
|
||||
return []
|
||||
|
||||
|
||||
def main():
|
||||
state = load_state()
|
||||
faults = [f for f in (state.get("pipeline_faults") or []) if isinstance(f, str)]
|
||||
@@ -42,19 +50,21 @@ def main():
|
||||
detail = "the critique stage died without recording a failure detail"
|
||||
if len(detail) > MAX_DETAIL_CHARS:
|
||||
detail = detail[: MAX_DETAIL_CHARS - 1] + "…"
|
||||
faults.append(f"PIPELINE-FAULT: critique failed — {detail}")
|
||||
fault = f"PIPELINE-FAULT: critique failed — {detail}"
|
||||
if fault not in faults:
|
||||
faults.append(fault)
|
||||
print(json.dumps({"pipeline_faults": faults, "critique": CRITIQUE_NOTE}))
|
||||
|
||||
|
||||
try:
|
||||
main()
|
||||
except Exception as e: # noqa: BLE001 — the fault marker itself must never crash
|
||||
faults = safe_faults()
|
||||
faults.append(f"PIPELINE-FAULT: critique failed — fault-marker script error: {e}")
|
||||
print(
|
||||
json.dumps(
|
||||
{
|
||||
"pipeline_faults": [
|
||||
f"PIPELINE-FAULT: critique failed — fault-marker script error: {e}"
|
||||
],
|
||||
"pipeline_faults": faults,
|
||||
"critique": CRITIQUE_NOTE,
|
||||
}
|
||||
)
|
||||
|
||||
@@ -12,12 +12,15 @@ PIPELINE-FAULT finding and the chain ends here — the map collects the fault
|
||||
as the lane's `finding`, so one dead research lane never sinks the whole
|
||||
map. combine_findings later lifts PIPELINE-FAULT findings into
|
||||
`pipeline_faults` so the fault also surfaces in the report's Pipeline notes.
|
||||
The question text is part of the fault so distinct dead lanes with identical
|
||||
engine failure text do not collapse into one entry there.
|
||||
"""
|
||||
|
||||
import json
|
||||
import os
|
||||
|
||||
MAX_DETAIL_CHARS = 500
|
||||
MAX_QUESTION_CHARS = 200
|
||||
|
||||
|
||||
def load_state():
|
||||
@@ -27,14 +30,25 @@ def load_state():
|
||||
return json.loads(os.environ.get("GRAPH_STATE", "{}"))
|
||||
|
||||
|
||||
def fault_finding(detail):
|
||||
def fault_finding(question, detail):
|
||||
return {
|
||||
"finding": (
|
||||
f"PIPELINE-FAULT: question research failed — finding unavailable — {detail}"
|
||||
"PIPELINE-FAULT: question research failed — finding unavailable — "
|
||||
f'{detail} (question: "{question}")'
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
def question_label(state):
|
||||
raw = state.get("question")
|
||||
question = raw.strip().replace("\n", " ") if isinstance(raw, str) else ""
|
||||
if not question:
|
||||
return "(unknown question)"
|
||||
if len(question) > MAX_QUESTION_CHARS:
|
||||
question = question[: MAX_QUESTION_CHARS - 1] + "…"
|
||||
return question
|
||||
|
||||
|
||||
def main():
|
||||
state = load_state()
|
||||
raw = state.get("finding")
|
||||
@@ -43,10 +57,14 @@ def main():
|
||||
detail = "the research lane died without recording a failure detail"
|
||||
if len(detail) > MAX_DETAIL_CHARS:
|
||||
detail = detail[: MAX_DETAIL_CHARS - 1] + "…"
|
||||
print(json.dumps(fault_finding(detail)))
|
||||
print(json.dumps(fault_finding(question_label(state), detail)))
|
||||
|
||||
|
||||
try:
|
||||
main()
|
||||
except Exception as e: # noqa: BLE001 — the fault marker itself must never crash
|
||||
print(json.dumps(fault_finding(f"fault-marker script error: {e}")))
|
||||
print(
|
||||
json.dumps(
|
||||
fault_finding("(unknown question)", f"fault-marker script error: {e}")
|
||||
)
|
||||
)
|
||||
@@ -20,7 +20,9 @@ MAX_DETAIL_CHARS = 500
|
||||
UNVETTED_NOTE = (
|
||||
"Source credibility assessment unavailable — the vetting stage failed "
|
||||
"(pipeline fault); treat every cited source as unvetted and needing "
|
||||
"corroboration."
|
||||
"corroboration. Do not request revision solely because this assessment "
|
||||
"is unavailable — re-research cannot restore it; judge the findings on "
|
||||
"their own evidence."
|
||||
)
|
||||
|
||||
|
||||
@@ -31,6 +33,14 @@ def load_state():
|
||||
return json.loads(os.environ.get("GRAPH_STATE", "{}"))
|
||||
|
||||
|
||||
def safe_faults():
|
||||
try:
|
||||
state = load_state()
|
||||
return [f for f in (state.get("pipeline_faults") or []) if isinstance(f, str)]
|
||||
except Exception: # noqa: BLE001 — best-effort preservation only
|
||||
return []
|
||||
|
||||
|
||||
def main():
|
||||
state = load_state()
|
||||
faults = [f for f in (state.get("pipeline_faults") or []) if isinstance(f, str)]
|
||||
@@ -40,20 +50,21 @@ def main():
|
||||
detail = "the vetting stage died without recording a failure detail"
|
||||
if len(detail) > MAX_DETAIL_CHARS:
|
||||
detail = detail[: MAX_DETAIL_CHARS - 1] + "…"
|
||||
faults.append(f"PIPELINE-FAULT: source vetting failed — {detail}")
|
||||
fault = f"PIPELINE-FAULT: source vetting failed — {detail}"
|
||||
if fault not in faults:
|
||||
faults.append(fault)
|
||||
print(json.dumps({"pipeline_faults": faults, "source_assessment": UNVETTED_NOTE}))
|
||||
|
||||
|
||||
try:
|
||||
main()
|
||||
except Exception as e: # noqa: BLE001 — the fault marker itself must never crash
|
||||
faults = safe_faults()
|
||||
faults.append(f"PIPELINE-FAULT: source vetting failed — fault-marker script error: {e}")
|
||||
print(
|
||||
json.dumps(
|
||||
{
|
||||
"pipeline_faults": [
|
||||
f"PIPELINE-FAULT: source vetting failed — "
|
||||
f"fault-marker script error: {e}"
|
||||
],
|
||||
"pipeline_faults": faults,
|
||||
"source_assessment": UNVETTED_NOTE,
|
||||
}
|
||||
)
|
||||
|
||||
Reference in new issue
Block a user