Implement native BPMN workflows and guided modes

This commit is contained in:
2026-07-31 02:48:57 +02:00
parent c505e81006
commit f4974b4949
40 changed files with 8203 additions and 489 deletions
+720 -22
View File
@@ -4,13 +4,18 @@ from govoplan_core.core.definition_graphs import (
DefinitionConfigField,
DefinitionGraphConstraints,
DefinitionGraphLibrary,
DefinitionNodeCountConstraint,
DefinitionNodeType,
DefinitionPort,
)
CATEGORY_LABELS = {
"bpmn_event": "Events",
"bpmn_activity": "Activities",
"bpmn_gateway": "Gateways",
"bpmn_data": "Data",
"bpmn_collaboration": "Collaboration",
"bpmn_artifact": "Artifacts",
"trigger": "Start",
"activity": "Activities",
"decision": "Decisions",
@@ -19,7 +24,17 @@ CATEGORY_LABELS = {
"outcome": "Outcomes",
}
WORKFLOW_NODE_TYPES = (
FOCUSED_VIEW_SURFACES_FIELD = DefinitionConfigField(
id="view_surface_ids",
label="Focused View surfaces",
kind="view_surfaces",
description=(
"Optionally narrow the workflow View while this step requires "
"attention. Administrator limits and protected surfaces still apply."
),
)
LEGACY_WORKFLOW_NODE_TYPES = (
DefinitionNodeType(
type="workflow.start.manual",
category="trigger",
@@ -154,12 +169,14 @@ WORKFLOW_NODE_TYPES = (
DefinitionConfigField(id="instructions", label="Instructions", kind="textarea"),
DefinitionConfigField(id="assignee", label="Assignee", kind="subject"),
DefinitionConfigField(id="due_after", label="Due after", kind="duration"),
FOCUSED_VIEW_SURFACES_FIELD,
),
default_config={
"title": "",
"instructions": "",
"assignee": "",
"due_after": "",
"view_surface_ids": [],
},
),
DefinitionNodeType(
@@ -182,8 +199,14 @@ WORKFLOW_NODE_TYPES = (
label="Required evidence",
kind="string_list",
),
FOCUSED_VIEW_SURFACES_FIELD,
),
default_config={"title": "", "reviewer": "", "required_evidence": []},
default_config={
"title": "",
"reviewer": "",
"required_evidence": [],
"view_surface_ids": [],
},
),
DefinitionNodeType(
type="workflow.decision",
@@ -231,8 +254,13 @@ WORKFLOW_NODE_TYPES = (
),
),
DefinitionConfigField(id="value", label="Value", kind="text"),
FOCUSED_VIEW_SURFACES_FIELD,
),
default_config={"mode": "manual", "value": ""},
default_config={
"mode": "manual",
"value": "",
"view_surface_ids": [],
},
),
DefinitionNodeType(
type="workflow.capability",
@@ -282,13 +310,15 @@ WORKFLOW_NODE_TYPES = (
("continue", "Continue on failure"),
),
),
FOCUSED_VIEW_SURFACES_FIELD,
),
default_config={
"capability": "",
"operation": "",
"input_mapping": {},
"idempotency_key": "",
"idempotency_key": "workflow-step",
"failure_policy": "manual",
"view_surface_ids": [],
},
),
DefinitionNodeType(
@@ -357,7 +387,18 @@ WORKFLOW_NODE_TYPES = (
("continue", "Continue"),
),
),
DefinitionConfigField(
id="failure_policy",
label="Failures",
kind="select",
options=(
("manual", "Require intervention"),
("fail", "Fail the workflow"),
("continue", "Follow failure path"),
),
),
DefinitionConfigField(id="input_mapping", label="Input mapping", kind="mapping"),
FOCUSED_VIEW_SURFACES_FIELD,
),
default_config={
"pipeline_ref": "",
@@ -366,7 +407,9 @@ WORKFLOW_NODE_TYPES = (
"row_limit": 500,
"publication_target_ref": "",
"warning_policy": "review",
"failure_policy": "manual",
"input_mapping": {},
"view_surface_ids": [],
},
),
DefinitionNodeType(
@@ -411,31 +454,684 @@ WORKFLOW_NODE_TYPES = (
),
)
_INCOMING = (
DefinitionPort(
id="incoming",
label="Incoming",
required=False,
multiple=True,
minimum_connections=0,
),
)
_OPTIONAL_INCOMING = (
DefinitionPort(
id="incoming",
label="Incoming",
required=False,
multiple=True,
minimum_connections=0,
),
)
_OUTGOING = (
DefinitionPort(
id="outgoing",
label="Outgoing",
required=False,
multiple=True,
minimum_connections=0,
),
)
_DOCUMENTATION_FIELD = DefinitionConfigField(
id="documentation",
label="Documentation",
kind="textarea",
)
_EVENT_DEFINITION_FIELD = DefinitionConfigField(
id="event_definition",
label="Event definition",
kind="select",
options=(
("none", "None"),
("message", "Message"),
("timer", "Timer"),
("conditional", "Conditional"),
("signal", "Signal"),
("error", "Error"),
("escalation", "Escalation"),
("compensation", "Compensation"),
("link", "Link"),
("cancel", "Cancel"),
("terminate", "Terminate"),
("multiple", "Multiple"),
("parallelMultiple", "Parallel multiple"),
),
)
def _bpmn_node(
*,
type_id: str,
category: str,
label: str,
description: str,
icon: str,
shape: str,
input_ports: tuple[DefinitionPort, ...] = _INCOMING,
output_ports: tuple[DefinitionPort, ...] = _OUTGOING,
config_fields: tuple[DefinitionConfigField, ...] = (),
default_config: dict[str, object] | None = None,
runtime_support: str = "model_only",
) -> DefinitionNodeType:
return DefinitionNodeType(
type=type_id,
category=category,
label=label,
description=description,
icon=icon,
input_ports=input_ports,
output_ports=output_ports,
config_fields=(*config_fields, _DOCUMENTATION_FIELD),
default_config={
**(default_config or {}),
"documentation": "",
},
metadata={
"notation": "bpmn-2.0",
"shape": shape,
"runtime_support": runtime_support,
},
)
_TASK_FIELDS = (
DefinitionConfigField(id="title", label="Task title", kind="text"),
DefinitionConfigField(id="instructions", label="Instructions", kind="textarea"),
)
_HUMAN_TASK_FIELDS = (
*_TASK_FIELDS,
DefinitionConfigField(id="assignee", label="Assignee", kind="subject"),
DefinitionConfigField(id="due_after", label="Due after", kind="duration"),
FOCUSED_VIEW_SURFACES_FIELD,
)
_SERVICE_TASK_FIELDS = (
*_TASK_FIELDS,
DefinitionConfigField(
id="implementation",
label="Implementation",
kind="select",
options=(
("capability", "Module capability"),
("dataflow", "Dataflow"),
),
),
DefinitionConfigField(id="capability", label="Capability", kind="capability"),
DefinitionConfigField(id="operation", label="Operation", kind="text"),
DefinitionConfigField(id="pipeline_ref", label="Dataflow", kind="dataflow"),
DefinitionConfigField(id="input_mapping", label="Input mapping", kind="mapping"),
DefinitionConfigField(
id="idempotency_key",
label="Idempotency key",
kind="expression",
),
DefinitionConfigField(
id="failure_policy",
label="On failure",
kind="select",
options=(
("retry", "Retry"),
("manual", "Require intervention"),
("continue", "Continue"),
("fail", "Fail the workflow"),
),
),
FOCUSED_VIEW_SURFACES_FIELD,
)
BPMN_NODE_TYPES = (
_bpmn_node(
type_id="bpmn.startEvent",
category="bpmn_event",
label="Start event",
description="Start a BPMN process from a user, API, schedule, event, or parent flow.",
icon="circle-play",
shape="event-start",
input_ports=(),
config_fields=(
_EVENT_DEFINITION_FIELD,
DefinitionConfigField(
id="start_kind",
label="GovOPlaN start",
kind="select",
required=True,
options=(
("manual", "Manual"),
("api", "API"),
("schedule", "Schedule"),
("event", "Platform event"),
("parent_workflow", "Parent workflow"),
),
),
DefinitionConfigField(
id="input_schema_ref",
label="Input schema",
kind="text",
),
DefinitionConfigField(
id="authorization_policy_ref",
label="Authorization policy",
kind="text",
),
DefinitionConfigField(id="schedule", label="Schedule", kind="schedule"),
DefinitionConfigField(id="timezone", label="Time zone", kind="timezone"),
DefinitionConfigField(id="event_type", label="Event type", kind="text"),
DefinitionConfigField(id="filter", label="Event filter", kind="expression"),
),
default_config={
"event_definition": "none",
"start_kind": "manual",
"input_schema_ref": "",
"authorization_policy_ref": "",
"schedule": "",
"timezone": "Europe/Berlin",
"event_type": "",
"filter": "",
},
runtime_support="native",
),
_bpmn_node(
type_id="bpmn.intermediateCatchEvent",
category="bpmn_event",
label="Intermediate catch event",
description="Wait until the configured BPMN event is caught.",
icon="circle-dot",
shape="event-intermediate-catch",
config_fields=(
_EVENT_DEFINITION_FIELD,
DefinitionConfigField(
id="wait_mode",
label="Wait for",
kind="select",
options=(
("duration", "Duration"),
("deadline", "Deadline"),
("event", "Event"),
("manual", "Manual resume"),
),
),
DefinitionConfigField(id="value", label="Value", kind="text"),
DefinitionConfigField(id="event_ref", label="Event reference", kind="text"),
FOCUSED_VIEW_SURFACES_FIELD,
),
default_config={
"event_definition": "none",
"wait_mode": "manual",
"value": "",
"event_ref": "",
"view_surface_ids": [],
},
runtime_support="native",
),
_bpmn_node(
type_id="bpmn.intermediateThrowEvent",
category="bpmn_event",
label="Intermediate throw event",
description="Emit an intermediate BPMN event.",
icon="circle-dot-dashed",
shape="event-intermediate-throw",
config_fields=(
_EVENT_DEFINITION_FIELD,
DefinitionConfigField(id="event_ref", label="Event reference", kind="text"),
),
default_config={"event_definition": "none", "event_ref": ""},
),
_bpmn_node(
type_id="bpmn.boundaryEvent",
category="bpmn_event",
label="Boundary event",
description="Catch an interrupting or non-interrupting event attached to an activity.",
icon="circle-dashed",
shape="event-boundary",
input_ports=(),
config_fields=(
_EVENT_DEFINITION_FIELD,
DefinitionConfigField(
id="attached_to_ref",
label="Attached activity",
kind="text",
required=True,
),
DefinitionConfigField(
id="cancel_activity",
label="Activity behavior",
kind="select",
options=(
("true", "Interrupt activity"),
("false", "Keep activity running"),
),
),
),
default_config={
"event_definition": "none",
"attached_to_ref": "",
"cancel_activity": "true",
},
),
_bpmn_node(
type_id="bpmn.endEvent",
category="bpmn_event",
label="End event",
description="End a BPMN process with an optional result event.",
icon="circle-stop",
shape="event-end",
output_ports=(),
config_fields=(
_EVENT_DEFINITION_FIELD,
DefinitionConfigField(
id="outcome",
label="GovOPlaN outcome",
kind="select",
options=(
("completed", "Completed"),
("cancelled", "Cancelled"),
),
),
DefinitionConfigField(
id="output_mapping",
label="Output mapping",
kind="mapping",
),
),
default_config={
"event_definition": "none",
"outcome": "completed",
"output_mapping": {},
},
runtime_support="native",
),
_bpmn_node(
type_id="bpmn.task",
category="bpmn_activity",
label="Task",
description="A generic BPMN task completed as a governed human activity.",
icon="square",
shape="activity",
config_fields=_HUMAN_TASK_FIELDS,
default_config={
"title": "",
"instructions": "",
"assignee": "",
"due_after": "",
"view_surface_ids": [],
},
runtime_support="native",
),
_bpmn_node(
type_id="bpmn.userTask",
category="bpmn_activity",
label="User task",
description="A task requiring a user action, review, or evidence.",
icon="user-round-check",
shape="activity",
config_fields=(
*_HUMAN_TASK_FIELDS,
DefinitionConfigField(
id="task_mode",
label="Task mode",
kind="select",
options=(
("activity", "Activity"),
("review", "Review"),
),
),
DefinitionConfigField(
id="required_evidence",
label="Required evidence",
kind="string_list",
),
),
default_config={
"title": "",
"instructions": "",
"assignee": "",
"due_after": "",
"task_mode": "activity",
"required_evidence": [],
"view_surface_ids": [],
},
runtime_support="native",
),
_bpmn_node(
type_id="bpmn.manualTask",
category="bpmn_activity",
label="Manual task",
description="A task performed outside the automated runtime.",
icon="hand",
shape="activity",
config_fields=_HUMAN_TASK_FIELDS,
default_config={
"title": "",
"instructions": "",
"assignee": "",
"due_after": "",
"view_surface_ids": [],
},
runtime_support="native",
),
_bpmn_node(
type_id="bpmn.serviceTask",
category="bpmn_activity",
label="Service task",
description="Invoke a versioned module capability or published Dataflow.",
icon="cog",
shape="activity",
config_fields=_SERVICE_TASK_FIELDS,
default_config={
"title": "",
"instructions": "",
"implementation": "capability",
"capability": "",
"operation": "",
"pipeline_ref": "",
"input_mapping": {},
"idempotency_key": "workflow-step",
"failure_policy": "manual",
"view_surface_ids": [],
},
runtime_support="native",
),
_bpmn_node(
type_id="bpmn.sendTask",
category="bpmn_activity",
label="Send task",
description="Send a message through a configured implementation.",
icon="send",
shape="activity",
config_fields=_SERVICE_TASK_FIELDS,
default_config={
"title": "",
"instructions": "",
"implementation": "capability",
"capability": "",
"operation": "",
"pipeline_ref": "",
"input_mapping": {},
"idempotency_key": "workflow-step",
"failure_policy": "manual",
"view_surface_ids": [],
},
runtime_support="native",
),
_bpmn_node(
type_id="bpmn.receiveTask",
category="bpmn_activity",
label="Receive task",
description="Wait until a matching message is received.",
icon="inbox",
shape="activity",
config_fields=(
*_TASK_FIELDS,
DefinitionConfigField(id="message_ref", label="Message reference", kind="text"),
FOCUSED_VIEW_SURFACES_FIELD,
),
default_config={
"title": "",
"instructions": "",
"message_ref": "",
"view_surface_ids": [],
},
runtime_support="native",
),
*(
_bpmn_node(
type_id=f"bpmn.{type_name}",
category="bpmn_activity",
label=label,
description=description,
icon=icon,
shape="activity",
config_fields=fields,
default_config=defaults,
)
for type_name, label, description, icon, fields, defaults in (
(
"scriptTask",
"Script task",
"A BPMN script task retained as notation; arbitrary scripts are not executed.",
"file-code-2",
(*_TASK_FIELDS, DefinitionConfigField(id="script_format", label="Script format", kind="text"), DefinitionConfigField(id="script", label="Script", kind="textarea")),
{"title": "", "instructions": "", "script_format": "", "script": ""},
),
(
"businessRuleTask",
"Business rule task",
"Evaluate a governed business-rule implementation.",
"scale",
(*_TASK_FIELDS, DefinitionConfigField(id="implementation_ref", label="Implementation reference", kind="text")),
{"title": "", "instructions": "", "implementation_ref": ""},
),
(
"callActivity",
"Call activity",
"Call another reusable BPMN process or GovOPlaN workflow.",
"external-link",
(*_TASK_FIELDS, DefinitionConfigField(id="called_element", label="Called element", kind="text", required=True)),
{"title": "", "instructions": "", "called_element": ""},
),
(
"subProcess",
"Sub-process",
"Contain a nested BPMN process.",
"box-select",
_TASK_FIELDS,
{"title": "", "instructions": ""},
),
(
"transaction",
"Transaction",
"Contain transaction-scoped BPMN activity semantics.",
"badge-dollar-sign",
_TASK_FIELDS,
{"title": "", "instructions": ""},
),
(
"adHocSubProcess",
"Ad-hoc sub-process",
"Contain activities whose order is governed at runtime.",
"shuffle",
_TASK_FIELDS,
{"title": "", "instructions": ""},
),
)
),
*(
_bpmn_node(
type_id=f"bpmn.{type_name}",
category="bpmn_gateway",
label=label,
description=description,
icon=icon,
shape="gateway",
config_fields=(),
default_config={},
runtime_support=runtime_support,
)
for type_name, label, description, icon, runtime_support in (
("exclusiveGateway", "Exclusive gateway", "Choose exactly one matching sequence flow.", "diamond", "native"),
("parallelGateway", "Parallel gateway", "Split or join concurrent sequence flows.", "plus", "model_only"),
("inclusiveGateway", "Inclusive gateway", "Choose one or more matching sequence flows.", "circle-plus", "model_only"),
("eventBasedGateway", "Event-based gateway", "Choose a path according to the first caught event.", "radio-tower", "model_only"),
("complexGateway", "Complex gateway", "Apply a complex activation condition.", "asterisk", "model_only"),
)
),
_bpmn_node(
type_id="bpmn.dataObjectReference",
category="bpmn_data",
label="Data object",
description="Reference data produced or consumed by an activity.",
icon="file",
shape="data-object",
input_ports=_OPTIONAL_INCOMING,
config_fields=(
DefinitionConfigField(id="data_object_ref", label="Data object reference", kind="text"),
DefinitionConfigField(id="item_subject_ref", label="Item definition", kind="text"),
),
default_config={"data_object_ref": "", "item_subject_ref": ""},
),
_bpmn_node(
type_id="bpmn.dataStoreReference",
category="bpmn_data",
label="Data store",
description="Reference persistent data used by the process.",
icon="database",
shape="data-store",
input_ports=_OPTIONAL_INCOMING,
config_fields=(
DefinitionConfigField(id="data_store_ref", label="Data store reference", kind="text"),
DefinitionConfigField(id="item_subject_ref", label="Item definition", kind="text"),
),
default_config={"data_store_ref": "", "item_subject_ref": ""},
),
_bpmn_node(
type_id="bpmn.participant",
category="bpmn_collaboration",
label="Participant / pool",
description="Represent a BPMN collaboration participant and its process.",
icon="rectangle-horizontal",
shape="participant",
input_ports=_OPTIONAL_INCOMING,
config_fields=(
DefinitionConfigField(id="process_ref", label="Process reference", kind="text"),
),
default_config={"process_ref": ""},
),
_bpmn_node(
type_id="bpmn.lane",
category="bpmn_collaboration",
label="Lane",
description="Group flow nodes by role or responsibility.",
icon="rows-3",
shape="lane",
input_ports=_OPTIONAL_INCOMING,
config_fields=(
DefinitionConfigField(id="flow_node_refs", label="Flow node references", kind="string_list"),
),
default_config={"flow_node_refs": []},
),
_bpmn_node(
type_id="bpmn.textAnnotation",
category="bpmn_artifact",
label="Text annotation",
description="Attach explanatory text without changing process execution.",
icon="text-quote",
shape="text-annotation",
input_ports=_OPTIONAL_INCOMING,
config_fields=(
DefinitionConfigField(id="text", label="Text", kind="textarea"),
DefinitionConfigField(id="text_format", label="Text format", kind="text"),
),
default_config={"text": "", "text_format": "text/plain"},
),
_bpmn_node(
type_id="bpmn.group",
category="bpmn_artifact",
label="Group",
description="Visually group BPMN elements without changing execution.",
icon="box-select",
shape="group",
input_ports=_OPTIONAL_INCOMING,
config_fields=(
DefinitionConfigField(id="category_value_ref", label="Category value", kind="text"),
),
default_config={"category_value_ref": ""},
),
*(
_bpmn_node(
type_id=f"bpmn.{type_name}",
category="bpmn_collaboration",
label=label,
description=description,
icon=icon,
shape=shape,
input_ports=ports,
config_fields=(
DefinitionConfigField(
id="participant_refs",
label="Participants",
kind="string_list",
),
DefinitionConfigField(
id="called_element",
label="Called element",
kind="text",
),
),
default_config={"participant_refs": [], "called_element": ""},
)
for type_name, label, description, icon, shape, ports in (
(
"choreographyTask",
"Choreography task",
"Model a message exchange between two or more participants.",
"messages-square",
"choreography",
_INCOMING,
),
(
"callChoreography",
"Call choreography",
"Call a reusable choreography.",
"external-link",
"choreography",
_INCOMING,
),
(
"subChoreography",
"Sub-choreography",
"Contain a nested choreography.",
"box-select",
"choreography",
_INCOMING,
),
(
"conversation",
"Conversation",
"Group logically related message exchanges.",
"messages-square",
"conversation",
_OPTIONAL_INCOMING,
),
(
"callConversation",
"Call conversation",
"Call a reusable global conversation.",
"external-link",
"conversation",
_OPTIONAL_INCOMING,
),
(
"subConversation",
"Sub-conversation",
"Contain a nested conversation.",
"box-select",
"conversation",
_OPTIONAL_INCOMING,
),
)
),
)
WORKFLOW_NODE_TYPES = (*BPMN_NODE_TYPES, *LEGACY_WORKFLOW_NODE_TYPES)
WORKFLOW_GRAPH_LIBRARY = DefinitionGraphLibrary(
id="workflow",
version="0.1.0",
version="1.0.0",
category_labels=CATEGORY_LABELS,
node_types=WORKFLOW_NODE_TYPES,
constraints=DefinitionGraphConstraints(
max_nodes=150,
max_edges=300,
allow_cycles=True,
require_connected=True,
node_counts=(
DefinitionNodeCountConstraint(
code="graph.trigger_count",
label="start",
minimum=1,
maximum=1,
categories=("trigger",),
),
DefinitionNodeCountConstraint(
code="graph.outcome_count",
label="outcome",
minimum=1,
categories=("outcome",),
),
),
require_connected=False,
),
)
@@ -445,7 +1141,9 @@ WORKFLOW_NODE_TYPES_BY_ID = {
__all__ = [
"BPMN_NODE_TYPES",
"CATEGORY_LABELS",
"LEGACY_WORKFLOW_NODE_TYPES",
"WORKFLOW_GRAPH_LIBRARY",
"WORKFLOW_NODE_TYPES",
"WORKFLOW_NODE_TYPES_BY_ID",