report.py python
104 lines 3.8 KB
Raw
sha256:0e9549ec7b463911bc08b7d586dc320b1ac9b1f5c943ee7e3865dcc6cb0f6f83 chore(governance): sync handover+roadmap to 84db8c8 (drift:… Human 1 day ago
1 """§K5.9 report payload and stdout rendering."""
2
3 from __future__ import annotations
4
5 from typing import Any
6
7 from adapters.config import FreezeContractConfig
8 from tools.freeze_reviewer.engine import HUMAN_INSTRUCTIONS
9 from tools.freeze_reviewer.types import Finding, ReviewResult, ReviewerSettings
10
11 HUMAN_INSTRUCTIONS_TEXT = HUMAN_INSTRUCTIONS
12
13
14 def reviewer_payload(settings: ReviewerSettings, config: FreezeContractConfig) -> dict[str, Any]:
15 """Reviewer section for JSON report."""
16 if settings.mode == "human":
17 return {
18 "mode": "human",
19 "model": config.reviewer.model,
20 "provider": config.reviewer.provider,
21 "fallback": config.reviewer.fallback,
22 }
23 return {
24 "mode": settings.mode,
25 "model": settings.model,
26 "provider": settings.provider,
27 "fallback": settings.fallback,
28 }
29
30
31 def finding_to_dict(finding: Finding) -> dict[str, Any]:
32 return {
33 "id": finding.id,
34 "check": finding.check,
35 "severity": finding.severity,
36 "category": finding.category,
37 "path": finding.path,
38 "line": finding.line,
39 "message": finding.message,
40 "citation": finding.citation,
41 }
42
43
44 def build_report(
45 *,
46 freeze_path: str,
47 result: ReviewResult,
48 reviewer: ReviewerSettings,
49 config: FreezeContractConfig,
50 enabled: bool,
51 ) -> dict[str, Any]:
52 """Build the unified §K5.9 report object."""
53 exit_code = 8 if result.escalation == "human" else (
54 7 if result.verdict == "findings" else (0 if result.verdict == "pass" else 8)
55 )
56 stamp_payload = result.stamp.to_mapping() if result.stamp else None
57 return {
58 "command": "review",
59 "freeze": freeze_path,
60 "verdict": result.verdict,
61 "exit_code": exit_code,
62 "escalation": result.escalation,
63 "reason": result.reason,
64 "provider_cause": result.provider_cause,
65 "checklist": list(result.checklist_ids),
66 "instructions": HUMAN_INSTRUCTIONS_TEXT if result.escalation == "human" else None,
67 "enabled": enabled,
68 "declaration": result.declaration,
69 "reviewer": reviewer_payload(reviewer, config),
70 "findings": [finding_to_dict(item) for item in result.findings],
71 "stamp": stamp_payload,
72 "dry_run": result.dry_run,
73 }
74
75
76 def render_human_report(*, freeze_path: str, result: ReviewResult) -> str:
77 """Render human stdout per §K5.9."""
78 lines = [f"Freeze review: {freeze_path}", f"Verdict: {result.verdict}"]
79 lines.append(f"Findings ({len(result.findings)}):")
80 for finding in result.findings:
81 lines.append(
82 f" {finding.id} {finding.severity} {finding.category} "
83 f"{finding.citation} {finding.message}"
84 )
85 if result.escalation == "human":
86 lines.append("Escalation: human")
87 lines.append(f"Reason: {result.reason}")
88 cause = result.provider_cause or "(none)"
89 lines.append(f"Provider cause: {cause}")
90 lines.append(f"Checklist: {', '.join(result.checklist_ids)}")
91 lines.append(f"Instructions: {HUMAN_INSTRUCTIONS_TEXT}")
92 else:
93 lines.append("Escalation: none")
94 if result.verdict == "pass" and result.stamp and not result.dry_run and result.stamp_written:
95 lines.append("Stamp: written")
96 elif result.verdict == "pass" and result.stamp and result.dry_run:
97 lines.append("Stamp: (dry-run — would write)")
98 elif result.verdict == "pass" and result.stamp and result.no_stamp:
99 lines.append("Stamp: (not written — no-stamp)")
100 elif result.verdict == "pass" and result.stamp and not result.stamp_written:
101 lines.append("Stamp: (unchanged — idempotent)")
102 else:
103 lines.append("Stamp: (not written — verdict != pass)")
104 return "\n".join(lines)
File History 1 commit
sha256:6abcf1fa82a7a621ccbc945f19acdba5bc0db54569599404a1452fb4a096a199 fix(ISR): default require_independent_second_reviewer to require Human minor 1 day ago