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