igniter 0.2.0 → 0.3.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 (80) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +12 -0
  3. data/README.md +224 -1
  4. data/docs/API_V2.md +238 -1
  5. data/docs/BACKLOG.md +166 -0
  6. data/docs/BRANCHES_V1.md +213 -0
  7. data/docs/COLLECTIONS_V1.md +303 -0
  8. data/docs/EXECUTION_MODEL_V2.md +79 -0
  9. data/docs/PATTERNS.md +222 -0
  10. data/docs/STORE_ADAPTERS.md +126 -0
  11. data/examples/README.md +124 -0
  12. data/examples/async_store.rb +47 -0
  13. data/examples/collection.rb +43 -0
  14. data/examples/collection_partial_failure.rb +50 -0
  15. data/examples/marketing_ergonomics.rb +57 -0
  16. data/examples/ringcentral_routing.rb +278 -0
  17. data/lib/igniter/compiler/compiled_graph.rb +82 -0
  18. data/lib/igniter/compiler/graph_compiler.rb +12 -2
  19. data/lib/igniter/compiler/type_resolver.rb +54 -0
  20. data/lib/igniter/compiler/validation_context.rb +61 -0
  21. data/lib/igniter/compiler/validation_pipeline.rb +30 -0
  22. data/lib/igniter/compiler/validator.rb +1 -187
  23. data/lib/igniter/compiler/validators/callable_validator.rb +107 -0
  24. data/lib/igniter/compiler/validators/dependencies_validator.rb +151 -0
  25. data/lib/igniter/compiler/validators/outputs_validator.rb +66 -0
  26. data/lib/igniter/compiler/validators/type_compatibility_validator.rb +84 -0
  27. data/lib/igniter/compiler/validators/uniqueness_validator.rb +60 -0
  28. data/lib/igniter/compiler.rb +8 -0
  29. data/lib/igniter/contract.rb +136 -4
  30. data/lib/igniter/diagnostics/auditing/report/console_formatter.rb +80 -0
  31. data/lib/igniter/diagnostics/auditing/report/markdown_formatter.rb +22 -0
  32. data/lib/igniter/diagnostics/introspection/formatters/mermaid_formatter.rb +58 -0
  33. data/lib/igniter/diagnostics/introspection/formatters/text_tree_formatter.rb +44 -0
  34. data/lib/igniter/diagnostics/report.rb +84 -8
  35. data/lib/igniter/dsl/contract_builder.rb +208 -5
  36. data/lib/igniter/dsl/schema_builder.rb +73 -0
  37. data/lib/igniter/dsl.rb +1 -0
  38. data/lib/igniter/errors.rb +11 -0
  39. data/lib/igniter/events/bus.rb +5 -0
  40. data/lib/igniter/events/event.rb +29 -0
  41. data/lib/igniter/executor.rb +74 -0
  42. data/lib/igniter/executor_registry.rb +44 -0
  43. data/lib/igniter/extensions/auditing/timeline.rb +4 -0
  44. data/lib/igniter/extensions/introspection/graph_formatter.rb +29 -3
  45. data/lib/igniter/extensions/introspection/plan_formatter.rb +55 -0
  46. data/lib/igniter/extensions/introspection/runtime_formatter.rb +18 -3
  47. data/lib/igniter/extensions/introspection.rb +1 -0
  48. data/lib/igniter/extensions/reactive/engine.rb +49 -2
  49. data/lib/igniter/extensions/reactive/reaction.rb +3 -2
  50. data/lib/igniter/model/branch_node.rb +40 -0
  51. data/lib/igniter/model/collection_node.rb +25 -0
  52. data/lib/igniter/model/composition_node.rb +2 -2
  53. data/lib/igniter/model/compute_node.rb +58 -2
  54. data/lib/igniter/model/input_node.rb +2 -2
  55. data/lib/igniter/model/output_node.rb +24 -4
  56. data/lib/igniter/model.rb +2 -0
  57. data/lib/igniter/runtime/cache.rb +64 -25
  58. data/lib/igniter/runtime/collection_result.rb +111 -0
  59. data/lib/igniter/runtime/deferred_result.rb +40 -0
  60. data/lib/igniter/runtime/execution.rb +261 -11
  61. data/lib/igniter/runtime/input_validator.rb +2 -24
  62. data/lib/igniter/runtime/invalidator.rb +1 -1
  63. data/lib/igniter/runtime/job_worker.rb +18 -0
  64. data/lib/igniter/runtime/node_state.rb +20 -0
  65. data/lib/igniter/runtime/planner.rb +126 -0
  66. data/lib/igniter/runtime/resolver.rb +269 -15
  67. data/lib/igniter/runtime/result.rb +14 -2
  68. data/lib/igniter/runtime/runner_factory.rb +20 -0
  69. data/lib/igniter/runtime/runners/inline_runner.rb +21 -0
  70. data/lib/igniter/runtime/runners/store_runner.rb +29 -0
  71. data/lib/igniter/runtime/runners/thread_pool_runner.rb +37 -0
  72. data/lib/igniter/runtime/stores/active_record_store.rb +41 -0
  73. data/lib/igniter/runtime/stores/file_store.rb +43 -0
  74. data/lib/igniter/runtime/stores/memory_store.rb +40 -0
  75. data/lib/igniter/runtime/stores/redis_store.rb +44 -0
  76. data/lib/igniter/runtime.rb +12 -0
  77. data/lib/igniter/type_system.rb +44 -0
  78. data/lib/igniter/version.rb +1 -1
  79. data/lib/igniter.rb +23 -0
  80. metadata +43 -2
