""" Database models for the ARF API Control Plane. This module defines the SQLAlchemy ORM models for: - Intents (InfrastructureIntent evaluations) - Outcomes (recorded results of executed intents) - Beta state (conjugate Bayesian posteriors per tenant and category) - Audit logs (immutable decision records for compliance) - Tenants (multi‑tenant isolation) All tables include a `tenant_id` column to enforce data partitioning. The BetaStateDB now stores parameters per (tenant, category) pair. """ from sqlalchemy import ( Column, Integer, String, DateTime, Boolean, Text, JSON, Float, ForeignKey, UniqueConstraint, Index ) from sqlalchemy.orm import relationship import datetime from .base import Base # ============================================================================ # Tenant table – root of multi‑tenancy # ============================================================================ class TenantDB(Base): """ Represents a customer tenant (organisation). All other tables reference this table via a foreign key `tenant_id`. Attributes: id (str): UUID of the tenant (primary key). name (str): Human‑readable organisation name. created_at (datetime): UTC timestamp of creation. created_by (str, optional): Email or user ID of the creator. """ __tablename__ = "tenants" id = Column(String(64), primary_key=True, index=True) name = Column(String(256), nullable=False) created_at = Column(DateTime, default=datetime.datetime.utcnow, nullable=False) created_by = Column(String(128), nullable=True) # Relationships api_keys = relationship("APIKeyDB", back_populates="tenant", cascade="all, delete-orphan") intents = relationship("IntentDB", back_populates="tenant") beta_states = relationship("BetaStateDB", back_populates="tenant") audit_logs = relationship("DecisionAuditLogDB", back_populates="tenant") # ============================================================================ # API keys (extended with tenant_id) # ============================================================================ class APIKeyDB(Base): """ Stores API keys for authentication and tiered quota. Each key belongs to exactly one tenant. The `tier` determines monthly evaluation limits. Attributes: key (str): The hashed API key (primary key). tenant_id (str): Foreign key to `tenants.id`. tier (str): Tier enumeration value (free, pro, premium, enterprise). created_at (datetime): UTC creation time. last_used_at (datetime, optional): Timestamp of last successful request. is_active (bool): Soft‑delete flag. """ __tablename__ = "api_keys" key = Column(String(256), primary_key=True, index=True) tenant_id = Column(String(64), ForeignKey("tenants.id", ondelete="CASCADE"), nullable=False, index=True) tier = Column(String(32), nullable=False) created_at = Column(DateTime, default=datetime.datetime.utcnow, nullable=False) last_used_at = Column(DateTime, nullable=True) is_active = Column(Boolean, default=True, nullable=False) # Relationships tenant = relationship("TenantDB", back_populates="api_keys") usage_logs = relationship("UsageLogDB", back_populates="api_key") # ============================================================================ # Intents (evaluations) – now tenant‑scoped # ============================================================================ class IntentDB(Base): """ Stores each InfrastructureIntent evaluation request and its resulting risk score. One‑to‑many with OutcomeDB. Attributes: id (int): Auto‑increment primary key. deterministic_id (str): Client‑provided idempotency identifier (unique). tenant_id (str): Tenant that owns this intent. intent_type (str): Type of intent (e.g., "provision_resource"). payload (JSON): Original API request payload. oss_payload (JSON): Canonical OSS intent representation. environment (str, optional): Environment label (prod, staging, etc.). created_at (datetime): UTC timestamp of evaluation. evaluated_at (datetime, optional): When the risk engine processed it. risk_score (str, optional): String representation of the risk score. """ __tablename__ = "intents" id = Column(Integer, primary_key=True, index=True) deterministic_id = Column(String(64), unique=True, index=True, nullable=False) tenant_id = Column(String(64), ForeignKey("tenants.id", ondelete="CASCADE"), nullable=False, index=True) intent_type = Column(String(64), nullable=False) payload = Column(JSON, nullable=False) oss_payload = Column(JSON, nullable=True) environment = Column(String(32), nullable=True) created_at = Column(DateTime, default=datetime.datetime.utcnow, nullable=False) evaluated_at = Column(DateTime, nullable=True) risk_score = Column(String(32), nullable=True) # Relationships tenant = relationship("TenantDB", back_populates="intents") outcomes = relationship("OutcomeDB", back_populates="intent", cascade="all, delete-orphan") class OutcomeDB(Base): """ Records the outcome (success/failure) of a previously evaluated intent. Only one outcome per intent is allowed (unique constraint on intent_id). Attributes: id (int): Primary key. intent_id (int): Foreign key to `intents.id`. success (bool): Whether the executed action succeeded. recorded_by (str, optional): Identity of the caller (e.g., API key owner). notes (str, optional): Free‑text notes. recorded_at (datetime): UTC timestamp. idempotency_key (str, optional): Unique idempotency key for this outcome. """ __tablename__ = "intent_outcomes" id = Column(Integer, primary_key=True, index=True) intent_id = Column(Integer, ForeignKey("intents.id", ondelete="CASCADE"), nullable=False) success = Column(Boolean, nullable=False) recorded_by = Column(String(128), nullable=True) notes = Column(Text, nullable=True) recorded_at = Column(DateTime, default=datetime.datetime.utcnow, nullable=False) idempotency_key = Column(String(128), unique=True, nullable=True) intent = relationship("IntentDB", back_populates="outcomes") __table_args__ = ( UniqueConstraint("intent_id", name="uq_outcome_intentid"), ) # ============================================================================ # Bayesian conjugate state – now per tenant and per category # ============================================================================ class BetaStateDB(Base): """ Stores the posterior parameters (α, β) of the conjugate Beta model for each (tenant, category) pair. This allows online learning to be isolated per customer. Attributes: id (int): Primary key. tenant_id (str): Tenant that owns this state. category (str): ActionCategory value (e.g., "database", "compute"). alpha (float): α parameter of the Beta distribution. beta (float): β parameter of the Beta distribution. updated_at (datetime): Last update timestamp (auto‑set). """ __tablename__ = "beta_state" id = Column(Integer, primary_key=True, index=True) tenant_id = Column(String(64), ForeignKey("tenants.id", ondelete="CASCADE"), nullable=False, index=True) category = Column(String(32), nullable=False, index=True) alpha = Column(Float, nullable=False) beta = Column(Float, nullable=False) updated_at = Column(DateTime, default=datetime.datetime.utcnow, onupdate=datetime.datetime.utcnow) # Composite unique constraint: (tenant_id, category) __table_args__ = ( UniqueConstraint("tenant_id", "category", name="uq_beta_state_tenant_category"), ) # Relationships tenant = relationship("TenantDB", back_populates="beta_states") # ============================================================================ # NEW: Audit log for compliance (immutable decision records) # ============================================================================ class DecisionAuditLogDB(Base): """ Immutable, tamper‑evident record of every governance decision. Designed for compliance (SOC2, ISO) and forensic analysis. Attributes: id (str): UUID primary key. tenant_id (str): Tenant that owns the decision. deterministic_id (str): Intent identifier (idempotency key). timestamp (datetime): UTC decision time. risk_score (float): Fused Bayesian risk score (0‑1). action (str): Selected action (approve, deny, escalate). justification (str): Human‑readable explanation. memory_success_rate (float, optional): Memory‑based correction value. memory_weight (float, optional): Weight assigned to memory. counterfactual (JSON, optional): Structured counterfactual explanation. trace_id (str, optional): OpenTelemetry trace ID for debugging. signature (str, optional): Ed25519 signature for tamper‑proofing. """ __tablename__ = "decision_audit_log" id = Column(String(64), primary_key=True, default=lambda: str(uuid.uuid4())) tenant_id = Column(String(64), ForeignKey("tenants.id", ondelete="CASCADE"), nullable=False, index=True) deterministic_id = Column(String(64), nullable=False, index=True) timestamp = Column(DateTime, default=datetime.datetime.utcnow, nullable=False, index=True) risk_score = Column(Float, nullable=False) action = Column(String(32), nullable=False) justification = Column(Text, nullable=False) memory_success_rate = Column(Float, nullable=True) memory_weight = Column(Float, nullable=True) counterfactual = Column(JSON, nullable=True) trace_id = Column(String(128), nullable=True) signature = Column(String(256), nullable=True) # Composite index for fast filtered queries __table_args__ = ( Index("idx_audit_tenant_time", "tenant_id", "timestamp"), ) tenant = relationship("TenantDB", back_populates="audit_logs")