"""Typed Agent Zone and check-in contracts.""" from ipaddress import ip_network from typing import Any from uuid import UUID from pydantic import BaseModel, ConfigDict, Field, field_validator, model_validator class AgentZoneCreateRequest(BaseModel): model_config = ConfigDict(extra="forbid") site_id: UUID name: str = Field(min_length=1, max_length=120) description: str | None = Field(default=None, max_length=1000) networks: list[str] = Field(default_factory=list, max_length=64) @field_validator("name") @classmethod def normalize_name(cls, value: str) -> str: normalized = value.strip() if not normalized: raise ValueError("name cannot be blank") return normalized @field_validator("description") @classmethod def normalize_description(cls, value: str | None) -> str | None: if value is None: return None return value.strip() or None @field_validator("networks") @classmethod def normalize_networks(cls, values: list[str]) -> list[str]: normalized: list[str] = [] for value in values: try: network = str(ip_network(value.strip(), strict=True)) except ValueError as exc: raise ValueError(f"invalid CIDR network: {value}") from exc if network in normalized: raise ValueError(f"duplicate CIDR network: {network}") normalized.append(network) return normalized class AgentZoneNetwork(BaseModel): id: UUID network: str class AgentZoneSummary(BaseModel): id: UUID site_id: UUID name: str description: str | None = None is_enabled: bool assigned_agent_id: str | None = None networks: list[AgentZoneNetwork] = Field(default_factory=list) class AgentZoneListResponse(BaseModel): data: list[AgentZoneSummary] meta: dict[str, object] = Field(default_factory=dict) error: None = None class AgentZoneCreateResponse(BaseModel): data: AgentZoneSummary meta: dict[str, object] = Field(default_factory=dict) error: None = None class AgentCheckinRequest(BaseModel): model_config = ConfigDict(extra="allow") agent_id: str | None = Field(default=None, max_length=255) site_id: UUID | None = None agent_zone_id: UUID | None = None site_name: str | None = Field(default=None, max_length=255) hostname: str | None = Field(default=None, max_length=255) version: str | None = Field(default=None, max_length=120) ip_address: str | None = Field(default=None, max_length=255) @model_validator(mode="after") def require_site_for_explicit_zone(self) -> "AgentCheckinRequest": if self.agent_zone_id is not None and self.site_id is None: raise ValueError("site_id is required when agent_zone_id is supplied") return self class AgentCheckinResponse(BaseModel): ok: bool = True hub_mode: bool = True agent_id: str site_id: UUID | None = None agent_zone_id: UUID | None = None routing_available: bool routing_reason: str | None = None site_name: str message: str = "Agent check-in received." class AgentZoneRoutingContext(BaseModel): site_id: UUID | None = None agent_zone_id: UUID | None = None routing_reason: str | None = None class MinerZoneAssociationResult(BaseModel): agent_zone_id: UUID | None = None routing_reason: str | None = None outcomes: dict[UUID, str] = Field(default_factory=dict)