103 lines
3.6 KiB
Python
103 lines
3.6 KiB
Python
#!/usr/bin/env python3
|
|
"""Deterministic conformance verdict for the adversary graph.
|
|
|
|
CONFORMS requires: at least one criterion, every criterion MET, and zero
|
|
holistic complaints. Everything else — including a missing plan — is
|
|
DIVERGES (fail-closed). Assembles the exact sentinel format the callers
|
|
route on. Never crashes into a silent verdict.
|
|
"""
|
|
|
|
import json
|
|
import os
|
|
|
|
def load_state():
|
|
if path := os.environ.get("GRAPH_STATE_FILE"):
|
|
with open(path) as f:
|
|
return json.load(f)
|
|
return json.loads(os.environ.get("GRAPH_STATE", "{}"))
|
|
|
|
|
|
def main():
|
|
state = load_state()
|
|
verdicts = [v for v in (state.get("crit_verdicts") or []) if isinstance(v, dict)]
|
|
extra = [c for c in (state.get("extra_complaints") or []) if isinstance(c, dict)]
|
|
observations = (state.get("observations") or "").strip()
|
|
n = len(verdicts)
|
|
|
|
met = [v for v in verdicts if v.get("status") == "MET"]
|
|
partial = [v for v in verdicts if v.get("status") == "PARTIAL"]
|
|
bad = [v for v in verdicts if v.get("status") in ("UNMET", "DIVERGED")]
|
|
|
|
lines = []
|
|
if n == 0:
|
|
lines.append("ADVERSARIAL_REVIEW: DIVERGES")
|
|
lines.append("Criteria: none provided.")
|
|
lines.append("Complaints:")
|
|
lines.append(
|
|
"1. No acceptance criteria were provided or extractable — conformance cannot "
|
|
"be judged without a spec (fail-closed). Supply the plan's Objective/Tasks/"
|
|
"Acceptance criteria and re-run."
|
|
)
|
|
elif not partial and not bad and not extra:
|
|
lines.append("ADVERSARIAL_REVIEW: CONFORMS")
|
|
lines.append(f"Criteria: {n}/{n} met (all with tests).")
|
|
if observations:
|
|
lines.append("")
|
|
lines.append("Non-blocking observations:")
|
|
lines.append(observations)
|
|
else:
|
|
lines.append("ADVERSARIAL_REVIEW: DIVERGES")
|
|
lines.append(
|
|
f"Criteria: {len(met)}/{n} met, {len(partial)} partial, {len(bad)} unmet/diverged."
|
|
)
|
|
lines.append("Complaints:")
|
|
i = 0
|
|
for v in verdicts:
|
|
if v.get("status") == "MET":
|
|
continue
|
|
i += 1
|
|
text = (v.get("text") or "").strip().replace("\n", " ")
|
|
if len(text) > 140:
|
|
text = text[:137] + "…"
|
|
lines.append(
|
|
f'{i}. Acceptance criterion "{text}" — {v.get("status", "?").title()} — '
|
|
f'{(v.get("complaint") or "").strip()}'
|
|
)
|
|
for c in extra:
|
|
i += 1
|
|
lines.append(
|
|
f"{i}. {c.get('kind', 'violation')} — {c.get('location', '?')} — "
|
|
f"{c.get('violation', '')} — {c.get('fix', '')}"
|
|
)
|
|
if observations:
|
|
lines.append("")
|
|
lines.append("Non-blocking observations:")
|
|
lines.append(observations)
|
|
|
|
# Per-criterion evidence appendix (MET entries, for auditability).
|
|
if met:
|
|
lines.append("")
|
|
lines.append("Met criteria (evidence):")
|
|
for v in met:
|
|
text = (v.get("text") or "").strip().replace("\n", " ")
|
|
if len(text) > 100:
|
|
text = text[:97] + "…"
|
|
lines.append(f'- "{text}" — {(v.get("evidence") or "").strip()}')
|
|
|
|
print(json.dumps({"adv_report": "\n".join(lines)}))
|
|
|
|
|
|
try:
|
|
main()
|
|
except Exception as e: # noqa: BLE001 — never a silent verdict
|
|
print(
|
|
json.dumps(
|
|
{
|
|
"adv_report": (
|
|
"ADVERSARIAL_REVIEW: DIVERGES\n"
|
|
f"Criteria: verdict computation error: {e} — treat as failed, not as conforming."
|
|
)
|
|
}
|
|
)
|
|
)
|