data/docs/BACKLOG.md ADDED
@@ -0,0 +1,166 @@
1
+ # Igniter Backlog
2
+
3
+ This file is a lightweight backlog for ideas that are worth preserving before they turn into active implementation work.
4
+
5
+ ## Collections v1
6
+
7
+ Status: idea
8
+ Priority: high
9
+
10
+ Problem:
11
+
12
+ - Real orchestration often needs fan-out over a list of homogeneous items.
13
+ - Without a collection primitive, users will hide loops inside `compute` nodes.
14
+ - That reduces graph transparency and makes diagnostics, invalidation, and async execution weaker.
15
+
16
+ Example direction:
17
+
18
+ ```ruby
19
+ collection :technicians,
20
+ depends_on: :technician_inputs,
21
+ each: TechnicianContract,
22
+ key: :technician_id,
23
+ mode: :collect
24
+ ```
25
+
26
+ Likely semantics:
27
+
28
+ - `depends_on:` should resolve to an array of item input hashes
29
+ - `each:` should point to a child contract or executor-like collection worker
30
+ - `key:` should provide stable item identity for invalidation, resume, and diagnostics
31
+ - `mode:` should control failure semantics, for example `:collect` vs `:fail_fast`
32
+
33
+ Why it matters:
34
+
35
+ - enables explicit fan-out/fan-in in the graph model
36
+ - fits naturally with `thread_pool` execution
37
+ - creates a path toward item-level async/pending/resume
38
+ - improves diagnostics over collection workflows
39
+
40
+ Open design questions:
41
+
42
+ - result shape: array, keyed hash, or dedicated `CollectionResult`
43
+ - compile-time validation for item schema and key extraction
44
+ - per-item invalidation and snapshot/restore behavior
45
+ - item-level events and auditing model
46
+ - parent failure semantics when some items fail or remain pending
47
+
48
+ Suggested implementation order:
49
+
50
+ 1. write a short design doc for Collections v1
51
+ 2. define graph/model/runtime semantics
52
+ 3. add compile-time validators
53
+ 4. add a minimal synchronous implementation
54
+ 5. extend to parallel runner
55
+ 6. later extend to pending/store-backed item execution
56
+
57
+ ## Conditional Branches v1
58
+
59
+ Status: idea
60
+ Priority: high
61
+
62
+ Problem:
63
+
64
+ - Real orchestration often needs explicit conditional routing.
65
+ - Without a branching primitive, users push control flow into `compute` blocks or executors.
66
+ - That hides workflow structure and weakens diagnostics and introspection.
67
+
68
+ Example direction:
69
+
70
+ ```ruby
71
+ branch :delivery_strategy, depends_on: :country do
72
+ on "US", contract: USDeliveryContract
73
+ on "UA", contract: LocalDeliveryContract
74
+ else contract: DefaultDeliveryContract
75
+ end
76
+ ```
77
+
78
+ Why it matters:
79
+
80
+ - makes control flow explicit in the graph
81
+ - keeps routing logic out of generic `compute` nodes
82
+ - improves explainability by showing which branch was selected
83
+ - fits future schema/UI-driven graph composition
84
+
85
+ Likely semantics:
86
+
87
+ - `depends_on:` resolves the selector input
88
+ - one branch is selected at runtime based on ordered matching
89
+ - the selected branch behaves like a composition-like node
90
+ - diagnostics and events should include which branch matched
91
+
92
+ Open design questions:
93
+
94
+ - exact-match only vs predicate-based matching
95
+ - whether `else` is required or optional
96
+ - compatibility of outputs across different branches
97
+ - how branch nodes appear in plans, graphs, and runtime state
98
+ - whether branches select contracts only or also arbitrary nodes/executors
99
+
100
+ Suggested implementation order:
101
+
102
+ 1. write a short design doc for Branches v1
103
+ 2. define graph/model/runtime semantics
104
+ 3. add compile-time validation for branch definitions
105
+ 4. implement a minimal contract-branching version
106
+ 5. add introspection and diagnostics for selected branch visibility
107
+ 6. later extend to schema-driven graph builders
108
+
109
+ ## Namespaces / Scopes v1
110
+
111
+ Status: idea
112
+ Priority: medium
113
+
114
+ Problem:
115
+
116
+ - Larger contracts need a way to group related nodes without extracting every subgraph into a separate top-level contract.
117
+ - Without grouping, large orchestration graphs become flat and harder to scan.
118
+ - Users may want a local container for related calculations before deciding whether the logic deserves a standalone contract.
119
+
120
+ Example direction:
121
+
122
+ ```ruby
123
+ scope :availability do
124
+ lookup :vendor, depends_on: %i[trade vendor_id], call: LookupVendor
125
+ lookup :zip_code, depends_on: [:zip_code_raw], call: LookupZipCode
126
+ compute :geo_bids, depends_on: %i[zip_code vendor], call: LookupGeoBids
127
+ compute :availability, depends_on: %i[vendor zip_code], call: CalculateAvailability
128
+ end
129
+ ```
130
+
131
+ Possible extended direction:
132
+
133
+ ```ruby
134
+ namespace :availability do
135
+ input :vendor_id
136
+ input :zip_code_raw
137
+
138
+ compute :vendor, ...
139
+ compute :availability, ...
140
+
141
+ output :availability
142
+ end
143
+ ```
144
+
145
+ Why it matters:
146
+
147
+ - improves readability of larger contracts
148
+ - creates a middle layer between a flat graph and full contract composition
149
+ - may provide a path toward inline-contract semantics
150
+ - could improve future graph visualization and schema editing UX
151
+
152
+ Open design questions:
153
+
154
+ - purely visual grouping vs real runtime/model boundary
155
+ - whether scoped nodes get path prefixes like `availability.vendor`
156
+ - whether scopes can define local inputs/outputs
157
+ - how scopes differ from composition and when users should prefer one over the other
158
+ - whether inline contracts should compile into the same graph or nested child executions
159
+
160
+ Suggested implementation order:
161
+
162
+ 1. write a short design note comparing scopes vs composition
163
+ 2. decide whether v1 is visual/logical grouping only or a true subgraph primitive
164
+ 3. define path, output, and introspection semantics
165
+ 4. implement minimal grouping support
166
+ 5. later evaluate inline-contract execution semantics
@@ -0,0 +1,213 @@
1
+ # Branches v1
2
+
3
+ ## Goal
4
+
5
+ `branch` introduces explicit conditional routing as a graph primitive.
6
+
7
+ The feature should make control flow:
8
+
9
+ - declarative
10
+ - visible at compile time
11
+ - visible in graph introspection
12
+ - visible in runtime diagnostics and events
13
+
14
+ It should avoid pushing routing logic into generic `compute` blocks or executors.
15
+
16
+ ## DSL
17
+
18
+ ### Basic form
19
+
20
+ ```ruby
21
+ branch :delivery_strategy, with: :country do
22
+ on "US", contract: USDeliveryContract
23
+ on "UA", contract: LocalDeliveryContract
24
+ default contract: DefaultDeliveryContract
25
+ end
26
+ ```
27
+
28
+ ### Exporting outputs
29
+
30
+ ```ruby
31
+ branch :delivery_strategy, with: :country do
32
+ on "US", contract: USDeliveryContract
33
+ on "UA", contract: LocalDeliveryContract
34
+ default contract: DefaultDeliveryContract
35
+ end
36
+
37
+ export :price, :eta, from: :delivery_strategy
38
+ ```
39
+
40
+ ### Nested result
41
+
42
+ ```ruby
43
+ output :delivery_strategy
44
+ ```
45
+
46
+ This should return a child result object, similar to composition.
47
+
48
+ ## Why `default` and not `else`
49
+
50
+ `default` is preferred because:
51
+
52
+ - it is valid Ruby DSL syntax
53
+ - it is not a reserved keyword
54
+ - it reads clearly as a fallback branch
55
+ - it serializes more cleanly into schema-driven representations
56
+
57
+ ## Scope of v1
58
+
59
+ Branches v1 should stay intentionally narrow.
60
+
61
+ Supported:
62
+
63
+ - exact-match branch selection
64
+ - one selector dependency
65
+ - child contracts as branch targets
66
+ - explicit fallback through `default`
67
+ - branch result as a composition-like nested result
68
+ - `export` from the selected branch
69
+
70
+ Not supported in v1:
71
+
72
+ - predicate lambdas
73
+ - regex or set matching
74
+ - multiple selectors
75
+ - executor targets
76
+ - node-level arbitrary targets
77
+ - implicit output merging
78
+
79
+ ## Runtime Semantics
80
+
81
+ 1. Resolve the selector dependency.
82
+ 2. Match the selector value against declared `on` cases in order.
83
+ 3. If no case matches, use `default`.
84
+ 4. Instantiate and resolve the selected child contract.
85
+ 5. Mark the branch node as succeeded only after the selected child contract succeeds.
86
+
87
+ If the selected child contract fails, the branch node fails.
88
+
89
+ Unselected branches must not execute.
90
+
91
+ ## Compile-Time Validation
92
+
93
+ The compiler should validate:
94
+
95
+ - branch name uniqueness
96
+ - selector dependency existence
97
+ - at least one `on` case
98
+ - exactly one `default`
99
+ - unique case values within the branch
100
+ - each case has a valid contract
101
+ - the default has a valid contract
102
+ - exported outputs exist across all possible branch contracts
103
+
104
+ The last point is important:
105
+
106
+ If the contract exports `price` from a branch, then every possible branch contract must expose `price`.
107
+
108
+ ## Result Semantics
109
+
110
+ The branch node should behave similarly to composition:
111
+
112
+ - nested result available via `output :branch_name`
113
+ - child outputs re-exportable through `export`
114
+
115
+ This keeps branch consistent with the existing composition mental model.
116
+
117
+ ## Graph Model
118
+
119
+ Branches v1 should introduce a dedicated node kind:
120
+
121
+ - `:branch`
122
+
123
+ Suggested internal shape:
124
+
125
+ ```ruby
126
+ BranchNode.new(
127
+ name: :delivery_strategy,
128
+ selector: :country,
129
+ cases: [
130
+ { match: "US", contract: USDeliveryContract },
131
+ { match: "UA", contract: LocalDeliveryContract }
132
+ ],
133
+ default_contract: DefaultDeliveryContract
134
+ )
135
+ ```
136
+
137
+ This should not be modeled as a normal compute node.
138
+
139
+ ## Events
140
+
141
+ Branches v1 should add a specific runtime event:
142
+
143
+ - `branch_selected`
144
+
145
+ Suggested payload:
146
+
147
+ ```ruby
148
+ {
149
+ selector: :country,
150
+ selector_value: "US",
151
+ matched_case: "US",
152
+ selected_contract: "USDeliveryContract"
153
+ }
154
+ ```
155
+
156
+ This should improve observability and diagnostics without forcing users to infer branch choice from child execution state.
157
+
158
+ ## Introspection
159
+
160
+ ### Graph text / Mermaid
161
+
162
+ Branch nodes should render distinctly from compute and composition nodes.
163
+
164
+ The graph should show:
165
+
166
+ - selector dependency
167
+ - available branch cases
168
+ - default contract
169
+
170
+ ### Plan
171
+
172
+ Before execution:
173
+
174
+ - branch node is blocked on selector resolution
175
+ - available branches are visible as candidates
176
+
177
+ After execution:
178
+
179
+ - selected branch is visible in runtime state or diagnostics
180
+
181
+ ### Diagnostics
182
+
183
+ Diagnostics should surface:
184
+
185
+ - selector value
186
+ - selected branch case
187
+ - selected child contract
188
+
189
+ ## Error Model
190
+
191
+ Suggested runtime error:
192
+
193
+ - `Igniter::BranchSelectionError`
194
+
195
+ Used for:
196
+
197
+ - invalid branch configuration reaching runtime
198
+ - missing match if a branch somehow has no `default`
199
+
200
+ In a well-validated graph, this error should be rare.
201
+
202
+ ## Future Extensions
203
+
204
+ Possible later additions:
205
+
206
+ - `in:` matching
207
+ - `matches:` matching
208
+ - predicate matching
209
+ - branch-to-executor targets
210
+ - schema-driven branch authoring
211
+ - branch-aware collections
212
+
213
+ These should not be part of v1.
@@ -0,0 +1,303 @@
1
+ # Collections v1
2
+
3
+ ## Goal
4
+
5
+ `collection` introduces explicit fan-out over a list of homogeneous items as a graph primitive.
6
+
7
+ The feature should make iteration:
8
+
9
+ - declarative
10
+ - visible in the graph model
11
+ - visible in runtime diagnostics
12
+ - compatible with future parallel and async execution
13
+
14
+ It should avoid hiding loops inside generic `compute` blocks.
15
+
16
+ ## DSL
17
+
18
+ ### Basic form
19
+
20
+ ```ruby
21
+ collection :technicians,
22
+ with: :technician_inputs,
23
+ each: TechnicianContract,
24
+ key: :technician_id,
25
+ mode: :collect
26
+ ```
27
+
28
+ Where `technician_inputs` resolves to an array of hashes:
29
+
30
+ ```ruby
31
+ [
32
+ { technician_id: 1 },
33
+ { technician_id: 2 }
34
+ ]
35
+ ```
36
+
37
+ ### Exporting item results
38
+
39
+ ```ruby
40
+ output :technicians
41
+ ```
42
+
43
+ This should return a dedicated collection result object.
44
+
45
+ ## Why the input should be item hashes
46
+
47
+ For v1, the input to `collection` should already be normalized item inputs.
48
+
49
+ Preferred:
50
+
51
+ ```ruby
52
+ compute :technician_inputs, with: :technician_ids do |technician_ids:|
53
+ technician_ids.map { |id| { technician_id: id } }
54
+ end
55
+ ```
56
+
57
+ Then:
58
+
59
+ ```ruby
60
+ collection :technicians,
61
+ with: :technician_inputs,
62
+ each: TechnicianContract,
63
+ key: :technician_id
64
+ ```
65
+
66
+ This is preferable to implicit input mapping because:
67
+
68
+ - the graph remains explicit
69
+ - compile-time validation is simpler
70
+ - schema-driven graph building is easier
71
+
72
+ ## Scope of v1
73
+
74
+ Supported:
75
+
76
+ - one dependency providing an array of item input hashes
77
+ - child contract execution per item
78
+ - stable key extraction
79
+ - synchronous collection execution
80
+ - explicit failure mode configuration
81
+
82
+ Not supported in v1:
83
+
84
+ - executor-based collections
85
+ - nested async item execution
86
+ - item-level `defer` semantics
87
+ - schema inference from arbitrary item shapes
88
+ - implicit item input mapping from primitives
89
+
90
+ ## Suggested DSL Options
91
+
92
+ - `with:` selector for the collection input
93
+ - `each:` child contract class
94
+ - `key:` stable item identity
95
+ - `mode:` failure behavior
96
+
97
+ Example:
98
+
99
+ ```ruby
100
+ collection :technicians,
101
+ with: :technician_inputs,
102
+ each: TechnicianContract,
103
+ key: :technician_id,
104
+ mode: :collect
105
+ ```
106
+
107
+ ## Runtime Semantics
108
+
109
+ 1. Resolve the collection input dependency.
110
+ 2. Expect an array of hashes.
111
+ 3. For each item:
112
+ - derive item key
113
+ - instantiate child contract with item hash
114
+ - resolve child contract
115
+ 4. Aggregate results according to `mode`
116
+
117
+ ## Stable Key
118
+
119
+ `key:` should be required in v1.
120
+
121
+ Why:
122
+
123
+ - supports item identity in diagnostics
124
+ - supports future invalidation and resume semantics
125
+ - makes collection output easier to inspect
126
+
127
+ Possible accepted forms:
128
+
129
+ - symbol key:
130
+ ```ruby
131
+ key: :technician_id
132
+ ```
133
+ - proc form can be a future extension
134
+
135
+ For v1, symbol key is enough.
136
+
137
+ ## Failure Modes
138
+
139
+ Collections need explicit failure semantics.
140
+
141
+ Suggested v1 options:
142
+
143
+ - `mode: :collect`
144
+ - run all items
145
+ - keep successes and failures
146
+ - collection node itself succeeds unless collection setup is invalid
147
+
148
+ - `mode: :fail_fast`
149
+ - stop on first failed item
150
+ - collection node fails
151
+
152
+ Recommended default for v1:
153
+
154
+ - `:collect`
155
+
156
+ This is more practical for real orchestration and more useful for diagnostics.
157
+
158
+ ## Result Shape
159
+
160
+ Collections v1 should return a dedicated result object:
161
+
162
+ - `Igniter::Runtime::CollectionResult`
163
+
164
+ Suggested surface:
165
+
166
+ - `items`
167
+ - `keys`
168
+ - `successes`
169
+ - `failures`
170
+ - `pending`
171
+ - `to_h`
172
+ - `as_json`
173
+
174
+ Example conceptual shape:
175
+
176
+ ```ruby
177
+ {
178
+ items: {
179
+ 1 => { status: :succeeded, result: ... },
180
+ 2 => { status: :failed, error: ... }
181
+ }
182
+ }
183
+ ```
184
+
185
+ Returning a plain array would be too weak for diagnostics and future runtime extensions.
186
+
187
+ ## Compile-Time Validation
188
+
189
+ The compiler should validate:
190
+
191
+ - `with:` points to a resolvable dependency
192
+ - `each:` is an `Igniter::Contract` subclass
193
+ - `key:` is present
194
+ - child contract is compiled
195
+ - if the source type is known, it is compatible with an array-like input
196
+
197
+ ## Runtime Validation
198
+
199
+ At runtime, the collection should validate:
200
+
201
+ - source value is an array
202
+ - each item is a hash
203
+ - the declared key exists in each item
204
+ - keys are unique within the collection input
205
+
206
+ These should raise structured runtime errors.
207
+
208
+ ## Events
209
+
210
+ Collections should introduce collection-aware events in v1 or v2.
211
+
212
+ Minimum useful event:
213
+
214
+ - `collection_item_started`
215
+ - `collection_item_succeeded`
216
+ - `collection_item_failed`
217
+
218
+ Each event should include:
219
+
220
+ - collection node name
221
+ - item key
222
+ - child contract graph
223
+
224
+ If this feels too heavy for v1, item information can first live only inside collection result and diagnostics.
225
+
226
+ ## Introspection
227
+
228
+ ### Graph
229
+
230
+ Collection nodes should render distinctly from compute/composition/branch nodes.
231
+
232
+ The graph should show:
233
+
234
+ - source dependency
235
+ - child contract
236
+ - key
237
+ - mode
238
+
239
+ ### Plan
240
+
241
+ Before execution:
242
+
243
+ - collection node is blocked on the source dependency
244
+
245
+ After execution:
246
+
247
+ - item count
248
+ - item statuses
249
+
250
+ ### Diagnostics
251
+
252
+ Diagnostics should surface:
253
+
254
+ - collection size
255
+ - per-item key
256
+ - per-item status
257
+ - per-item errors when present
258
+
259
+ ## Relation to Parallel Execution
260
+
261
+ Collections are a natural fit for `thread_pool` execution.
262
+
263
+ But v1 does not need to solve parallel execution immediately.
264
+
265
+ Recommended rollout:
266
+
267
+ 1. synchronous collection primitive
268
+ 2. parallel runner support
269
+ 3. later async/pending/store support for items
270
+
271
+ ## Relation to Future Async
272
+
273
+ Collections should be designed with future item-level async in mind:
274
+
275
+ - stable keys
276
+ - collection result object
277
+ - explicit item states
278
+
279
+ But v1 should stay synchronous.
280
+
281
+ ## Error Model
282
+
283
+ Suggested runtime errors:
284
+
285
+ - `Igniter::CollectionInputError`
286
+ - `Igniter::CollectionKeyError`
287
+
288
+ Potentially later:
289
+
290
+ - `Igniter::CollectionItemError`
291
+
292
+ ## Future Extensions
293
+
294
+ Possible later additions:
295
+
296
+ - proc-based `key:`
297
+ - `map_inputs:` from primitive arrays
298
+ - item-level async
299
+ - item-level retries
300
+ - collection-level aggregation helpers
301
+ - branch-aware collections
302
+
303
+ These should not be part of v1.