feat: extract headless workflow engine
This commit is contained in:
@@ -0,0 +1,573 @@
|
||||
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 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
|
||||
|
||||
|
||||
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",
|
||||
"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]
|
||||
Reference in New Issue
Block a user