igniter 0.2.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/CHANGELOG.md +20 -0
- data/LICENSE.txt +21 -0
- data/README.md +264 -0
- data/docs/API_V2.md +242 -0
- data/docs/ARCHITECTURE_V2.md +317 -0
- data/docs/EXECUTION_MODEL_V2.md +245 -0
- data/docs/IGNITER_CONCEPTS.md +81 -0
- data/examples/README.md +77 -0
- data/examples/basic_pricing.rb +27 -0
- data/examples/composition.rb +39 -0
- data/examples/diagnostics.rb +28 -0
- data/lib/igniter/compiler/compiled_graph.rb +78 -0
- data/lib/igniter/compiler/graph_compiler.rb +60 -0
- data/lib/igniter/compiler/validator.rb +205 -0
- data/lib/igniter/compiler.rb +10 -0
- data/lib/igniter/contract.rb +117 -0
- data/lib/igniter/diagnostics/report.rb +174 -0
- data/lib/igniter/diagnostics.rb +8 -0
- data/lib/igniter/dsl/contract_builder.rb +95 -0
- data/lib/igniter/dsl.rb +8 -0
- data/lib/igniter/errors.rb +53 -0
- data/lib/igniter/events/bus.rb +39 -0
- data/lib/igniter/events/event.rb +53 -0
- data/lib/igniter/events.rb +9 -0
- data/lib/igniter/extensions/auditing/timeline.rb +99 -0
- data/lib/igniter/extensions/auditing.rb +10 -0
- data/lib/igniter/extensions/introspection/graph_formatter.rb +73 -0
- data/lib/igniter/extensions/introspection/runtime_formatter.rb +102 -0
- data/lib/igniter/extensions/introspection.rb +11 -0
- data/lib/igniter/extensions/reactive/engine.rb +36 -0
- data/lib/igniter/extensions/reactive/matcher.rb +21 -0
- data/lib/igniter/extensions/reactive/reaction.rb +17 -0
- data/lib/igniter/extensions/reactive.rb +12 -0
- data/lib/igniter/extensions.rb +10 -0
- data/lib/igniter/model/composition_node.rb +22 -0
- data/lib/igniter/model/compute_node.rb +21 -0
- data/lib/igniter/model/graph.rb +15 -0
- data/lib/igniter/model/input_node.rb +27 -0
- data/lib/igniter/model/node.rb +22 -0
- data/lib/igniter/model/output_node.rb +21 -0
- data/lib/igniter/model.rb +13 -0
- data/lib/igniter/runtime/cache.rb +58 -0
- data/lib/igniter/runtime/execution.rb +142 -0
- data/lib/igniter/runtime/input_validator.rb +145 -0
- data/lib/igniter/runtime/invalidator.rb +52 -0
- data/lib/igniter/runtime/node_state.rb +31 -0
- data/lib/igniter/runtime/resolver.rb +114 -0
- data/lib/igniter/runtime/result.rb +105 -0
- data/lib/igniter/runtime.rb +14 -0
- data/lib/igniter/version.rb +5 -0
- data/lib/igniter.rb +20 -0
- data/sig/igniter.rbs +4 -0
- metadata +126 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: b9146f111caa1071ea7ba8ede536e39a00450193c60748a176ab2e6a0cb0fc15
|
|
4
|
+
data.tar.gz: 6f120c1d14c286dea458b58dbefbdd1d908a3eca77edad22fe933b145103639a
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: d6de8e1d3228aaa17148abba206fe1185458ef316bd9a7153e3b02b460297bc7d838df47f6c3a95b266a56759bbc3aab3a2de0af68d0834005b863ae1a64f66a
|
|
7
|
+
data.tar.gz: 74f0b01d50f7d301c5519af7e54ce4042b02736321af2ae6d9beba3cb79dcbf1464e6ec59751668025cc0ad9012cce5a42ec36ee2fb1b46d23d43306e4366656
|
data/CHANGELOG.md
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
## [Unreleased]
|
|
2
|
+
|
|
3
|
+
## [0.2.0] - 2026-03-18
|
|
4
|
+
|
|
5
|
+
- Complete the `arbor` to `igniter` rename across runtime, docs, examples, console setup, and shipped signatures.
|
|
6
|
+
- Strengthen compile-time validation for proc signatures, composition input mappings, node ids, and node paths.
|
|
7
|
+
- Refine runtime semantics with a dedicated invalidation object, stricter execution lifecycle events, and explicit `execution_failed` signaling.
|
|
8
|
+
- Make composition resolution eager and reliable so parent composition nodes fail when child executions fail.
|
|
9
|
+
- Add structured error context with graph, node, path, and source location metadata.
|
|
10
|
+
- Expand introspection with stable node ids, invalidation details, richer explain output, and machine-readable execution/result/event payloads.
|
|
11
|
+
- Add diagnostics reports with structured, text, and markdown summaries for successful and failed executions.
|
|
12
|
+
- Add runnable example scripts plus smoke-tested quick-start examples and refresh the public documentation.
|
|
13
|
+
|
|
14
|
+
- Rename the gem and top-level namespace from `arbor` to `igniter`.
|
|
15
|
+
- Replace the legacy prototype with the v2 core runtime, compiler, DSL, and extensions.
|
|
16
|
+
- Add typed inputs, composition, auditing, reactive subscriptions, and introspection.
|
|
17
|
+
|
|
18
|
+
## [0.1.0] - 2025-08-03
|
|
19
|
+
|
|
20
|
+
- Initial release
|
data/LICENSE.txt
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
The MIT License (MIT)
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Alexander
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
|
13
|
+
all copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
21
|
+
THE SOFTWARE.
|
data/README.md
ADDED
|
@@ -0,0 +1,264 @@
|
|
|
1
|
+
# Igniter
|
|
2
|
+
|
|
3
|
+
Igniter is a Ruby gem for expressing business logic as a validated dependency graph and executing that graph with:
|
|
4
|
+
|
|
5
|
+
- lazy output resolution
|
|
6
|
+
- selective invalidation after input updates
|
|
7
|
+
- typed input validation
|
|
8
|
+
- nested contract composition
|
|
9
|
+
- runtime auditing
|
|
10
|
+
- diagnostics reports
|
|
11
|
+
- reactive side effects
|
|
12
|
+
- graph and runtime introspection
|
|
13
|
+
|
|
14
|
+
The repository now contains a working v2 core built around explicit compile-time and runtime boundaries.
|
|
15
|
+
|
|
16
|
+
## Installation
|
|
17
|
+
|
|
18
|
+
```ruby
|
|
19
|
+
gem "igniter"
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
## Quick Start
|
|
23
|
+
|
|
24
|
+
```ruby
|
|
25
|
+
require "igniter"
|
|
26
|
+
|
|
27
|
+
class PriceContract < Igniter::Contract
|
|
28
|
+
define do
|
|
29
|
+
input :order_total, type: :numeric
|
|
30
|
+
input :country, type: :string
|
|
31
|
+
input :vat_rate, type: :numeric, default: 0.2
|
|
32
|
+
|
|
33
|
+
compute :effective_vat_rate, depends_on: %i[country vat_rate] do |country:, vat_rate:|
|
|
34
|
+
country == "UA" ? vat_rate : 0.0
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
compute :gross_total, depends_on: %i[order_total effective_vat_rate] do |order_total:, effective_vat_rate:|
|
|
38
|
+
order_total * (1 + effective_vat_rate)
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
output :gross_total
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
contract = PriceContract.new(order_total: 100, country: "UA")
|
|
46
|
+
|
|
47
|
+
contract.result.gross_total
|
|
48
|
+
# => 120.0
|
|
49
|
+
|
|
50
|
+
contract.update_inputs(order_total: 150)
|
|
51
|
+
contract.result.gross_total
|
|
52
|
+
# => 180.0
|
|
53
|
+
|
|
54
|
+
contract.diagnostics_text
|
|
55
|
+
# => compact execution summary
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
## Features
|
|
59
|
+
|
|
60
|
+
- Contracts: declare inputs, compute nodes, outputs, and compositions.
|
|
61
|
+
- Compiler: validate dependency graphs before runtime.
|
|
62
|
+
- Runtime: cache resolved nodes and invalidate only affected downstream nodes.
|
|
63
|
+
- Typed inputs: validate types, defaults, and required fields.
|
|
64
|
+
- Composition: execute nested contracts with isolated child executions.
|
|
65
|
+
- Auditing: collect execution timelines and snapshots.
|
|
66
|
+
- Diagnostics: build compact text, markdown, or structured reports for triage.
|
|
67
|
+
- Reactive: subscribe declaratively to runtime events.
|
|
68
|
+
- Introspection: render graphs as text or Mermaid and inspect runtime state.
|
|
69
|
+
|
|
70
|
+
## Quick Start Recipes
|
|
71
|
+
|
|
72
|
+
The repository contains runnable examples in [`examples/`](examples).
|
|
73
|
+
They also have matching specs, so they stay in sync with the implementation.
|
|
74
|
+
The examples folder also has its own quick index in [`examples/README.md`](examples/README.md).
|
|
75
|
+
|
|
76
|
+
| Example | Run | Shows |
|
|
77
|
+
| --- | --- | --- |
|
|
78
|
+
| `basic_pricing.rb` | `ruby examples/basic_pricing.rb` | basic contract, lazy resolution, input updates |
|
|
79
|
+
| `composition.rb` | `ruby examples/composition.rb` | nested contracts and composed results |
|
|
80
|
+
| `diagnostics.rb` | `ruby examples/diagnostics.rb` | diagnostics text plus machine-readable output |
|
|
81
|
+
|
|
82
|
+
There are also matching living examples in `spec/igniter/examples_spec.rb`.
|
|
83
|
+
Those are useful if you want to read the examples in test form.
|
|
84
|
+
|
|
85
|
+
### 1. Basic Pricing Contract
|
|
86
|
+
|
|
87
|
+
```ruby
|
|
88
|
+
class PriceContract < Igniter::Contract
|
|
89
|
+
define do
|
|
90
|
+
input :order_total, type: :numeric
|
|
91
|
+
input :country, type: :string
|
|
92
|
+
|
|
93
|
+
compute :vat_rate, depends_on: [:country] do |country:|
|
|
94
|
+
country == "UA" ? 0.2 : 0.0
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
compute :gross_total, depends_on: %i[order_total vat_rate] do |order_total:, vat_rate:|
|
|
98
|
+
order_total * (1 + vat_rate)
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
output :gross_total
|
|
102
|
+
end
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
PriceContract.new(order_total: 100, country: "UA").result.gross_total
|
|
106
|
+
# => 120.0
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
### 2. Nested Composition
|
|
110
|
+
|
|
111
|
+
```ruby
|
|
112
|
+
class CheckoutContract < Igniter::Contract
|
|
113
|
+
define do
|
|
114
|
+
input :order_total, type: :numeric
|
|
115
|
+
input :country, type: :string
|
|
116
|
+
|
|
117
|
+
compose :pricing, contract: PriceContract, inputs: {
|
|
118
|
+
order_total: :order_total,
|
|
119
|
+
country: :country
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
output :pricing
|
|
123
|
+
end
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
CheckoutContract.new(order_total: 100, country: "UA").result.pricing.gross_total
|
|
127
|
+
# => 120.0
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
### 3. Diagnostics And Introspection
|
|
131
|
+
|
|
132
|
+
```ruby
|
|
133
|
+
contract = PriceContract.new(order_total: 100, country: "UA")
|
|
134
|
+
contract.result.gross_total
|
|
135
|
+
|
|
136
|
+
contract.result.states
|
|
137
|
+
contract.result.explain(:gross_total)
|
|
138
|
+
contract.diagnostics.to_h
|
|
139
|
+
contract.diagnostics_text
|
|
140
|
+
contract.diagnostics_markdown
|
|
141
|
+
contract.audit_snapshot
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
### 4. Machine-Readable Data
|
|
145
|
+
|
|
146
|
+
```ruby
|
|
147
|
+
contract = PriceContract.new(order_total: 100, country: "UA")
|
|
148
|
+
contract.result.gross_total
|
|
149
|
+
|
|
150
|
+
contract.result.to_h
|
|
151
|
+
# => { gross_total: 120.0 }
|
|
152
|
+
|
|
153
|
+
contract.result.as_json
|
|
154
|
+
contract.execution.as_json
|
|
155
|
+
contract.events.map(&:as_json)
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
## Composition Example
|
|
159
|
+
|
|
160
|
+
```ruby
|
|
161
|
+
class PricingContract < Igniter::Contract
|
|
162
|
+
define do
|
|
163
|
+
input :order_total, type: :numeric
|
|
164
|
+
|
|
165
|
+
compute :gross_total, depends_on: [:order_total] do |order_total:|
|
|
166
|
+
order_total * 1.2
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
output :gross_total
|
|
170
|
+
end
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
class CheckoutContract < Igniter::Contract
|
|
174
|
+
define do
|
|
175
|
+
input :order_total, type: :numeric
|
|
176
|
+
|
|
177
|
+
compose :pricing, contract: PricingContract, inputs: {
|
|
178
|
+
order_total: :order_total
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
output :pricing
|
|
182
|
+
end
|
|
183
|
+
end
|
|
184
|
+
|
|
185
|
+
CheckoutContract.new(order_total: 100).result.pricing.gross_total
|
|
186
|
+
# => 120.0
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
## Reactive Example
|
|
190
|
+
|
|
191
|
+
```ruby
|
|
192
|
+
class NotifyingContract < Igniter::Contract
|
|
193
|
+
define do
|
|
194
|
+
input :order_total, type: :numeric
|
|
195
|
+
output :order_total
|
|
196
|
+
end
|
|
197
|
+
|
|
198
|
+
react_to :node_succeeded, path: "order_total" do |event:, **|
|
|
199
|
+
puts "Resolved #{event.path}"
|
|
200
|
+
end
|
|
201
|
+
end
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
## Introspection
|
|
205
|
+
|
|
206
|
+
```ruby
|
|
207
|
+
PriceContract.graph.to_text
|
|
208
|
+
PriceContract.graph.to_mermaid
|
|
209
|
+
|
|
210
|
+
contract = PriceContract.new(order_total: 100, country: "UA")
|
|
211
|
+
contract.result.gross_total
|
|
212
|
+
|
|
213
|
+
contract.result.states
|
|
214
|
+
contract.result.explain(:gross_total)
|
|
215
|
+
contract.execution.to_h
|
|
216
|
+
contract.execution.as_json
|
|
217
|
+
contract.result.as_json
|
|
218
|
+
contract.events.map(&:as_json)
|
|
219
|
+
contract.diagnostics.to_h
|
|
220
|
+
contract.diagnostics_text
|
|
221
|
+
contract.diagnostics_markdown
|
|
222
|
+
contract.audit_snapshot
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
## v2 Design Docs
|
|
226
|
+
|
|
227
|
+
- [Architecture v2](docs/ARCHITECTURE_V2.md)
|
|
228
|
+
- [Execution Model v2](docs/EXECUTION_MODEL_V2.md)
|
|
229
|
+
- [API Draft v2](docs/API_V2.md)
|
|
230
|
+
- [Concepts and Principles](docs/IGNITER_CONCEPTS.md)
|
|
231
|
+
|
|
232
|
+
## Direction
|
|
233
|
+
|
|
234
|
+
The v2 rewrite is based on these rules:
|
|
235
|
+
|
|
236
|
+
- model, compiler, runtime, DSL, and extensions are separate layers
|
|
237
|
+
- graph validation happens before runtime
|
|
238
|
+
- auditing and reactive behavior are extensions over events, not runtime internals
|
|
239
|
+
- the first target is a deterministic synchronous kernel
|
|
240
|
+
|
|
241
|
+
## Status
|
|
242
|
+
|
|
243
|
+
The public Ruby surface in `lib/` now contains only the v2 core exposed from `require "igniter"`.
|
|
244
|
+
|
|
245
|
+
## Development
|
|
246
|
+
|
|
247
|
+
```bash
|
|
248
|
+
rake spec
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
Current baseline:
|
|
252
|
+
|
|
253
|
+
- synchronous runtime
|
|
254
|
+
- compile-time graph validation
|
|
255
|
+
- typed inputs
|
|
256
|
+
- composition
|
|
257
|
+
- auditing
|
|
258
|
+
- diagnostics reporting
|
|
259
|
+
- reactive subscriptions
|
|
260
|
+
- graph/runtime introspection
|
|
261
|
+
|
|
262
|
+
## License
|
|
263
|
+
|
|
264
|
+
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
data/docs/API_V2.md
ADDED
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
# Igniter v2 API Draft
|
|
2
|
+
|
|
3
|
+
## Public API Goals
|
|
4
|
+
|
|
5
|
+
The public API should be:
|
|
6
|
+
|
|
7
|
+
- small
|
|
8
|
+
- explicit
|
|
9
|
+
- inspectable
|
|
10
|
+
- stable
|
|
11
|
+
|
|
12
|
+
The user should understand Igniter through three concepts:
|
|
13
|
+
|
|
14
|
+
- declare a contract
|
|
15
|
+
- execute it with inputs
|
|
16
|
+
- inspect outputs and events
|
|
17
|
+
|
|
18
|
+
## Contract Shape
|
|
19
|
+
|
|
20
|
+
Recommended public entry point:
|
|
21
|
+
|
|
22
|
+
```ruby
|
|
23
|
+
class PriceContract < Igniter::Contract
|
|
24
|
+
define do
|
|
25
|
+
input :order_total
|
|
26
|
+
input :country
|
|
27
|
+
|
|
28
|
+
compute :vat_rate, depends_on: [:country], call: :resolve_vat_rate
|
|
29
|
+
compute :gross_total, depends_on: [:order_total, :vat_rate] do |order_total:, vat_rate:|
|
|
30
|
+
order_total * (1 + vat_rate)
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
output :gross_total
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def resolve_vat_rate(country:)
|
|
37
|
+
country == "UA" ? 0.2 : 0.0
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
### Why `define`
|
|
43
|
+
|
|
44
|
+
`define` clearly communicates compile-time declaration.
|
|
45
|
+
|
|
46
|
+
It is preferable to a more ambiguous `context` name because the graph being created is a model definition, not a runtime scope.
|
|
47
|
+
|
|
48
|
+
## Contract Runtime API
|
|
49
|
+
|
|
50
|
+
```ruby
|
|
51
|
+
contract = PriceContract.new(order_total: 100, country: "UA")
|
|
52
|
+
|
|
53
|
+
contract.result.gross_total
|
|
54
|
+
contract.result.to_h
|
|
55
|
+
contract.success?
|
|
56
|
+
contract.failed?
|
|
57
|
+
|
|
58
|
+
contract.update_inputs(order_total: 120)
|
|
59
|
+
contract.result.gross_total
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
Suggested instance methods:
|
|
63
|
+
|
|
64
|
+
- `result`
|
|
65
|
+
- `resolve`
|
|
66
|
+
- `resolve_all`
|
|
67
|
+
- `update_inputs`
|
|
68
|
+
- `events`
|
|
69
|
+
- `execution`
|
|
70
|
+
- `diagnostics`
|
|
71
|
+
- `success?`
|
|
72
|
+
- `failed?`
|
|
73
|
+
|
|
74
|
+
## Result API
|
|
75
|
+
|
|
76
|
+
`Result` is a read facade over outputs and execution state.
|
|
77
|
+
|
|
78
|
+
Suggested methods:
|
|
79
|
+
|
|
80
|
+
- output readers by name
|
|
81
|
+
- `to_h`
|
|
82
|
+
- `as_json`
|
|
83
|
+
- `success?`
|
|
84
|
+
- `failed?`
|
|
85
|
+
- `errors`
|
|
86
|
+
- `states`
|
|
87
|
+
- `explain`
|
|
88
|
+
|
|
89
|
+
Avoid relying on `method_missing` as the main implementation mechanism if explicit generated readers are practical.
|
|
90
|
+
|
|
91
|
+
## DSL Draft
|
|
92
|
+
|
|
93
|
+
### Inputs
|
|
94
|
+
|
|
95
|
+
```ruby
|
|
96
|
+
input :order_total
|
|
97
|
+
input :country
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
Optional later extension:
|
|
101
|
+
|
|
102
|
+
```ruby
|
|
103
|
+
input :order_total, type: :decimal, required: true
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
### Compute Nodes
|
|
107
|
+
|
|
108
|
+
Block form:
|
|
109
|
+
|
|
110
|
+
```ruby
|
|
111
|
+
compute :gross_total, depends_on: [:order_total, :vat_rate] do |order_total:, vat_rate:|
|
|
112
|
+
order_total * (1 + vat_rate)
|
|
113
|
+
end
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
Method form:
|
|
117
|
+
|
|
118
|
+
```ruby
|
|
119
|
+
compute :vat_rate, depends_on: [:country], call: :resolve_vat_rate
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
Rules:
|
|
123
|
+
|
|
124
|
+
- one compute node has one callable
|
|
125
|
+
- dependencies are explicit
|
|
126
|
+
- runtime injects only declared dependencies
|
|
127
|
+
|
|
128
|
+
### Outputs
|
|
129
|
+
|
|
130
|
+
```ruby
|
|
131
|
+
output :gross_total
|
|
132
|
+
output :vat_rate
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
Optional alias form:
|
|
136
|
+
|
|
137
|
+
```ruby
|
|
138
|
+
output :total, from: :gross_total
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
### Composition
|
|
142
|
+
|
|
143
|
+
```ruby
|
|
144
|
+
compose :pricing, contract: PriceContract, inputs: {
|
|
145
|
+
order_total: :order_total,
|
|
146
|
+
country: :country
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
output :pricing, from: :pricing
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
Collection composition can be added later, but should not complicate the first kernel API.
|
|
153
|
+
|
|
154
|
+
## Introspection API
|
|
155
|
+
|
|
156
|
+
Recommended API:
|
|
157
|
+
|
|
158
|
+
```ruby
|
|
159
|
+
PriceContract.graph
|
|
160
|
+
PriceContract.graph.to_h
|
|
161
|
+
PriceContract.graph.to_mermaid
|
|
162
|
+
|
|
163
|
+
contract.execution.states
|
|
164
|
+
contract.execution.to_h
|
|
165
|
+
contract.execution.as_json
|
|
166
|
+
contract.result.as_json
|
|
167
|
+
contract.events.map(&:as_json)
|
|
168
|
+
contract.diagnostics.to_h
|
|
169
|
+
contract.diagnostics.to_text
|
|
170
|
+
contract.diagnostics.to_markdown
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
The main rule is that introspection reads stable compile/runtime objects rather than poking through private internals.
|
|
174
|
+
|
|
175
|
+
## Events API
|
|
176
|
+
|
|
177
|
+
Suggested access patterns:
|
|
178
|
+
|
|
179
|
+
```ruby
|
|
180
|
+
contract.events.each do |event|
|
|
181
|
+
puts "#{event.type} #{event.path}"
|
|
182
|
+
end
|
|
183
|
+
|
|
184
|
+
contract.events.map(&:to_h)
|
|
185
|
+
contract.events.map(&:as_json)
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
Or:
|
|
189
|
+
|
|
190
|
+
```ruby
|
|
191
|
+
contract.subscribe(auditor)
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
Where a subscriber responds to:
|
|
195
|
+
|
|
196
|
+
```ruby
|
|
197
|
+
call(event)
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
## Errors API
|
|
201
|
+
|
|
202
|
+
Suggested public behavior:
|
|
203
|
+
|
|
204
|
+
```ruby
|
|
205
|
+
begin
|
|
206
|
+
contract.result.gross_total
|
|
207
|
+
rescue Igniter::ResolutionError => e
|
|
208
|
+
puts e.message
|
|
209
|
+
end
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
Result-level inspection should still expose node failures without forcing exception-driven control flow for every error path.
|
|
213
|
+
|
|
214
|
+
Each Igniter error also carries structured context when available:
|
|
215
|
+
|
|
216
|
+
- `graph`
|
|
217
|
+
- `node_id`
|
|
218
|
+
- `node_name`
|
|
219
|
+
- `node_path`
|
|
220
|
+
- `source_location`
|
|
221
|
+
|
|
222
|
+
## Roadmap API Decisions
|
|
223
|
+
|
|
224
|
+
### Include in the first rewrite
|
|
225
|
+
|
|
226
|
+
- `Igniter::Contract`
|
|
227
|
+
- `define`
|
|
228
|
+
- `input`
|
|
229
|
+
- `compute`
|
|
230
|
+
- `output`
|
|
231
|
+
- `result`
|
|
232
|
+
- `update_inputs`
|
|
233
|
+
- `events`
|
|
234
|
+
- graph introspection
|
|
235
|
+
|
|
236
|
+
### Postpone
|
|
237
|
+
|
|
238
|
+
- collection composition
|
|
239
|
+
- advanced typed schemas
|
|
240
|
+
- retries
|
|
241
|
+
- async executors
|
|
242
|
+
- Rails-specific DSL sugar
|