"""add multi-owner review contract projections and immutable receipts Revision ID: c9e5a1b3d7f2 Revises: b8d4f0a2c6e1 Create Date: 2026-08-22 """ import sqlalchemy as sa from alembic import op from sqlalchemy.dialects.postgresql import JSONB, UUID revision = "c9e5a1b3d7f2" down_revision = "b8d4f0a2c6e1" branch_labels = None depends_on = None def upgrade() -> None: op.create_table( "review_contracts", sa.Column("id", UUID(as_uuid=True), primary_key=True), sa.Column("contract_key", sa.String(160), nullable=False), sa.Column("schema_version", sa.String(40), nullable=False), sa.Column("contract_digest", sa.String(64), nullable=False, unique=True), sa.Column("source_repo", sa.String(100), nullable=False), sa.Column("source_path", sa.Text(), nullable=False), sa.Column("source_revision", sa.String(64), nullable=False), sa.Column("document", JSONB(), nullable=False), sa.Column("active", sa.Boolean(), nullable=False, server_default=sa.true()), sa.Column( "required_for_decision", sa.Boolean(), nullable=False, server_default=sa.false(), ), sa.Column( "decision_id", UUID(as_uuid=True), sa.ForeignKey("decisions.id", ondelete="RESTRICT"), nullable=True, ), sa.Column( "workplan_id", UUID(as_uuid=True), sa.ForeignKey("workplans.id", ondelete="RESTRICT", onupdate="CASCADE"), nullable=True, ), sa.Column( "task_id", UUID(as_uuid=True), sa.ForeignKey("tasks.id", ondelete="RESTRICT", onupdate="CASCADE"), nullable=True, ), sa.Column("projected_at", sa.DateTime(timezone=True), nullable=False), sa.Column( "created_at", sa.DateTime(timezone=True), server_default=sa.text("now()"), nullable=False, ), sa.Column( "updated_at", sa.DateTime(timezone=True), server_default=sa.text("now()"), nullable=False, ), sa.UniqueConstraint( "contract_key", "source_repo", "source_path", "source_revision", name="uq_review_contract_source_revision", ), ) op.create_index( "ix_review_contracts_contract_key", "review_contracts", ["contract_key"] ) op.create_index("ix_review_contracts_active", "review_contracts", ["active"]) op.create_index( "ix_review_contract_key_active", "review_contracts", ["contract_key", "active"] ) op.create_index( "ix_review_contracts_decision_id", "review_contracts", ["decision_id"] ) op.create_index( "ix_review_contracts_workplan_id", "review_contracts", ["workplan_id"] ) op.create_index("ix_review_contracts_task_id", "review_contracts", ["task_id"]) op.create_table( "review_receipts", sa.Column("id", UUID(as_uuid=True), primary_key=True), sa.Column( "contract_id", UUID(as_uuid=True), sa.ForeignKey("review_contracts.id", ondelete="RESTRICT"), nullable=False, ), sa.Column("owner_id", sa.String(160), nullable=False), sa.Column("actor", sa.String(160), nullable=False), sa.Column("disposition", sa.String(32), nullable=False), sa.Column("contract_digest", sa.String(64), nullable=False), sa.Column("receipt_digest", sa.String(64), nullable=False, unique=True), sa.Column("artifact_hashes", JSONB(), nullable=False), sa.Column("assertion_ids", JSONB(), nullable=False), sa.Column("checks", JSONB(), nullable=False), sa.Column("note", sa.Text(), nullable=True), sa.Column("source_repo", sa.String(100), nullable=False), sa.Column("source_path", sa.Text(), nullable=False), sa.Column("source_revision", sa.String(64), nullable=False), sa.Column("submitted_at", sa.DateTime(timezone=True), nullable=False), sa.Column("document", JSONB(), nullable=False), sa.Column( "created_at", sa.DateTime(timezone=True), server_default=sa.text("now()"), nullable=False, ), sa.Column( "updated_at", sa.DateTime(timezone=True), server_default=sa.text("now()"), nullable=False, ), sa.UniqueConstraint( "source_repo", "source_path", "source_revision", name="uq_review_receipt_source_revision", ), ) op.create_index( "ix_review_receipts_contract_id", "review_receipts", ["contract_id"] ) op.create_index("ix_review_receipts_owner_id", "review_receipts", ["owner_id"]) op.create_index( "ix_review_receipts_contract_digest", "review_receipts", ["contract_digest"] ) op.create_index( "ix_review_receipts_submitted_at", "review_receipts", ["submitted_at"] ) op.create_index( "ix_review_receipt_contract_owner_time", "review_receipts", ["contract_id", "owner_id", "submitted_at"], ) def downgrade() -> None: op.drop_table("review_receipts") op.drop_table("review_contracts")