api_response.py python
78 lines 2.8 KB
Raw
sha256:a78e7e5a8740e03315f325d19edeb3aa1b306b3337d04abbaa9a9e0f3bbeb7a1 docs: MuseHub-first before ISR #74 — staging solidify NEXT Human 12 hours ago
1 """Parse headless API review responses into findings (§K5.6)."""
2
3 from __future__ import annotations
4
5 import json
6 from typing import Any
7
8 from tools.freeze_reviewer.types import Category, Finding, Severity
9
10 VALID_SEVERITIES = frozenset({"BLOCKER", "MAJOR", "MINOR"})
11 VALID_CATEGORIES = frozenset(
12 {
13 "security",
14 "irreversible",
15 "real_money",
16 "gates_tier3",
17 "completeness",
18 "consistency",
19 "other",
20 }
21 )
22
23
24 class ProviderReviewError(Exception):
25 """Raised when the API provider returns an invalid or failed review response."""
26
27
28 def parse_review_response(payload: bytes, *, default_path: str) -> list[Finding]:
29 """Parse a /review JSON response into pre-validation findings."""
30 try:
31 raw = json.loads(payload.decode("utf-8"))
32 except (UnicodeDecodeError, json.JSONDecodeError) as exc:
33 raise ProviderReviewError("invalid JSON response from review API") from exc
34 if not isinstance(raw, dict):
35 raise ProviderReviewError("review API response root must be a mapping")
36 findings_raw = raw.get("findings")
37 if findings_raw is None:
38 return []
39 if not isinstance(findings_raw, list):
40 raise ProviderReviewError("review API findings must be a list")
41 findings: list[Finding] = []
42 for index, entry in enumerate(findings_raw):
43 finding = _parse_finding_entry(entry, index=index, default_path=default_path)
44 findings.append(finding.with_citation())
45 return findings
46
47
48 def _parse_finding_entry(entry: Any, *, index: int, default_path: str) -> Finding:
49 if not isinstance(entry, dict):
50 raise ProviderReviewError(f"findings[{index}] must be a mapping")
51 check = entry.get("check")
52 severity = entry.get("severity")
53 category = entry.get("category")
54 path = entry.get("path")
55 line = entry.get("line")
56 message = entry.get("message")
57 if not isinstance(check, str) or not check.strip():
58 raise ProviderReviewError(f"findings[{index}].check must be a non-empty string")
59 if severity not in VALID_SEVERITIES:
60 raise ProviderReviewError(
61 f"findings[{index}].severity must be BLOCKER|MAJOR|MINOR"
62 )
63 if category not in VALID_CATEGORIES:
64 raise ProviderReviewError(f"findings[{index}].category is invalid")
65 if not isinstance(path, str) or not path.strip():
66 path = default_path
67 if not isinstance(line, int) or line < 1:
68 line = 1
69 if not isinstance(message, str) or not message.strip():
70 raise ProviderReviewError(f"findings[{index}].message must be a non-empty string")
71 return Finding(
72 check=check,
73 severity=severity, # type: ignore[arg-type]
74 category=category, # type: ignore[arg-type]
75 path=path,
76 line=line,
77 message=message,
78 )
File History 2 commits
sha256:a78e7e5a8740e03315f325d19edeb3aa1b306b3337d04abbaa9a9e0f3bbeb7a1 docs: MuseHub-first before ISR #74 — staging solidify NEXT Human 12 hours ago
sha256:4671b7f787ddbe63ced31c895b688c77ab495653b65a730b423329f26b3c1439 feat: K1-P1 complete — agent provenance, build-verification… Sonnet 4.6 patch 52 days ago