durable_workflow 0.1.0
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.
- checksums.yaml +7 -0
- data/.claude/todo/01.amend.md +133 -0
- data/.claude/todo/02.amend.md +444 -0
- data/.claude/todo/phase-1-core/01-GEMSPEC.md +193 -0
- data/.claude/todo/phase-1-core/02-TYPES.md +462 -0
- data/.claude/todo/phase-1-core/03-EXECUTION.md +551 -0
- data/.claude/todo/phase-1-core/04-STEPS.md +603 -0
- data/.claude/todo/phase-1-core/05-PARSER.md +719 -0
- data/.claude/todo/phase-1-core/todo.md +574 -0
- data/.claude/todo/phase-2-runtime/01-STORAGE.md +641 -0
- data/.claude/todo/phase-2-runtime/02-RUNNERS.md +511 -0
- data/.claude/todo/phase-3-extensions/01-EXTENSION-SYSTEM.md +298 -0
- data/.claude/todo/phase-3-extensions/02-AI-PLUGIN.md +936 -0
- data/.claude/todo/phase-3-extensions/todo.md +262 -0
- data/.claude/todo/phase-4-ai-rework/01-DEPENDENCIES.md +107 -0
- data/.claude/todo/phase-4-ai-rework/02-CONFIGURATION.md +123 -0
- data/.claude/todo/phase-4-ai-rework/03-TOOL-REGISTRY.md +237 -0
- data/.claude/todo/phase-4-ai-rework/04-MCP-SERVER.md +432 -0
- data/.claude/todo/phase-4-ai-rework/05-MCP-CLIENT.md +333 -0
- data/.claude/todo/phase-4-ai-rework/06-EXECUTORS.md +397 -0
- data/.claude/todo/phase-4-ai-rework/todo.md +265 -0
- data/.claude/todo/phase-5-validation/.DS_Store +0 -0
- data/.claude/todo/phase-5-validation/01-TEST-GAPS.md +615 -0
- data/.claude/todo/phase-5-validation/01-TESTS.md +2378 -0
- data/.claude/todo/phase-5-validation/02-EXAMPLES-SIMPLE.md +744 -0
- data/.claude/todo/phase-5-validation/02-EXAMPLES.md +1857 -0
- data/.claude/todo/phase-5-validation/03-EXAMPLE-SUPPORT-AGENT.md +95 -0
- data/.claude/todo/phase-5-validation/04-EXAMPLE-ORDER-FULFILLMENT.md +94 -0
- data/.claude/todo/phase-5-validation/05-EXAMPLE-DATA-PIPELINE.md +145 -0
- data/.env.example +3 -0
- data/.rubocop.yml +64 -0
- data/0.3.amend.md +89 -0
- data/CHANGELOG.md +5 -0
- data/CODE_OF_CONDUCT.md +84 -0
- data/Gemfile +22 -0
- data/Gemfile.lock +192 -0
- data/LICENSE.txt +21 -0
- data/README.md +39 -0
- data/Rakefile +16 -0
- data/durable_workflow.gemspec +43 -0
- data/examples/approval_request.rb +106 -0
- data/examples/calculator.rb +154 -0
- data/examples/file_search_demo.rb +77 -0
- data/examples/hello_workflow.rb +57 -0
- data/examples/item_processor.rb +96 -0
- data/examples/order_fulfillment/Gemfile +6 -0
- data/examples/order_fulfillment/README.md +84 -0
- data/examples/order_fulfillment/run.rb +85 -0
- data/examples/order_fulfillment/services.rb +146 -0
- data/examples/order_fulfillment/workflow.yml +188 -0
- data/examples/parallel_fetch.rb +102 -0
- data/examples/service_integration.rb +137 -0
- data/examples/support_agent/Gemfile +6 -0
- data/examples/support_agent/README.md +91 -0
- data/examples/support_agent/config/claude_desktop.json +12 -0
- data/examples/support_agent/mcp_server.rb +49 -0
- data/examples/support_agent/run.rb +67 -0
- data/examples/support_agent/services.rb +113 -0
- data/examples/support_agent/workflow.yml +286 -0
- data/lib/durable_workflow/core/condition.rb +45 -0
- data/lib/durable_workflow/core/engine.rb +145 -0
- data/lib/durable_workflow/core/executors/approval.rb +51 -0
- data/lib/durable_workflow/core/executors/assign.rb +18 -0
- data/lib/durable_workflow/core/executors/base.rb +90 -0
- data/lib/durable_workflow/core/executors/call.rb +76 -0
- data/lib/durable_workflow/core/executors/end.rb +19 -0
- data/lib/durable_workflow/core/executors/halt.rb +24 -0
- data/lib/durable_workflow/core/executors/loop.rb +118 -0
- data/lib/durable_workflow/core/executors/parallel.rb +77 -0
- data/lib/durable_workflow/core/executors/registry.rb +34 -0
- data/lib/durable_workflow/core/executors/router.rb +26 -0
- data/lib/durable_workflow/core/executors/start.rb +61 -0
- data/lib/durable_workflow/core/executors/transform.rb +71 -0
- data/lib/durable_workflow/core/executors/workflow.rb +32 -0
- data/lib/durable_workflow/core/parser.rb +189 -0
- data/lib/durable_workflow/core/resolver.rb +61 -0
- data/lib/durable_workflow/core/schema_validator.rb +47 -0
- data/lib/durable_workflow/core/types/base.rb +41 -0
- data/lib/durable_workflow/core/types/condition.rb +25 -0
- data/lib/durable_workflow/core/types/configs.rb +103 -0
- data/lib/durable_workflow/core/types/entry.rb +26 -0
- data/lib/durable_workflow/core/types/results.rb +41 -0
- data/lib/durable_workflow/core/types/state.rb +95 -0
- data/lib/durable_workflow/core/types/step_def.rb +15 -0
- data/lib/durable_workflow/core/types/workflow_def.rb +43 -0
- data/lib/durable_workflow/core/types.rb +29 -0
- data/lib/durable_workflow/core/validator.rb +318 -0
- data/lib/durable_workflow/extensions/ai/ai.rb +149 -0
- data/lib/durable_workflow/extensions/ai/configuration.rb +41 -0
- data/lib/durable_workflow/extensions/ai/executors/agent.rb +150 -0
- data/lib/durable_workflow/extensions/ai/executors/file_search.rb +52 -0
- data/lib/durable_workflow/extensions/ai/executors/guardrail.rb +152 -0
- data/lib/durable_workflow/extensions/ai/executors/handoff.rb +33 -0
- data/lib/durable_workflow/extensions/ai/executors/mcp.rb +47 -0
- data/lib/durable_workflow/extensions/ai/mcp/adapter.rb +73 -0
- data/lib/durable_workflow/extensions/ai/mcp/client.rb +77 -0
- data/lib/durable_workflow/extensions/ai/mcp/rack_app.rb +66 -0
- data/lib/durable_workflow/extensions/ai/mcp/server.rb +122 -0
- data/lib/durable_workflow/extensions/ai/tool_registry.rb +63 -0
- data/lib/durable_workflow/extensions/ai/types.rb +213 -0
- data/lib/durable_workflow/extensions/ai.rb +6 -0
- data/lib/durable_workflow/extensions/base.rb +77 -0
- data/lib/durable_workflow/runners/adapters/inline.rb +42 -0
- data/lib/durable_workflow/runners/adapters/sidekiq.rb +69 -0
- data/lib/durable_workflow/runners/async.rb +100 -0
- data/lib/durable_workflow/runners/stream.rb +126 -0
- data/lib/durable_workflow/runners/sync.rb +40 -0
- data/lib/durable_workflow/storage/active_record.rb +148 -0
- data/lib/durable_workflow/storage/redis.rb +133 -0
- data/lib/durable_workflow/storage/sequel.rb +144 -0
- data/lib/durable_workflow/storage/store.rb +43 -0
- data/lib/durable_workflow/utils.rb +25 -0
- data/lib/durable_workflow/version.rb +5 -0
- data/lib/durable_workflow.rb +70 -0
- data/sig/durable_workflow.rbs +4 -0
- metadata +275 -0
|
@@ -0,0 +1,574 @@
|
|
|
1
|
+
# Phase 1 Core - Implementation & Test Coverage Todo
|
|
2
|
+
|
|
3
|
+
## Status Legend
|
|
4
|
+
|
|
5
|
+
- [ ] Not started
|
|
6
|
+
- [~] In progress
|
|
7
|
+
- [x] Completed
|
|
8
|
+
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
## 1. GEMSPEC & BASE SETUP (01-GEMSPEC.md)
|
|
12
|
+
|
|
13
|
+
### 1.1 Implementation
|
|
14
|
+
|
|
15
|
+
- [x] Update `durable_workflow.gemspec` with proper metadata and dependencies (dry-types, dry-struct)
|
|
16
|
+
- [x] Update `Gemfile` with development dependencies (rake, minitest, rubocop, async, redis, ruby_llm)
|
|
17
|
+
- [x] Update `lib/durable_workflow/version.rb` to VERSION = "0.1.0" (already correct)
|
|
18
|
+
- [x] Rewrite `lib/durable_workflow.rb` with module structure, Config, error classes, registry, and requires
|
|
19
|
+
- [x] Create `lib/durable_workflow/utils.rb` with `deep_symbolize` helper
|
|
20
|
+
|
|
21
|
+
### 1.2 Tests
|
|
22
|
+
|
|
23
|
+
- [x] Test: `DurableWorkflow::VERSION` is "0.1.0"
|
|
24
|
+
- [x] Test: `DurableWorkflow::Error` exists and is a StandardError
|
|
25
|
+
- [x] Test: `DurableWorkflow::ConfigError` exists
|
|
26
|
+
- [x] Test: `DurableWorkflow::ValidationError` exists
|
|
27
|
+
- [x] Test: `DurableWorkflow::ExecutionError` exists
|
|
28
|
+
- [x] Test: `DurableWorkflow.configure` yields a Config struct
|
|
29
|
+
- [x] Test: `DurableWorkflow.config` returns configured values
|
|
30
|
+
- [x] Test: `DurableWorkflow.registry` returns a hash
|
|
31
|
+
- [x] Test: `DurableWorkflow.register(workflow)` adds to registry
|
|
32
|
+
- [x] Test: `DurableWorkflow::Utils.deep_symbolize` converts string keys to symbols recursively
|
|
33
|
+
- [x] Test: `DurableWorkflow::Utils.deep_symbolize` handles nested hashes
|
|
34
|
+
- [x] Test: `DurableWorkflow::Utils.deep_symbolize` handles arrays with hashes
|
|
35
|
+
|
|
36
|
+
---
|
|
37
|
+
|
|
38
|
+
## 2. CORE TYPES (02-TYPES.md)
|
|
39
|
+
|
|
40
|
+
### 2.1 Implementation
|
|
41
|
+
|
|
42
|
+
- [x] Create directory `lib/durable_workflow/core/types/`
|
|
43
|
+
- [x] Create `lib/durable_workflow/core/types.rb` (loader)
|
|
44
|
+
- [x] Create `lib/durable_workflow/core/types/base.rb` with Types module and BaseStruct
|
|
45
|
+
- [x] Create `lib/durable_workflow/core/types/condition.rb` with Condition and Route structs
|
|
46
|
+
- [x] Create `lib/durable_workflow/core/types/configs.rb` with all config classes (StartConfig, EndConfig, CallConfig, AssignConfig, RouterConfig, LoopConfig, HaltConfig, ApprovalConfig, TransformConfig, ParallelConfig, WorkflowConfig)
|
|
47
|
+
- [x] Create `lib/durable_workflow/core/types/step_def.rb` with StepDef struct
|
|
48
|
+
- [x] Create `lib/durable_workflow/core/types/workflow_def.rb` with InputDef and WorkflowDef structs
|
|
49
|
+
- [x] Create `lib/durable_workflow/core/types/state.rb` with State and Execution structs
|
|
50
|
+
- [x] Create `lib/durable_workflow/core/types/entry.rb` with Entry struct
|
|
51
|
+
- [x] Create `lib/durable_workflow/core/types/results.rb` with ContinueResult, HaltResult, ErrorResult, ExecutionResult, StepOutcome structs
|
|
52
|
+
- [x] Add CONFIG_REGISTRY hash and `Core.register_config(type, klass)` method
|
|
53
|
+
|
|
54
|
+
### 2.2 Tests - Base Types
|
|
55
|
+
|
|
56
|
+
- [x] Test: `Types::StepType` accepts any string
|
|
57
|
+
- [x] Test: `Types::Operator` accepts valid operators (eq, neq, gt, gte, lt, lte, contains, starts_with, ends_with, in, exists)
|
|
58
|
+
- [x] Test: `Types::Operator` rejects invalid operators
|
|
59
|
+
- [x] Test: `Types::EntryAction` accepts :completed, :halted, :failed
|
|
60
|
+
- [x] Test: `Types::WaitMode` defaults to "all"
|
|
61
|
+
- [x] Test: `Types::WaitMode` accepts "all", "any", or integer
|
|
62
|
+
|
|
63
|
+
### 2.3 Tests - Condition & Route
|
|
64
|
+
|
|
65
|
+
- [x] Test: `Condition` can be created with field, op, value
|
|
66
|
+
- [x] Test: `Condition.op` defaults to "eq"
|
|
67
|
+
- [x] Test: `Route` can be created with field, op, value, target
|
|
68
|
+
|
|
69
|
+
### 2.4 Tests - Config Structs
|
|
70
|
+
|
|
71
|
+
- [x] Test: `StartConfig` can be created with validate_input option
|
|
72
|
+
- [x] Test: `EndConfig` can be created with result option
|
|
73
|
+
- [x] Test: `CallConfig` requires service and method_name
|
|
74
|
+
- [x] Test: `CallConfig` retries defaults to 0
|
|
75
|
+
- [x] Test: `CallConfig` retry_delay defaults to 1.0
|
|
76
|
+
- [x] Test: `CallConfig` retry_backoff defaults to 2.0
|
|
77
|
+
- [x] Test: `AssignConfig` set defaults to empty hash
|
|
78
|
+
- [x] Test: `RouterConfig` routes defaults to empty array
|
|
79
|
+
- [x] Test: `LoopConfig` as defaults to :item
|
|
80
|
+
- [x] Test: `LoopConfig` index_as defaults to :index
|
|
81
|
+
- [x] Test: `LoopConfig` max defaults to 100
|
|
82
|
+
- [x] Test: `HaltConfig` data defaults to empty hash
|
|
83
|
+
- [x] Test: `ApprovalConfig` requires prompt
|
|
84
|
+
- [x] Test: `TransformConfig` requires expression and output
|
|
85
|
+
- [x] Test: `ParallelConfig` branches defaults to empty array
|
|
86
|
+
- [x] Test: `WorkflowConfig` requires workflow_id
|
|
87
|
+
|
|
88
|
+
### 2.5 Tests - StepDef
|
|
89
|
+
|
|
90
|
+
- [x] Test: `StepDef` can be created with id, type, config
|
|
91
|
+
- [x] Test: `StepDef.type` is a string (not enum)
|
|
92
|
+
- [x] Test: `StepDef.terminal?` returns true for "end" type
|
|
93
|
+
- [x] Test: `StepDef.terminal?` returns false for other types
|
|
94
|
+
|
|
95
|
+
### 2.6 Tests - WorkflowDef
|
|
96
|
+
|
|
97
|
+
- [x] Test: `InputDef` can be created with name
|
|
98
|
+
- [x] Test: `InputDef.type` defaults to "string"
|
|
99
|
+
- [x] Test: `InputDef.required` defaults to true
|
|
100
|
+
- [x] Test: `WorkflowDef` can be created with id, name, steps
|
|
101
|
+
- [x] Test: `WorkflowDef.version` defaults to "1.0"
|
|
102
|
+
- [x] Test: `WorkflowDef.find_step(id)` returns correct step
|
|
103
|
+
- [x] Test: `WorkflowDef.first_step` returns first step
|
|
104
|
+
- [x] Test: `WorkflowDef.step_ids` returns array of step ids
|
|
105
|
+
- [x] Test: `WorkflowDef.extensions` defaults to empty hash
|
|
106
|
+
|
|
107
|
+
### 2.7 Tests - State
|
|
108
|
+
|
|
109
|
+
- [x] Test: `State` can be created with execution_id, workflow_id
|
|
110
|
+
- [x] Test: `State.input` defaults to empty hash
|
|
111
|
+
- [x] Test: `State.ctx` defaults to empty hash
|
|
112
|
+
- [x] Test: `State.history` defaults to empty array
|
|
113
|
+
- [x] Test: `State.with(**updates)` returns new State with updates
|
|
114
|
+
- [x] Test: `State.with_ctx(**updates)` merges into ctx
|
|
115
|
+
- [x] Test: `State.with_current_step(step_id)` updates current_step
|
|
116
|
+
- [x] Test: `State.from_h(hash)` creates State from hash
|
|
117
|
+
|
|
118
|
+
### 2.8 Tests - Execution
|
|
119
|
+
|
|
120
|
+
- [x] Test: `Execution` can be created with required fields
|
|
121
|
+
- [x] Test: `Execution` stores status, halt_data, recover_to
|
|
122
|
+
|
|
123
|
+
### 2.9 Tests - Entry
|
|
124
|
+
|
|
125
|
+
- [x] Test: `Entry` can be created with required fields
|
|
126
|
+
- [x] Test: `Entry.from_h(hash)` parses action as symbol
|
|
127
|
+
- [x] Test: `Entry.from_h(hash)` parses timestamp string
|
|
128
|
+
|
|
129
|
+
### 2.10 Tests - Results
|
|
130
|
+
|
|
131
|
+
- [x] Test: `ContinueResult` can be created with next_step, output
|
|
132
|
+
- [x] Test: `HaltResult` requires data hash
|
|
133
|
+
- [x] Test: `HaltResult.output` returns data
|
|
134
|
+
- [x] Test: `ErrorResult` requires error and step_id
|
|
135
|
+
- [x] Test: `ExecutionResult` status can be :completed, :halted, :failed
|
|
136
|
+
- [x] Test: `ExecutionResult.completed?` returns true when status is :completed
|
|
137
|
+
- [x] Test: `ExecutionResult.halted?` returns true when status is :halted
|
|
138
|
+
- [x] Test: `ExecutionResult.failed?` returns true when status is :failed
|
|
139
|
+
- [x] Test: `StepOutcome` contains state and result
|
|
140
|
+
|
|
141
|
+
### 2.11 Tests - Registry
|
|
142
|
+
|
|
143
|
+
- [x] Test: `CONFIG_REGISTRY` contains all core config types
|
|
144
|
+
- [x] Test: `Core.register_config(type, klass)` adds to registry
|
|
145
|
+
|
|
146
|
+
---
|
|
147
|
+
|
|
148
|
+
## 3. EXECUTION INFRASTRUCTURE (03-EXECUTION.md)
|
|
149
|
+
|
|
150
|
+
### 3.1 Implementation
|
|
151
|
+
|
|
152
|
+
- [x] Create directory `lib/durable_workflow/core/executors/`
|
|
153
|
+
- [x] Create `lib/durable_workflow/core/executors/registry.rb` with Executors::Registry class
|
|
154
|
+
- [x] Create `lib/durable_workflow/core/executors/base.rb` with Executors::Base class
|
|
155
|
+
- [x] Create `lib/durable_workflow/core/resolver.rb` with Resolver class
|
|
156
|
+
- [x] Create `lib/durable_workflow/core/condition.rb` with ConditionEvaluator class
|
|
157
|
+
- [x] Create `lib/durable_workflow/core/validator.rb` with Validator class
|
|
158
|
+
- [x] Create `lib/durable_workflow/core/engine.rb` with Engine class
|
|
159
|
+
|
|
160
|
+
### 3.2 Tests - Executors::Registry
|
|
161
|
+
|
|
162
|
+
- [x] Test: `Registry.register(type, klass)` stores executor
|
|
163
|
+
- [x] Test: `Registry[type]` returns registered executor
|
|
164
|
+
- [x] Test: `Registry.types` returns all registered types
|
|
165
|
+
- [x] Test: `Registry.registered?(type)` returns true for registered
|
|
166
|
+
- [x] Test: `Registry.registered?(type)` returns false for unregistered
|
|
167
|
+
|
|
168
|
+
### 3.3 Tests - Executors::Base
|
|
169
|
+
|
|
170
|
+
- [x] Test: Base initializes with step
|
|
171
|
+
- [x] Test: Base exposes step and config
|
|
172
|
+
- [x] Test: Base.call raises NotImplementedError
|
|
173
|
+
- [x] Test: Base.resolve delegates to Resolver
|
|
174
|
+
- [x] Test: Base.continue returns StepOutcome with ContinueResult
|
|
175
|
+
- [x] Test: Base.halt returns StepOutcome with HaltResult
|
|
176
|
+
- [x] Test: Base.store returns new state with value in ctx
|
|
177
|
+
- [x] Test: Base.with_timeout raises ExecutionError on timeout
|
|
178
|
+
- [x] Test: Base.with_retry retries on failure
|
|
179
|
+
- [x] Test: Base.with_retry respects max_retries
|
|
180
|
+
- [x] Test: Base.with_retry uses backoff delay
|
|
181
|
+
|
|
182
|
+
### 3.4 Tests - Resolver
|
|
183
|
+
|
|
184
|
+
- [x] Test: `Resolver.resolve` returns non-string values unchanged
|
|
185
|
+
- [x] Test: `Resolver.resolve` resolves `$input` reference
|
|
186
|
+
- [x] Test: `Resolver.resolve` resolves `$input.field` nested reference
|
|
187
|
+
- [x] Test: `Resolver.resolve` resolves `$ctx_var` from ctx
|
|
188
|
+
- [x] Test: `Resolver.resolve` resolves `$now` to Time
|
|
189
|
+
- [x] Test: `Resolver.resolve` resolves `$history` to history array
|
|
190
|
+
- [x] Test: `Resolver.resolve` interpolates multiple refs in string
|
|
191
|
+
- [x] Test: `Resolver.resolve` handles hashes recursively
|
|
192
|
+
- [x] Test: `Resolver.resolve` handles arrays recursively
|
|
193
|
+
- [x] Test: `Resolver.resolve_ref` digs into nested hash
|
|
194
|
+
- [x] Test: `Resolver.resolve_ref` accesses array by index
|
|
195
|
+
|
|
196
|
+
### 3.5 Tests - ConditionEvaluator
|
|
197
|
+
|
|
198
|
+
- [x] Test: `match?` with "eq" operator
|
|
199
|
+
- [x] Test: `match?` with "neq" operator
|
|
200
|
+
- [x] Test: `match?` with "gt" operator
|
|
201
|
+
- [x] Test: `match?` with "gte" operator
|
|
202
|
+
- [x] Test: `match?` with "lt" operator
|
|
203
|
+
- [x] Test: `match?` with "lte" operator
|
|
204
|
+
- [x] Test: `match?` with "in" operator
|
|
205
|
+
- [x] Test: `match?` with "not_in" operator
|
|
206
|
+
- [x] Test: `match?` with "contains" operator
|
|
207
|
+
- [x] Test: `match?` with "starts_with" operator
|
|
208
|
+
- [x] Test: `match?` with "ends_with" operator
|
|
209
|
+
- [x] Test: `match?` with "matches" operator (regex)
|
|
210
|
+
- [x] Test: `match?` with "exists" operator
|
|
211
|
+
- [x] Test: `match?` with "empty" operator
|
|
212
|
+
- [x] Test: `match?` with "truthy" operator
|
|
213
|
+
- [x] Test: `match?` with "falsy" operator
|
|
214
|
+
- [x] Test: `find_route` returns first matching route
|
|
215
|
+
- [x] Test: `find_route` returns nil when no match
|
|
216
|
+
|
|
217
|
+
### 3.6 Tests - Validator
|
|
218
|
+
|
|
219
|
+
- [x] Test: `validate!` passes for valid workflow
|
|
220
|
+
- [x] Test: `validate!` fails on duplicate step IDs
|
|
221
|
+
- [x] Test: `validate!` fails on unknown step types
|
|
222
|
+
- [x] Test: `validate!` fails on invalid next_step reference
|
|
223
|
+
- [x] Test: `validate!` fails on invalid on_error reference
|
|
224
|
+
- [x] Test: `validate!` fails on invalid router route target
|
|
225
|
+
- [x] Test: `validate!` fails on invalid router default
|
|
226
|
+
- [x] Test: `validate!` fails on invalid loop on_exhausted
|
|
227
|
+
- [x] Test: `validate!` fails on invalid halt resume_step
|
|
228
|
+
- [x] Test: `validate!` fails on invalid approval on_reject
|
|
229
|
+
- [x] Test: `validate!` fails on unreachable steps
|
|
230
|
+
- [x] Test: `valid?` returns true for valid workflow
|
|
231
|
+
- [x] Test: `valid?` returns false for invalid workflow
|
|
232
|
+
|
|
233
|
+
### 3.7 Tests - Engine
|
|
234
|
+
|
|
235
|
+
- [x] Test: Engine initializes with workflow
|
|
236
|
+
- [x] Test: Engine raises ConfigError without store configured
|
|
237
|
+
- [x] Test: Engine uses provided store
|
|
238
|
+
- [x] Test: `run(input)` returns ExecutionResult
|
|
239
|
+
- [x] Test: `run(input)` generates execution_id
|
|
240
|
+
- [x] Test: `run(input, execution_id:)` uses provided id
|
|
241
|
+
- [x] Test: `run(input)` executes steps in sequence
|
|
242
|
+
- [x] Test: `run(input)` returns :completed status on success
|
|
243
|
+
- [x] Test: `run(input)` returns :halted status on halt
|
|
244
|
+
- [x] Test: `run(input)` returns :failed status on error
|
|
245
|
+
- [x] Test: `run(input)` saves state to store
|
|
246
|
+
- [x] Test: `run(input)` records entries for each step
|
|
247
|
+
- [x] Test: `run(input)` handles on_error routing
|
|
248
|
+
- [x] Test: `run(input)` respects workflow timeout
|
|
249
|
+
- [x] Test: `resume(execution_id)` continues halted workflow
|
|
250
|
+
- [x] Test: `resume(execution_id, response:)` adds response to ctx
|
|
251
|
+
- [x] Test: `resume(execution_id, approved:)` adds approved to ctx
|
|
252
|
+
- [x] Test: `resume(execution_id)` raises for unknown execution
|
|
253
|
+
|
|
254
|
+
---
|
|
255
|
+
|
|
256
|
+
## 4. STEP EXECUTORS (04-STEPS.md)
|
|
257
|
+
|
|
258
|
+
### 4.1 Implementation
|
|
259
|
+
|
|
260
|
+
- [x] Create `lib/durable_workflow/core/executors/start.rb`
|
|
261
|
+
- [x] Create `lib/durable_workflow/core/executors/end.rb`
|
|
262
|
+
- [x] Create `lib/durable_workflow/core/executors/assign.rb`
|
|
263
|
+
- [x] Create `lib/durable_workflow/core/executors/call.rb`
|
|
264
|
+
- [x] Create `lib/durable_workflow/core/executors/router.rb`
|
|
265
|
+
- [x] Create `lib/durable_workflow/core/executors/loop.rb`
|
|
266
|
+
- [x] Create `lib/durable_workflow/core/executors/parallel.rb`
|
|
267
|
+
- [x] Create `lib/durable_workflow/core/executors/transform.rb`
|
|
268
|
+
- [x] Create `lib/durable_workflow/core/executors/halt.rb`
|
|
269
|
+
- [x] Create `lib/durable_workflow/core/executors/approval.rb`
|
|
270
|
+
- [x] Create `lib/durable_workflow/core/executors/workflow.rb` (sub-workflow)
|
|
271
|
+
|
|
272
|
+
### 4.2 Tests - Start Executor
|
|
273
|
+
|
|
274
|
+
- [x] Test: Start executor is registered as "start"
|
|
275
|
+
- [x] Test: Start stores input in ctx
|
|
276
|
+
- [x] Test: Start validates required inputs
|
|
277
|
+
- [x] Test: Start raises ValidationError for missing required input
|
|
278
|
+
- [x] Test: Start validates input types
|
|
279
|
+
- [x] Test: Start applies default values
|
|
280
|
+
- [x] Test: Start continues to next step
|
|
281
|
+
|
|
282
|
+
### 4.3 Tests - End Executor
|
|
283
|
+
|
|
284
|
+
- [x] Test: End executor is registered as "end"
|
|
285
|
+
- [x] Test: End stores result in ctx
|
|
286
|
+
- [x] Test: End resolves result expression
|
|
287
|
+
- [x] Test: End returns FINISHED as next_step
|
|
288
|
+
|
|
289
|
+
### 4.4 Tests - Assign Executor
|
|
290
|
+
|
|
291
|
+
- [x] Test: Assign executor is registered as "assign"
|
|
292
|
+
- [x] Test: Assign sets single variable
|
|
293
|
+
- [x] Test: Assign sets multiple variables
|
|
294
|
+
- [x] Test: Assign resolves values before assignment
|
|
295
|
+
- [x] Test: Assign continues to next step
|
|
296
|
+
|
|
297
|
+
### 4.5 Tests - Call Executor
|
|
298
|
+
|
|
299
|
+
- [x] Test: Call executor is registered as "call"
|
|
300
|
+
- [x] Test: Call invokes service method
|
|
301
|
+
- [x] Test: Call resolves service name via service_resolver
|
|
302
|
+
- [x] Test: Call resolves service name via Object.const_get
|
|
303
|
+
- [x] Test: Call resolves input before invocation
|
|
304
|
+
- [x] Test: Call stores result in output key
|
|
305
|
+
- [x] Test: Call handles method with keyword args
|
|
306
|
+
- [x] Test: Call handles method with no args
|
|
307
|
+
- [x] Test: Call handles method with positional arg
|
|
308
|
+
- [x] Test: Call respects timeout
|
|
309
|
+
- [x] Test: Call retries on failure
|
|
310
|
+
- [x] Test: Call retries with backoff delay
|
|
311
|
+
- [x] Test: Call continues to next step
|
|
312
|
+
|
|
313
|
+
### 4.6 Tests - Router Executor
|
|
314
|
+
|
|
315
|
+
- [x] Test: Router executor is registered as "router"
|
|
316
|
+
- [x] Test: Router evaluates routes in order
|
|
317
|
+
- [x] Test: Router returns first matching route target
|
|
318
|
+
- [x] Test: Router uses default when no match
|
|
319
|
+
- [x] Test: Router raises ExecutionError when no match and no default
|
|
320
|
+
|
|
321
|
+
### 4.7 Tests - Loop Executor
|
|
322
|
+
|
|
323
|
+
- [x] Test: Loop executor is registered as "loop"
|
|
324
|
+
- [x] Test: Loop foreach iterates over array
|
|
325
|
+
- [x] Test: Loop foreach sets item variable (config.as)
|
|
326
|
+
- [x] Test: Loop foreach sets index variable (config.index_as)
|
|
327
|
+
- [x] Test: Loop foreach collects body outputs
|
|
328
|
+
- [x] Test: Loop foreach stores results in output key
|
|
329
|
+
- [x] Test: Loop foreach raises on non-array
|
|
330
|
+
- [x] Test: Loop foreach raises when collection exceeds max
|
|
331
|
+
- [x] Test: Loop foreach bubbles up halts from body
|
|
332
|
+
- [x] Test: Loop while iterates while condition true
|
|
333
|
+
- [x] Test: Loop while sets iteration counter
|
|
334
|
+
- [x] Test: Loop while goes to on_exhausted when max exceeded
|
|
335
|
+
- [x] Test: Loop while raises when max exceeded and no on_exhausted
|
|
336
|
+
- [x] Test: Loop while stops on break_loop in ctx
|
|
337
|
+
- [x] Test: Loop cleans up loop variables after completion
|
|
338
|
+
|
|
339
|
+
### 4.8 Tests - Parallel Executor
|
|
340
|
+
|
|
341
|
+
- [x] Test: Parallel executor is registered as "parallel"
|
|
342
|
+
- [x] Test: Parallel executes branches concurrently
|
|
343
|
+
- [x] Test: Parallel wait "all" waits for all branches
|
|
344
|
+
- [x] Test: Parallel wait "any" completes when first finishes
|
|
345
|
+
- [x] Test: Parallel wait integer waits for N branches
|
|
346
|
+
- [x] Test: Parallel merges branch contexts
|
|
347
|
+
- [x] Test: Parallel stores results array in output
|
|
348
|
+
- [x] Test: Parallel raises on insufficient completions
|
|
349
|
+
- [x] Test: Parallel raises on error when wait is "all"
|
|
350
|
+
|
|
351
|
+
### 4.9 Tests - Transform Executor
|
|
352
|
+
|
|
353
|
+
- [x] Test: Transform executor is registered as "transform"
|
|
354
|
+
- [x] Test: Transform "map" operation
|
|
355
|
+
- [x] Test: Transform "select" operation
|
|
356
|
+
- [x] Test: Transform "reject" operation
|
|
357
|
+
- [x] Test: Transform "pluck" operation
|
|
358
|
+
- [x] Test: Transform "first" operation
|
|
359
|
+
- [x] Test: Transform "last" operation
|
|
360
|
+
- [x] Test: Transform "flatten" operation
|
|
361
|
+
- [x] Test: Transform "compact" operation
|
|
362
|
+
- [x] Test: Transform "uniq" operation
|
|
363
|
+
- [x] Test: Transform "reverse" operation
|
|
364
|
+
- [x] Test: Transform "sort" operation
|
|
365
|
+
- [x] Test: Transform "count" operation
|
|
366
|
+
- [x] Test: Transform "sum" operation
|
|
367
|
+
- [x] Test: Transform "keys" operation
|
|
368
|
+
- [x] Test: Transform "values" operation
|
|
369
|
+
- [x] Test: Transform "pick" operation
|
|
370
|
+
- [x] Test: Transform "omit" operation
|
|
371
|
+
- [x] Test: Transform "merge" operation
|
|
372
|
+
- [x] Test: Transform chains multiple operations
|
|
373
|
+
- [x] Test: Transform uses input from ctx
|
|
374
|
+
|
|
375
|
+
### 4.10 Tests - Halt Executor
|
|
376
|
+
|
|
377
|
+
- [x] Test: Halt executor is registered as "halt"
|
|
378
|
+
- [x] Test: Halt returns HaltResult
|
|
379
|
+
- [x] Test: Halt includes reason in data
|
|
380
|
+
- [x] Test: Halt includes halted_at timestamp
|
|
381
|
+
- [x] Test: Halt includes extra data
|
|
382
|
+
- [x] Test: Halt sets resume_step
|
|
383
|
+
|
|
384
|
+
### 4.11 Tests - Approval Executor
|
|
385
|
+
|
|
386
|
+
- [x] Test: Approval executor is registered as "approval"
|
|
387
|
+
- [x] Test: Approval halts for approval request
|
|
388
|
+
- [x] Test: Approval halt data includes prompt
|
|
389
|
+
- [x] Test: Approval halt data includes context
|
|
390
|
+
- [x] Test: Approval halt data includes approvers
|
|
391
|
+
- [x] Test: Approval continues when approved=true
|
|
392
|
+
- [x] Test: Approval goes to on_reject when approved=false
|
|
393
|
+
- [x] Test: Approval raises when rejected and no on_reject
|
|
394
|
+
- [x] Test: Approval checks timeout on resume
|
|
395
|
+
- [x] Test: Approval goes to on_timeout when timed out
|
|
396
|
+
|
|
397
|
+
### 4.12 Tests - Workflow (Sub-workflow) Executor
|
|
398
|
+
|
|
399
|
+
- [x] Test: Workflow executor is registered as "workflow"
|
|
400
|
+
- [x] Test: Workflow loads child workflow from registry
|
|
401
|
+
- [x] Test: Workflow raises for unknown workflow_id
|
|
402
|
+
- [x] Test: Workflow passes resolved input to child
|
|
403
|
+
- [x] Test: Workflow stores child output in output key
|
|
404
|
+
- [x] Test: Workflow bubbles up child halts
|
|
405
|
+
- [x] Test: Workflow raises on child failure
|
|
406
|
+
- [x] Test: Workflow respects timeout
|
|
407
|
+
|
|
408
|
+
---
|
|
409
|
+
|
|
410
|
+
## 5. PARSER (05-PARSER.md)
|
|
411
|
+
|
|
412
|
+
### 5.1 Implementation
|
|
413
|
+
|
|
414
|
+
- [x] Create `lib/durable_workflow/core/parser.rb`
|
|
415
|
+
- [x] Create `lib/durable_workflow/core/schema_validator.rb`
|
|
416
|
+
- [x] Update `lib/durable_workflow/core/types/configs.rb` to add OutputConfig
|
|
417
|
+
- [x] Update `lib/durable_workflow/core/executors/call.rb` to support schema validation
|
|
418
|
+
|
|
419
|
+
### 5.2 Tests - Parser Core
|
|
420
|
+
|
|
421
|
+
- [x] Test: `Parser.parse(yaml_string)` returns WorkflowDef
|
|
422
|
+
- [x] Test: `Parser.parse(file_path)` loads from file
|
|
423
|
+
- [x] Test: `Parser.parse(hash)` accepts hash directly
|
|
424
|
+
- [x] Test: Parser raises Error for invalid source type
|
|
425
|
+
- [x] Test: Parser symbolizes keys deeply
|
|
426
|
+
- [x] Test: Parser parses workflow id, name, version, description
|
|
427
|
+
- [x] Test: Parser parses workflow timeout
|
|
428
|
+
- [x] Test: Parser parses inputs with defaults
|
|
429
|
+
- [x] Test: Parser parses input required field
|
|
430
|
+
- [x] Test: Parser parses input type field
|
|
431
|
+
|
|
432
|
+
### 5.3 Tests - Parser Steps
|
|
433
|
+
|
|
434
|
+
- [x] Test: Parser parses step id and type
|
|
435
|
+
- [x] Test: Parser parses step next and on_error
|
|
436
|
+
- [x] Test: Parser extracts config from step
|
|
437
|
+
- [x] Test: Parser renames method to method_name for call
|
|
438
|
+
- [x] Test: Parser parses router routes with when/then
|
|
439
|
+
- [x] Test: Parser parses loop while condition
|
|
440
|
+
- [x] Test: Parser parses loop do steps recursively
|
|
441
|
+
- [x] Test: Parser parses parallel branches recursively
|
|
442
|
+
- [x] Test: Parser raises ValidationError for invalid config
|
|
443
|
+
|
|
444
|
+
### 5.4 Tests - Parser Hooks
|
|
445
|
+
|
|
446
|
+
- [x] Test: `Parser.before_parse` hooks receive raw YAML hash
|
|
447
|
+
- [x] Test: `Parser.before_parse` hooks can modify hash
|
|
448
|
+
- [x] Test: `Parser.after_parse` hooks receive WorkflowDef
|
|
449
|
+
- [x] Test: `Parser.after_parse` hooks can return modified WorkflowDef
|
|
450
|
+
- [x] Test: `Parser.transform_config(type)` transforms config for type
|
|
451
|
+
|
|
452
|
+
### 5.5 Tests - Parser Output Config
|
|
453
|
+
|
|
454
|
+
- [x] Test: Parser parses output as symbol
|
|
455
|
+
- [x] Test: Parser parses output as string (converts to symbol)
|
|
456
|
+
- [x] Test: Parser parses output as hash with key and schema
|
|
457
|
+
- [x] Test: Parser creates OutputConfig for schema'd output
|
|
458
|
+
|
|
459
|
+
### 5.6 Tests - Validator (Extended)
|
|
460
|
+
|
|
461
|
+
- [x] Test: Validator checks variable reachability
|
|
462
|
+
- [x] Test: Validator allows $input references
|
|
463
|
+
- [x] Test: Validator allows $now references
|
|
464
|
+
- [x] Test: Validator allows $history references
|
|
465
|
+
- [x] Test: Validator fails for undefined variable reference
|
|
466
|
+
- [x] Test: Validator tracks output keys through steps
|
|
467
|
+
- [x] Test: Validator checks schema path compatibility
|
|
468
|
+
- [x] Test: Validator fails for invalid schema path
|
|
469
|
+
|
|
470
|
+
### 5.7 Tests - SchemaValidator
|
|
471
|
+
|
|
472
|
+
- [x] Test: SchemaValidator returns true when schema is nil
|
|
473
|
+
- [x] Test: SchemaValidator returns true when json_schemer not available
|
|
474
|
+
- [x] Test: SchemaValidator validates value against schema
|
|
475
|
+
- [x] Test: SchemaValidator raises ValidationError on schema violation
|
|
476
|
+
|
|
477
|
+
---
|
|
478
|
+
|
|
479
|
+
## 6. STORAGE (Test Support Only - Phase 2 has real implementations)
|
|
480
|
+
|
|
481
|
+
### 6.1 Implementation
|
|
482
|
+
|
|
483
|
+
- [x] Create `lib/durable_workflow/storage/store.rb` (base interface)
|
|
484
|
+
- [x] Create `test/support/test_store.rb` (minimal test-only implementation)
|
|
485
|
+
|
|
486
|
+
### 6.2 Tests - Store Interface
|
|
487
|
+
|
|
488
|
+
- [x] Test: TestStore implements save(state)
|
|
489
|
+
- [x] Test: TestStore implements load(execution_id)
|
|
490
|
+
- [x] Test: TestStore implements record(entry)
|
|
491
|
+
- [x] Test: TestStore implements entries(execution_id)
|
|
492
|
+
- [x] Test: TestStore returns nil for unknown execution
|
|
493
|
+
|
|
494
|
+
---
|
|
495
|
+
|
|
496
|
+
## 7. INTEGRATION TESTS
|
|
497
|
+
|
|
498
|
+
### 7.1 End-to-End Workflow Tests
|
|
499
|
+
|
|
500
|
+
- [x] Test: Simple linear workflow (start -> assign -> end)
|
|
501
|
+
- [x] Test: Workflow with call step
|
|
502
|
+
- [x] Test: Workflow with router branching
|
|
503
|
+
- [x] Test: Workflow with loop foreach
|
|
504
|
+
- [x] Test: Workflow with loop while
|
|
505
|
+
- [x] Test: Workflow with halt and resume
|
|
506
|
+
- [x] Test: Workflow with approval and approve
|
|
507
|
+
- [x] Test: Workflow with approval and reject
|
|
508
|
+
- [x] Test: Workflow with transform
|
|
509
|
+
- [x] Test: Workflow with parallel branches
|
|
510
|
+
- [x] Test: Workflow with sub-workflow
|
|
511
|
+
- [x] Test: Workflow with error handling (on_error)
|
|
512
|
+
- [x] Test: Workflow timeout
|
|
513
|
+
|
|
514
|
+
---
|
|
515
|
+
|
|
516
|
+
## 8. TEST FILE ORGANIZATION
|
|
517
|
+
|
|
518
|
+
### 8.1 Test Files to Create
|
|
519
|
+
|
|
520
|
+
- [x] Create `test/test_helper.rb` (update with requires)
|
|
521
|
+
- [x] Create `test/support/test_store.rb`
|
|
522
|
+
- [x] Create `test/unit/utils_test.rb`
|
|
523
|
+
- [x] Create `test/unit/core/types/base_test.rb`
|
|
524
|
+
- [x] Create `test/unit/core/types/condition_test.rb`
|
|
525
|
+
- [x] Create `test/unit/core/types/configs_test.rb`
|
|
526
|
+
- [x] Create `test/unit/core/types/step_def_test.rb`
|
|
527
|
+
- [x] Create `test/unit/core/types/workflow_def_test.rb`
|
|
528
|
+
- [x] Create `test/unit/core/types/state_test.rb`
|
|
529
|
+
- [x] Create `test/unit/core/types/entry_test.rb`
|
|
530
|
+
- [x] Create `test/unit/core/types/results_test.rb`
|
|
531
|
+
- [x] Create `test/unit/core/executors/registry_test.rb`
|
|
532
|
+
- [x] Create `test/unit/core/executors/base_test.rb`
|
|
533
|
+
- [x] Create `test/unit/core/resolver_test.rb`
|
|
534
|
+
- [x] Create `test/unit/core/condition_test.rb`
|
|
535
|
+
- [x] Create `test/unit/core/validator_test.rb`
|
|
536
|
+
- [x] Create `test/unit/core/engine_test.rb`
|
|
537
|
+
- [x] Create `test/unit/core/parser_test.rb`
|
|
538
|
+
- [x] Create `test/unit/core/schema_validator_test.rb`
|
|
539
|
+
- [x] Create `test/unit/core/executors/start_test.rb`
|
|
540
|
+
- [x] Create `test/unit/core/executors/end_test.rb`
|
|
541
|
+
- [x] Create `test/unit/core/executors/assign_test.rb`
|
|
542
|
+
- [x] Create `test/unit/core/executors/call_test.rb`
|
|
543
|
+
- [x] Create `test/unit/core/executors/router_test.rb`
|
|
544
|
+
- [x] Create `test/unit/core/executors/loop_test.rb`
|
|
545
|
+
- [x] Create `test/unit/core/executors/parallel_test.rb`
|
|
546
|
+
- [x] Create `test/unit/core/executors/transform_test.rb`
|
|
547
|
+
- [x] Create `test/unit/core/executors/halt_test.rb`
|
|
548
|
+
- [x] Create `test/unit/core/executors/approval_test.rb`
|
|
549
|
+
- [x] Create `test/unit/core/executors/workflow_test.rb`
|
|
550
|
+
- [x] Create `test/integration/workflow_test.rb`
|
|
551
|
+
|
|
552
|
+
---
|
|
553
|
+
|
|
554
|
+
## Summary Stats
|
|
555
|
+
|
|
556
|
+
| Section | Implementation Tasks | Test Tasks | Total |
|
|
557
|
+
| --------------------------- | -------------------- | ---------- | ------- |
|
|
558
|
+
| 1. Gemspec & Base | 5 | 12 | 17 |
|
|
559
|
+
| 2. Core Types | 11 | 51 | 62 |
|
|
560
|
+
| 3. Execution Infrastructure | 7 | 52 | 59 |
|
|
561
|
+
| 4. Step Executors | 11 | 64 | 75 |
|
|
562
|
+
| 5. Parser | 4 | 28 | 32 |
|
|
563
|
+
| 6. Storage (Test Support) | 2 | 5 | 7 |
|
|
564
|
+
| 7. Integration Tests | 0 | 13 | 13 |
|
|
565
|
+
| 8. Test Files | 31 | 0 | 31 |
|
|
566
|
+
| **TOTAL** | **71** | **225** | **296** |
|
|
567
|
+
|
|
568
|
+
---
|
|
569
|
+
|
|
570
|
+
## ✅ PHASE 1 COMPLETE
|
|
571
|
+
|
|
572
|
+
**Test Results:** 239 runs, 438 assertions, 0 failures, 0 errors, 0 skips
|
|
573
|
+
|
|
574
|
+
All implementation tasks and test coverage requirements have been completed.
|