from __future__ import annotations from dataclasses import dataclass from typing import Any from typing import Protocol from repo_registry.acceptance.gates import QualityGateOutcome from repo_registry.core.models import CandidateGraph, Repository AGENTIC_REVIEW_ACTIONS = { "approve", "approve_with_edits", "reject", "downgrade", "request_human_review", "propose_edit", "relink", } AGENTIC_APPROVAL_ACTIONS = {"approve", "approve_with_edits"} @dataclass(frozen=True) class AgenticReviewRequest: repository: Repository candidate_graph: CandidateGraph criteria_version: str quality_gate_outcomes: list[QualityGateOutcome] context: str @dataclass(frozen=True) class AgenticReviewDecision: action: str target_type: str target_id: int rationale: str criterion_ids: list[str] evidence_refs: list[str] notes: str = "" proposed_changes: dict[str, Any] | None = None class AgenticReviewer(Protocol): reviewer_id: str policy_version: str def review(self, request: AgenticReviewRequest) -> list[AgenticReviewDecision]: """Review a candidate graph and return structured decisions.""" def validate_agentic_review_decision(decision: AgenticReviewDecision) -> None: if decision.action not in AGENTIC_REVIEW_ACTIONS: raise ValueError(f"unsupported agentic review action: {decision.action}") if not decision.target_type: raise ValueError("agentic review decision target_type is required") if decision.target_id < 0: raise ValueError("agentic review decision target_id must be non-negative") if not decision.rationale.strip(): raise ValueError("agentic review decision rationale is required") if not decision.criterion_ids: raise ValueError("agentic review decision criterion_ids are required") if decision.action in AGENTIC_APPROVAL_ACTIONS and not decision.evidence_refs: raise ValueError( "agentic approval requires evidence refs tied to the rationale" ) def validate_agentic_review_decisions( decisions: list[AgenticReviewDecision], ) -> list[AgenticReviewDecision]: for decision in decisions: validate_agentic_review_decision(decision) return decisions