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.
Files changed (54) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +20 -0
  3. data/LICENSE.txt +21 -0
  4. data/README.md +264 -0
  5. data/docs/API_V2.md +242 -0
  6. data/docs/ARCHITECTURE_V2.md +317 -0
  7. data/docs/EXECUTION_MODEL_V2.md +245 -0
  8. data/docs/IGNITER_CONCEPTS.md +81 -0
  9. data/examples/README.md +77 -0
  10. data/examples/basic_pricing.rb +27 -0
  11. data/examples/composition.rb +39 -0
  12. data/examples/diagnostics.rb +28 -0
  13. data/lib/igniter/compiler/compiled_graph.rb +78 -0
  14. data/lib/igniter/compiler/graph_compiler.rb +60 -0
  15. data/lib/igniter/compiler/validator.rb +205 -0
  16. data/lib/igniter/compiler.rb +10 -0
  17. data/lib/igniter/contract.rb +117 -0
  18. data/lib/igniter/diagnostics/report.rb +174 -0
  19. data/lib/igniter/diagnostics.rb +8 -0
  20. data/lib/igniter/dsl/contract_builder.rb +95 -0
  21. data/lib/igniter/dsl.rb +8 -0
  22. data/lib/igniter/errors.rb +53 -0
  23. data/lib/igniter/events/bus.rb +39 -0
  24. data/lib/igniter/events/event.rb +53 -0
  25. data/lib/igniter/events.rb +9 -0
  26. data/lib/igniter/extensions/auditing/timeline.rb +99 -0
  27. data/lib/igniter/extensions/auditing.rb +10 -0
  28. data/lib/igniter/extensions/introspection/graph_formatter.rb +73 -0
  29. data/lib/igniter/extensions/introspection/runtime_formatter.rb +102 -0
  30. data/lib/igniter/extensions/introspection.rb +11 -0
  31. data/lib/igniter/extensions/reactive/engine.rb +36 -0
  32. data/lib/igniter/extensions/reactive/matcher.rb +21 -0
  33. data/lib/igniter/extensions/reactive/reaction.rb +17 -0
  34. data/lib/igniter/extensions/reactive.rb +12 -0
  35. data/lib/igniter/extensions.rb +10 -0
  36. data/lib/igniter/model/composition_node.rb +22 -0
  37. data/lib/igniter/model/compute_node.rb +21 -0
  38. data/lib/igniter/model/graph.rb +15 -0
  39. data/lib/igniter/model/input_node.rb +27 -0
  40. data/lib/igniter/model/node.rb +22 -0
  41. data/lib/igniter/model/output_node.rb +21 -0
  42. data/lib/igniter/model.rb +13 -0
  43. data/lib/igniter/runtime/cache.rb +58 -0
  44. data/lib/igniter/runtime/execution.rb +142 -0
  45. data/lib/igniter/runtime/input_validator.rb +145 -0
  46. data/lib/igniter/runtime/invalidator.rb +52 -0
  47. data/lib/igniter/runtime/node_state.rb +31 -0
  48. data/lib/igniter/runtime/resolver.rb +114 -0
  49. data/lib/igniter/runtime/result.rb +105 -0
  50. data/lib/igniter/runtime.rb +14 -0
  51. data/lib/igniter/version.rb +5 -0
  52. data/lib/igniter.rb +20 -0
  53. data/sig/igniter.rbs +4 -0
  54. 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