from __future__ import annotations import uuid from datetime import datetime from typing import Any from sqlalchemy import ( Boolean, DateTime, ForeignKey, Index, Integer, JSON, String, Text, UniqueConstraint, ) from sqlalchemy.orm import Mapped, mapped_column, relationship from govoplan_core.db.base import Base, TimestampMixin def new_uuid() -> str: return str(uuid.uuid4()) class WorkflowDefinition(Base, TimestampMixin): __tablename__ = "workflow_definitions" __table_args__ = ( UniqueConstraint( "scope_key", "definition_key", name="uq_workflow_definition_key", ), Index("ix_workflow_definitions_tenant_status", "tenant_id", "status"), Index("ix_workflow_definitions_tenant_updated", "tenant_id", "updated_at"), Index( "uq_workflow_standard_origin_scope", "scope_key", "standard_origin_module_id", "standard_definition_key", unique=True, ), ) id: Mapped[str] = mapped_column(String(36), primary_key=True, default=new_uuid) tenant_id: Mapped[str | None] = mapped_column( String(36), nullable=True, index=True, ) scope_type: Mapped[str] = mapped_column( String(20), default="tenant", nullable=False, index=True, ) scope_id: Mapped[str | None] = mapped_column( String(36), nullable=True, index=True, ) scope_key: Mapped[str] = mapped_column( String(80), nullable=False, index=True, ) definition_kind: Mapped[str] = mapped_column( String(20), default="flow", nullable=False, index=True, ) inherit_to_lower_scopes: Mapped[bool] = mapped_column( Boolean, default=False, nullable=False, ) allow_start: Mapped[bool] = mapped_column( Boolean, default=True, nullable=False, ) allow_reuse: Mapped[bool] = mapped_column( Boolean, default=False, nullable=False, ) allow_automation: Mapped[bool] = mapped_column( Boolean, default=False, nullable=False, ) derived_from_definition_id: Mapped[str | None] = mapped_column( String(36), nullable=True, index=True, ) derived_from_revision: Mapped[int | None] = mapped_column( Integer, nullable=True, ) derived_from_hash: Mapped[str | None] = mapped_column( String(64), nullable=True, ) derivation_provenance: Mapped[dict[str, Any]] = mapped_column( JSON, default=dict, nullable=False, ) standard_origin_module_id: Mapped[str | None] = mapped_column( String(120), nullable=True, index=True, ) standard_definition_key: Mapped[str | None] = mapped_column( String(120), nullable=True, index=True, ) standard_origin_module_version: Mapped[str | None] = mapped_column( String(40), nullable=True, ) standard_contribution_schema_version: Mapped[str | None] = mapped_column( String(40), nullable=True, ) standard_contribution_hash: Mapped[str | None] = mapped_column( String(64), nullable=True, index=True, ) standard_reconciled_at: Mapped[datetime | None] = mapped_column( DateTime(timezone=True), nullable=True, ) definition_key: Mapped[str] = mapped_column(String(120), nullable=False) name: Mapped[str] = mapped_column(String(300), nullable=False) description: Mapped[str | None] = mapped_column(Text) status: Mapped[str] = mapped_column( String(32), default="draft", nullable=False, index=True, ) current_revision: Mapped[int] = mapped_column(Integer, default=1, nullable=False) active_revision: Mapped[int | None] = mapped_column(Integer, nullable=True) metadata_: Mapped[dict[str, Any]] = mapped_column( "metadata", JSON, default=dict, nullable=False, ) created_by: Mapped[str | None] = mapped_column( String(255), nullable=True, index=True, ) updated_by: Mapped[str | None] = mapped_column( String(255), nullable=True, index=True, ) deleted_at: Mapped[datetime | None] = mapped_column( DateTime(timezone=True), nullable=True, index=True, ) revisions: Mapped[list["WorkflowDefinitionRevision"]] = relationship( back_populates="definition", cascade="all, delete-orphan", order_by="WorkflowDefinitionRevision.revision", ) instances: Mapped[list["WorkflowInstance"]] = relationship( back_populates="definition", cascade="all, delete-orphan", order_by="WorkflowInstance.created_at", ) class WorkflowDefinitionRevision(Base, TimestampMixin): __tablename__ = "workflow_definition_revisions" __table_args__ = ( UniqueConstraint( "definition_id", "revision", name="uq_workflow_definition_revision", ), Index( "ix_workflow_definition_revisions_tenant_definition", "tenant_id", "definition_id", ), Index( "ix_workflow_definition_revisions_content_hash", "tenant_id", "content_hash", ), Index( "ix_workflow_definition_revisions_contribution_hash", "contribution_hash", ), ) id: Mapped[str] = mapped_column(String(36), primary_key=True, default=new_uuid) tenant_id: Mapped[str | None] = mapped_column( String(36), nullable=True, index=True, ) definition_id: Mapped[str] = mapped_column( ForeignKey("workflow_definitions.id", ondelete="CASCADE"), nullable=False, index=True, ) revision: Mapped[int] = mapped_column(Integer, nullable=False) schema_version: Mapped[int] = mapped_column(Integer, default=1, nullable=False) graph: Mapped[dict[str, Any]] = mapped_column(JSON, nullable=False) content_hash: Mapped[str] = mapped_column(String(64), nullable=False) library_id: Mapped[str] = mapped_column(String(100), nullable=False) library_version: Mapped[str] = mapped_column(String(40), nullable=False) execution_mode: Mapped[str] = mapped_column( String(20), default="hybrid", nullable=False, ) view_id: Mapped[str | None] = mapped_column(String(36), nullable=True) view_revision_id: Mapped[str | None] = mapped_column( String(36), nullable=True, ) bpmn_xml: Mapped[str | None] = mapped_column(Text, nullable=True) bpmn_hash: Mapped[str | None] = mapped_column( String(64), nullable=True, index=True, ) bpmn_adapter_id: Mapped[str | None] = mapped_column( String(120), nullable=True, ) bpmn_adapter_version: Mapped[str | None] = mapped_column( String(40), nullable=True, ) bpmn_runtime_kind: Mapped[str | None] = mapped_column( String(20), nullable=True, ) bpmn_executable: Mapped[bool | None] = mapped_column( Boolean, nullable=True, ) contribution_origin_module_version: Mapped[str | None] = mapped_column( String(40), nullable=True, ) contribution_schema_version: Mapped[str | None] = mapped_column( String(40), nullable=True, ) contribution_hash: Mapped[str | None] = mapped_column( String(64), nullable=True, ) contribution_metadata: Mapped[dict[str, Any] | None] = mapped_column( JSON, nullable=True, ) created_by: Mapped[str | None] = mapped_column( String(255), nullable=True, index=True, ) definition: Mapped[WorkflowDefinition] = relationship(back_populates="revisions") class WorkflowInstance(Base, TimestampMixin): __tablename__ = "workflow_instances" __table_args__ = ( UniqueConstraint( "tenant_id", "definition_id", "idempotency_key", name="uq_workflow_instance_idempotency", ), Index( "ix_workflow_instances_tenant_status", "tenant_id", "status", ), Index( "ix_workflow_instances_reconcile", "status", "updated_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) definition_id: Mapped[str] = mapped_column( ForeignKey("workflow_definitions.id", ondelete="CASCADE"), nullable=False, index=True, ) definition_revision_id: Mapped[str] = mapped_column( ForeignKey("workflow_definition_revisions.id", ondelete="RESTRICT"), nullable=False, index=True, ) status: Mapped[str] = mapped_column( String(30), default="running", nullable=False, index=True, ) start_origin: Mapped[str] = mapped_column( String(30), default="user", nullable=False, index=True, ) idempotency_key: Mapped[str] = mapped_column( String(255), nullable=False, index=True, ) correlation_id: Mapped[str | None] = mapped_column( String(128), nullable=True, index=True, ) current_step_id: Mapped[str | None] = mapped_column( String(36), nullable=True, index=True, ) input_: Mapped[dict[str, Any]] = mapped_column( "input", JSON, default=dict, nullable=False, ) context_: Mapped[dict[str, Any]] = mapped_column( "context", JSON, default=dict, nullable=False, ) output_: Mapped[dict[str, Any]] = mapped_column( "output", JSON, default=dict, nullable=False, ) authorization_: Mapped[dict[str, Any]] = mapped_column( "authorization", JSON, default=dict, nullable=False, ) started_at: Mapped[datetime] = mapped_column( DateTime(timezone=True), nullable=False, ) finished_at: Mapped[datetime | None] = mapped_column( DateTime(timezone=True), nullable=True, ) cancellation_requested_at: Mapped[datetime | None] = mapped_column( DateTime(timezone=True), nullable=True, ) error: Mapped[str | None] = mapped_column(Text, nullable=True) created_by: Mapped[str | None] = mapped_column( String(255), nullable=True, index=True, ) definition: Mapped[WorkflowDefinition] = relationship(back_populates="instances") steps: Mapped[list["WorkflowInstanceStep"]] = relationship( back_populates="instance", cascade="all, delete-orphan", order_by="WorkflowInstanceStep.sequence", ) events: Mapped[list["WorkflowInstanceEvent"]] = relationship( back_populates="instance", cascade="all, delete-orphan", order_by="WorkflowInstanceEvent.sequence", ) class WorkflowInstanceStep(Base, TimestampMixin): __tablename__ = "workflow_instance_steps" __table_args__ = ( UniqueConstraint( "instance_id", "sequence", name="uq_workflow_instance_step_sequence", ), Index( "ix_workflow_instance_steps_tenant_status", "tenant_id", "status", ), Index( "ix_workflow_instance_steps_work_assignment", "tenant_id", "status", "work_assignment_kind", "work_assignment_id", ), ) 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) instance_id: Mapped[str] = mapped_column( ForeignKey("workflow_instances.id", ondelete="CASCADE"), nullable=False, index=True, ) sequence: Mapped[int] = mapped_column(Integer, nullable=False) node_id: Mapped[str] = mapped_column(String(120), nullable=False, index=True) node_type: Mapped[str] = mapped_column(String(120), nullable=False, index=True) status: Mapped[str] = mapped_column( String(30), nullable=False, index=True, ) attempt: Mapped[int] = mapped_column(Integer, default=1, nullable=False) idempotency_key: Mapped[str] = mapped_column( String(255), nullable=False, ) input_: Mapped[dict[str, Any]] = mapped_column( "input", JSON, default=dict, nullable=False, ) output_: Mapped[dict[str, Any]] = mapped_column( "output", JSON, default=dict, nullable=False, ) handoff: Mapped[dict[str, Any]] = mapped_column( JSON, default=dict, nullable=False, ) external_ref: Mapped[str | None] = mapped_column( String(500), nullable=True, index=True, ) started_at: Mapped[datetime | None] = mapped_column( DateTime(timezone=True), nullable=True, ) finished_at: Mapped[datetime | None] = mapped_column( DateTime(timezone=True), nullable=True, ) error: Mapped[str | None] = mapped_column(Text, nullable=True) completed_by: Mapped[str | None] = mapped_column( String(255), nullable=True, ) work_assignment_kind: Mapped[str | None] = mapped_column( String(40), nullable=True, index=True, ) work_assignment_id: Mapped[str | None] = mapped_column( String(255), nullable=True, index=True, ) work_assignment_label: Mapped[str | None] = mapped_column( String(500), nullable=True, ) work_due_at: Mapped[datetime | None] = mapped_column( DateTime(timezone=True), nullable=True, index=True, ) instance: Mapped[WorkflowInstance] = relationship(back_populates="steps") class WorkflowInstanceEvent(Base): __tablename__ = "workflow_instance_events" __table_args__ = ( UniqueConstraint( "instance_id", "sequence", name="uq_workflow_instance_event_sequence", ), Index( "ix_workflow_instance_events_tenant_created", "tenant_id", "created_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) instance_id: Mapped[str] = mapped_column( ForeignKey("workflow_instances.id", ondelete="CASCADE"), nullable=False, index=True, ) step_id: Mapped[str | None] = mapped_column( String(36), nullable=True, index=True, ) sequence: Mapped[int] = mapped_column(Integer, nullable=False) kind: Mapped[str] = mapped_column(String(120), nullable=False, index=True) actor_id: Mapped[str | None] = mapped_column( String(255), nullable=True, index=True, ) payload: Mapped[dict[str, Any]] = mapped_column( JSON, default=dict, nullable=False, ) created_at: Mapped[datetime] = mapped_column( DateTime(timezone=True), nullable=False, ) instance: Mapped[WorkflowInstance] = relationship(back_populates="events") class WorkflowTrigger(Base, TimestampMixin): __tablename__ = "workflow_triggers" __table_args__ = ( UniqueConstraint( "definition_id", "node_id", name="uq_workflow_trigger_definition_node", ), Index("ix_workflow_triggers_due", "status", "next_fire_at"), Index( "ix_workflow_triggers_event", "tenant_id", "status", "event_type", ), ) 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) definition_id: Mapped[str] = mapped_column( ForeignKey("workflow_definitions.id", ondelete="CASCADE"), nullable=False, index=True, ) definition_revision_id: Mapped[str] = mapped_column( ForeignKey("workflow_definition_revisions.id", ondelete="RESTRICT"), nullable=False, index=True, ) node_id: Mapped[str] = mapped_column(String(120), nullable=False) kind: Mapped[str] = mapped_column(String(30), nullable=False, index=True) status: Mapped[str] = mapped_column( String(30), default="disabled", nullable=False, index=True ) config_: Mapped[dict[str, Any]] = mapped_column( "config", JSON, default=dict, nullable=False ) event_type: Mapped[str | None] = mapped_column( String(120), nullable=True, index=True ) next_fire_at: Mapped[datetime | None] = mapped_column( DateTime(timezone=True), nullable=True, index=True ) last_fire_at: Mapped[datetime | None] = mapped_column( DateTime(timezone=True), nullable=True ) last_status: Mapped[str | None] = mapped_column(String(30), nullable=True) last_error: Mapped[str | None] = mapped_column(Text, nullable=True) authorization_subject_kind: Mapped[str] = mapped_column(String(30), nullable=False) authorization_account_id: Mapped[str | None] = mapped_column( String(36), nullable=True ) authorization_membership_id: Mapped[str | None] = mapped_column( String(36), nullable=True ) authorization_service_account_id: Mapped[str | None] = mapped_column( String(36), nullable=True ) authorization_ref: Mapped[str] = mapped_column( String(255), nullable=False, unique=True ) grant_scopes: Mapped[list[str]] = mapped_column(JSON, default=list, nullable=False) created_by: Mapped[str | None] = mapped_column( String(255), nullable=True, index=True ) updated_by: Mapped[str | None] = mapped_column( String(255), nullable=True, index=True ) class WorkflowTriggerDelivery(Base, TimestampMixin): __tablename__ = "workflow_trigger_deliveries" __table_args__ = ( UniqueConstraint( "trigger_id", "source_key", name="uq_workflow_trigger_delivery_source", ), Index( "ix_workflow_trigger_deliveries_queue", "status", "created_at", ), Index( "ix_workflow_trigger_deliveries_tenant_trigger", "tenant_id", "trigger_id", ), ) 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) trigger_id: Mapped[str] = mapped_column( ForeignKey("workflow_triggers.id", ondelete="CASCADE"), nullable=False, index=True, ) definition_id: Mapped[str] = mapped_column( ForeignKey("workflow_definitions.id", ondelete="CASCADE"), nullable=False, index=True, ) definition_revision_id: Mapped[str] = mapped_column( ForeignKey("workflow_definition_revisions.id", ondelete="RESTRICT"), nullable=False, index=True, ) source_key: Mapped[str] = mapped_column(String(255), nullable=False) invocation_kind: Mapped[str] = mapped_column(String(30), nullable=False) status: Mapped[str] = mapped_column( String(30), default="queued", nullable=False, index=True ) scheduled_for: Mapped[datetime | None] = mapped_column( DateTime(timezone=True), nullable=True ) event_: Mapped[dict[str, Any] | None] = mapped_column("event", JSON, nullable=True) instance_id: Mapped[str | None] = mapped_column( ForeignKey("workflow_instances.id", ondelete="SET NULL"), nullable=True, index=True, ) attempts: Mapped[int] = mapped_column(Integer, default=0, nullable=False) error: Mapped[str | None] = mapped_column(Text, nullable=True) class WorkflowWaitState(Base, TimestampMixin): __tablename__ = "workflow_wait_states" __table_args__ = ( UniqueConstraint("step_id", name="uq_workflow_wait_state_step"), Index("ix_workflow_wait_states_due", "status", "due_at"), Index( "ix_workflow_wait_states_event", "tenant_id", "status", "event_type", ), ) 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) instance_id: Mapped[str] = mapped_column( ForeignKey("workflow_instances.id", ondelete="CASCADE"), nullable=False, index=True, ) step_id: Mapped[str] = mapped_column( ForeignKey("workflow_instance_steps.id", ondelete="CASCADE"), nullable=False, index=True, ) mode: Mapped[str] = mapped_column(String(30), nullable=False, index=True) status: Mapped[str] = mapped_column( String(30), default="waiting", nullable=False, index=True ) due_at: Mapped[datetime | None] = mapped_column( DateTime(timezone=True), nullable=True, index=True ) event_type: Mapped[str | None] = mapped_column( String(120), nullable=True, index=True ) config_: Mapped[dict[str, Any]] = mapped_column( "config", JSON, default=dict, nullable=False ) source_event_id: Mapped[str | None] = mapped_column( String(128), nullable=True, index=True ) event_: Mapped[dict[str, Any] | None] = mapped_column("event", JSON, nullable=True) error: Mapped[str | None] = mapped_column(Text, nullable=True) revision: Mapped[int] = mapped_column(Integer, default=1, nullable=False) __all__ = [ "WorkflowDefinition", "WorkflowDefinitionRevision", "WorkflowInstance", "WorkflowInstanceEvent", "WorkflowInstanceStep", "WorkflowTrigger", "WorkflowTriggerDelivery", "WorkflowWaitState", "new_uuid", ]