from __future__ import annotations from datetime import datetime from typing import Any import uuid from sqlalchemy import ( DateTime, ForeignKey, Index, Integer, JSON, String, UniqueConstraint, ) from sqlalchemy.orm import Mapped, mapped_column from govoplan_core.db.base import Base, TimestampMixin def new_uuid() -> str: return str(uuid.uuid4()) class ApprovalRequestRevision(Base, TimestampMixin): __tablename__ = "approval_request_revisions" __table_args__ = ( UniqueConstraint( "tenant_id", "request_id", "revision", name="uq_approval_request_revision" ), Index( "ix_approval_request_current", "tenant_id", "request_id", "superseded_at" ), Index( "ix_approval_request_subject", "tenant_id", "subject_module", "subject_type", "subject_id", "state", ), ) id: Mapped[str] = mapped_column(String(36), primary_key=True, default=new_uuid) tenant_id: Mapped[str] = mapped_column(String(36), nullable=False, index=True) request_id: Mapped[str] = mapped_column(String(36), nullable=False, index=True) revision: Mapped[int] = mapped_column(Integer, nullable=False) previous_revision_id: Mapped[str | None] = mapped_column( ForeignKey("approval_request_revisions.id", ondelete="RESTRICT"), nullable=True, index=True, ) state: Mapped[str] = mapped_column(String(30), nullable=False, index=True) current_step_key: Mapped[str | None] = mapped_column( String(120), nullable=True, index=True ) subject_module: Mapped[str] = mapped_column(String(120), nullable=False, index=True) subject_type: Mapped[str] = mapped_column(String(120), nullable=False, index=True) subject_id: Mapped[str] = mapped_column(String(255), nullable=False, index=True) subject_version: Mapped[str | None] = mapped_column( String(120), nullable=True, index=True ) subject_digest: Mapped[str] = mapped_column(String(64), nullable=False, index=True) recorded_at: Mapped[datetime] = mapped_column( DateTime(timezone=True), nullable=False, index=True ) superseded_at: Mapped[datetime | None] = mapped_column( DateTime(timezone=True), nullable=True, index=True ) payload: Mapped[dict[str, Any]] = mapped_column(JSON, nullable=False) actor_id: Mapped[str | None] = mapped_column(String(255), nullable=True, index=True) class ApprovalDecisionRecord(Base, TimestampMixin): __tablename__ = "approval_decision_records" __table_args__ = ( UniqueConstraint( "tenant_id", "request_id", "step_key", "effective_actor_id", name="uq_approval_step_actor", ), UniqueConstraint( "tenant_id", "request_id", "idempotency_key", name="uq_approval_decision_replay", ), Index("ix_approval_decision_history", "tenant_id", "request_id", "recorded_at"), ) id: Mapped[str] = mapped_column(String(36), primary_key=True, default=new_uuid) tenant_id: Mapped[str] = mapped_column(String(36), nullable=False, index=True) request_id: Mapped[str] = mapped_column(String(36), nullable=False, index=True) request_revision: Mapped[int] = mapped_column(Integer, nullable=False) step_key: Mapped[str] = mapped_column(String(120), nullable=False, index=True) outcome: Mapped[str] = mapped_column(String(20), nullable=False, index=True) reason: Mapped[str] = mapped_column(String(4000), nullable=False) actor_id: Mapped[str] = mapped_column(String(255), nullable=False, index=True) effective_actor_id: Mapped[str] = mapped_column( String(255), nullable=False, index=True ) delegation_id: Mapped[str | None] = mapped_column(String(255), nullable=True) authority_provenance: Mapped[dict[str, Any]] = mapped_column( JSON, nullable=False, default=dict ) signature_ref: Mapped[dict[str, Any] | None] = mapped_column(JSON, nullable=True) recorded_at: Mapped[datetime] = mapped_column( DateTime(timezone=True), nullable=False, index=True ) idempotency_key: Mapped[str] = mapped_column(String(160), nullable=False) receipt_sha256: Mapped[str] = mapped_column(String(64), nullable=False, index=True) class ApprovalLifecycleEvent(Base, TimestampMixin): __tablename__ = "approval_lifecycle_events" __table_args__ = ( UniqueConstraint( "tenant_id", "request_id", "sequence", name="uq_approval_event_sequence" ), ) id: Mapped[str] = mapped_column(String(36), primary_key=True, default=new_uuid) tenant_id: Mapped[str] = mapped_column(String(36), nullable=False, index=True) request_id: Mapped[str] = mapped_column(String(36), nullable=False, index=True) sequence: Mapped[int] = mapped_column(Integer, nullable=False) event_type: Mapped[str] = mapped_column(String(60), nullable=False, index=True) recorded_at: Mapped[datetime] = mapped_column( DateTime(timezone=True), nullable=False, index=True ) actor_id: Mapped[str | None] = mapped_column(String(255), nullable=True) payload: Mapped[dict[str, Any]] = mapped_column(JSON, nullable=False, default=dict) class ApprovalReplay(Base, TimestampMixin): __tablename__ = "approval_replays" __table_args__ = ( UniqueConstraint( "tenant_id", "operation", "idempotency_key", name="uq_approval_replay" ), ) id: Mapped[str] = mapped_column(String(36), primary_key=True, default=new_uuid) tenant_id: Mapped[str] = mapped_column(String(36), nullable=False, index=True) operation: Mapped[str] = mapped_column(String(80), nullable=False, index=True) idempotency_key: Mapped[str] = mapped_column(String(160), nullable=False) request_sha256: Mapped[str] = mapped_column(String(64), nullable=False) response: Mapped[dict[str, Any]] = mapped_column(JSON, nullable=False) class ApprovalTemplateRevision(Base, TimestampMixin): __tablename__ = "approval_template_revisions" __table_args__ = ( UniqueConstraint( "tenant_id", "template_id", "revision", name="uq_approval_template_revision" ), UniqueConstraint( "tenant_id", "key", "revision", name="uq_approval_template_key_revision" ), Index( "ix_approval_template_current", "tenant_id", "template_id", "superseded_at" ), ) id: Mapped[str] = mapped_column(String(36), primary_key=True, default=new_uuid) tenant_id: Mapped[str] = mapped_column(String(36), nullable=False, index=True) template_id: Mapped[str] = mapped_column(String(36), nullable=False, index=True) key: Mapped[str] = mapped_column(String(120), nullable=False, index=True) revision: Mapped[int] = mapped_column(Integer, nullable=False) previous_revision_id: Mapped[str | None] = mapped_column( ForeignKey("approval_template_revisions.id", ondelete="RESTRICT"), nullable=True, index=True, ) state: Mapped[str] = mapped_column(String(30), nullable=False, index=True) content_sha256: Mapped[str] = mapped_column(String(64), nullable=False, index=True) recorded_at: Mapped[datetime] = mapped_column( DateTime(timezone=True), nullable=False, index=True ) superseded_at: Mapped[datetime | None] = mapped_column( DateTime(timezone=True), nullable=True, index=True ) payload: Mapped[dict[str, Any]] = mapped_column(JSON, nullable=False) actor_id: Mapped[str | None] = mapped_column(String(255), nullable=True, index=True) __all__ = [ "ApprovalDecisionRecord", "ApprovalLifecycleEvent", "ApprovalReplay", "ApprovalRequestRevision", "ApprovalTemplateRevision", ]