from __future__ import annotations import math from datetime import datetime from typing import Any, Literal from pydantic import BaseModel, Field, field_validator, model_validator WorkflowDefinitionStatus = Literal["draft", "active", "archived"] DefinitionScopeType = Literal["system", "tenant", "group", "user"] DefinitionKind = Literal["flow", "template"] BpmnRuntimeKind = Literal["model_only", "native_graph", "external"] WorkflowExecutionMode = Literal["guided", "automated", "hybrid"] WorkflowStartOrigin = Literal[ "user", "api", "schedule", "event", "parent_workflow", "dependency", "retry", "replay", "backfill", ] BpmnSupportLevel = Literal[ "interchange_only", "native_mapping", "native_execution", ] class WorkflowPosition(BaseModel): x: float = 0 y: float = 0 @field_validator("x", "y") @classmethod def finite_coordinate(cls, value: float) -> float: if not math.isfinite(value): raise ValueError("Graph coordinates must be finite.") return value class WorkflowSize(BaseModel): width: float = Field(default=100, gt=0, le=10_000) height: float = Field(default=80, gt=0, le=10_000) class WorkflowNode(BaseModel): id: str = Field(min_length=1, max_length=120) type: str = Field(min_length=1, max_length=120) label: str = Field(default="", max_length=300) position: WorkflowPosition = Field(default_factory=WorkflowPosition) size: WorkflowSize | None = None parent_id: str | None = Field(default=None, max_length=120) process_id: str | None = Field(default=None, max_length=120) config: dict[str, Any] = Field(default_factory=dict) class WorkflowWaypoint(BaseModel): x: float y: float @field_validator("x", "y") @classmethod def finite_coordinate(cls, value: float) -> float: if not math.isfinite(value): raise ValueError("Edge coordinates must be finite.") return value class WorkflowEdge(BaseModel): id: str = Field(min_length=1, max_length=120) type: Literal[ "bpmn.sequenceFlow", "bpmn.messageFlow", "bpmn.association", "bpmn.dataInputAssociation", "bpmn.dataOutputAssociation", "bpmn.conversationLink", ] = "bpmn.sequenceFlow" label: str = Field(default="", max_length=300) source: str = Field(min_length=1, max_length=120) target: str = Field(min_length=1, max_length=120) source_port: str = Field(default="output", min_length=1, max_length=120) target_port: str = Field(default="input", min_length=1, max_length=120) config: dict[str, Any] = Field(default_factory=dict) waypoints: list[WorkflowWaypoint] = Field(default_factory=list, max_length=500) class WorkflowGraph(BaseModel): schema_version: Literal[1] = 1 nodes: list[WorkflowNode] = Field(default_factory=list, max_length=150) edges: list[WorkflowEdge] = Field(default_factory=list, max_length=300) metadata: dict[str, Any] = Field(default_factory=dict) class WorkflowGraphValidationRequest(BaseModel): graph: WorkflowGraph class WorkflowDiagnosticResponse(BaseModel): severity: Literal["error", "warning"] code: str message: str node_id: str | None = None field: str | None = None class WorkflowGraphValidationResponse(BaseModel): valid: bool diagnostics: list[WorkflowDiagnosticResponse] class WorkflowPortResponse(BaseModel): id: str label: str required: bool multiple: bool minimum_connections: int class WorkflowConfigFieldResponse(BaseModel): id: str label: str kind: str required: bool description: str | None options: list[tuple[str, str]] class WorkflowNodeTypeResponse(BaseModel): type: str category: str category_label: str label: str description: str icon: str input_ports: list[WorkflowPortResponse] output_ports: list[WorkflowPortResponse] config_fields: list[WorkflowConfigFieldResponse] default_config: dict[str, Any] metadata: dict[str, Any] = Field(default_factory=dict) class WorkflowNodeLibraryResponse(BaseModel): id: str version: str allows_cycles: bool nodes: list[WorkflowNodeTypeResponse] class BpmnInspectionRequest(BaseModel): xml: str = Field(min_length=1, max_length=1_048_576) adapter_id: str = Field( default="govoplan.native.bpmn", min_length=1, max_length=120, ) adapter_version: str | None = Field(default=None, max_length=40) activation: bool = False class BpmnElementSupportResponse(BaseModel): element_type: str element_id: str | None = None name: str | None = None parent_type: str | None = None parent_id: str | None = None support_level: BpmnSupportLevel class BpmnDiagnosticResponse(BaseModel): severity: Literal["error", "warning", "info"] code: str message: str element_id: str | None = None class BpmnInspectionResponse(BaseModel): valid_xml: bool definitions_id: str | None = None target_namespace: str | None = None process_count: int executable_process_count: int collaboration_count: int choreography_count: int element_counts: dict[str, int] support_counts: dict[str, int] elements: list[BpmnElementSupportResponse] diagnostics: list[BpmnDiagnosticResponse] adapter_id: str | None = None adapter_version: str | None = None runtime_kind: BpmnRuntimeKind | None = None executable: bool = False activatable: bool = False class BpmnAdapterProfileResponse(BaseModel): id: str version: str label: str description: str conformance: str runtime_kind: BpmnRuntimeKind executable: bool supported_elements: list[str] supported_event_definitions: list[str] requirements: list[str] class BpmnSupportProfileResponse(BaseModel): specification: str model_namespace: str interchange: str native_runtime: str native_execution_elements: list[str] native_mapping_elements: list[str] adapters: list[BpmnAdapterProfileResponse] = Field(default_factory=list) class BpmnRevisionInput(BaseModel): xml: str = Field(min_length=1, max_length=1_048_576) adapter_id: str = Field( default="govoplan.native.bpmn", min_length=1, max_length=120, ) adapter_version: str | None = Field(default=None, max_length=40) class BpmnRevisionSummaryResponse(BaseModel): format: Literal["bpmn-2.0"] = "bpmn-2.0" content_hash: str adapter_id: str adapter_version: str runtime_kind: BpmnRuntimeKind executable: bool adapter_available: bool class BpmnRevisionDocumentResponse(BpmnRevisionSummaryResponse): definition_id: str revision: int xml: str inspection: BpmnInspectionResponse class BpmnCompileRequest(BaseModel): xml: str = Field(min_length=1, max_length=1_048_576) adapter_id: str = Field( default="govoplan.native.bpmn", min_length=1, max_length=120, ) adapter_version: str | None = Field(default=None, max_length=40) class BpmnCompileResponse(BaseModel): adapter: BpmnAdapterProfileResponse graph: WorkflowGraph inspection: BpmnInspectionResponse class BpmnRenderRequest(BaseModel): graph: WorkflowGraph name: str = Field(default="", max_length=300) class BpmnRenderResponse(BaseModel): xml: str inspection: BpmnInspectionResponse class WorkflowDefinitionRevisionResponse(BaseModel): id: str revision: int schema_version: int graph: WorkflowGraph content_hash: str library_id: str library_version: str execution_mode: WorkflowExecutionMode view_id: str | None = None view_revision_id: str | None = None bpmn: BpmnRevisionSummaryResponse | None = None contribution_origin_module_version: str | None = None contribution_schema_version: str | None = None contribution_hash: str | None = None contribution_metadata: dict[str, Any] = Field(default_factory=dict) created_by: str | None created_at: datetime class WorkflowStandardProvenanceResponse(BaseModel): kind: Literal["baseline", "override"] origin_module_id: str origin_module_version: str | None = None definition_key: str contribution_schema_version: str | None = None contribution_hash: str | None = None baseline_definition_id: str latest_baseline_revision: int active_baseline_revision: int | None = None pinned_baseline_revision: int | None = None pinned_baseline_hash: str | None = None update_available: bool = False reset_available: bool = False class WorkflowStandardDiffItemResponse(BaseModel): resource_type: Literal["graph", "node", "edge"] resource_id: str state: Literal[ "unchanged", "local_only", "upstream_only", "same_change", "conflict", ] recommended_action: Literal[ "none", "keep_local", "adopt_upstream", "either", "manual_resolution", ] changed_fields: list[str] = Field(default_factory=list) baseline: dict[str, Any] | None = None local: dict[str, Any] | None = None latest: dict[str, Any] | None = None class WorkflowStandardDiffResponse(BaseModel): override_definition_id: str baseline_definition_id: str pinned_baseline_revision: int local_revision: int latest_baseline_revision: int counts: dict[str, int] conflict_count: int auto_mergeable: bool items: list[WorkflowStandardDiffItemResponse] class WorkflowActionDecisionResponse(BaseModel): allowed: bool reason: str | None = None source_path: list[dict[str, Any]] = Field(default_factory=list) requirements: list[str] = Field(default_factory=list) details: dict[str, Any] = Field(default_factory=dict) class WorkflowGovernanceResponse(BaseModel): scope_type: DefinitionScopeType scope_id: str | None definition_kind: DefinitionKind inherit_to_lower_scopes: bool allow_start: bool allow_reuse: bool allow_automation: bool derived_from_definition_id: str | None derived_from_revision: int | None derived_from_hash: str | None derivation_provenance: dict[str, Any] = Field(default_factory=dict) actions: dict[str, WorkflowActionDecisionResponse] automation_runtime_available: bool = False automation_runtime_reason: str | None = None class WorkflowDefinitionResponse(BaseModel): id: str tenant_id: str | None key: str name: str description: str | None status: WorkflowDefinitionStatus current_revision: int active_revision: int | None metadata: dict[str, Any] created_by: str | None updated_by: str | None created_at: datetime updated_at: datetime revision: WorkflowDefinitionRevisionResponse governance: WorkflowGovernanceResponse standard: WorkflowStandardProvenanceResponse | None = None class WorkflowDefinitionListResponse(BaseModel): definitions: list[WorkflowDefinitionResponse] class WorkflowDefinitionRevisionListResponse(BaseModel): revisions: list[WorkflowDefinitionRevisionResponse] class WorkflowDefinitionCreateRequest(BaseModel): key: str | None = Field( default=None, min_length=1, max_length=120, pattern=r"^[A-Za-z0-9][A-Za-z0-9_-]*$", ) name: str = Field(min_length=1, max_length=300) description: str | None = Field(default=None, max_length=4_000) graph: WorkflowGraph bpmn: BpmnRevisionInput | None = None metadata: dict[str, Any] = Field(default_factory=dict) scope_type: DefinitionScopeType = "tenant" scope_id: str | None = Field(default=None, max_length=36) definition_kind: DefinitionKind = "flow" inherit_to_lower_scopes: bool = False allow_start: bool = True allow_reuse: bool = False allow_automation: bool = False execution_mode: WorkflowExecutionMode = "hybrid" view_id: str | None = Field(default=None, min_length=1, max_length=36) view_revision_id: str | None = Field( default=None, min_length=1, max_length=36, ) @model_validator(mode="after") def validate_view_pin(self): if self.view_revision_id and not self.view_id: raise ValueError("A pinned View revision requires a View") return self class WorkflowDefinitionUpdateRequest(BaseModel): name: str = Field(min_length=1, max_length=300) description: str | None = Field(default=None, max_length=4_000) graph: WorkflowGraph bpmn: BpmnRevisionInput | None = None metadata: dict[str, Any] = Field(default_factory=dict) expected_revision: int = Field(ge=1) scope_type: DefinitionScopeType = "tenant" scope_id: str | None = Field(default=None, max_length=36) definition_kind: DefinitionKind = "flow" inherit_to_lower_scopes: bool = False allow_start: bool = True allow_reuse: bool = False allow_automation: bool = False execution_mode: WorkflowExecutionMode = "hybrid" view_id: str | None = Field(default=None, min_length=1, max_length=36) view_revision_id: str | None = Field( default=None, min_length=1, max_length=36, ) @model_validator(mode="after") def validate_view_pin(self): if self.view_revision_id and not self.view_id: raise ValueError("A pinned View revision requires a View") return self class WorkflowDefinitionDeriveRequest(BaseModel): key: str | None = Field( default=None, min_length=1, max_length=120, pattern=r"^[A-Za-z0-9][A-Za-z0-9_-]*$", ) name: str = Field(min_length=1, max_length=300) description: str | None = Field(default=None, max_length=4_000) source_revision: int | None = Field(default=None, ge=1) metadata: dict[str, Any] = Field(default_factory=dict) scope_type: DefinitionScopeType = "tenant" scope_id: str | None = Field(default=None, max_length=36) definition_kind: DefinitionKind = "flow" inherit_to_lower_scopes: bool = False allow_start: bool = True allow_reuse: bool = False allow_automation: bool = False execution_mode: WorkflowExecutionMode | None = None view_id: str | None = Field(default=None, min_length=1, max_length=36) view_revision_id: str | None = Field( default=None, min_length=1, max_length=36, ) @model_validator(mode="after") def validate_view_pin(self): if self.view_revision_id and not self.view_id: raise ValueError("A pinned View revision requires a View") return self class WorkflowDefinitionActivateRequest(BaseModel): revision: int | None = Field(default=None, ge=1) class WorkflowDefinitionDeleteResponse(BaseModel): deleted: bool definition_id: str class WorkflowTriggerResponse(BaseModel): id: str definition_id: str definition_revision_id: str node_id: str kind: Literal["schedule", "event"] status: str event_type: str | None = None next_fire_at: datetime | None = None last_fire_at: datetime | None = None last_status: str | None = None last_error: str | None = None authorization_subject_kind: Literal["delegated_user", "service_account"] grant_scopes: list[str] = Field(default_factory=list) class WorkflowTriggerListResponse(BaseModel): triggers: list[WorkflowTriggerResponse] WorkflowInstanceStatus = Literal[ "running", "waiting", "completed", "failed", "cancelled", ] WorkflowStepStatus = Literal[ "running", "waiting", "completed", "failed", "cancelled", "superseded", ] class WorkflowInstanceStartRequest(BaseModel): idempotency_key: str = Field(min_length=1, max_length=255) input: dict[str, Any] = Field(default_factory=dict) correlation_id: str | None = Field(default=None, max_length=128) class WorkflowStepActionRequest(BaseModel): action: Literal[ "complete", "approve", "changes", "reject", "resume", "retry", "confirm_effect", "confirm_absent", "cancel", ] output: dict[str, Any] = Field(default_factory=dict) evidence: list[str] = Field(default_factory=list, max_length=100) comment: str | None = Field(default=None, max_length=4_000) class WorkflowInstanceStepResponse(BaseModel): id: str sequence: int node_id: str node_type: str status: WorkflowStepStatus attempt: int input: dict[str, Any] output: dict[str, Any] handoff: dict[str, Any] external_ref: str | None started_at: datetime | None finished_at: datetime | None error: str | None completed_by: str | None created_at: datetime updated_at: datetime class WorkflowViewContextResponse(BaseModel): view_id: str revision_id: str | None = None visible_surface_ids: list[str] = Field(default_factory=list) step_id: str | None = None node_id: str | None = None class WorkflowInstanceEventResponse(BaseModel): id: str sequence: int step_id: str | None kind: str actor_id: str | None payload: dict[str, Any] created_at: datetime class WorkflowInstanceResponse(BaseModel): id: str definition_id: str definition_name: str definition_revision: int definition_hash: str execution_mode: WorkflowExecutionMode start_origin: WorkflowStartOrigin view_context: WorkflowViewContextResponse | None = None status: WorkflowInstanceStatus idempotency_key: str correlation_id: str | None current_step_id: str | None input: dict[str, Any] context: dict[str, Any] output: dict[str, Any] started_at: datetime finished_at: datetime | None cancellation_requested_at: datetime | None error: str | None created_by: str | None created_at: datetime updated_at: datetime steps: list[WorkflowInstanceStepResponse] events: list[WorkflowInstanceEventResponse] replayed: bool = False class WorkflowInstanceListResponse(BaseModel): instances: list[WorkflowInstanceResponse]