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.
Files changed (116) hide show
  1. checksums.yaml +7 -0
  2. data/.claude/todo/01.amend.md +133 -0
  3. data/.claude/todo/02.amend.md +444 -0
  4. data/.claude/todo/phase-1-core/01-GEMSPEC.md +193 -0
  5. data/.claude/todo/phase-1-core/02-TYPES.md +462 -0
  6. data/.claude/todo/phase-1-core/03-EXECUTION.md +551 -0
  7. data/.claude/todo/phase-1-core/04-STEPS.md +603 -0
  8. data/.claude/todo/phase-1-core/05-PARSER.md +719 -0
  9. data/.claude/todo/phase-1-core/todo.md +574 -0
  10. data/.claude/todo/phase-2-runtime/01-STORAGE.md +641 -0
  11. data/.claude/todo/phase-2-runtime/02-RUNNERS.md +511 -0
  12. data/.claude/todo/phase-3-extensions/01-EXTENSION-SYSTEM.md +298 -0
  13. data/.claude/todo/phase-3-extensions/02-AI-PLUGIN.md +936 -0
  14. data/.claude/todo/phase-3-extensions/todo.md +262 -0
  15. data/.claude/todo/phase-4-ai-rework/01-DEPENDENCIES.md +107 -0
  16. data/.claude/todo/phase-4-ai-rework/02-CONFIGURATION.md +123 -0
  17. data/.claude/todo/phase-4-ai-rework/03-TOOL-REGISTRY.md +237 -0
  18. data/.claude/todo/phase-4-ai-rework/04-MCP-SERVER.md +432 -0
  19. data/.claude/todo/phase-4-ai-rework/05-MCP-CLIENT.md +333 -0
  20. data/.claude/todo/phase-4-ai-rework/06-EXECUTORS.md +397 -0
  21. data/.claude/todo/phase-4-ai-rework/todo.md +265 -0
  22. data/.claude/todo/phase-5-validation/.DS_Store +0 -0
  23. data/.claude/todo/phase-5-validation/01-TEST-GAPS.md +615 -0
  24. data/.claude/todo/phase-5-validation/01-TESTS.md +2378 -0
  25. data/.claude/todo/phase-5-validation/02-EXAMPLES-SIMPLE.md +744 -0
  26. data/.claude/todo/phase-5-validation/02-EXAMPLES.md +1857 -0
  27. data/.claude/todo/phase-5-validation/03-EXAMPLE-SUPPORT-AGENT.md +95 -0
  28. data/.claude/todo/phase-5-validation/04-EXAMPLE-ORDER-FULFILLMENT.md +94 -0
  29. data/.claude/todo/phase-5-validation/05-EXAMPLE-DATA-PIPELINE.md +145 -0
  30. data/.env.example +3 -0
  31. data/.rubocop.yml +64 -0
  32. data/0.3.amend.md +89 -0
  33. data/CHANGELOG.md +5 -0
  34. data/CODE_OF_CONDUCT.md +84 -0
  35. data/Gemfile +22 -0
  36. data/Gemfile.lock +192 -0
  37. data/LICENSE.txt +21 -0
  38. data/README.md +39 -0
  39. data/Rakefile +16 -0
  40. data/durable_workflow.gemspec +43 -0
  41. data/examples/approval_request.rb +106 -0
  42. data/examples/calculator.rb +154 -0
  43. data/examples/file_search_demo.rb +77 -0
  44. data/examples/hello_workflow.rb +57 -0
  45. data/examples/item_processor.rb +96 -0
  46. data/examples/order_fulfillment/Gemfile +6 -0
  47. data/examples/order_fulfillment/README.md +84 -0
  48. data/examples/order_fulfillment/run.rb +85 -0
  49. data/examples/order_fulfillment/services.rb +146 -0
  50. data/examples/order_fulfillment/workflow.yml +188 -0
  51. data/examples/parallel_fetch.rb +102 -0
  52. data/examples/service_integration.rb +137 -0
  53. data/examples/support_agent/Gemfile +6 -0
  54. data/examples/support_agent/README.md +91 -0
  55. data/examples/support_agent/config/claude_desktop.json +12 -0
  56. data/examples/support_agent/mcp_server.rb +49 -0
  57. data/examples/support_agent/run.rb +67 -0
  58. data/examples/support_agent/services.rb +113 -0
  59. data/examples/support_agent/workflow.yml +286 -0
  60. data/lib/durable_workflow/core/condition.rb +45 -0
  61. data/lib/durable_workflow/core/engine.rb +145 -0
  62. data/lib/durable_workflow/core/executors/approval.rb +51 -0
  63. data/lib/durable_workflow/core/executors/assign.rb +18 -0
  64. data/lib/durable_workflow/core/executors/base.rb +90 -0
  65. data/lib/durable_workflow/core/executors/call.rb +76 -0
  66. data/lib/durable_workflow/core/executors/end.rb +19 -0
  67. data/lib/durable_workflow/core/executors/halt.rb +24 -0
  68. data/lib/durable_workflow/core/executors/loop.rb +118 -0
  69. data/lib/durable_workflow/core/executors/parallel.rb +77 -0
  70. data/lib/durable_workflow/core/executors/registry.rb +34 -0
  71. data/lib/durable_workflow/core/executors/router.rb +26 -0
  72. data/lib/durable_workflow/core/executors/start.rb +61 -0
  73. data/lib/durable_workflow/core/executors/transform.rb +71 -0
  74. data/lib/durable_workflow/core/executors/workflow.rb +32 -0
  75. data/lib/durable_workflow/core/parser.rb +189 -0
  76. data/lib/durable_workflow/core/resolver.rb +61 -0
  77. data/lib/durable_workflow/core/schema_validator.rb +47 -0
  78. data/lib/durable_workflow/core/types/base.rb +41 -0
  79. data/lib/durable_workflow/core/types/condition.rb +25 -0
  80. data/lib/durable_workflow/core/types/configs.rb +103 -0
  81. data/lib/durable_workflow/core/types/entry.rb +26 -0
  82. data/lib/durable_workflow/core/types/results.rb +41 -0
  83. data/lib/durable_workflow/core/types/state.rb +95 -0
  84. data/lib/durable_workflow/core/types/step_def.rb +15 -0
  85. data/lib/durable_workflow/core/types/workflow_def.rb +43 -0
  86. data/lib/durable_workflow/core/types.rb +29 -0
  87. data/lib/durable_workflow/core/validator.rb +318 -0
  88. data/lib/durable_workflow/extensions/ai/ai.rb +149 -0
  89. data/lib/durable_workflow/extensions/ai/configuration.rb +41 -0
  90. data/lib/durable_workflow/extensions/ai/executors/agent.rb +150 -0
  91. data/lib/durable_workflow/extensions/ai/executors/file_search.rb +52 -0
  92. data/lib/durable_workflow/extensions/ai/executors/guardrail.rb +152 -0
  93. data/lib/durable_workflow/extensions/ai/executors/handoff.rb +33 -0
  94. data/lib/durable_workflow/extensions/ai/executors/mcp.rb +47 -0
  95. data/lib/durable_workflow/extensions/ai/mcp/adapter.rb +73 -0
  96. data/lib/durable_workflow/extensions/ai/mcp/client.rb +77 -0
  97. data/lib/durable_workflow/extensions/ai/mcp/rack_app.rb +66 -0
  98. data/lib/durable_workflow/extensions/ai/mcp/server.rb +122 -0
  99. data/lib/durable_workflow/extensions/ai/tool_registry.rb +63 -0
  100. data/lib/durable_workflow/extensions/ai/types.rb +213 -0
  101. data/lib/durable_workflow/extensions/ai.rb +6 -0
  102. data/lib/durable_workflow/extensions/base.rb +77 -0
  103. data/lib/durable_workflow/runners/adapters/inline.rb +42 -0
  104. data/lib/durable_workflow/runners/adapters/sidekiq.rb +69 -0
  105. data/lib/durable_workflow/runners/async.rb +100 -0
  106. data/lib/durable_workflow/runners/stream.rb +126 -0
  107. data/lib/durable_workflow/runners/sync.rb +40 -0
  108. data/lib/durable_workflow/storage/active_record.rb +148 -0
  109. data/lib/durable_workflow/storage/redis.rb +133 -0
  110. data/lib/durable_workflow/storage/sequel.rb +144 -0
  111. data/lib/durable_workflow/storage/store.rb +43 -0
  112. data/lib/durable_workflow/utils.rb +25 -0
  113. data/lib/durable_workflow/version.rb +5 -0
  114. data/lib/durable_workflow.rb +70 -0
  115. data/sig/durable_workflow.rbs +4 -0
  116. 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.