"""SQLAlchemy ORM tables. This is the *only* module that knows about the relational schema. The domain layer is expressed as plain dataclasses in ``app.domain.*``; repositories map between the two. Keeping the schema here means a migration change never forces a change to domain logic. """ from __future__ import annotations from datetime import datetime from sqlalchemy import ( JSON, Boolean, DateTime, Float, ForeignKey, Index, Integer, String, Text, UniqueConstraint, ) from sqlalchemy.orm import Mapped, mapped_column from app.core.database import Base from app.domain.base import new_id, utcnow def _pk() -> Mapped[str]: return mapped_column(String(32), primary_key=True, default=new_id) def _created() -> Mapped[datetime]: return mapped_column(DateTime(timezone=True), default=utcnow, nullable=False) # --------------------------------------------------------------------------- # Identity & Access # --------------------------------------------------------------------------- class UserRow(Base): __tablename__ = "users" id: Mapped[str] = _pk() email: Mapped[str] = mapped_column(String(320), unique=True, index=True, nullable=False) password_hash: Mapped[str] = mapped_column(String(255), nullable=False) name: Mapped[str] = mapped_column(String(160), default="", nullable=False) avatar_url: Mapped[str | None] = mapped_column(String(512), nullable=True) is_platform_owner: Mapped[bool] = mapped_column(Boolean, default=False, nullable=False) is_active: Mapped[bool] = mapped_column(Boolean, default=True, nullable=False) created_at: Mapped[datetime] = _created() updated_at: Mapped[datetime] = mapped_column( DateTime(timezone=True), default=utcnow, onupdate=utcnow, nullable=False ) class OrganizationRow(Base): __tablename__ = "organizations" id: Mapped[str] = _pk() name: Mapped[str] = mapped_column(String(160), nullable=False) slug: Mapped[str] = mapped_column(String(64), unique=True, index=True, nullable=False) owner_user_id: Mapped[str] = mapped_column(ForeignKey("users.id"), nullable=False, index=True) created_at: Mapped[datetime] = _created() class MembershipRow(Base): __tablename__ = "memberships" __table_args__ = ( UniqueConstraint("organization_id", "user_id", name="uq_membership_org_user"), Index("ix_membership_org_role", "organization_id", "role"), ) id: Mapped[str] = _pk() organization_id: Mapped[str] = mapped_column( ForeignKey("organizations.id", ondelete="CASCADE"), nullable=False, index=True ) user_id: Mapped[str] = mapped_column( ForeignKey("users.id", ondelete="CASCADE"), nullable=False, index=True ) role: Mapped[str] = mapped_column(String(16), default="member", nullable=False) status: Mapped[str] = mapped_column(String(16), default="active", nullable=False) created_at: Mapped[datetime] = _created() class InviteRow(Base): __tablename__ = "invites" __table_args__ = (Index("ix_invite_org_email", "organization_id", "email"),) id: Mapped[str] = _pk() organization_id: Mapped[str] = mapped_column( ForeignKey("organizations.id", ondelete="CASCADE"), nullable=False, index=True ) email: Mapped[str] = mapped_column(String(320), nullable=False) role: Mapped[str] = mapped_column(String(16), default="member", nullable=False) token_hash: Mapped[str] = mapped_column(String(128), unique=True, nullable=False) invited_by_user_id: Mapped[str | None] = mapped_column( ForeignKey("users.id"), nullable=True, index=True ) expires_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), nullable=False) accepted_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True) created_at: Mapped[datetime] = _created() # --------------------------------------------------------------------------- # AI Control Plane (platform-owner only) # --------------------------------------------------------------------------- class AiProviderRow(Base): __tablename__ = "ai_providers" id: Mapped[str] = _pk() name: Mapped[str] = mapped_column(String(120), unique=True, nullable=False) #: agy is the only provider type; the column stays a string so old rows #: (mock / openai_compatible, deleted by migration c2d3e4f5b6a7) cannot #: break the enum mapping. type: Mapped[str] = mapped_column(String(32), default="agy", nullable=False) base_url: Mapped[str] = mapped_column(String(512), default="", nullable=False) is_active: Mapped[bool] = mapped_column(Boolean, default=True, nullable=False) created_at: Mapped[datetime] = _created() class AiModelCredentialRow(Base): __tablename__ = "ai_model_credentials" id: Mapped[str] = _pk() provider_id: Mapped[str] = mapped_column( ForeignKey("ai_providers.id", ondelete="CASCADE"), nullable=False, index=True ) name: Mapped[str] = mapped_column(String(120), nullable=False) encrypted_api_key: Mapped[str] = mapped_column(Text, default="", nullable=False) is_active: Mapped[bool] = mapped_column(Boolean, default=True, nullable=False) created_at: Mapped[datetime] = _created() # --- agy account columns (empty for API-key credentials) ----------------- #: Google account email this credential belongs to; also the rotation label. email: Mapped[str] = mapped_column(String(320), default="", nullable=False) #: Antigravity ``cloudaicompanionProject`` id resolved during login. project_id: Mapped[str] = mapped_column(String(160), default="", nullable=False) #: ``active`` | ``cooldown`` | ``error`` — see ``CredentialStatus``. status: Mapped[str] = mapped_column(String(16), default="active", nullable=False) #: When set and in the future, the rotation pool skips this account. cooldown_until: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True) last_error: Mapped[str] = mapped_column(String(512), default="", nullable=False) last_used_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True) class AiModelRow(Base): __tablename__ = "ai_models" __table_args__ = ( UniqueConstraint("provider_id", "internal_name", name="uq_model_provider_name"), ) id: Mapped[str] = _pk() provider_id: Mapped[str] = mapped_column( ForeignKey("ai_providers.id", ondelete="CASCADE"), nullable=False, index=True ) internal_name: Mapped[str] = mapped_column(String(160), nullable=False) display_name_for_admin: Mapped[str] = mapped_column(String(160), nullable=False) capabilities: Mapped[list] = mapped_column(JSON, default=list, nullable=False) input_price_per_1m: Mapped[float] = mapped_column(Float, default=0.0, nullable=False) output_price_per_1m: Mapped[float] = mapped_column(Float, default=0.0, nullable=False) #: agy reasoning effort ("" = provider default); see ``AiModel``. reasoning_effort: Mapped[str] = mapped_column(String(16), default="", nullable=False) is_active: Mapped[bool] = mapped_column(Boolean, default=True, nullable=False) created_at: Mapped[datetime] = _created() class RoutingPolicyRow(Base): __tablename__ = "routing_policies" id: Mapped[str] = _pk() name: Mapped[str] = mapped_column(String(120), unique=True, nullable=False) description: Mapped[str] = mapped_column(Text, default="", nullable=False) mode: Mapped[str] = mapped_column(String(16), default="balanced", nullable=False) fallback_policy_json: Mapped[dict] = mapped_column(JSON, default=dict, nullable=False) budget_limits_json: Mapped[dict] = mapped_column(JSON, default=dict, nullable=False) is_default: Mapped[bool] = mapped_column(Boolean, default=False, nullable=False) is_active: Mapped[bool] = mapped_column(Boolean, default=True, nullable=False) created_at: Mapped[datetime] = _created() class RateCardRow(Base): __tablename__ = "rate_cards" id: Mapped[str] = _pk() name: Mapped[str] = mapped_column(String(120), nullable=False) unit: Mapped[str] = mapped_column(String(40), unique=True, nullable=False) customer_price: Mapped[float] = mapped_column(Float, default=0.0, nullable=False) internal_cost_estimate: Mapped[float] = mapped_column(Float, default=0.0, nullable=False) is_active: Mapped[bool] = mapped_column(Boolean, default=True, nullable=False) created_at: Mapped[datetime] = _created() # --------------------------------------------------------------------------- # Projects & Git # --------------------------------------------------------------------------- class ProjectRow(Base): __tablename__ = "projects" __table_args__ = ( UniqueConstraint("organization_id", "slug", name="uq_project_org_slug"), # The project list is always scoped to one organization and ordered by # creation, so one composite index serves both the filter and the sort. Index("ix_project_org_created", "organization_id", "created_at"), ) id: Mapped[str] = _pk() organization_id: Mapped[str] = mapped_column( ForeignKey("organizations.id", ondelete="CASCADE"), nullable=False, index=True ) name: Mapped[str] = mapped_column(String(160), nullable=False) slug: Mapped[str] = mapped_column(String(64), nullable=False) description: Mapped[str] = mapped_column(Text, default="", nullable=False) created_by_user_id: Mapped[str | None] = mapped_column( ForeignKey("users.id"), nullable=True, index=True ) default_branch: Mapped[str] = mapped_column(String(80), default="main", nullable=False) created_at: Mapped[datetime] = _created() class ProjectRepositoryRow(Base): __tablename__ = "project_repositories" id: Mapped[str] = _pk() project_id: Mapped[str] = mapped_column( ForeignKey("projects.id", ondelete="CASCADE"), unique=True, nullable=False, index=True ) provider: Mapped[str] = mapped_column(String(32), default="gitea", nullable=False) owner: Mapped[str] = mapped_column(String(120), default="", nullable=False) repo_name: Mapped[str] = mapped_column(String(160), default="", nullable=False) clone_url: Mapped[str] = mapped_column(String(512), default="", nullable=False) web_url: Mapped[str] = mapped_column(String(512), default="", nullable=False) default_branch: Mapped[str] = mapped_column(String(80), default="main", nullable=False) created_at: Mapped[datetime] = _created() class ProjectGitCredentialRow(Base): __tablename__ = "project_git_credentials" id: Mapped[str] = _pk() project_id: Mapped[str] = mapped_column( ForeignKey("projects.id", ondelete="CASCADE"), unique=True, nullable=False, index=True ) username: Mapped[str] = mapped_column(String(160), nullable=False) encrypted_password: Mapped[str] = mapped_column(Text, nullable=False) created_at: Mapped[datetime] = _created() # --------------------------------------------------------------------------- # Preview domain routing # --------------------------------------------------------------------------- class PreviewPortRow(Base): __tablename__ = "preview_ports" __table_args__ = ( UniqueConstraint("project_id", "service_name", name="uq_preview_port_project_service"), UniqueConstraint("port", name="uq_preview_port_port"), ) id: Mapped[str] = _pk() organization_id: Mapped[str] = mapped_column( ForeignKey("organizations.id", ondelete="CASCADE"), nullable=False, index=True ) project_id: Mapped[str] = mapped_column( ForeignKey("projects.id", ondelete="CASCADE"), nullable=False, index=True ) service_name: Mapped[str] = mapped_column(String(80), default="web", nullable=False) port: Mapped[int] = mapped_column(Integer, nullable=False) status: Mapped[str] = mapped_column(String(16), default="active", nullable=False) created_at: Mapped[datetime] = _created() class PreviewDomainRow(Base): __tablename__ = "preview_domains" __table_args__ = ( UniqueConstraint("host", name="uq_preview_domain_host"), Index("ix_preview_domain_org_created", "organization_id", "created_at"), ) id: Mapped[str] = _pk() organization_id: Mapped[str] = mapped_column( ForeignKey("organizations.id", ondelete="CASCADE"), nullable=False, index=True ) project_id: Mapped[str] = mapped_column( ForeignKey("projects.id", ondelete="CASCADE"), nullable=False, index=True ) host: Mapped[str] = mapped_column(String(255), nullable=False) port: Mapped[int] = mapped_column(Integer, nullable=False) status: Mapped[str] = mapped_column(String(16), default="active", nullable=False) protected: Mapped[bool] = mapped_column(Boolean, default=False, nullable=False) created_by_user_id: Mapped[str | None] = mapped_column( ForeignKey("users.id"), nullable=True, index=True ) created_at: Mapped[datetime] = _created() # --------------------------------------------------------------------------- # Sandbox # --------------------------------------------------------------------------- class SandboxTemplateRow(Base): __tablename__ = "sandbox_templates" id: Mapped[str] = _pk() name: Mapped[str] = mapped_column(String(120), unique=True, nullable=False) provider: Mapped[str] = mapped_column(String(32), default="mock", nullable=False) image: Mapped[str] = mapped_column(String(255), default="python:3.13-slim", nullable=False) description: Mapped[str] = mapped_column(Text, default="", nullable=False) default_cpu: Mapped[float] = mapped_column(Float, default=1.0, nullable=False) default_memory_mb: Mapped[int] = mapped_column(Integer, default=1024, nullable=False) default_disk_mb: Mapped[int] = mapped_column(Integer, default=2048, nullable=False) is_system: Mapped[bool] = mapped_column(Boolean, default=True, nullable=False) created_at: Mapped[datetime] = _created() class WorkspaceRow(Base): __tablename__ = "workspaces" __table_args__ = (Index("ix_workspace_project_created", "project_id", "created_at"),) id: Mapped[str] = _pk() organization_id: Mapped[str] = mapped_column( ForeignKey("organizations.id", ondelete="CASCADE"), nullable=False, index=True ) project_id: Mapped[str] = mapped_column( ForeignKey("projects.id", ondelete="CASCADE"), nullable=False, index=True ) template_id: Mapped[str] = mapped_column( ForeignKey("sandbox_templates.id"), nullable=False, index=True ) provider: Mapped[str] = mapped_column(String(32), default="mock", nullable=False) external_workspace_id: Mapped[str | None] = mapped_column(String(160), nullable=True) status: Mapped[str] = mapped_column(String(16), default="pending", nullable=False) created_at: Mapped[datetime] = _created() class SandboxSessionRow(Base): __tablename__ = "sandbox_sessions" id: Mapped[str] = _pk() workspace_id: Mapped[str] = mapped_column( ForeignKey("workspaces.id", ondelete="CASCADE"), nullable=False, index=True ) external_session_id: Mapped[str | None] = mapped_column(String(160), nullable=True) status: Mapped[str] = mapped_column(String(16), default="starting", nullable=False) started_at: Mapped[datetime] = _created() stopped_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True) last_active_at: Mapped[datetime] = _created() class CommandRunRow(Base): __tablename__ = "command_runs" id: Mapped[str] = _pk() session_id: Mapped[str] = mapped_column( ForeignKey("sandbox_sessions.id", ondelete="CASCADE"), nullable=False, index=True ) command: Mapped[str] = mapped_column(Text, nullable=False) exit_code: Mapped[int | None] = mapped_column(Integer, nullable=True) stdout_ref: Mapped[str] = mapped_column(Text, default="", nullable=False) stderr_ref: Mapped[str] = mapped_column(Text, default="", nullable=False) duration_ms: Mapped[int] = mapped_column(Integer, default=0, nullable=False) truncated: Mapped[bool] = mapped_column(Boolean, default=False, nullable=False) created_at: Mapped[datetime] = _created() class SnapshotRow(Base): __tablename__ = "snapshots" id: Mapped[str] = _pk() workspace_id: Mapped[str] = mapped_column( ForeignKey("workspaces.id", ondelete="CASCADE"), nullable=False, index=True ) name: Mapped[str] = mapped_column(String(120), nullable=False) external_snapshot_id: Mapped[str | None] = mapped_column(String(160), nullable=True) created_at: Mapped[datetime] = _created() class PreviewUrlRow(Base): __tablename__ = "preview_urls" id: Mapped[str] = _pk() workspace_id: Mapped[str] = mapped_column( ForeignKey("workspaces.id", ondelete="CASCADE"), nullable=False, index=True ) port: Mapped[int] = mapped_column(Integer, nullable=False) url: Mapped[str] = mapped_column(String(512), nullable=False) created_at: Mapped[datetime] = _created() class SandboxResourceLimitRow(Base): __tablename__ = "sandbox_resource_limits" __table_args__ = (UniqueConstraint("scope", "scope_id", name="uq_limit_scope"),) id: Mapped[str] = _pk() scope: Mapped[str] = mapped_column(String(32), default="organization", nullable=False) scope_id: Mapped[str | None] = mapped_column(String(32), nullable=True) cpu: Mapped[float] = mapped_column(Float, default=1.0, nullable=False) memory_mb: Mapped[int] = mapped_column(Integer, default=1024, nullable=False) disk_mb: Mapped[int] = mapped_column(Integer, default=2048, nullable=False) timeout_seconds: Mapped[int] = mapped_column(Integer, default=120, nullable=False) max_output_bytes: Mapped[int] = mapped_column(Integer, default=200_000, nullable=False) network_policy: Mapped[str] = mapped_column(String(16), default="deny_all", nullable=False) # --------------------------------------------------------------------------- # Workforce: templates, employees, chat, tasks, runs, artifacts # --------------------------------------------------------------------------- class AgentTemplateRow(Base): __tablename__ = "agent_templates" id: Mapped[str] = _pk() name: Mapped[str] = mapped_column(String(120), unique=True, nullable=False) role_key: Mapped[str] = mapped_column(String(32), nullable=False) description: Mapped[str] = mapped_column(Text, default="", nullable=False) default_prompt: Mapped[str] = mapped_column(Text, default="", nullable=False) default_tools_json: Mapped[list] = mapped_column(JSON, default=list, nullable=False) default_routing_policy_id: Mapped[str | None] = mapped_column(String(32), nullable=True) is_system: Mapped[bool] = mapped_column(Boolean, default=True, nullable=False) created_at: Mapped[datetime] = _created() class EmployeeRow(Base): __tablename__ = "employees" __table_args__ = (Index("ix_employee_org_created", "organization_id", "created_at"),) id: Mapped[str] = _pk() organization_id: Mapped[str] = mapped_column( ForeignKey("organizations.id", ondelete="CASCADE"), nullable=False, index=True ) project_id: Mapped[str | None] = mapped_column( ForeignKey("projects.id", ondelete="SET NULL"), nullable=True, index=True ) template_id: Mapped[str | None] = mapped_column(String(32), nullable=True) name: Mapped[str] = mapped_column(String(160), nullable=False) role_key: Mapped[str] = mapped_column(String(32), nullable=False) status: Mapped[str] = mapped_column(String(16), default="active", nullable=False) routing_policy_id: Mapped[str | None] = mapped_column(String(32), nullable=True) budget_limit_json: Mapped[dict] = mapped_column(JSON, default=dict, nullable=False) created_at: Mapped[datetime] = _created() class PromptVersionRow(Base): __tablename__ = "prompt_versions" __table_args__ = ( UniqueConstraint("employee_id", "version", name="uq_prompt_employee_version"), ) id: Mapped[str] = _pk() employee_id: Mapped[str] = mapped_column( ForeignKey("employees.id", ondelete="CASCADE"), nullable=False, index=True ) version: Mapped[int] = mapped_column(Integer, nullable=False) content: Mapped[str] = mapped_column(Text, nullable=False) created_by_user_id: Mapped[str | None] = mapped_column( ForeignKey("users.id"), nullable=True, index=True ) created_at: Mapped[datetime] = _created() class EmployeeToolPermissionRow(Base): __tablename__ = "employee_tool_permissions" __table_args__ = ( UniqueConstraint("employee_id", "tool_key", name="uq_employee_tool"), ) id: Mapped[str] = _pk() employee_id: Mapped[str] = mapped_column( ForeignKey("employees.id", ondelete="CASCADE"), nullable=False, index=True ) tool_key: Mapped[str] = mapped_column(String(80), nullable=False) allowed: Mapped[bool] = mapped_column(Boolean, default=False, nullable=False) policy_json: Mapped[dict] = mapped_column(JSON, default=dict, nullable=False) class ThreadRow(Base): __tablename__ = "threads" __table_args__ = (Index("ix_thread_org_created", "organization_id", "created_at"),) id: Mapped[str] = _pk() organization_id: Mapped[str] = mapped_column( ForeignKey("organizations.id", ondelete="CASCADE"), nullable=False, index=True ) project_id: Mapped[str | None] = mapped_column( ForeignKey("projects.id", ondelete="SET NULL"), nullable=True, index=True ) title: Mapped[str] = mapped_column(String(255), default="", nullable=False) created_by_user_id: Mapped[str | None] = mapped_column( ForeignKey("users.id"), nullable=True, index=True ) created_at: Mapped[datetime] = _created() class MessageRow(Base): __tablename__ = "messages" # A conversation is always read as "one thread, oldest first", so the # composite index serves the filter and the ordering in one lookup. __table_args__ = (Index("ix_message_thread_created", "thread_id", "created_at"),) id: Mapped[str] = _pk() thread_id: Mapped[str] = mapped_column( ForeignKey("threads.id", ondelete="CASCADE"), nullable=False, index=True ) role: Mapped[str] = mapped_column(String(16), nullable=False) content: Mapped[str] = mapped_column(Text, default="", nullable=False) employee_id: Mapped[str | None] = mapped_column(String(32), nullable=True, index=True) created_at: Mapped[datetime] = _created() class TaskRow(Base): __tablename__ = "tasks" __table_args__ = ( Index("ix_task_org_status", "organization_id", "status"), Index("ix_task_org_created", "organization_id", "created_at"), ) id: Mapped[str] = _pk() organization_id: Mapped[str] = mapped_column( ForeignKey("organizations.id", ondelete="CASCADE"), nullable=False, index=True ) project_id: Mapped[str | None] = mapped_column( ForeignKey("projects.id", ondelete="SET NULL"), nullable=True, index=True ) thread_id: Mapped[str | None] = mapped_column( ForeignKey("threads.id", ondelete="SET NULL"), nullable=True, index=True ) title: Mapped[str] = mapped_column(String(255), nullable=False) description: Mapped[str] = mapped_column(Text, default="", nullable=False) status: Mapped[str] = mapped_column(String(24), default="new", nullable=False) assigned_employee_id: Mapped[str | None] = mapped_column( String(32), nullable=True, index=True ) created_by_user_id: Mapped[str | None] = mapped_column( ForeignKey("users.id"), nullable=True, index=True ) created_at: Mapped[datetime] = _created() updated_at: Mapped[datetime] = mapped_column( DateTime(timezone=True), default=utcnow, onupdate=utcnow, nullable=False ) class RunRow(Base): __tablename__ = "runs" # A run has no organization_id of its own: it is scoped through its task, # which is the object that carries the tenant. Indexing a column that does # not exist here would be wrong, so the composite stays on task_id. __table_args__ = (Index("ix_run_task_created", "task_id", "created_at"),) id: Mapped[str] = _pk() task_id: Mapped[str] = mapped_column( ForeignKey("tasks.id", ondelete="CASCADE"), nullable=False, index=True ) employee_id: Mapped[str | None] = mapped_column(String(32), nullable=True, index=True) status: Mapped[str] = mapped_column(String(24), default="queued", nullable=False) error: Mapped[str | None] = mapped_column(Text, nullable=True) started_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True) finished_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True) created_at: Mapped[datetime] = _created() class RunStepRow(Base): __tablename__ = "run_steps" __table_args__ = (Index("ix_run_step_run_index", "run_id", "index"),) id: Mapped[str] = _pk() run_id: Mapped[str] = mapped_column( ForeignKey("runs.id", ondelete="CASCADE"), nullable=False, index=True ) index: Mapped[int] = mapped_column(Integer, default=0, nullable=False) kind: Mapped[str] = mapped_column(String(40), default="note", nullable=False) title: Mapped[str] = mapped_column(String(255), default="", nullable=False) payload_json: Mapped[dict] = mapped_column(JSON, default=dict, nullable=False) created_at: Mapped[datetime] = _created() class ToolCallRow(Base): __tablename__ = "tool_calls" __table_args__ = ( Index("ix_tool_call_run_created", "run_id", "created_at"), # The pending-approval queue is "one organization, still awaiting a # decision", which is a filter on status plus a join back to the run. Index("ix_tool_call_status", "status"), ) id: Mapped[str] = _pk() run_id: Mapped[str] = mapped_column( ForeignKey("runs.id", ondelete="CASCADE"), nullable=False, index=True ) tool_key: Mapped[str] = mapped_column(String(80), nullable=False) arguments_json: Mapped[dict] = mapped_column(JSON, default=dict, nullable=False) result_json: Mapped[dict] = mapped_column(JSON, default=dict, nullable=False) status: Mapped[str] = mapped_column(String(24), default="pending", nullable=False) requires_approval: Mapped[bool] = mapped_column(Boolean, default=False, nullable=False) approved_by_user_id: Mapped[str | None] = mapped_column( ForeignKey("users.id"), nullable=True, index=True ) created_at: Mapped[datetime] = _created() class ArtifactRow(Base): __tablename__ = "artifacts" __table_args__ = (Index("ix_artifact_org_created", "organization_id", "created_at"),) id: Mapped[str] = _pk() organization_id: Mapped[str] = mapped_column( ForeignKey("organizations.id", ondelete="CASCADE"), nullable=False, index=True ) project_id: Mapped[str | None] = mapped_column( ForeignKey("projects.id", ondelete="SET NULL"), nullable=True, index=True ) run_id: Mapped[str | None] = mapped_column( ForeignKey("runs.id", ondelete="SET NULL"), nullable=True, index=True ) name: Mapped[str] = mapped_column(String(255), nullable=False) kind: Mapped[str] = mapped_column(String(32), default="file", nullable=False) content_ref: Mapped[str] = mapped_column(Text, default="", nullable=False) size_bytes: Mapped[int] = mapped_column(Integer, default=0, nullable=False) created_at: Mapped[datetime] = _created() # --------------------------------------------------------------------------- # Billing & Usage # --------------------------------------------------------------------------- class PlanRow(Base): __tablename__ = "plans" id: Mapped[str] = _pk() name: Mapped[str] = mapped_column(String(120), unique=True, nullable=False) monthly_price: Mapped[float] = mapped_column(Float, default=0.0, nullable=False) included_credit: Mapped[float] = mapped_column(Float, default=0.0, nullable=False) features_json: Mapped[dict] = mapped_column(JSON, default=dict, nullable=False) is_active: Mapped[bool] = mapped_column(Boolean, default=True, nullable=False) created_at: Mapped[datetime] = _created() class SubscriptionRow(Base): __tablename__ = "subscriptions" id: Mapped[str] = _pk() organization_id: Mapped[str] = mapped_column( ForeignKey("organizations.id", ondelete="CASCADE"), nullable=False, index=True ) plan_id: Mapped[str] = mapped_column(ForeignKey("plans.id"), nullable=False, index=True) status: Mapped[str] = mapped_column(String(16), default="active", nullable=False) external_subscription_id: Mapped[str | None] = mapped_column(String(160), nullable=True) current_period_end: Mapped[datetime | None] = mapped_column( DateTime(timezone=True), nullable=True ) created_at: Mapped[datetime] = _created() class CreditWalletRow(Base): __tablename__ = "credit_wallets" id: Mapped[str] = _pk() organization_id: Mapped[str] = mapped_column( ForeignKey("organizations.id", ondelete="CASCADE"), unique=True, nullable=False, index=True ) balance: Mapped[float] = mapped_column(Float, default=0.0, nullable=False) currency: Mapped[str] = mapped_column(String(8), default="USD", nullable=False) low_balance_threshold: Mapped[float] = mapped_column(Float, default=5.0, nullable=False) updated_at: Mapped[datetime] = mapped_column( DateTime(timezone=True), default=utcnow, onupdate=utcnow, nullable=False ) class UsageEventRow(Base): __tablename__ = "usage_events" __table_args__ = ( Index("ix_usage_org_created", "organization_id", "created_at"), Index("ix_usage_org_unit", "organization_id", "unit"), ) id: Mapped[str] = _pk() organization_id: Mapped[str] = mapped_column( ForeignKey("organizations.id", ondelete="CASCADE"), nullable=False, index=True ) project_id: Mapped[str | None] = mapped_column(String(32), nullable=True, index=True) user_id: Mapped[str | None] = mapped_column(String(32), nullable=True) employee_id: Mapped[str | None] = mapped_column(String(32), nullable=True, index=True) run_id: Mapped[str | None] = mapped_column(String(32), nullable=True, index=True) unit: Mapped[str] = mapped_column(String(40), nullable=False) quantity: Mapped[float] = mapped_column(Float, default=0.0, nullable=False) unit_price: Mapped[float] = mapped_column(Float, default=0.0, nullable=False) internal_cost_estimate: Mapped[float] = mapped_column(Float, default=0.0, nullable=False) total_price: Mapped[float] = mapped_column(Float, default=0.0, nullable=False) metadata_json: Mapped[dict] = mapped_column(JSON, default=dict, nullable=False) created_at: Mapped[datetime] = _created() class InvoiceRow(Base): __tablename__ = "invoices" id: Mapped[str] = _pk() organization_id: Mapped[str] = mapped_column( ForeignKey("organizations.id", ondelete="CASCADE"), nullable=False, index=True ) amount: Mapped[float] = mapped_column(Float, default=0.0, nullable=False) currency: Mapped[str] = mapped_column(String(8), default="USD", nullable=False) status: Mapped[str] = mapped_column(String(16), default="draft", nullable=False) external_invoice_id: Mapped[str | None] = mapped_column(String(160), nullable=True) period_start: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True) period_end: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True) created_at: Mapped[datetime] = _created() class PaymentRow(Base): __tablename__ = "payments" id: Mapped[str] = _pk() organization_id: Mapped[str] = mapped_column( ForeignKey("organizations.id", ondelete="CASCADE"), nullable=False, index=True ) amount: Mapped[float] = mapped_column(Float, default=0.0, nullable=False) status: Mapped[str] = mapped_column(String(16), default="pending", nullable=False) provider: Mapped[str] = mapped_column(String(32), default="mock", nullable=False) external_payment_id: Mapped[str | None] = mapped_column(String(160), nullable=True) invoice_id: Mapped[str | None] = mapped_column(String(32), nullable=True) created_at: Mapped[datetime] = _created() # --------------------------------------------------------------------------- # Knowledge # --------------------------------------------------------------------------- class KnowledgeSourceRow(Base): __tablename__ = "knowledge_sources" id: Mapped[str] = _pk() organization_id: Mapped[str] = mapped_column( ForeignKey("organizations.id", ondelete="CASCADE"), nullable=False, index=True ) project_id: Mapped[str | None] = mapped_column(String(32), nullable=True, index=True) name: Mapped[str] = mapped_column(String(255), nullable=False) kind: Mapped[str] = mapped_column(String(16), default="text", nullable=False) uri: Mapped[str] = mapped_column(String(512), default="", nullable=False) created_at: Mapped[datetime] = _created() class DocumentRow(Base): __tablename__ = "documents" id: Mapped[str] = _pk() source_id: Mapped[str] = mapped_column( ForeignKey("knowledge_sources.id", ondelete="CASCADE"), nullable=False, index=True ) title: Mapped[str] = mapped_column(String(255), default="", nullable=False) content: Mapped[str] = mapped_column(Text, default="", nullable=False) created_at: Mapped[datetime] = _created() class DocumentChunkRow(Base): __tablename__ = "document_chunks" __table_args__ = (Index("ix_chunk_document_index", "document_id", "index"),) id: Mapped[str] = _pk() document_id: Mapped[str] = mapped_column( ForeignKey("documents.id", ondelete="CASCADE"), nullable=False, index=True ) index: Mapped[int] = mapped_column(Integer, default=0, nullable=False) content: Mapped[str] = mapped_column(Text, default="", nullable=False) embedding_json: Mapped[list] = mapped_column(JSON, default=list, nullable=False) class EmbeddingJobRow(Base): __tablename__ = "embedding_jobs" id: Mapped[str] = _pk() source_id: Mapped[str] = mapped_column( ForeignKey("knowledge_sources.id", ondelete="CASCADE"), nullable=False, index=True ) status: Mapped[str] = mapped_column(String(16), default="pending", nullable=False) error: Mapped[str | None] = mapped_column(Text, nullable=True) created_at: Mapped[datetime] = _created() class RetrievalPolicyRow(Base): __tablename__ = "retrieval_policies" id: Mapped[str] = _pk() organization_id: Mapped[str] = mapped_column( ForeignKey("organizations.id", ondelete="CASCADE"), nullable=False, index=True ) name: Mapped[str] = mapped_column(String(120), nullable=False) scope_json: Mapped[dict] = mapped_column(JSON, default=dict, nullable=False) created_at: Mapped[datetime] = _created() # --------------------------------------------------------------------------- # Audit & Observability # --------------------------------------------------------------------------- class AuditEventRow(Base): __tablename__ = "audit_events" __table_args__ = ( Index("ix_audit_org_created", "organization_id", "created_at"), Index("ix_audit_action", "action"), ) id: Mapped[str] = _pk() organization_id: Mapped[str | None] = mapped_column(String(32), nullable=True, index=True) actor_user_id: Mapped[str | None] = mapped_column(String(32), nullable=True, index=True) action: Mapped[str] = mapped_column(String(80), nullable=False) target_type: Mapped[str] = mapped_column(String(40), default="", nullable=False) target_id: Mapped[str] = mapped_column(String(64), default="", nullable=False) metadata_json: Mapped[dict] = mapped_column(JSON, default=dict, nullable=False) ip_address: Mapped[str] = mapped_column(String(64), default="", nullable=False) user_agent: Mapped[str] = mapped_column(String(255), default="", nullable=False) created_at: Mapped[datetime] = _created() class TraceRow(Base): __tablename__ = "traces" id: Mapped[str] = _pk() organization_id: Mapped[str] = mapped_column(String(32), nullable=False, index=True) run_id: Mapped[str | None] = mapped_column(String(32), nullable=True, index=True) name: Mapped[str] = mapped_column(String(160), default="", nullable=False) created_at: Mapped[datetime] = _created() class SpanRow(Base): __tablename__ = "spans" id: Mapped[str] = _pk() trace_id: Mapped[str] = mapped_column( ForeignKey("traces.id", ondelete="CASCADE"), nullable=False, index=True ) parent_span_id: Mapped[str | None] = mapped_column(String(32), nullable=True) name: Mapped[str] = mapped_column(String(160), nullable=False) kind: Mapped[str] = mapped_column(String(32), default="internal", nullable=False) attributes_json: Mapped[dict] = mapped_column(JSON, default=dict, nullable=False) started_at: Mapped[datetime] = _created() ended_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True) class ErrorEventRow(Base): """Persisted failures — the table the error console reads. Wide on purpose: a row that lacks the request path or the exception class forces whoever is debugging to reproduce the bug to learn what it was. The indexes cover the three access patterns — filter by tenant, filter by "still open", and group by fingerprint. """ __tablename__ = "error_events" __table_args__ = ( # Grouping view: "how often did this exact bug happen, and when last?" Index("ix_error_events_fingerprint_created", "fingerprint", "created_at"), # Feed view: "show me the newest failures for this tenant." Index("ix_error_events_org_created", "organization_id", "created_at"), ) id: Mapped[str] = _pk() organization_id: Mapped[str | None] = mapped_column(String(32), nullable=True, index=True) run_id: Mapped[str | None] = mapped_column(String(32), nullable=True, index=True) kind: Mapped[str] = mapped_column(String(80), nullable=False) message: Mapped[str] = mapped_column(Text, default="", nullable=False) stack: Mapped[str] = mapped_column(Text, default="", nullable=False) metadata_json: Mapped[dict] = mapped_column(JSON, default=dict, nullable=False) # --- classification ----------------------------------------------------- source: Mapped[str] = mapped_column(String(16), default="http", nullable=False) severity: Mapped[str] = mapped_column(String(16), default="error", nullable=False) error_type: Mapped[str] = mapped_column(String(200), default="", nullable=False) #: Groups identical failures. Empty string rather than NULL so the grouping #: index never has to deal with a NULL key. fingerprint: Mapped[str] = mapped_column(String(64), default="", nullable=False, index=True) # --- transport context -------------------------------------------------- status_code: Mapped[int | None] = mapped_column(Integer, nullable=True) method: Mapped[str] = mapped_column(String(10), default="", nullable=False) path: Mapped[str] = mapped_column(String(512), default="", nullable=False) # --- actor context ------------------------------------------------------ user_id: Mapped[str | None] = mapped_column(String(32), nullable=True) request_id: Mapped[str] = mapped_column(String(64), default="", nullable=False) ip: Mapped[str] = mapped_column(String(64), default="", nullable=False) user_agent: Mapped[str] = mapped_column(String(255), default="", nullable=False) # --- triage ------------------------------------------------------------- resolved_at: Mapped[datetime | None] = mapped_column( DateTime(timezone=True), nullable=True, index=True ) resolved_note: Mapped[str] = mapped_column(Text, default="", nullable=False) created_at: Mapped[datetime] = _created() class QualityMetricRow(Base): __tablename__ = "quality_metrics" __table_args__ = (Index("ix_metric_org_name", "organization_id", "name"),) id: Mapped[str] = _pk() organization_id: Mapped[str] = mapped_column(String(32), nullable=False, index=True) name: Mapped[str] = mapped_column(String(120), nullable=False) value: Mapped[float] = mapped_column(Float, default=0.0, nullable=False) dimensions_json: Mapped[dict] = mapped_column(JSON, default=dict, nullable=False) created_at: Mapped[datetime] = _created()