state-hub/api/models/repository_rename.py
tegwick 8988a093f2
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 1s
Build and Publish Multi-Context Image / build-and-push (push) Successful in 24s
feat: persist repository rename identity
Assistant: codex
Assistant-Model: gpt-5.6-sol
Assistant-Session: 01a049a4-ee9f-78e1-9d66-2cb0f9bea3e3
2026-08-28 22:08:21 +02:00

292 lines
11 KiB
Python

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"
)