hexastack-flow 0.5.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,59 @@
1
+ """Hexastack Flow - Hexagonal workflow orchestration framework.
2
+
3
+ Notes/Architectural Intent:
4
+ Integrates the zero-daemon hexaflow workflow execution engine into Hexastack,
5
+ providing CQRS command/query steps, saga migration, relational checkpoint persistence,
6
+ and lifecycle domain event broadcasting.
7
+ """
8
+
9
+ from hexaflow import RetryPolicy, StageExecutionMode, Workflow
10
+
11
+ from hexastack_flow import adapters, domain, infra, ports
12
+ from hexastack_flow.adapters.cqrs.runner import CqrsWorkflowRunner
13
+ from hexastack_flow.adapters.cqrs.steps import (
14
+ CommandStep,
15
+ QueryStep,
16
+ as_command_step,
17
+ as_query_step,
18
+ )
19
+ from hexastack_flow.adapters.events.publisher import WorkflowEventPublisher
20
+ from hexastack_flow.adapters.storage.sqlalchemy import SqlAlchemyWorkflowStore
21
+ from hexastack_flow.domain.events import (
22
+ WorkflowAbortedEvent,
23
+ WorkflowCompletedEvent,
24
+ WorkflowStartedEvent,
25
+ WorkflowStepCompletedEvent,
26
+ WorkflowSuspendedEvent,
27
+ )
28
+ from hexastack_flow.domain.models import (
29
+ CqrsStepMetadata,
30
+ WorkflowExecutionResult,
31
+ )
32
+ from hexastack_flow.infra.bootstrap import FlowBootstrapper
33
+ from hexastack_flow.ports.orchestrator import CqrsWorkflowOrchestratorPort
34
+
35
+ __all__ = [
36
+ "adapters",
37
+ "as_command_step",
38
+ "as_query_step",
39
+ "CommandStep",
40
+ "CqrsStepMetadata",
41
+ "CqrsWorkflowOrchestratorPort",
42
+ "CqrsWorkflowRunner",
43
+ "domain",
44
+ "FlowBootstrapper",
45
+ "infra",
46
+ "ports",
47
+ "QueryStep",
48
+ "RetryPolicy",
49
+ "SqlAlchemyWorkflowStore",
50
+ "StageExecutionMode",
51
+ "Workflow",
52
+ "WorkflowAbortedEvent",
53
+ "WorkflowCompletedEvent",
54
+ "WorkflowEventPublisher",
55
+ "WorkflowExecutionResult",
56
+ "WorkflowStartedEvent",
57
+ "WorkflowStepCompletedEvent",
58
+ "WorkflowSuspendedEvent",
59
+ ]
@@ -0,0 +1,29 @@
1
+ """Adapters for hexastack-flow.
2
+
3
+ Notes/Architectural Intent:
4
+ Exports CQRS, Storage, and Event adapters bridging hexaflow into Hexastack.
5
+ """
6
+
7
+ from hexastack_flow.adapters import cqrs, events, storage
8
+ from hexastack_flow.adapters.cqrs import (
9
+ CommandStep,
10
+ CqrsWorkflowRunner,
11
+ QueryStep,
12
+ as_command_step,
13
+ as_query_step,
14
+ )
15
+ from hexastack_flow.adapters.events import WorkflowEventPublisher
16
+ from hexastack_flow.adapters.storage import SqlAlchemyWorkflowStore
17
+
18
+ __all__ = [
19
+ "as_command_step",
20
+ "as_query_step",
21
+ "CommandStep",
22
+ "cqrs",
23
+ "CqrsWorkflowRunner",
24
+ "events",
25
+ "QueryStep",
26
+ "SqlAlchemyWorkflowStore",
27
+ "storage",
28
+ "WorkflowEventPublisher",
29
+ ]
@@ -0,0 +1,21 @@
1
+ """CQRS adapters for hexaflow workflow execution.
2
+
3
+ Notes/Architectural Intent:
4
+ Adapters connecting Hexastack CQRS buses and legacy saga models to hexaflow.
5
+ """
6
+
7
+ from hexastack_flow.adapters.cqrs.runner import CqrsWorkflowRunner
8
+ from hexastack_flow.adapters.cqrs.steps import (
9
+ CommandStep,
10
+ QueryStep,
11
+ as_command_step,
12
+ as_query_step,
13
+ )
14
+
15
+ __all__ = [
16
+ "as_command_step",
17
+ "as_query_step",
18
+ "CommandStep",
19
+ "CqrsWorkflowRunner",
20
+ "QueryStep",
21
+ ]
@@ -0,0 +1,212 @@
1
+ """CQRS workflow runner adapter implementing CqrsWorkflowOrchestratorPort.
2
+
3
+ Notes/Architectural Intent:
4
+ Glues the hexaflow execution engine, state store, and event bus together.
5
+ Translates hexaflow state objects into Hexastack domain results and
6
+ publishes lifecycle domain events.
7
+ """
8
+
9
+ from datetime import UTC, datetime
10
+ from typing import Any
11
+
12
+ from hexaflow.adapters.engines.local_async import AsyncioWorkflowEngine
13
+ from hexaflow.adapters.storage.in_memory import InMemoryStateStore
14
+ from hexaflow.domain.models import WorkflowDefinition
15
+ from hexaflow.domain.state import StepStatus, WorkflowExecutionState, WorkflowStatus
16
+ from hexaflow.ports.engine import WorkflowEnginePort
17
+ from hexaflow.ports.storage import WorkflowStateStorePort
18
+
19
+ from hexastack_cqrs.ports.buses import EventBusPort
20
+ from hexastack_flow.domain.events import (
21
+ WorkflowAbortedEvent,
22
+ WorkflowCompletedEvent,
23
+ WorkflowStartedEvent,
24
+ WorkflowSuspendedEvent,
25
+ )
26
+ from hexastack_flow.domain.models import WorkflowExecutionResult
27
+ from hexastack_flow.ports.orchestrator import CqrsWorkflowOrchestratorPort
28
+
29
+ __all__ = [
30
+ "CqrsWorkflowRunner",
31
+ ]
32
+
33
+
34
+ class CqrsWorkflowRunner(CqrsWorkflowOrchestratorPort):
35
+ """Orchestrator runner coordinating workflow execution with CQRS buses.
36
+
37
+ Notes/Architectural Intent:
38
+ Implements CqrsWorkflowOrchestratorPort to provide a complete
39
+ in-process execution runtime with automatic event publishing.
40
+ """
41
+
42
+ def __init__(
43
+ self,
44
+ state_store: WorkflowStateStorePort | None = None,
45
+ engine: WorkflowEnginePort | None = None,
46
+ event_bus: EventBusPort | None = None,
47
+ ) -> None:
48
+ """Initialize runner with state store, execution engine, and event bus.
49
+
50
+ Args:
51
+ state_store: WorkflowStateStorePort for persistence. Defaults to InMemoryStateStore.
52
+ engine: WorkflowEnginePort for DAG execution. Defaults to AsyncioWorkflowEngine.
53
+ event_bus: Optional EventBusPort for publishing workflow domain events.
54
+ """
55
+ self._store = state_store or InMemoryStateStore()
56
+ self._engine = engine or AsyncioWorkflowEngine(state_store=self._store)
57
+ self._event_bus = event_bus
58
+
59
+ def execute(
60
+ self,
61
+ workflow: WorkflowDefinition,
62
+ initial_inputs: dict[str, Any] | None = None,
63
+ ) -> WorkflowExecutionResult:
64
+ """Execute workflow definition from the first stage.
65
+
66
+ Args:
67
+ workflow: The WorkflowDefinition to run.
68
+ initial_inputs: Optional dictionary of root arguments.
69
+
70
+ Returns:
71
+ WorkflowExecutionResult summarizing status, step outputs, and timing.
72
+ """
73
+ start_time = datetime.now(UTC)
74
+ state = self._engine.run(workflow, initial_inputs=initial_inputs)
75
+
76
+ if self._event_bus is not None:
77
+ self._event_bus.publish(
78
+ WorkflowStartedEvent(
79
+ run_id=state.run_id,
80
+ workflow_name=state.workflow_name,
81
+ started_at=start_time,
82
+ )
83
+ )
84
+
85
+ self._emit_terminal_events(state)
86
+ return self._to_result(state)
87
+
88
+ def resume(
89
+ self,
90
+ run_id: str,
91
+ workflow: WorkflowDefinition,
92
+ patch_inputs: dict[str, Any] | None = None,
93
+ ) -> WorkflowExecutionResult:
94
+ """Resume a suspended workflow run from its latest checkpoints.
95
+
96
+ Args:
97
+ run_id: Unique identifier of the suspended run.
98
+ workflow: WorkflowDefinition specification matching the run.
99
+ patch_inputs: Optional updated inputs for the suspended step.
100
+
101
+ Returns:
102
+ WorkflowExecutionResult reflecting post-resumption status.
103
+ """
104
+ state = self._engine.resume(run_id, workflow, patch_inputs=patch_inputs)
105
+ self._emit_terminal_events(state)
106
+ return self._to_result(state)
107
+
108
+ def abort(
109
+ self,
110
+ run_id: str,
111
+ workflow: WorkflowDefinition,
112
+ reason: str = "Operator aborted",
113
+ ) -> WorkflowExecutionResult:
114
+ """Abort a workflow and unwind completed steps via compensation hooks.
115
+
116
+ Args:
117
+ run_id: Unique identifier of the workflow run.
118
+ workflow: WorkflowDefinition specification containing compensation hooks.
119
+ reason: Operator rationale or explanation for cancellation.
120
+
121
+ Returns:
122
+ WorkflowExecutionResult reflecting the aborted state.
123
+ """
124
+ state = self._engine.abort(run_id, workflow)
125
+
126
+ if self._event_bus is not None:
127
+ self._event_bus.publish(
128
+ WorkflowAbortedEvent(
129
+ run_id=state.run_id,
130
+ workflow_name=state.workflow_name,
131
+ reason=reason,
132
+ aborted_at=datetime.now(UTC),
133
+ )
134
+ )
135
+
136
+ return self._to_result(state)
137
+
138
+ def _emit_terminal_events(self, state: WorkflowExecutionState) -> None:
139
+ """Publish lifecycle events based on execution state outcome.
140
+
141
+ Args:
142
+ state: WorkflowExecutionState to inspect.
143
+ """
144
+ if self._event_bus is None:
145
+ return
146
+
147
+ if state.status == WorkflowStatus.COMPLETED:
148
+ self._event_bus.publish(
149
+ WorkflowCompletedEvent(
150
+ run_id=state.run_id,
151
+ workflow_name=state.workflow_name,
152
+ completed_at=state.finished_at or datetime.now(UTC),
153
+ total_steps=len(state.step_checkpoints),
154
+ )
155
+ )
156
+ elif state.status == WorkflowStatus.SUSPENDED:
157
+ failed_step = next(
158
+ (
159
+ name
160
+ for name, chk in state.step_checkpoints.items()
161
+ if chk.status == StepStatus.FAILED
162
+ ),
163
+ "unknown",
164
+ )
165
+ self._event_bus.publish(
166
+ WorkflowSuspendedEvent(
167
+ run_id=state.run_id,
168
+ stage_name=state.current_stage or "unknown",
169
+ step_name=failed_step,
170
+ error_type="StepExecutionError",
171
+ error_message=state.error_summary
172
+ or "Step failed and exhausted retries.",
173
+ )
174
+ )
175
+
176
+ @staticmethod
177
+ def _to_result(state: WorkflowExecutionState) -> WorkflowExecutionResult:
178
+ """Map a hexaflow WorkflowExecutionState into a WorkflowExecutionResult.
179
+
180
+ Args:
181
+ state: Engine execution state.
182
+
183
+ Returns:
184
+ Pydantic WorkflowExecutionResult value object.
185
+ """
186
+ completed = tuple(
187
+ name
188
+ for name, chk in state.step_checkpoints.items()
189
+ if chk.status == StepStatus.COMPLETED
190
+ )
191
+ failed = tuple(
192
+ name
193
+ for name, chk in state.step_checkpoints.items()
194
+ if chk.status == StepStatus.FAILED
195
+ )
196
+ outputs = {
197
+ name: chk.output_payload
198
+ for name, chk in state.step_checkpoints.items()
199
+ if chk.output_payload is not None
200
+ }
201
+
202
+ return WorkflowExecutionResult(
203
+ run_id=state.run_id,
204
+ workflow_name=state.workflow_name,
205
+ status=state.status.value,
206
+ completed_steps=completed,
207
+ failed_steps=failed,
208
+ outputs=outputs,
209
+ started_at=state.started_at,
210
+ ended_at=state.finished_at or datetime.now(UTC),
211
+ error_summary=state.error_summary,
212
+ )
@@ -0,0 +1,200 @@
1
+ """CQRS Command and Query adapters for hexaflow steps.
2
+
3
+ Notes/Architectural Intent:
4
+ Bridges hexaflow StepDefinition callables directly to Hexastack CommandBusPort
5
+ and QueryBusPort. Enables declarative execution of typed domain commands
6
+ within workflow DAGs, automatically inheriting the full CQRS middleware chain.
7
+ """
8
+
9
+ from collections.abc import Callable
10
+ from typing import Any
11
+
12
+ from hexaflow.domain.models import StepDefinition
13
+ from hexaflow.domain.state import StepContext
14
+
15
+ from hexastack_core.domain import Command, Query
16
+ from hexastack_cqrs.ports.buses import CommandBusPort, QueryBusPort
17
+ from hexastack_flow.domain.models import CqrsStepMetadata
18
+
19
+ __all__ = [
20
+ "as_command_step",
21
+ "as_query_step",
22
+ "CommandStep",
23
+ "QueryStep",
24
+ ]
25
+
26
+
27
+ class CommandStep:
28
+ """Step action adapter dispatching a domain Command via CommandBusPort.
29
+
30
+ Notes/Architectural Intent:
31
+ Callable step action that takes StepContext, extracts inputs to construct
32
+ a typed Command instance, and dispatches it through the CommandBus.
33
+ """
34
+
35
+ def __init__(
36
+ self,
37
+ command_factory: Callable[[StepContext], Command],
38
+ command_bus: CommandBusPort,
39
+ ) -> None:
40
+ """Initialize CommandStep with command factory and bus.
41
+
42
+ Args:
43
+ command_factory: Callable receiving StepContext and producing a Command.
44
+ command_bus: CommandBusPort implementation for dispatching.
45
+ """
46
+ self._factory = command_factory
47
+ self._bus = command_bus
48
+
49
+ def __call__(self, ctx: StepContext) -> Any:
50
+ """Execute step by creating and dispatching the domain Command.
51
+
52
+ Args:
53
+ ctx: Runtime StepContext provided by hexaflow engine.
54
+
55
+ Returns:
56
+ The result returned by the registered command handler.
57
+
58
+ Raises:
59
+ Exception: Propagates handler execution or validation errors.
60
+ """
61
+ command = self._factory(ctx)
62
+ return self._bus.dispatch(command)
63
+
64
+
65
+ class QueryStep:
66
+ """Step action adapter dispatching a domain Query via QueryBusPort.
67
+
68
+ Notes/Architectural Intent:
69
+ Callable step action that takes StepContext, constructs a typed Query,
70
+ and retrieves data via QueryBus, making query results available to downstream steps.
71
+ """
72
+
73
+ def __init__(
74
+ self,
75
+ query_factory: Callable[[StepContext], Query[Any]],
76
+ query_bus: QueryBusPort,
77
+ ) -> None:
78
+ """Initialize QueryStep with query factory and bus.
79
+
80
+ Args:
81
+ query_factory: Callable receiving StepContext and producing a Query.
82
+ query_bus: QueryBusPort implementation for dispatching.
83
+ """
84
+ self._factory = query_factory
85
+ self._bus = query_bus
86
+
87
+ def __call__(self, ctx: StepContext) -> Any:
88
+ """Execute step by creating and dispatching the domain Query.
89
+
90
+ Args:
91
+ ctx: Runtime StepContext provided by hexaflow engine.
92
+
93
+ Returns:
94
+ The query result returned by the registered query handler.
95
+
96
+ Raises:
97
+ Exception: Propagates query handler execution errors.
98
+ """
99
+ query = self._factory(ctx)
100
+ return self._bus.dispatch(query)
101
+
102
+
103
+ def as_command_step(
104
+ name: str,
105
+ command_factory: Callable[[StepContext], Command],
106
+ command_bus: CommandBusPort,
107
+ depends_on: tuple[str, ...] = (),
108
+ compensation_factory: Callable[[StepContext], Command] | None = None,
109
+ description: str = "",
110
+ timeout_seconds: float | None = None,
111
+ ) -> StepDefinition:
112
+ """Construct a hexaflow StepDefinition backed by a CommandStep.
113
+
114
+ Args:
115
+ name: Unique step identifier name.
116
+ command_factory: Callable producing the domain Command from StepContext.
117
+ command_bus: CommandBusPort to dispatch through.
118
+ depends_on: Prerequisites for join barrier synchronization.
119
+ compensation_factory: Optional compensating command factory for rollbacks.
120
+ description: Architectural description of the step.
121
+ timeout_seconds: Maximum allowed execution seconds.
122
+
123
+ Returns:
124
+ Configured hexaflow StepDefinition with attached CqrsStepMetadata.
125
+
126
+ Raises:
127
+ ValueError: If name is empty.
128
+ """
129
+ if not name:
130
+ raise ValueError("Step name must not be empty.")
131
+
132
+ action = CommandStep(command_factory, command_bus)
133
+ compensation = (
134
+ CommandStep(compensation_factory, command_bus)
135
+ if compensation_factory is not None
136
+ else None
137
+ )
138
+
139
+ metadata = {
140
+ "cqrs": CqrsStepMetadata(
141
+ step_type="command",
142
+ message_type=name,
143
+ description=description,
144
+ ).model_dump(),
145
+ }
146
+
147
+ return StepDefinition(
148
+ name=name,
149
+ action=action,
150
+ compensation=compensation,
151
+ depends_on=depends_on,
152
+ timeout_seconds=timeout_seconds,
153
+ metadata=metadata,
154
+ )
155
+
156
+
157
+ def as_query_step(
158
+ name: str,
159
+ query_factory: Callable[[StepContext], Query[Any]],
160
+ query_bus: QueryBusPort,
161
+ depends_on: tuple[str, ...] = (),
162
+ description: str = "",
163
+ timeout_seconds: float | None = None,
164
+ ) -> StepDefinition:
165
+ """Construct a hexaflow StepDefinition backed by a QueryStep.
166
+
167
+ Args:
168
+ name: Unique step identifier name.
169
+ query_factory: Callable producing the domain Query from StepContext.
170
+ query_bus: QueryBusPort to dispatch through.
171
+ depends_on: Prerequisites for join barrier synchronization.
172
+ description: Architectural description of the step.
173
+ timeout_seconds: Maximum allowed execution seconds.
174
+
175
+ Returns:
176
+ Configured hexaflow StepDefinition with attached CqrsStepMetadata.
177
+
178
+ Raises:
179
+ ValueError: If name is empty.
180
+ """
181
+ if not name:
182
+ raise ValueError("Step name must not be empty.")
183
+
184
+ action = QueryStep(query_factory, query_bus)
185
+ metadata = {
186
+ "cqrs": CqrsStepMetadata(
187
+ step_type="query",
188
+ message_type=name,
189
+ description=description,
190
+ requires_transaction=False,
191
+ ).model_dump(),
192
+ }
193
+
194
+ return StepDefinition(
195
+ name=name,
196
+ action=action,
197
+ depends_on=depends_on,
198
+ timeout_seconds=timeout_seconds,
199
+ metadata=metadata,
200
+ )
@@ -0,0 +1,11 @@
1
+ """Event adapters for hexastack-flow.
2
+
3
+ Notes/Architectural Intent:
4
+ Adapters publishing workflow lifecycle events over Hexastack event buses.
5
+ """
6
+
7
+ from hexastack_flow.adapters.events.publisher import WorkflowEventPublisher
8
+
9
+ __all__ = [
10
+ "WorkflowEventPublisher",
11
+ ]
@@ -0,0 +1,151 @@
1
+ """Event publishing adapter broadcasting workflow state transitions.
2
+
3
+ Notes/Architectural Intent:
4
+ Connects workflow engine state transitions to Hexastack event buses,
5
+ allowing distributed subscribers or local event handlers to react to
6
+ workflow progress, suspension, or completion.
7
+ """
8
+
9
+ from datetime import UTC, datetime
10
+ from typing import Any
11
+
12
+ from hexastack_cqrs.ports.buses import EventBusPort
13
+ from hexastack_flow.domain.events import (
14
+ WorkflowAbortedEvent,
15
+ WorkflowCompletedEvent,
16
+ WorkflowStartedEvent,
17
+ WorkflowStepCompletedEvent,
18
+ WorkflowSuspendedEvent,
19
+ )
20
+
21
+ __all__ = [
22
+ "WorkflowEventPublisher",
23
+ ]
24
+
25
+
26
+ class WorkflowEventPublisher:
27
+ """Publisher broadcasting workflow domain events to an EventBusPort.
28
+
29
+ Notes/Architectural Intent:
30
+ Encapsulates construction and dispatching of domain events across
31
+ in-process or distributed event buses.
32
+ """
33
+
34
+ def __init__(self, event_bus: EventBusPort) -> None:
35
+ """Initialize publisher with an event bus.
36
+
37
+ Args:
38
+ event_bus: The EventBusPort to publish events through.
39
+ """
40
+ self._bus = event_bus
41
+
42
+ def publish_started(self, run_id: str, workflow_name: str) -> None:
43
+ """Broadcast WorkflowStartedEvent.
44
+
45
+ Args:
46
+ run_id: Workflow execution run identifier.
47
+ workflow_name: Name of the workflow definition.
48
+ """
49
+ event = WorkflowStartedEvent(
50
+ run_id=run_id,
51
+ workflow_name=workflow_name,
52
+ started_at=datetime.now(UTC),
53
+ )
54
+ self._bus.publish(event)
55
+
56
+ def publish_step_completed(
57
+ self,
58
+ run_id: str,
59
+ stage_name: str,
60
+ step_name: str,
61
+ attempt_number: int,
62
+ duration_seconds: float,
63
+ output_summary: dict[str, Any] | None = None,
64
+ ) -> None:
65
+ """Broadcast WorkflowStepCompletedEvent.
66
+
67
+ Args:
68
+ run_id: Workflow execution run identifier.
69
+ stage_name: Enclosing stage name.
70
+ step_name: Name of the completed step.
71
+ attempt_number: Attempt count.
72
+ duration_seconds: Step execution time in seconds.
73
+ output_summary: Optional dictionary summarizing key output values.
74
+ """
75
+ event = WorkflowStepCompletedEvent(
76
+ run_id=run_id,
77
+ stage_name=stage_name,
78
+ step_name=step_name,
79
+ attempt_number=attempt_number,
80
+ duration_seconds=duration_seconds,
81
+ output_summary=output_summary or {},
82
+ )
83
+ self._bus.publish(event)
84
+
85
+ def publish_suspended(
86
+ self,
87
+ run_id: str,
88
+ stage_name: str,
89
+ step_name: str,
90
+ error_type: str,
91
+ error_message: str,
92
+ ) -> None:
93
+ """Broadcast WorkflowSuspendedEvent.
94
+
95
+ Args:
96
+ run_id: Workflow execution run identifier.
97
+ stage_name: Stage where suspension occurred.
98
+ step_name: Step that failed permanently.
99
+ error_type: Exception class name.
100
+ error_message: Human-readable error description.
101
+ """
102
+ event = WorkflowSuspendedEvent(
103
+ run_id=run_id,
104
+ stage_name=stage_name,
105
+ step_name=step_name,
106
+ error_type=error_type,
107
+ error_message=error_message,
108
+ )
109
+ self._bus.publish(event)
110
+
111
+ def publish_completed(
112
+ self,
113
+ run_id: str,
114
+ workflow_name: str,
115
+ total_steps: int,
116
+ ) -> None:
117
+ """Broadcast WorkflowCompletedEvent.
118
+
119
+ Args:
120
+ run_id: Workflow execution run identifier.
121
+ workflow_name: Name of the completed workflow.
122
+ total_steps: Total count of steps evaluated.
123
+ """
124
+ event = WorkflowCompletedEvent(
125
+ run_id=run_id,
126
+ workflow_name=workflow_name,
127
+ completed_at=datetime.now(UTC),
128
+ total_steps=total_steps,
129
+ )
130
+ self._bus.publish(event)
131
+
132
+ def publish_aborted(
133
+ self,
134
+ run_id: str,
135
+ workflow_name: str,
136
+ reason: str,
137
+ ) -> None:
138
+ """Broadcast WorkflowAbortedEvent.
139
+
140
+ Args:
141
+ run_id: Workflow execution run identifier.
142
+ workflow_name: Name of the aborted workflow.
143
+ reason: Cancellation reason description.
144
+ """
145
+ event = WorkflowAbortedEvent(
146
+ run_id=run_id,
147
+ workflow_name=workflow_name,
148
+ reason=reason,
149
+ aborted_at=datetime.now(UTC),
150
+ )
151
+ self._bus.publish(event)
@@ -0,0 +1,12 @@
1
+ """Storage adapters for hexastack-flow state persistence.
2
+
3
+ Notes/Architectural Intent:
4
+ Provides relational database persistence for hexaflow workflow states
5
+ and step checkpoints.
6
+ """
7
+
8
+ from hexastack_flow.adapters.storage.sqlalchemy import SqlAlchemyWorkflowStore
9
+
10
+ __all__ = [
11
+ "SqlAlchemyWorkflowStore",
12
+ ]