import uuid from datetime import datetime from sqlalchemy import ( BigInteger, Boolean, CheckConstraint, DateTime, ForeignKey, ForeignKeyConstraint, Index, String, Text, UniqueConstraint, text, ) from sqlalchemy.dialects.postgresql import JSONB, UUID from sqlalchemy.orm import Mapped, mapped_column, relationship from api.models.base import Base, TimestampMixin, new_uuid FORGE_IDENTITY_STATES = ("unverified", "verified") REPOSITORY_SLUG_KINDS = ("canonical", "alias") REPOSITORY_RENAME_PHASES = ( "draft", "preflighted", "forge-renamed", "statehub-rebound", "source-synced", "consumers-verified", "completed", "rollback-preflight", "rolled-back", ) TERMINAL_REPOSITORY_RENAME_PHASES = ("completed", "rolled-back") class RepositoryForgeIdentity(Base, TimestampMixin): """One explicitly verified (or explicitly unverified) Forge identity per repo.""" __tablename__ = "repository_forge_identities" __table_args__ = ( UniqueConstraint("repo_id", name="uq_repository_forge_identities_repo_id"), UniqueConstraint( "id", "repo_id", "verification_state", name="uq_repository_forge_identity_verified_ref", ), UniqueConstraint( "provider", "forge_instance", "forge_owner", "forge_repository_id", name="uq_repository_forge_identity_tuple", ), CheckConstraint( "verification_state IN ('unverified', 'verified')", name="ck_repository_forge_identity_state", ), CheckConstraint( "forge_repository_id IS NULL OR forge_repository_id > 0", name="ck_repository_forge_id_positive", ), CheckConstraint( "verification_state != 'verified' OR " "(provider IS NOT NULL AND forge_instance IS NOT NULL " "AND forge_owner IS NOT NULL AND forge_repository_id IS NOT NULL " "AND verified_at IS NOT NULL AND verified_by IS NOT NULL)", name="ck_repository_forge_verified_complete", ), CheckConstraint( "verification_state = 'verified' OR " "(verified_at IS NULL AND verified_by IS NULL)", name="ck_repository_forge_unverified_has_no_attestation", ), ) id: Mapped[uuid.UUID] = mapped_column( UUID(as_uuid=True), primary_key=True, default=new_uuid ) repo_id: Mapped[uuid.UUID] = mapped_column( UUID(as_uuid=True), ForeignKey("managed_repos.id", ondelete="RESTRICT"), nullable=False, ) provider: Mapped[str | None] = mapped_column(String(40), nullable=True) forge_instance: Mapped[str | None] = mapped_column(String(255), nullable=True) forge_owner: Mapped[str | None] = mapped_column(String(255), nullable=True) forge_repository_id: Mapped[int | None] = mapped_column(BigInteger, nullable=True) verification_state: Mapped[str] = mapped_column( String(20), nullable=False, default="unverified", server_default="unverified" ) verified_at: Mapped[datetime | None] = mapped_column( DateTime(timezone=True), nullable=True ) verified_by: Mapped[str | None] = mapped_column(String(160), nullable=True) verification_evidence: Mapped[dict] = mapped_column( JSONB, nullable=False, default=dict, server_default="{}" ) repo: Mapped["ManagedRepo"] = relationship( # noqa: F821 "ManagedRepo", back_populates="forge_identity", lazy="selectin" ) rename_operations: Mapped[list["RepositoryRenameOperation"]] = relationship( "RepositoryRenameOperation", back_populates="forge_identity", lazy="selectin", viewonly=True, ) class RepositoryRenameOperation(Base, TimestampMixin): """Append-preserving journal for one phased canonical-coordinate change.""" __tablename__ = "repository_rename_operations" __table_args__ = ( CheckConstraint( "phase IN ('draft', 'preflighted', 'forge-renamed', " "'statehub-rebound', 'source-synced', 'consumers-verified', " "'completed', 'rollback-preflight', 'rolled-back')", name="ck_repository_rename_phase", ), CheckConstraint( "old_slug != new_slug", name="ck_repository_rename_distinct_slugs" ), CheckConstraint( "expected_forge_repository_id > 0", name="ck_repository_rename_forge_id_positive", ), CheckConstraint( "forge_identity_state = 'verified'", name="ck_repository_rename_verified_identity", ), CheckConstraint( "phase = 'draft' OR preflighted_at IS NOT NULL", name="ck_repository_rename_preflight_timestamp", ), CheckConstraint( "phase != 'completed' OR completed_at IS NOT NULL", name="ck_repository_rename_completed_timestamp", ), CheckConstraint( "phase != 'rolled-back' OR rolled_back_at IS NOT NULL", name="ck_repository_rename_rollback_timestamp", ), Index( "uq_repository_rename_active_repo", "repo_id", unique=True, postgresql_where=text("phase NOT IN ('completed', 'rolled-back')"), ), Index( "uq_repository_rename_active_new_slug", "new_slug", unique=True, postgresql_where=text("phase NOT IN ('completed', 'rolled-back')"), ), Index( "ix_repository_rename_phase", "phase" ), ForeignKeyConstraint( ["forge_identity_id", "repo_id", "forge_identity_state"], [ "repository_forge_identities.id", "repository_forge_identities.repo_id", "repository_forge_identities.verification_state", ], name="fk_repository_rename_verified_identity", ondelete="RESTRICT", ), ) id: Mapped[uuid.UUID] = mapped_column( UUID(as_uuid=True), primary_key=True, default=new_uuid ) repo_id: Mapped[uuid.UUID] = mapped_column( UUID(as_uuid=True), ForeignKey("managed_repos.id", ondelete="RESTRICT"), nullable=False, index=True, ) forge_identity_id: Mapped[uuid.UUID] = mapped_column( UUID(as_uuid=True), nullable=False ) forge_identity_state: Mapped[str] = mapped_column( String(20), nullable=False, default="verified", server_default="verified" ) expected_provider: Mapped[str] = mapped_column(String(40), nullable=False) expected_forge_instance: Mapped[str] = mapped_column(String(255), nullable=False) expected_forge_owner: Mapped[str] = mapped_column(String(255), nullable=False) expected_forge_repository_id: Mapped[int] = mapped_column( BigInteger, nullable=False ) expected_source_commit: Mapped[str] = mapped_column(String(64), nullable=False) expected_default_branch: Mapped[str] = mapped_column(String(255), nullable=False) old_slug: Mapped[str] = mapped_column(String(100), nullable=False, index=True) new_slug: Mapped[str] = mapped_column(String(100), nullable=False, index=True) old_coordinates: Mapped[dict] = mapped_column(JSONB, nullable=False) new_coordinates: Mapped[dict] = mapped_column(JSONB, nullable=False) phase: Mapped[str] = mapped_column( String(32), nullable=False, default="draft", server_default="draft" ) actor: Mapped[str] = mapped_column(String(160), nullable=False) phase_changed_at: Mapped[datetime] = mapped_column( DateTime(timezone=True), nullable=False ) preflighted_at: Mapped[datetime | None] = mapped_column( DateTime(timezone=True), nullable=True ) preflight_expires_at: Mapped[datetime | None] = mapped_column( DateTime(timezone=True), nullable=True ) completed_at: Mapped[datetime | None] = mapped_column( DateTime(timezone=True), nullable=True ) rolled_back_at: Mapped[datetime | None] = mapped_column( DateTime(timezone=True), nullable=True ) evidence: Mapped[dict] = mapped_column( JSONB, nullable=False, default=dict, server_default="{}" ) error_code: Mapped[str | None] = mapped_column(String(80), nullable=True) error_message: Mapped[str | None] = mapped_column(Text, nullable=True) error_details: Mapped[dict | None] = mapped_column(JSONB, nullable=True) error_at: Mapped[datetime | None] = mapped_column( DateTime(timezone=True), nullable=True ) repo: Mapped["ManagedRepo"] = relationship( # noqa: F821 "ManagedRepo", back_populates="rename_operations", lazy="selectin" ) forge_identity: Mapped[RepositoryForgeIdentity] = relationship( "RepositoryForgeIdentity", back_populates="rename_operations", lazy="selectin", viewonly=True, ) slugs: Mapped[list["RepositorySlug"]] = relationship( "RepositorySlug", back_populates="source_operation", lazy="selectin" ) class RepositorySlug(Base, TimestampMixin): """Global uniqueness boundary for current and protected prior slugs.""" __tablename__ = "repository_slugs" __table_args__ = ( UniqueConstraint("slug", name="uq_repository_slugs_slug"), CheckConstraint( "kind IN ('canonical', 'alias')", name="ck_repository_slug_kind" ), CheckConstraint( "kind != 'alias' OR protected", name="ck_repository_slug_alias_protected", ), Index( "uq_repository_slugs_one_canonical", "repo_id", unique=True, postgresql_where=text("kind = 'canonical'"), ), Index("ix_repository_slugs_repo_kind", "repo_id", "kind"), ) id: Mapped[uuid.UUID] = mapped_column( UUID(as_uuid=True), primary_key=True, default=new_uuid ) repo_id: Mapped[uuid.UUID] = mapped_column( UUID(as_uuid=True), ForeignKey("managed_repos.id", ondelete="RESTRICT"), nullable=False, ) slug: Mapped[str] = mapped_column(String(100), nullable=False) kind: Mapped[str] = mapped_column(String(16), nullable=False) protected: Mapped[bool] = mapped_column( Boolean, nullable=False, default=True, server_default="true" ) source_operation_id: Mapped[uuid.UUID | None] = mapped_column( UUID(as_uuid=True), ForeignKey("repository_rename_operations.id", ondelete="RESTRICT"), nullable=True, index=True, ) repo: Mapped["ManagedRepo"] = relationship( # noqa: F821 "ManagedRepo", back_populates="slug_records", lazy="selectin" ) source_operation: Mapped[RepositoryRenameOperation | None] = relationship( "RepositoryRenameOperation", back_populates="slugs", lazy="selectin" )