"""Owner approval gate for architecture changes. Architecture changes may be proposed, reviewed and optimized by agents, but they must not be implemented until owner approval is recorded. This gate sits before implementation and before promotion. """ from __future__ import annotations from dataclasses import asdict, dataclass, field from datetime import datetime, timezone from typing import Any def utc_now() -> str: return datetime.now(timezone.utc).isoformat() @dataclass(frozen=True) class ArchitectureOwnerApprovalRequest: request_id: str change_id: str title: str reason: str risk: str architect_decision: str status: str = "pending_owner_approval" created_at: str = field(default_factory=utc_now) def to_dict(self) -> dict[str, Any]: return asdict(self) @dataclass(frozen=True) class ArchitectureOwnerDecision: request_id: str change_id: str decision: str decided_by: str reason: str = "" decided_at: str = field(default_factory=utc_now) def to_dict(self) -> dict[str, Any]: return asdict(self) class ArchitectureOwnerGate: """Blocks architecture implementation until owner explicitly approves.""" def create_request(self, change: dict[str, Any], architect_decision: dict[str, Any]) -> ArchitectureOwnerApprovalRequest: return ArchitectureOwnerApprovalRequest( request_id=f"ARCH-OWNER-{change.get('change_id', change.get('id', 'unknown'))}", change_id=change.get("change_id", change.get("id", "unknown")), title=change.get("title", "architecture change"), reason=change.get("reason", "architecture impact requires owner approval"), risk=change.get("risk", "medium"), architect_decision=architect_decision.get("status", "reviewed"), ) def can_implement(self, request: dict[str, Any], decision: dict[str, Any] | None = None) -> dict[str, Any]: if not decision: return { "allowed": False, "status": "waiting_owner_approval", "reason": "architecture change requires owner approval before implementation", "request": request, } if decision.get("decision") == "approved": return { "allowed": True, "status": "owner_approved", "reason": decision.get("reason", "approved by owner"), "request": request, "decision": decision, } return { "allowed": False, "status": "owner_rejected", "reason": decision.get("reason", "rejected by owner"), "request": request, "decision": decision, } __all__ = [ "ArchitectureOwnerApprovalRequest", "ArchitectureOwnerDecision", "ArchitectureOwnerGate", ]