engine.py python
218 lines 7.6 KB
Raw
sha256:8461d44b77376fbf06fa7c3e085d309e3010fd8d5886d63c63e69ce118811ad4 docs: record AFF-b feature-tip SHAs after AFF-b-ISR commit. Human 3 days ago
1 """Freeze review orchestration (§K5.2 steps 6–12 / §FRV)."""
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_authorization.resolve import resolve_stamp_record
10 from tools.freeze_reviewer.artifact import extract_existing_stamp, parse_artifact
11 from tools.freeze_reviewer.checklist import builtin_checklist
12 from tools.freeze_reviewer.findings import (
13 derive_verdict,
14 validate_and_repair_findings,
15 verdict_exit_code,
16 )
17 from tools.freeze_reviewer.providers.api_response import ProviderReviewError
18 from tools.freeze_reviewer.providers.base import ReviewProvider, provider_for
19 from tools.freeze_reviewer.stamp import build_stamp, write_stamp_or_fail
20 from tools.freeze_reviewer.types import ChecklistItem, ReviewResult, ReviewerSettings
21
22 HUMAN_INSTRUCTIONS = (
23 "Perform Freeze-Step Review per SPEC §6; cite file+line for every finding; "
24 "record verdict in the artifact review record."
25 )
26
27 EXIT_STAMP_ESCALATION_REFUSED = 39
28
29
30 @dataclass
31 class ReviewOptions:
32 """Per-invocation review options."""
33
34 dry_run: bool = False
35 no_stamp: bool = False
36 mode: str | None = None
37 provider: str | None = None
38 model: str | None = None
39 checklist: list[ChecklistItem] | None = None
40 kit_version: str = "0.1.0"
41 kit_root: Path | None = None
42 injected_provider: ReviewProvider | None = None
43 override_non_pass_stamp: bool = False
44 checklist_source: str = "builtin"
45
46
47 def resolve_reviewer_settings(
48 config: FreezeContractConfig,
49 options: ReviewOptions,
50 ) -> ReviewerSettings:
51 """Resolve CLI overrides > config > defaults."""
52 mode = options.mode or config.reviewer.mode
53 if mode == "human":
54 return ReviewerSettings(mode="human", model=None, provider=None, fallback=None)
55 model = options.model or config.reviewer.model
56 provider = options.provider or config.reviewer.provider
57 fallback = config.reviewer.fallback
58 return ReviewerSettings(mode=mode, model=model, provider=provider, fallback=fallback)
59
60
61 def _read_operator_block(parsed) -> bool | None:
62 """Return operator_block report value from freeze mapping (§FRV.8.3)."""
63 mapping = parsed.freeze_mapping
64 if not isinstance(mapping, dict) or "auto_may_start" not in mapping:
65 return None
66 val = mapping.get("auto_may_start")
67 if val is True:
68 return False
69 return True
70
71
72 def run_freeze_review(
73 *,
74 artifact_path: Path,
75 rel_path: str,
76 config: OverseerConfig,
77 options: ReviewOptions,
78 ) -> ReviewResult:
79 """Execute the freeze review pipeline."""
80 checklist = options.checklist or builtin_checklist()
81 checklist_ids = [item.id for item in checklist]
82 result = ReviewResult(checklist_ids=checklist_ids)
83
84 if not config.freeze_contract.enabled:
85 result.refused = True
86 result.refuse_cause = "freeze_contract.enabled is false"
87 result.verdict = "blocked"
88 return result
89
90 try:
91 parsed = parse_artifact(artifact_path, rel_path=rel_path)
92 except ValueError as exc:
93 if str(exc) == "not-utf8":
94 result.refused = True
95 result.refuse_cause = "not-utf8"
96 result.verdict = "blocked"
97 return result
98 raise
99
100 result.declaration = parsed.declaration
101 result.artifact_kind = parsed.kind
102 result.dry_run = options.dry_run
103 result.no_stamp = options.no_stamp
104 result.operator_block = _read_operator_block(parsed)
105 reviewer = resolve_reviewer_settings(config.freeze_contract, options)
106
107 if reviewer.mode == "human":
108 result.verdict = "blocked"
109 result.escalation = "human"
110 result.reason = "mode_human"
111 return result
112
113 provider = provider_for(reviewer, options.injected_provider, kit_root=options.kit_root)
114 reachable, cause = provider.reachable()
115 if not reachable:
116 result.verdict = "blocked"
117 result.escalation = "human"
118 result.reason = "provider_unreachable"
119 result.provider_cause = cause
120 return result
121
122 try:
123 raw_findings = provider.review(
124 artifact_text=parsed.text,
125 artifact_path=rel_path,
126 checklist=checklist,
127 reviewer=reviewer,
128 )
129 except ProviderReviewError as exc:
130 result.verdict = "blocked"
131 result.escalation = "human"
132 result.reason = "provider_unreachable"
133 result.provider_cause = str(exc)
134 return result
135
136 # §FRV.4.2 — producer_identity AFTER review()
137 identity_fn = getattr(provider, "producer_identity", None)
138 if callable(identity_fn):
139 produced_by, provider_kind = identity_fn()
140 else:
141 produced_by, provider_kind = ("unknown", "rule_engine")
142
143 findings = validate_and_repair_findings(raw_findings, artifact_path=rel_path)
144 result.findings = findings
145 result.verdict = derive_verdict(findings, human_escalation=config.freeze_contract.human_escalation)
146
147 if result.verdict == "pass":
148 existing = extract_existing_stamp(parsed)
149 resolved = resolve_stamp_record(existing) if existing else None
150 existing_verdict = (
151 resolved.verdict if resolved is not None and resolved.kind == "mechanical" else None
152 )
153 refuse_escalation = (
154 existing_verdict is not None
155 and existing_verdict != ""
156 and existing_verdict != "pass"
157 and not options.override_non_pass_stamp
158 )
159 if refuse_escalation:
160 result.escalation_refused = True
161 result.escalation_refuse_cause = "stamp_escalation_refused"
162 result.existing_stamp_verdict = existing_verdict
163 stamp = build_stamp(
164 parsed,
165 reviewer=reviewer,
166 kit_version=options.kit_version,
167 produced_by=produced_by,
168 provider_kind=provider_kind,
169 checklist_ids=checklist_ids,
170 checklist_source=options.checklist_source,
171 findings_count=len(findings),
172 override_applied=False,
173 )
174 result.stamp = stamp
175 return result
176
177 override_applied = bool(
178 options.override_non_pass_stamp
179 and existing_verdict
180 and existing_verdict != "pass"
181 )
182 stamp = build_stamp(
183 parsed,
184 reviewer=reviewer,
185 kit_version=options.kit_version,
186 produced_by=produced_by,
187 provider_kind=provider_kind,
188 checklist_ids=checklist_ids,
189 checklist_source=options.checklist_source,
190 findings_count=len(findings),
191 override_applied=override_applied,
192 )
193 result.stamp = stamp
194 if not options.dry_run and not options.no_stamp:
195 written, io_failed = write_stamp_or_fail(artifact_path, parsed, stamp)
196 result.stamp_written = written
197 result.io_error = io_failed
198
199 return result
200
201
202 def resolve_exit_code(result: ReviewResult, *, config_error: bool = False, refused: bool = False) -> int:
203 """Apply frozen precedence 2 > 4 > 5 > 39 > 8 > 7 > 0 (§FRV.5.2 / §FRV.10)."""
204 if config_error:
205 return 2
206 if refused or result.refused:
207 return 4
208 if result.io_error:
209 return 5
210 if result.escalation_refused and not result.dry_run and not result.no_stamp:
211 return EXIT_STAMP_ESCALATION_REFUSED
212 if result.escalation == "human" or result.verdict == "blocked":
213 return 8
214 if result.verdict == "findings":
215 return 7
216 if result.verdict == "pass":
217 return 0
218 return verdict_exit_code(result.verdict)
File History 1 commit
sha256:8461d44b77376fbf06fa7c3e085d309e3010fd8d5886d63c63e69ce118811ad4 docs: record AFF-b feature-tip SHAs after AFF-b-ISR commit. Human 3 days ago