feat: add reusable workflow definition graphs

This commit is contained in:
2026-07-28 12:43:26 +02:00
parent 0d099b05b7
commit 85eef00913
13 changed files with 972 additions and 3 deletions
@@ -0,0 +1,328 @@
from __future__ import annotations
from govoplan_core.core.definition_graphs import (
DefinitionConfigField,
DefinitionGraphConstraints,
DefinitionGraphLibrary,
DefinitionNodeCountConstraint,
DefinitionNodeType,
DefinitionPort,
)
CATEGORY_LABELS = {
"trigger": "Start",
"activity": "Activities",
"decision": "Decisions",
"wait": "Wait",
"integration": "Integrations",
"outcome": "Outcomes",
}
WORKFLOW_NODE_TYPES = (
DefinitionNodeType(
type="workflow.start.manual",
category="trigger",
label="Manual start",
description="Start an instance through an explicit user or API action.",
icon="circle-play",
default_config={"input_schema_ref": ""},
config_fields=(
DefinitionConfigField(
id="input_schema_ref",
label="Input schema",
kind="text",
description="Optional schema contract for instance input.",
),
),
),
DefinitionNodeType(
type="workflow.start.event",
category="trigger",
label="Event start",
description="Start when a matching platform event is received.",
icon="radio",
config_fields=(
DefinitionConfigField(
id="event_type",
label="Event type",
kind="text",
required=True,
),
DefinitionConfigField(
id="filter",
label="Event filter",
kind="expression",
description="A constrained expression evaluated against the event envelope.",
),
),
default_config={"event_type": "", "filter": ""},
),
DefinitionNodeType(
type="workflow.start.schedule",
category="trigger",
label="Scheduled start",
description="Start according to a governed schedule.",
icon="calendar-clock",
config_fields=(
DefinitionConfigField(
id="schedule",
label="Schedule",
kind="schedule",
required=True,
),
DefinitionConfigField(
id="timezone",
label="Time zone",
kind="timezone",
required=True,
),
),
default_config={"schedule": "", "timezone": "Europe/Berlin"},
),
DefinitionNodeType(
type="workflow.activity",
category="activity",
label="Activity",
description="Record and complete a governed unit of work.",
icon="square-check-big",
input_ports=(DefinitionPort(id="input", label="Input"),),
config_fields=(
DefinitionConfigField(id="title", label="Title", kind="text", required=True),
DefinitionConfigField(id="instructions", label="Instructions", kind="textarea"),
DefinitionConfigField(id="assignee", label="Assignee", kind="subject"),
DefinitionConfigField(id="due_after", label="Due after", kind="duration"),
),
default_config={
"title": "",
"instructions": "",
"assignee": "",
"due_after": "",
},
),
DefinitionNodeType(
type="workflow.review",
category="activity",
label="Review",
description="Pause for a human review with explicit outcomes.",
icon="clipboard-check",
input_ports=(DefinitionPort(id="input", label="Input"),),
output_ports=(
DefinitionPort(id="approved", label="Approved", required=False),
DefinitionPort(id="changes", label="Changes requested", required=False),
DefinitionPort(id="rejected", label="Rejected", required=False),
),
config_fields=(
DefinitionConfigField(id="title", label="Title", kind="text", required=True),
DefinitionConfigField(id="reviewer", label="Reviewer", kind="subject"),
DefinitionConfigField(
id="required_evidence",
label="Required evidence",
kind="string_list",
),
),
default_config={"title": "", "reviewer": "", "required_evidence": []},
),
DefinitionNodeType(
type="workflow.decision",
category="decision",
label="Decision",
description="Choose a path using a constrained expression.",
icon="split",
input_ports=(DefinitionPort(id="input", label="Input"),),
output_ports=(
DefinitionPort(id="true", label="Matches", required=False),
DefinitionPort(id="false", label="Does not match", required=False),
),
config_fields=(
DefinitionConfigField(
id="expression",
label="Expression",
kind="expression",
required=True,
),
),
default_config={"expression": ""},
),
DefinitionNodeType(
type="workflow.wait",
category="wait",
label="Wait",
description="Wait for a duration, deadline, event, or explicit resume action.",
icon="timer",
input_ports=(DefinitionPort(id="input", label="Input"),),
output_ports=(
DefinitionPort(id="resumed", label="Resumed", required=False),
DefinitionPort(id="timed_out", label="Timed out", required=False),
),
config_fields=(
DefinitionConfigField(
id="mode",
label="Wait for",
kind="select",
required=True,
options=(
("duration", "Duration"),
("deadline", "Deadline"),
("event", "Event"),
("manual", "Manual resume"),
),
),
DefinitionConfigField(id="value", label="Value", kind="text"),
),
default_config={"mode": "manual", "value": ""},
),
DefinitionNodeType(
type="workflow.capability",
category="integration",
label="Module action",
description="Invoke a versioned module capability without importing its implementation.",
icon="plug-zap",
input_ports=(DefinitionPort(id="input", label="Input"),),
output_ports=(
DefinitionPort(id="success", label="Success", required=False),
DefinitionPort(id="failure", label="Failure", required=False),
),
config_fields=(
DefinitionConfigField(
id="capability",
label="Capability",
kind="capability",
required=True,
),
DefinitionConfigField(
id="operation",
label="Operation",
kind="text",
required=True,
),
DefinitionConfigField(id="input_mapping", label="Input mapping", kind="mapping"),
DefinitionConfigField(
id="idempotency_key",
label="Idempotency key",
kind="expression",
required=True,
),
DefinitionConfigField(
id="failure_policy",
label="On failure",
kind="select",
required=True,
options=(
("retry", "Retry"),
("manual", "Require intervention"),
("continue", "Continue on failure"),
),
),
),
default_config={
"capability": "",
"operation": "",
"input_mapping": {},
"idempotency_key": "",
"failure_policy": "manual",
},
),
DefinitionNodeType(
type="workflow.dataflow",
category="integration",
label="Run dataflow",
description="Run a published Dataflow pipeline and retain its output reference.",
icon="waypoints",
input_ports=(DefinitionPort(id="input", label="Input"),),
output_ports=(
DefinitionPort(id="success", label="Success", required=False),
DefinitionPort(id="failure", label="Failure", required=False),
),
config_fields=(
DefinitionConfigField(
id="pipeline_ref",
label="Pipeline",
kind="dataflow",
required=True,
),
DefinitionConfigField(id="input_mapping", label="Input mapping", kind="mapping"),
),
default_config={"pipeline_ref": "", "input_mapping": {}},
),
DefinitionNodeType(
type="workflow.end.completed",
category="outcome",
label="Completed",
description="Complete the workflow successfully.",
icon="circle-check-big",
input_ports=(
DefinitionPort(
id="input",
label="Input",
multiple=True,
minimum_connections=1,
),
),
output_ports=(),
config_fields=(
DefinitionConfigField(id="output_mapping", label="Output mapping", kind="mapping"),
),
default_config={"output_mapping": {}},
),
DefinitionNodeType(
type="workflow.end.cancelled",
category="outcome",
label="Cancelled",
description="End the workflow without a successful outcome.",
icon="circle-x",
input_ports=(
DefinitionPort(
id="input",
label="Input",
multiple=True,
minimum_connections=1,
),
),
output_ports=(),
config_fields=(
DefinitionConfigField(id="reason", label="Reason", kind="text"),
),
default_config={"reason": ""},
),
)
WORKFLOW_GRAPH_LIBRARY = DefinitionGraphLibrary(
id="workflow",
version="0.1.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",),
),
),
),
)
WORKFLOW_NODE_TYPES_BY_ID = {
definition.type: definition for definition in WORKFLOW_NODE_TYPES
}
__all__ = [
"CATEGORY_LABELS",
"WORKFLOW_GRAPH_LIBRARY",
"WORKFLOW_NODE_TYPES",
"WORKFLOW_NODE_TYPES_BY_ID",
]