engine.py python
147 lines 4.9 KB
Raw
sha256:0e9549ec7b463911bc08b7d586dc320b1ac9b1f5c943ee7e3865dcc6cb0f6f83 chore(governance): sync handover+roadmap to 84db8c8 (drift:… Human 2 days ago
1 """Freeze review orchestration (§K5.2 steps 6–12)."""
2
3 from __future__ import annotations
4
5 from dataclasses import dataclass
6 from pathlib import Path
7
8 from adapters.config import FreezeContractConfig, OverseerConfig
9 from tools.freeze_reviewer.artifact import parse_artifact
10 from tools.freeze_reviewer.checklist import builtin_checklist
11 from tools.freeze_reviewer.findings import (
12 derive_verdict,
13 validate_and_repair_findings,
14 verdict_exit_code,
15 )
16 from tools.freeze_reviewer.providers.api_response import ProviderReviewError
17 from tools.freeze_reviewer.providers.base import ReviewProvider, provider_for
18 from tools.freeze_reviewer.stamp import build_stamp, write_stamp_or_fail
19 from tools.freeze_reviewer.types import ChecklistItem, ReviewResult, ReviewerSettings
20
21 HUMAN_INSTRUCTIONS = (
22 "Perform Freeze-Step Review per SPEC §6; cite file+line for every finding; "
23 "record verdict in the artifact review record."
24 )
25
26
27 @dataclass
28 class ReviewOptions:
29 """Per-invocation review options."""
30
31 dry_run: bool = False
32 no_stamp: bool = False
33 mode: str | None = None
34 provider: str | None = None
35 model: str | None = None
36 checklist: list[ChecklistItem] | None = None
37 kit_version: str = "0.1.0"
38 kit_root: Path | None = None
39 injected_provider: ReviewProvider | None = None
40
41
42 def resolve_reviewer_settings(
43 config: FreezeContractConfig,
44 options: ReviewOptions,
45 ) -> ReviewerSettings:
46 """Resolve CLI overrides > config > defaults."""
47 mode = options.mode or config.reviewer.mode
48 if mode == "human":
49 return ReviewerSettings(mode="human", model=None, provider=None, fallback=None)
50 model = options.model or config.reviewer.model
51 provider = options.provider or config.reviewer.provider
52 fallback = config.reviewer.fallback
53 return ReviewerSettings(mode=mode, model=model, provider=provider, fallback=fallback)
54
55
56 def run_freeze_review(
57 *,
58 artifact_path: Path,
59 rel_path: str,
60 config: OverseerConfig,
61 options: ReviewOptions,
62 ) -> ReviewResult:
63 """Execute the freeze review pipeline."""
64 checklist = options.checklist or builtin_checklist()
65 checklist_ids = [item.id for item in checklist]
66 result = ReviewResult(checklist_ids=checklist_ids)
67
68 if not config.freeze_contract.enabled:
69 result.refused = True
70 result.refuse_cause = "freeze_contract.enabled is false"
71 result.verdict = "blocked"
72 return result
73
74 try:
75 parsed = parse_artifact(artifact_path, rel_path=rel_path)
76 except ValueError as exc:
77 if str(exc) == "not-utf8":
78 result.refused = True
79 result.refuse_cause = "not-utf8"
80 result.verdict = "blocked"
81 return result
82 raise
83
84 result.declaration = parsed.declaration
85 result.artifact_kind = parsed.kind
86 result.dry_run = options.dry_run
87 result.no_stamp = options.no_stamp
88 reviewer = resolve_reviewer_settings(config.freeze_contract, options)
89
90 if reviewer.mode == "human":
91 result.verdict = "blocked"
92 result.escalation = "human"
93 result.reason = "mode_human"
94 return result
95
96 provider = provider_for(reviewer, options.injected_provider, kit_root=options.kit_root)
97 reachable, cause = provider.reachable()
98 if not reachable:
99 result.verdict = "blocked"
100 result.escalation = "human"
101 result.reason = "provider_unreachable"
102 result.provider_cause = cause
103 return result
104
105 try:
106 raw_findings = provider.review(
107 artifact_text=parsed.text,
108 artifact_path=rel_path,
109 checklist=checklist,
110 reviewer=reviewer,
111 )
112 except ProviderReviewError as exc:
113 result.verdict = "blocked"
114 result.escalation = "human"
115 result.reason = "provider_unreachable"
116 result.provider_cause = str(exc)
117 return result
118 findings = validate_and_repair_findings(raw_findings, artifact_path=rel_path)
119 result.findings = findings
120 result.verdict = derive_verdict(findings, human_escalation=config.freeze_contract.human_escalation)
121
122 if result.verdict == "pass":
123 stamp = build_stamp(parsed, reviewer=reviewer, kit_version=options.kit_version)
124 result.stamp = stamp
125 if not options.dry_run and not options.no_stamp:
126 written, io_failed = write_stamp_or_fail(artifact_path, parsed, stamp)
127 result.stamp_written = written
128 result.io_error = io_failed
129
130 return result
131
132
133 def resolve_exit_code(result: ReviewResult, *, config_error: bool = False, refused: bool = False) -> int:
134 """Apply frozen precedence 2>4>5>8>7>0."""
135 if config_error:
136 return 2
137 if refused or result.refused:
138 return 4
139 if result.io_error:
140 return 5
141 if result.escalation == "human" or result.verdict == "blocked":
142 return 8
143 if result.verdict == "findings":
144 return 7
145 if result.verdict == "pass":
146 return 0
147 return verdict_exit_code(result.verdict)
File History 1 commit
sha256:6abcf1fa82a7a621ccbc945f19acdba5bc0db54569599404a1452fb4a096a199 fix(ISR): default require_independent_second_reviewer to require Human minor 2 days ago