"""Instance model - represents a deployed orchestrator with licensing.""" from datetime import datetime from typing import TYPE_CHECKING, Optional from uuid import UUID from sqlalchemy import DateTime, ForeignKey, Integer, String from sqlalchemy.orm import Mapped, mapped_column, relationship from app.models.base import Base, TimestampMixin, UUIDMixin if TYPE_CHECKING: from app.models.client import Client class Instance(UUIDMixin, TimestampMixin, Base): """ A deployed orchestrator instance with licensing. Each instance is tied to a client and requires a valid license to operate. The Hub issues license keys and tracks activation status. """ __tablename__ = "instances" # Client relationship client_id: Mapped[UUID] = mapped_column( ForeignKey("clients.id", ondelete="CASCADE"), nullable=False, ) # Instance identification instance_id: Mapped[str] = mapped_column( String(255), unique=True, nullable=False, index=True, ) # e.g., "acme-orchestrator" # === LICENSING === license_key_hash: Mapped[str] = mapped_column( String(64), nullable=False, ) # SHA-256 hash of the license key (lb_inst_...) license_key_prefix: Mapped[str] = mapped_column( String(12), nullable=False, ) # First 12 chars for display: "lb_inst_abc1" license_status: Mapped[str] = mapped_column( String(50), default="active", nullable=False, ) # "active", "suspended", "expired", "revoked" license_issued_at: Mapped[datetime] = mapped_column( DateTime(timezone=True), nullable=False, ) license_expires_at: Mapped[Optional[datetime]] = mapped_column( DateTime(timezone=True), nullable=True, ) # None = no expiry (perpetual) # === ACTIVATION STATE === activated_at: Mapped[Optional[datetime]] = mapped_column( DateTime(timezone=True), nullable=True, ) # Set when instance first calls /activate last_activation_at: Mapped[Optional[datetime]] = mapped_column( DateTime(timezone=True), nullable=True, ) # Updated on each activation call activation_count: Mapped[int] = mapped_column( Integer, default=0, nullable=False, ) # === TELEMETRY === hub_api_key_hash: Mapped[Optional[str]] = mapped_column( String(64), nullable=True, ) # Generated on activation, used for telemetry auth # === METADATA === region: Mapped[Optional[str]] = mapped_column( String(50), nullable=True, ) # e.g., "eu-west-1" version: Mapped[Optional[str]] = mapped_column( String(50), nullable=True, ) # Last reported orchestrator version last_seen_at: Mapped[Optional[datetime]] = mapped_column( DateTime(timezone=True), nullable=True, ) # Last telemetry or heartbeat status: Mapped[str] = mapped_column( String(50), default="pending", nullable=False, ) # "pending" (created, not yet activated), "active", "inactive", "suspended" # Relationships client: Mapped["Client"] = relationship(back_populates="instances") def is_license_valid(self) -> bool: """Check if the license is currently valid.""" from app.models.base import utc_now if self.license_status not in ("active",): return False if self.license_expires_at and self.license_expires_at < utc_now(): return False return True