findings.py file-level

at sha256:a · View file ↗ · Intel ↗

History
1 files
1 commits
0 hotspots
0 🧊 dead
0 💥 blast risk
sha256:6 fix(ISR): default require_independent_second_reviewer to require Opera… · aaronrene · Sep 2, 2026
1 """Finding validation, stable sort, and verdict derivation (§K5.6)."""
2
3 from __future__ import annotations
4
5 from tools.freeze_reviewer.types import Finding, Verdict
6
7 SEVERITY_RANK = {"BLOCKER": 3, "MAJOR": 2, "MINOR": 1}
8
9 SYNTHETIC_UNCITED_MESSAGE = (
10 "Reviewer engine emitted an invalid finding (missing or mismatched citation)."
11 )
12
13
14 def stable_sort_findings(findings: list[Finding]) -> list[Finding]:
15 """Sort findings per §K5.6 stable sort rule."""
16 return sorted(
17 findings,
18 key=lambda item: (
19 item.path,
20 item.line,
21 -SEVERITY_RANK.get(item.severity, 0),
22 item.check,
23 item.message,
24 ),
25 )
26
27
28 def assign_finding_ids(findings: list[Finding]) -> list[Finding]:
29 """Assign F1..Fn after stable sort."""
30 sorted_items = stable_sort_findings(findings)
31 for index, finding in enumerate(sorted_items, start=1):
32 finding.id = f"F{index}"
33 return sorted_items
34
35
36 def validate_and_repair_findings(
37 findings: list[Finding],
38 *,
39 artifact_path: str,
40 ) -> list[Finding]:
41 """Enforce citation hard rule; synthesize blocked finding on violation."""
42 repaired: list[Finding] = []
43 for finding in findings:
44 if finding.is_valid_citation():
45 repaired.append(finding)
46 continue
47 repaired.append(
48 Finding(
49 check="OTHER",
50 severity="BLOCKER",
51 category="other",
52 path=artifact_path,
53 line=1,
54 message=SYNTHETIC_UNCITED_MESSAGE,
55 ).with_citation()
56 )
57 return assign_finding_ids(repaired)
58
59
60 def derive_verdict(
61 findings: list[Finding],
62 *,
63 human_escalation: list[str],
64 ) -> Verdict:
65 """Map findings to pass | findings | blocked."""
66 if not findings:
67 return "pass"
68 escalation = set(human_escalation)
69 for finding in findings:
70 if finding.category in escalation:
71 return "blocked"
72 for finding in findings:
73 if finding.severity == "BLOCKER":
74 return "blocked"
75 return "findings"
76
77
78 def verdict_exit_code(verdict: Verdict) -> int:
79 """Map verdict to review-specific exit code."""
80 if verdict == "pass":
81 return 0
82 if verdict == "findings":
83 return 7
84 return 8