engine.py
python
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