"""Data models for the autoscaler daemon.""" from __future__ import annotations from datetime import datetime from enum import StrEnum from typing import Any from pydantic import BaseModel, Field class SlotState(StrEnum): """Exhaustive slot states.""" EMPTY = "empty" LAUNCHING = "launching" BOOTING = "booting" BINDING = "binding" READY = "ready" DRAINING = "draining" TERMINATING = "terminating" ERROR = "error" class ReservationPhase(StrEnum): """Exhaustive reservation phases.""" PENDING = "pending" READY = "ready" FAILED = "failed" RELEASED = "released" EXPIRED = "expired" # --------------------------------------------------------------------------- # API request models # --------------------------------------------------------------------------- class ReservationRequest(BaseModel): """POST /v1/reservations request body.""" system: str reason: str build_id: int | None = None class CapacityHint(BaseModel): """POST /v1/hints/capacity request body.""" builder: str queued: int running: int system: str timestamp: datetime # --------------------------------------------------------------------------- # API response models # --------------------------------------------------------------------------- class ReservationResponse(BaseModel): """Reservation representation returned by the API.""" reservation_id: str phase: ReservationPhase slot: str | None = None instance_id: str | None = None system: str created_at: datetime updated_at: datetime expires_at: datetime released_at: datetime | None = None class SlotInfo(BaseModel): """Slot representation returned by the API.""" slot_id: str system: str state: SlotState instance_id: str | None = None instance_ip: str | None = None lease_count: int last_state_change: datetime class SlotsSummary(BaseModel): """Aggregate slot counts by state.""" total: int = 0 ready: int = 0 launching: int = 0 booting: int = 0 binding: int = 0 draining: int = 0 terminating: int = 0 empty: int = 0 error: int = 0 class ReservationsSummary(BaseModel): """Aggregate reservation counts by phase.""" pending: int = 0 ready: int = 0 failed: int = 0 class Ec2Summary(BaseModel): """EC2 subsystem health.""" api_ok: bool = True last_reconcile_at: datetime | None = None class HaproxySummary(BaseModel): """HAProxy subsystem health.""" socket_ok: bool = True last_stat_poll_at: datetime | None = None class StateSummary(BaseModel): """GET /v1/state/summary response.""" slots: SlotsSummary = Field(default_factory=SlotsSummary) reservations: ReservationsSummary = Field(default_factory=ReservationsSummary) ec2: Ec2Summary = Field(default_factory=Ec2Summary) haproxy: HaproxySummary = Field(default_factory=HaproxySummary) class ErrorDetail(BaseModel): """Structured error detail.""" code: str message: str retryable: bool = False details: dict[str, Any] | None = None class ErrorResponse(BaseModel): """Standard error response envelope.""" error: ErrorDetail request_id: str class HealthResponse(BaseModel): """Health check response.""" status: str