feat: extract headless workflow engine

This commit is contained in:
2026-07-31 16:58:51 +02:00
commit 9bccbd68da
54 changed files with 16275 additions and 0 deletions
@@ -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]