import uuid from datetime import datetime from sqlalchemy import ( Boolean, DateTime, ForeignKey, Index, String, Text, UniqueConstraint, ) from sqlalchemy.dialects.postgresql import JSONB, UUID from sqlalchemy.orm import Mapped, mapped_column, relationship from api.models.base import Base, TimestampMixin, new_uuid7 class ReviewContract(Base, TimestampMixin): """Replaceable projection of one authoritative review-contract revision.""" __tablename__ = "review_contracts" id: Mapped[uuid.UUID] = mapped_column( UUID(as_uuid=True), primary_key=True, default=new_uuid7 ) contract_key: Mapped[str] = mapped_column(String(160), nullable=False, index=True) schema_version: Mapped[str] = mapped_column(String(40), nullable=False) contract_digest: Mapped[str] = mapped_column( String(64), nullable=False, unique=True ) source_repo: Mapped[str] = mapped_column(String(100), nullable=False) source_path: Mapped[str] = mapped_column(Text, nullable=False) source_revision: Mapped[str] = mapped_column(String(64), nullable=False) document: Mapped[dict] = mapped_column(JSONB, nullable=False) active: Mapped[bool] = mapped_column( Boolean, nullable=False, default=True, index=True ) required_for_decision: Mapped[bool] = mapped_column( Boolean, nullable=False, default=False ) decision_id: Mapped[uuid.UUID | None] = mapped_column( UUID(as_uuid=True), ForeignKey("decisions.id", ondelete="RESTRICT"), nullable=True, index=True, ) workplan_id: Mapped[uuid.UUID | None] = mapped_column( UUID(as_uuid=True), ForeignKey("workplans.id", ondelete="RESTRICT", onupdate="CASCADE"), nullable=True, index=True, ) task_id: Mapped[uuid.UUID | None] = mapped_column( UUID(as_uuid=True), ForeignKey("tasks.id", ondelete="RESTRICT", onupdate="CASCADE"), nullable=True, index=True, ) projected_at: Mapped[datetime] = mapped_column( DateTime(timezone=True), nullable=False ) receipts: Mapped[list["ReviewReceipt"]] = relationship( "ReviewReceipt", back_populates="contract", lazy="selectin" ) __table_args__ = ( UniqueConstraint( "contract_key", "source_repo", "source_path", "source_revision", name="uq_review_contract_source_revision", ), Index("ix_review_contract_key_active", "contract_key", "active"), ) class ReviewReceipt(Base, TimestampMixin): """Immutable evidence submitted against an exact contract digest.""" __tablename__ = "review_receipts" id: Mapped[uuid.UUID] = mapped_column( UUID(as_uuid=True), primary_key=True, default=new_uuid7 ) contract_id: Mapped[uuid.UUID] = mapped_column( UUID(as_uuid=True), ForeignKey("review_contracts.id", ondelete="RESTRICT"), nullable=False, index=True, ) owner_id: Mapped[str] = mapped_column(String(160), nullable=False, index=True) actor: Mapped[str] = mapped_column(String(160), nullable=False) disposition: Mapped[str] = mapped_column(String(32), nullable=False) contract_digest: Mapped[str] = mapped_column(String(64), nullable=False, index=True) receipt_digest: Mapped[str] = mapped_column(String(64), nullable=False, unique=True) artifact_hashes: Mapped[dict] = mapped_column(JSONB, nullable=False, default=dict) assertion_ids: Mapped[list] = mapped_column(JSONB, nullable=False, default=list) checks: Mapped[list] = mapped_column(JSONB, nullable=False, default=list) note: Mapped[str | None] = mapped_column(Text, nullable=True) source_repo: Mapped[str] = mapped_column(String(100), nullable=False) source_path: Mapped[str] = mapped_column(Text, nullable=False) source_revision: Mapped[str] = mapped_column(String(64), nullable=False) submitted_at: Mapped[datetime] = mapped_column( DateTime(timezone=True), nullable=False, index=True ) document: Mapped[dict] = mapped_column(JSONB, nullable=False) contract: Mapped[ReviewContract] = relationship( "ReviewContract", back_populates="receipts" ) __table_args__ = ( UniqueConstraint( "source_repo", "source_path", "source_revision", name="uq_review_receipt_source_revision", ), Index( "ix_review_receipt_contract_owner_time", "contract_id", "owner_id", "submitted_at", ), )