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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +12 -0
- data/README.md +224 -1
- data/docs/API_V2.md +238 -1
- data/docs/BACKLOG.md +166 -0
- data/docs/BRANCHES_V1.md +213 -0
- data/docs/COLLECTIONS_V1.md +303 -0
- data/docs/EXECUTION_MODEL_V2.md +79 -0
- data/docs/PATTERNS.md +222 -0
- data/docs/STORE_ADAPTERS.md +126 -0
- data/examples/README.md +124 -0
- data/examples/async_store.rb +47 -0
- data/examples/collection.rb +43 -0
- data/examples/collection_partial_failure.rb +50 -0
- data/examples/marketing_ergonomics.rb +57 -0
- data/examples/ringcentral_routing.rb +278 -0
- data/lib/igniter/compiler/compiled_graph.rb +82 -0
- data/lib/igniter/compiler/graph_compiler.rb +12 -2
- data/lib/igniter/compiler/type_resolver.rb +54 -0
- data/lib/igniter/compiler/validation_context.rb +61 -0
- data/lib/igniter/compiler/validation_pipeline.rb +30 -0
- data/lib/igniter/compiler/validator.rb +1 -187
- data/lib/igniter/compiler/validators/callable_validator.rb +107 -0
- data/lib/igniter/compiler/validators/dependencies_validator.rb +151 -0
- data/lib/igniter/compiler/validators/outputs_validator.rb +66 -0
- data/lib/igniter/compiler/validators/type_compatibility_validator.rb +84 -0
- data/lib/igniter/compiler/validators/uniqueness_validator.rb +60 -0
- data/lib/igniter/compiler.rb +8 -0
- data/lib/igniter/contract.rb +136 -4
- data/lib/igniter/diagnostics/auditing/report/console_formatter.rb +80 -0
- data/lib/igniter/diagnostics/auditing/report/markdown_formatter.rb +22 -0
- data/lib/igniter/diagnostics/introspection/formatters/mermaid_formatter.rb +58 -0
- data/lib/igniter/diagnostics/introspection/formatters/text_tree_formatter.rb +44 -0
- data/lib/igniter/diagnostics/report.rb +84 -8
- data/lib/igniter/dsl/contract_builder.rb +208 -5
- data/lib/igniter/dsl/schema_builder.rb +73 -0
- data/lib/igniter/dsl.rb +1 -0
- data/lib/igniter/errors.rb +11 -0
- data/lib/igniter/events/bus.rb +5 -0
- data/lib/igniter/events/event.rb +29 -0
- data/lib/igniter/executor.rb +74 -0
- data/lib/igniter/executor_registry.rb +44 -0
- data/lib/igniter/extensions/auditing/timeline.rb +4 -0
- data/lib/igniter/extensions/introspection/graph_formatter.rb +29 -3
- data/lib/igniter/extensions/introspection/plan_formatter.rb +55 -0
- data/lib/igniter/extensions/introspection/runtime_formatter.rb +18 -3
- data/lib/igniter/extensions/introspection.rb +1 -0
- data/lib/igniter/extensions/reactive/engine.rb +49 -2
- data/lib/igniter/extensions/reactive/reaction.rb +3 -2
- data/lib/igniter/model/branch_node.rb +40 -0
- data/lib/igniter/model/collection_node.rb +25 -0
- data/lib/igniter/model/composition_node.rb +2 -2
- data/lib/igniter/model/compute_node.rb +58 -2
- data/lib/igniter/model/input_node.rb +2 -2
- data/lib/igniter/model/output_node.rb +24 -4
- data/lib/igniter/model.rb +2 -0
- data/lib/igniter/runtime/cache.rb +64 -25
- data/lib/igniter/runtime/collection_result.rb +111 -0
- data/lib/igniter/runtime/deferred_result.rb +40 -0
- data/lib/igniter/runtime/execution.rb +261 -11
- data/lib/igniter/runtime/input_validator.rb +2 -24
- data/lib/igniter/runtime/invalidator.rb +1 -1
- data/lib/igniter/runtime/job_worker.rb +18 -0
- data/lib/igniter/runtime/node_state.rb +20 -0
- data/lib/igniter/runtime/planner.rb +126 -0
- data/lib/igniter/runtime/resolver.rb +269 -15
- data/lib/igniter/runtime/result.rb +14 -2
- data/lib/igniter/runtime/runner_factory.rb +20 -0
- data/lib/igniter/runtime/runners/inline_runner.rb +21 -0
- data/lib/igniter/runtime/runners/store_runner.rb +29 -0
- data/lib/igniter/runtime/runners/thread_pool_runner.rb +37 -0
- data/lib/igniter/runtime/stores/active_record_store.rb +41 -0
- data/lib/igniter/runtime/stores/file_store.rb +43 -0
- data/lib/igniter/runtime/stores/memory_store.rb +40 -0
- data/lib/igniter/runtime/stores/redis_store.rb +44 -0
- data/lib/igniter/runtime.rb +12 -0
- data/lib/igniter/type_system.rb +44 -0
- data/lib/igniter/version.rb +1 -1
- data/lib/igniter.rb +23 -0
- 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
|
data/docs/BRANCHES_V1.md
ADDED
|
@@ -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.
|