igniter-extensions 0.5.2
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/README.md +381 -0
- data/lib/igniter/extensions/contracts/aggregate_pack.rb +103 -0
- data/lib/igniter/extensions/contracts/audit/builder.rb +132 -0
- data/lib/igniter/extensions/contracts/audit/event.rb +34 -0
- data/lib/igniter/extensions/contracts/audit/snapshot.rb +44 -0
- data/lib/igniter/extensions/contracts/audit_pack.rb +60 -0
- data/lib/igniter/extensions/contracts/branch_pack.rb +199 -0
- data/lib/igniter/extensions/contracts/capabilities/declaration.rb +31 -0
- data/lib/igniter/extensions/contracts/capabilities/error.rb +35 -0
- data/lib/igniter/extensions/contracts/capabilities/policy.rb +20 -0
- data/lib/igniter/extensions/contracts/capabilities/report.rb +47 -0
- data/lib/igniter/extensions/contracts/capabilities/violation.rb +30 -0
- data/lib/igniter/extensions/contracts/capabilities_pack.rb +146 -0
- data/lib/igniter/extensions/contracts/collection_pack.rb +212 -0
- data/lib/igniter/extensions/contracts/commerce_pack.rb +91 -0
- data/lib/igniter/extensions/contracts/compose_pack.rb +213 -0
- data/lib/igniter/extensions/contracts/content_addressing/cache.rb +59 -0
- data/lib/igniter/extensions/contracts/content_addressing/content_key.rb +63 -0
- data/lib/igniter/extensions/contracts/content_addressing/declaration.rb +47 -0
- data/lib/igniter/extensions/contracts/content_addressing_pack.rb +90 -0
- data/lib/igniter/extensions/contracts/creator/profile.rb +196 -0
- data/lib/igniter/extensions/contracts/creator/report.rb +85 -0
- data/lib/igniter/extensions/contracts/creator/scaffold.rb +461 -0
- data/lib/igniter/extensions/contracts/creator/scope.rb +79 -0
- data/lib/igniter/extensions/contracts/creator/wizard.rb +269 -0
- data/lib/igniter/extensions/contracts/creator/workflow.rb +189 -0
- data/lib/igniter/extensions/contracts/creator/workflow_step.rb +51 -0
- data/lib/igniter/extensions/contracts/creator/write_result.rb +48 -0
- data/lib/igniter/extensions/contracts/creator/write_step.rb +63 -0
- data/lib/igniter/extensions/contracts/creator/writer.rb +131 -0
- data/lib/igniter/extensions/contracts/creator_pack.rb +128 -0
- data/lib/igniter/extensions/contracts/dataflow/aggregate_operators.rb +119 -0
- data/lib/igniter/extensions/contracts/dataflow/aggregate_state.rb +60 -0
- data/lib/igniter/extensions/contracts/dataflow/builder.rb +66 -0
- data/lib/igniter/extensions/contracts/dataflow/collection_result.rb +70 -0
- data/lib/igniter/extensions/contracts/dataflow/diff.rb +37 -0
- data/lib/igniter/extensions/contracts/dataflow/item_result.rb +44 -0
- data/lib/igniter/extensions/contracts/dataflow/result.rb +58 -0
- data/lib/igniter/extensions/contracts/dataflow/session.rb +173 -0
- data/lib/igniter/extensions/contracts/dataflow/window_filter.rb +49 -0
- data/lib/igniter/extensions/contracts/dataflow_pack.rb +66 -0
- data/lib/igniter/extensions/contracts/debug/pack_audit.rb +181 -0
- data/lib/igniter/extensions/contracts/debug/pack_snapshot.rb +46 -0
- data/lib/igniter/extensions/contracts/debug/profile_snapshot.rb +50 -0
- data/lib/igniter/extensions/contracts/debug/report.rb +50 -0
- data/lib/igniter/extensions/contracts/debug_pack.rb +115 -0
- data/lib/igniter/extensions/contracts/differential/divergence.rb +37 -0
- data/lib/igniter/extensions/contracts/differential/formatter.rb +85 -0
- data/lib/igniter/extensions/contracts/differential/report.rb +83 -0
- data/lib/igniter/extensions/contracts/differential/runner.rb +136 -0
- data/lib/igniter/extensions/contracts/differential_pack.rb +61 -0
- data/lib/igniter/extensions/contracts/execution_report_pack.rb +38 -0
- data/lib/igniter/extensions/contracts/incremental/formatter.rb +60 -0
- data/lib/igniter/extensions/contracts/incremental/node_state.rb +30 -0
- data/lib/igniter/extensions/contracts/incremental/result.rb +65 -0
- data/lib/igniter/extensions/contracts/incremental/session.rb +146 -0
- data/lib/igniter/extensions/contracts/incremental_pack.rb +40 -0
- data/lib/igniter/extensions/contracts/invariants/builder.rb +27 -0
- data/lib/igniter/extensions/contracts/invariants/cases_report.rb +47 -0
- data/lib/igniter/extensions/contracts/invariants/error.rb +34 -0
- data/lib/igniter/extensions/contracts/invariants/invariant.rb +30 -0
- data/lib/igniter/extensions/contracts/invariants/report.rb +45 -0
- data/lib/igniter/extensions/contracts/invariants/suite.rb +36 -0
- data/lib/igniter/extensions/contracts/invariants/violation.rb +39 -0
- data/lib/igniter/extensions/contracts/invariants_pack.rb +88 -0
- data/lib/igniter/extensions/contracts/journal_pack.rb +55 -0
- data/lib/igniter/extensions/contracts/language/formula_pack.rb +185 -0
- data/lib/igniter/extensions/contracts/language/piecewise_pack.rb +166 -0
- data/lib/igniter/extensions/contracts/language/scale_pack.rb +147 -0
- data/lib/igniter/extensions/contracts/lookup_pack.rb +50 -0
- data/lib/igniter/extensions/contracts/mcp/creator_session.rb +105 -0
- data/lib/igniter/extensions/contracts/mcp/tool_argument.rb +35 -0
- data/lib/igniter/extensions/contracts/mcp/tool_definition.rb +33 -0
- data/lib/igniter/extensions/contracts/mcp/tool_result.rb +28 -0
- data/lib/igniter/extensions/contracts/mcp_pack.rb +335 -0
- data/lib/igniter/extensions/contracts/provenance/builder.rb +80 -0
- data/lib/igniter/extensions/contracts/provenance/lineage.rb +59 -0
- data/lib/igniter/extensions/contracts/provenance/node_trace.rb +53 -0
- data/lib/igniter/extensions/contracts/provenance/text_formatter.rb +62 -0
- data/lib/igniter/extensions/contracts/provenance_pack.rb +52 -0
- data/lib/igniter/extensions/contracts/reactive/builder.rb +43 -0
- data/lib/igniter/extensions/contracts/reactive/dispatch_result.rb +59 -0
- data/lib/igniter/extensions/contracts/reactive/engine.rb +79 -0
- data/lib/igniter/extensions/contracts/reactive/event.rb +36 -0
- data/lib/igniter/extensions/contracts/reactive/matcher.rb +20 -0
- data/lib/igniter/extensions/contracts/reactive/plan.rb +58 -0
- data/lib/igniter/extensions/contracts/reactive/subscription.rb +29 -0
- data/lib/igniter/extensions/contracts/reactive_pack.rb +169 -0
- data/lib/igniter/extensions/contracts/saga/compensation.rb +25 -0
- data/lib/igniter/extensions/contracts/saga/compensation_record.rb +28 -0
- data/lib/igniter/extensions/contracts/saga/compensation_set.rb +47 -0
- data/lib/igniter/extensions/contracts/saga/formatter.rb +39 -0
- data/lib/igniter/extensions/contracts/saga/result.rb +56 -0
- data/lib/igniter/extensions/contracts/saga/runner.rb +124 -0
- data/lib/igniter/extensions/contracts/saga_pack.rb +56 -0
- data/lib/igniter/extensions/contracts.rb +445 -0
- data/lib/igniter/extensions.rb +6 -0
- data/lib/igniter-extensions.rb +3 -0
- metadata +152 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: d70d38d90890f77551608abf6bb1989a7f51cd4c5973261f915145ef4abe05af
|
|
4
|
+
data.tar.gz: 1a842b9b1a9e7a7aa76ba3526055ec80adc482448fc55a31af47de8a6ba8d936
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: ddea43ae41d6914a88e5cc973565e53099c1ca19ec1b8b4f11df312e135839b414582cd41f72e513b7c01a3f57be49ef25802e76b98e16cedbc84675e53ac1ce
|
|
7
|
+
data.tar.gz: 73905d60c99ebf3814607eebc3e27544d403aff0b47ce77559dec53fdf6c384bf8d93d92e487134a9b849ac86e3f1127e7c87d5c4f7113bc3aad71cb85009d06
|
data/README.md
ADDED
|
@@ -0,0 +1,381 @@
|
|
|
1
|
+
# igniter-extensions
|
|
2
|
+
|
|
3
|
+
Contracts-native extension packs for Igniter.
|
|
4
|
+
|
|
5
|
+
This package now focuses only on packs built on top of `Igniter::Contracts`.
|
|
6
|
+
|
|
7
|
+
Primary entrypoints:
|
|
8
|
+
|
|
9
|
+
- `require "igniter-extensions"`
|
|
10
|
+
- `require "igniter/extensions/contracts"`
|
|
11
|
+
|
|
12
|
+
Contracts-facing external packs now live here too:
|
|
13
|
+
|
|
14
|
+
- `Igniter::Extensions::Contracts::ExecutionReportPack`
|
|
15
|
+
- `Igniter::Extensions::Contracts::LookupPack`
|
|
16
|
+
- `Igniter::Extensions::Contracts::AggregatePack`
|
|
17
|
+
- `Igniter::Extensions::Contracts::AuditPack`
|
|
18
|
+
- `Igniter::Extensions::Contracts::BranchPack`
|
|
19
|
+
- `Igniter::Extensions::Contracts::CapabilitiesPack`
|
|
20
|
+
- `Igniter::Extensions::Contracts::CollectionPack`
|
|
21
|
+
- `Igniter::Extensions::Contracts::CommercePack`
|
|
22
|
+
- `Igniter::Extensions::Contracts::ComposePack`
|
|
23
|
+
- `Igniter::Extensions::Contracts::ContentAddressingPack`
|
|
24
|
+
- `Igniter::Extensions::Contracts::CreatorPack`
|
|
25
|
+
- `Igniter::Extensions::Contracts::DataflowPack`
|
|
26
|
+
- `Igniter::Extensions::Contracts::DebugPack`
|
|
27
|
+
- `Igniter::Extensions::Contracts::DifferentialPack`
|
|
28
|
+
- `Igniter::Extensions::Contracts::JournalPack`
|
|
29
|
+
- `Igniter::Extensions::Contracts::InvariantsPack`
|
|
30
|
+
- `Igniter::Extensions::Contracts::McpPack`
|
|
31
|
+
- `Igniter::Extensions::Contracts::ProvenancePack`
|
|
32
|
+
- `Igniter::Extensions::Contracts::ReactivePack`
|
|
33
|
+
- `Igniter::Extensions::Contracts::SagaPack`
|
|
34
|
+
|
|
35
|
+
Those packs install into `Igniter::Contracts` through the public facade only:
|
|
36
|
+
|
|
37
|
+
```ruby
|
|
38
|
+
require "igniter/extensions/contracts"
|
|
39
|
+
|
|
40
|
+
environment = Igniter::Extensions::Contracts.with
|
|
41
|
+
|
|
42
|
+
result = environment.run(inputs: { rates: { ua: 0.2 } }) do
|
|
43
|
+
input :rates
|
|
44
|
+
lookup :tax_rate, from: :rates, dig: %i[eu ua], default: 0.2
|
|
45
|
+
output :tax_rate
|
|
46
|
+
end
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
Default helpers like `Igniter::Extensions::Contracts.with` currently install the
|
|
50
|
+
safe default packs (`ExecutionReportPack` and `LookupPack`). Operational packs
|
|
51
|
+
like `JournalPack` stay opt-in:
|
|
52
|
+
|
|
53
|
+
```ruby
|
|
54
|
+
environment = Igniter::Extensions::Contracts.with(
|
|
55
|
+
Igniter::Extensions::Contracts::JournalPack
|
|
56
|
+
)
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
`BranchPack` adds a contracts-native decision DSL that still lowers to ordinary
|
|
60
|
+
`compute` semantics:
|
|
61
|
+
|
|
62
|
+
```ruby
|
|
63
|
+
environment = Igniter::Contracts.with(
|
|
64
|
+
Igniter::Contracts::ProjectPack,
|
|
65
|
+
Igniter::Extensions::Contracts::BranchPack
|
|
66
|
+
)
|
|
67
|
+
|
|
68
|
+
result = environment.run(inputs: { country: "DE", vip: true }) do
|
|
69
|
+
input :country
|
|
70
|
+
input :vip
|
|
71
|
+
|
|
72
|
+
branch :delivery_strategy, on: :country, depends_on: [:vip] do
|
|
73
|
+
on "UA", id: :local, value: :local
|
|
74
|
+
on matches: /\A[A-Z]{2}\z/, id: :international do |vip:|
|
|
75
|
+
vip ? :priority_international : :international
|
|
76
|
+
end
|
|
77
|
+
default value: :fallback
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
project :delivery_mode, from: :delivery_strategy, key: :value
|
|
81
|
+
output :delivery_mode
|
|
82
|
+
end
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
`ComposePack` adds explicit nested contract invocation without restoring legacy
|
|
86
|
+
composition semantics into the kernel:
|
|
87
|
+
|
|
88
|
+
```ruby
|
|
89
|
+
environment = Igniter::Contracts.with(
|
|
90
|
+
Igniter::Extensions::Contracts::ComposePack
|
|
91
|
+
)
|
|
92
|
+
|
|
93
|
+
pricing_contract = environment.compile do
|
|
94
|
+
input :amount
|
|
95
|
+
input :tax_rate
|
|
96
|
+
compute :total, depends_on: %i[amount tax_rate] do |amount:, tax_rate:|
|
|
97
|
+
amount + (amount * tax_rate)
|
|
98
|
+
end
|
|
99
|
+
output :total
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
result = environment.run(inputs: { subtotal: 100, rate: 0.2 }) do
|
|
103
|
+
input :subtotal
|
|
104
|
+
input :rate
|
|
105
|
+
|
|
106
|
+
compose :pricing_total,
|
|
107
|
+
contract: pricing_contract,
|
|
108
|
+
inputs: { amount: :subtotal, tax_rate: :rate },
|
|
109
|
+
output: :total
|
|
110
|
+
|
|
111
|
+
output :pricing_total
|
|
112
|
+
end
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
The important forward-compatibility rule is that `ComposePack` keeps local
|
|
116
|
+
execution as the default, but also exposes `via:` for a custom invocation
|
|
117
|
+
adapter. That gives `igniter-application` or `igniter-cluster` room to add
|
|
118
|
+
remote compose later without rewriting the DSL contract.
|
|
119
|
+
|
|
120
|
+
`CollectionPack` follows the same idea for keyed collection execution:
|
|
121
|
+
|
|
122
|
+
```ruby
|
|
123
|
+
environment = Igniter::Contracts.with(
|
|
124
|
+
Igniter::Extensions::Contracts::CollectionPack
|
|
125
|
+
)
|
|
126
|
+
|
|
127
|
+
result = environment.run(inputs: {
|
|
128
|
+
items: [{ sku: "a", amount: 10 }, { sku: "b", amount: 20 }],
|
|
129
|
+
tax_rate: 0.2
|
|
130
|
+
}) do
|
|
131
|
+
input :items
|
|
132
|
+
input :tax_rate
|
|
133
|
+
|
|
134
|
+
collection :priced_items, from: :items, key: :sku, inputs: { tax_rate: :tax_rate } do
|
|
135
|
+
input :sku
|
|
136
|
+
input :amount
|
|
137
|
+
input :tax_rate
|
|
138
|
+
|
|
139
|
+
compute :total, depends_on: %i[amount tax_rate] do |amount:, tax_rate:|
|
|
140
|
+
amount + (amount * tax_rate)
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
output :total
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
output :priced_items
|
|
147
|
+
end
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
It returns a `CollectionResult` keyed by item identity, and keeps `via:` open
|
|
151
|
+
for a future remote/distributed collection invoker without changing the user DSL.
|
|
152
|
+
|
|
153
|
+
Applied presets can sit on top of those packs too:
|
|
154
|
+
|
|
155
|
+
```ruby
|
|
156
|
+
environment = Igniter::Extensions::Contracts.with_preset(:commerce)
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
For explicit content-addressed reuse, the contracts-side replacement is
|
|
160
|
+
`ContentAddressingPack`:
|
|
161
|
+
|
|
162
|
+
```ruby
|
|
163
|
+
environment = Igniter::Contracts.with(
|
|
164
|
+
Igniter::Extensions::Contracts::ContentAddressingPack
|
|
165
|
+
)
|
|
166
|
+
|
|
167
|
+
tax = Igniter::Extensions::Contracts.content_addressed(
|
|
168
|
+
fingerprint: "tax_v1"
|
|
169
|
+
) do |country:, amount:|
|
|
170
|
+
{ ua: 0.2, us: 0.1 }.fetch(country) * amount
|
|
171
|
+
end
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
For developer-focused observability, `DebugPack` can bundle profile,
|
|
175
|
+
compilation, execution, diagnostics, and provenance into one report:
|
|
176
|
+
|
|
177
|
+
```ruby
|
|
178
|
+
environment = Igniter::Extensions::Contracts.with(
|
|
179
|
+
Igniter::Extensions::Contracts::DebugPack
|
|
180
|
+
)
|
|
181
|
+
|
|
182
|
+
report = Igniter::Extensions::Contracts.debug_report(
|
|
183
|
+
environment,
|
|
184
|
+
inputs: { amount: 10 }
|
|
185
|
+
) do
|
|
186
|
+
input :amount
|
|
187
|
+
output :amount
|
|
188
|
+
end
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
It can also audit a custom pack before finalize, which is the first bridge
|
|
192
|
+
toward a future `CreatorPack` workflow:
|
|
193
|
+
|
|
194
|
+
```ruby
|
|
195
|
+
audit = Igniter::Extensions::Contracts.audit_pack(MyDraftPack, environment)
|
|
196
|
+
|
|
197
|
+
audit.ok?
|
|
198
|
+
audit.missing_node_definitions
|
|
199
|
+
audit.missing_registry_contracts
|
|
200
|
+
audit.finalize_error
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
`CreatorPack` now adds a minimal scaffold/report workflow on top of that:
|
|
204
|
+
|
|
205
|
+
```ruby
|
|
206
|
+
scaffold = Igniter::Extensions::Contracts.scaffold_pack(
|
|
207
|
+
name: :slug,
|
|
208
|
+
profile: :feature_node,
|
|
209
|
+
scope: :app_local,
|
|
210
|
+
namespace: "MyCompany::IgniterPacks"
|
|
211
|
+
)
|
|
212
|
+
|
|
213
|
+
report = Igniter::Extensions::Contracts.creator_report(
|
|
214
|
+
name: :slug,
|
|
215
|
+
profile: :feature_node
|
|
216
|
+
)
|
|
217
|
+
|
|
218
|
+
workflow = Igniter::Extensions::Contracts.creator_workflow(
|
|
219
|
+
name: :slug,
|
|
220
|
+
profile: :feature_node,
|
|
221
|
+
scope: :standalone_gem
|
|
222
|
+
)
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
Available authoring profiles:
|
|
226
|
+
|
|
227
|
+
- `:feature_node`
|
|
228
|
+
- `:operational_adapter`
|
|
229
|
+
- `:diagnostic_bundle`
|
|
230
|
+
- `:bundle_pack`
|
|
231
|
+
|
|
232
|
+
Available target scopes:
|
|
233
|
+
|
|
234
|
+
- `:app_local`
|
|
235
|
+
- `:monorepo_package`
|
|
236
|
+
- `:standalone_gem`
|
|
237
|
+
|
|
238
|
+
The workflow helper turns those decisions into an explicit authoring ladder:
|
|
239
|
+
|
|
240
|
+
- profile/scope selection
|
|
241
|
+
- scaffold generation
|
|
242
|
+
- implementation
|
|
243
|
+
- audit validation
|
|
244
|
+
- packaging readiness
|
|
245
|
+
|
|
246
|
+
It also separates recommended runtime dependency packs from development-only
|
|
247
|
+
tooling packs, so authoring guidance does not accidentally become runtime
|
|
248
|
+
bundle surface.
|
|
249
|
+
|
|
250
|
+
There is also a stateful wizard layer that can hold partial decisions before
|
|
251
|
+
you are ready to generate files:
|
|
252
|
+
|
|
253
|
+
```ruby
|
|
254
|
+
wizard = Igniter::Extensions::Contracts.creator_wizard(
|
|
255
|
+
name: :delivery,
|
|
256
|
+
capabilities: %i[effect executor]
|
|
257
|
+
)
|
|
258
|
+
|
|
259
|
+
wizard.current_decision
|
|
260
|
+
wizard.branching_hints
|
|
261
|
+
wizard.recommended_examples
|
|
262
|
+
completed = wizard.apply(scope: :standalone_gem)
|
|
263
|
+
```
|
|
264
|
+
|
|
265
|
+
For file generation, `CreatorPack` also exposes a multi-step writer with
|
|
266
|
+
explicit planning:
|
|
267
|
+
|
|
268
|
+
```ruby
|
|
269
|
+
writer = Igniter::Extensions::Contracts.creator_writer(
|
|
270
|
+
name: :slug,
|
|
271
|
+
profile: :feature_node,
|
|
272
|
+
scope: :app_local,
|
|
273
|
+
root: "/tmp/my_pack"
|
|
274
|
+
)
|
|
275
|
+
|
|
276
|
+
plan = writer.plan
|
|
277
|
+
result = writer.write
|
|
278
|
+
```
|
|
279
|
+
|
|
280
|
+
By default the writer uses `:skip_existing`, so existing files are preserved
|
|
281
|
+
unless you explicitly opt into `mode: :overwrite`.
|
|
282
|
+
|
|
283
|
+
`McpPack` is the first thin tooling adapter over those stabilized surfaces:
|
|
284
|
+
|
|
285
|
+
```ruby
|
|
286
|
+
environment = Igniter::Extensions::Contracts.with(
|
|
287
|
+
Igniter::Extensions::Contracts::McpPack
|
|
288
|
+
)
|
|
289
|
+
|
|
290
|
+
Igniter::Extensions::Contracts.mcp_tools
|
|
291
|
+
result = Igniter::Extensions::Contracts.mcp_call(
|
|
292
|
+
:creator_wizard,
|
|
293
|
+
target: environment,
|
|
294
|
+
name: :delivery,
|
|
295
|
+
capabilities: %i[effect executor]
|
|
296
|
+
)
|
|
297
|
+
```
|
|
298
|
+
|
|
299
|
+
The goal is to adapt existing debug/creator primitives for external tools, not
|
|
300
|
+
to invent a second authoring stack.
|
|
301
|
+
|
|
302
|
+
For stepwise external tooling, `McpPack` also exposes a serialized
|
|
303
|
+
`creator_session` flow:
|
|
304
|
+
|
|
305
|
+
```ruby
|
|
306
|
+
session = Igniter::Extensions::Contracts.mcp_creator_session(
|
|
307
|
+
target: environment,
|
|
308
|
+
name: :delivery,
|
|
309
|
+
capabilities: %i[effect executor]
|
|
310
|
+
)
|
|
311
|
+
|
|
312
|
+
updated = Igniter::Extensions::Contracts.mcp_call(
|
|
313
|
+
:creator_session_apply,
|
|
314
|
+
target: environment,
|
|
315
|
+
session: session.to_h.fetch(:payload).fetch(:session),
|
|
316
|
+
updates: { scope: :standalone_gem }
|
|
317
|
+
)
|
|
318
|
+
```
|
|
319
|
+
|
|
320
|
+
You can also drive scaffolding directly from capabilities:
|
|
321
|
+
|
|
322
|
+
```ruby
|
|
323
|
+
Igniter::Extensions::Contracts.scaffold_pack(
|
|
324
|
+
name: :delivery,
|
|
325
|
+
capabilities: %i[effect executor]
|
|
326
|
+
)
|
|
327
|
+
```
|
|
328
|
+
|
|
329
|
+
Older extension activators still exist for migration scenarios:
|
|
330
|
+
|
|
331
|
+
- `require "igniter/extensions/auditing"`
|
|
332
|
+
- `require "igniter/extensions/capabilities"`
|
|
333
|
+
- `require "igniter/extensions/dataflow"`
|
|
334
|
+
- `require "igniter/extensions/saga"`
|
|
335
|
+
- `require "igniter/extensions/provenance"`
|
|
336
|
+
- `require "igniter/extensions/differential"`
|
|
337
|
+
- `require "igniter/extensions/incremental"`
|
|
338
|
+
- `require "igniter/extensions/reactive"`
|
|
339
|
+
- `require "igniter/extensions/invariants"`
|
|
340
|
+
|
|
341
|
+
Those activators are migration context, not the long-term extension model.
|
|
342
|
+
|
|
343
|
+
The first canonical activator-to-pack migration target is now explicit:
|
|
344
|
+
|
|
345
|
+
- `require "igniter/extensions/execution_report"`
|
|
346
|
+
-> `Igniter::Extensions::Contracts::ExecutionReportPack`
|
|
347
|
+
- `require "igniter/extensions/auditing"`
|
|
348
|
+
-> `Igniter::Extensions::Contracts::AuditPack`
|
|
349
|
+
- `require "igniter/extensions/capabilities"`
|
|
350
|
+
-> `Igniter::Extensions::Contracts::CapabilitiesPack`
|
|
351
|
+
- `require "igniter/extensions/dataflow"`
|
|
352
|
+
-> `Igniter::Extensions::Contracts::DataflowPack`
|
|
353
|
+
- `require "igniter/extensions/provenance"`
|
|
354
|
+
-> `Igniter::Extensions::Contracts::ProvenancePack`
|
|
355
|
+
- `require "igniter/extensions/saga"`
|
|
356
|
+
-> `Igniter::Extensions::Contracts::SagaPack`
|
|
357
|
+
- `require "igniter/extensions/incremental"`
|
|
358
|
+
-> `Igniter::Extensions::Contracts::IncrementalPack`
|
|
359
|
+
- `require "igniter/extensions/differential"`
|
|
360
|
+
-> `Igniter::Extensions::Contracts::DifferentialPack`
|
|
361
|
+
- `require "igniter/extensions/reactive"`
|
|
362
|
+
-> `Igniter::Extensions::Contracts::ReactivePack`
|
|
363
|
+
- `require "igniter/extensions/invariants"`
|
|
364
|
+
-> `Igniter::Extensions::Contracts::InvariantsPack`
|
|
365
|
+
|
|
366
|
+
See [examples/contracts/auditing.rb](../../examples/contracts/auditing.rb)
|
|
367
|
+
and [examples/contracts/capabilities.rb](../../examples/contracts/capabilities.rb)
|
|
368
|
+
and [examples/contracts/dataflow.rb](../../examples/contracts/dataflow.rb)
|
|
369
|
+
and [examples/contracts/differential.rb](../../examples/contracts/differential.rb)
|
|
370
|
+
and [examples/contracts/invariants.rb](../../examples/contracts/invariants.rb)
|
|
371
|
+
and [examples/contracts/provenance.rb](../../examples/contracts/provenance.rb)
|
|
372
|
+
and [examples/contracts/reactive.rb](../../examples/contracts/reactive.rb)
|
|
373
|
+
and [examples/contracts/saga.rb](../../examples/contracts/saga.rb)
|
|
374
|
+
and [examples/contracts/incremental.rb](../../examples/contracts/incremental.rb)
|
|
375
|
+
for runnable migration walkthroughs.
|
|
376
|
+
|
|
377
|
+
Docs:
|
|
378
|
+
|
|
379
|
+
- [Guide](../../docs/guide/README.md)
|
|
380
|
+
- [Core guide](../../docs/guide/core.md)
|
|
381
|
+
- [Dev](../../docs/dev/README.md)
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Igniter
|
|
4
|
+
module Extensions
|
|
5
|
+
module Contracts
|
|
6
|
+
module AggregatePack
|
|
7
|
+
AGGREGATE_KEYWORDS = %i[count sum avg].freeze
|
|
8
|
+
|
|
9
|
+
class << self
|
|
10
|
+
def manifest
|
|
11
|
+
Igniter::Contracts::PackManifest.new(
|
|
12
|
+
name: :extensions_aggregate,
|
|
13
|
+
registry_contracts: AGGREGATE_KEYWORDS.map { |kind| Igniter::Contracts::PackManifest.dsl_keyword(kind) }
|
|
14
|
+
)
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def install_into(kernel)
|
|
18
|
+
install_dsl_keywords(kernel)
|
|
19
|
+
kernel
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def install_dsl_keywords(kernel)
|
|
23
|
+
kernel.dsl_keywords.register(:count, count_keyword)
|
|
24
|
+
kernel.dsl_keywords.register(:sum, sum_keyword)
|
|
25
|
+
kernel.dsl_keywords.register(:avg, avg_keyword)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def count_keyword
|
|
29
|
+
Igniter::Contracts::DslKeyword.new(:count) do |name, from:, builder:, matching: nil|
|
|
30
|
+
builder.add_operation(
|
|
31
|
+
kind: :compute,
|
|
32
|
+
name: name,
|
|
33
|
+
depends_on: [from.to_sym],
|
|
34
|
+
callable: lambda do |**values|
|
|
35
|
+
items = AggregatePack.enumerable_source(values.fetch(from.to_sym), source_name: from.to_sym,
|
|
36
|
+
operation_name: :count)
|
|
37
|
+
|
|
38
|
+
if matching
|
|
39
|
+
items.count { |item| matching.call(item) }
|
|
40
|
+
else
|
|
41
|
+
items.count
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
)
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def sum_keyword
|
|
49
|
+
Igniter::Contracts::DslKeyword.new(:sum) do |name, from:, builder:, using: nil|
|
|
50
|
+
builder.add_operation(
|
|
51
|
+
kind: :compute,
|
|
52
|
+
name: name,
|
|
53
|
+
depends_on: [from.to_sym],
|
|
54
|
+
callable: lambda do |**values|
|
|
55
|
+
items = AggregatePack.enumerable_source(values.fetch(from.to_sym), source_name: from.to_sym,
|
|
56
|
+
operation_name: :sum)
|
|
57
|
+
items.reduce(0) do |total, item|
|
|
58
|
+
total + AggregatePack.extract_value(item, using)
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
)
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def avg_keyword
|
|
66
|
+
Igniter::Contracts::DslKeyword.new(:avg) do |name, from:, builder:, using: nil|
|
|
67
|
+
builder.add_operation(
|
|
68
|
+
kind: :compute,
|
|
69
|
+
name: name,
|
|
70
|
+
depends_on: [from.to_sym],
|
|
71
|
+
callable: lambda do |**values|
|
|
72
|
+
items = AggregatePack.enumerable_source(values.fetch(from.to_sym), source_name: from.to_sym,
|
|
73
|
+
operation_name: :avg)
|
|
74
|
+
projected = items.map { |item| AggregatePack.extract_value(item, using) }
|
|
75
|
+
next nil if projected.empty?
|
|
76
|
+
|
|
77
|
+
projected.sum.to_f / projected.length
|
|
78
|
+
end
|
|
79
|
+
)
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
def enumerable_source(source, source_name:, operation_name:)
|
|
84
|
+
return source.to_a if source.respond_to?(:to_a)
|
|
85
|
+
|
|
86
|
+
raise TypeError, "#{operation_name} source #{source_name} is not enumerable"
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def extract_value(item, projection)
|
|
90
|
+
return item if projection.nil?
|
|
91
|
+
return projection.call(item) if projection.respond_to?(:call)
|
|
92
|
+
|
|
93
|
+
key = projection.to_sym
|
|
94
|
+
return item.fetch(key) if item.respond_to?(:key?) && item.key?(key)
|
|
95
|
+
return item.fetch(key.to_s) if item.respond_to?(:key?) && item.key?(key.to_s)
|
|
96
|
+
|
|
97
|
+
item.public_send(key)
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
end
|
|
103
|
+
end
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Igniter
|
|
4
|
+
module Extensions
|
|
5
|
+
module Contracts
|
|
6
|
+
module Audit
|
|
7
|
+
module Builder
|
|
8
|
+
module_function
|
|
9
|
+
|
|
10
|
+
def build(target)
|
|
11
|
+
result = unwrap_result(target)
|
|
12
|
+
compiled_graph = result.compiled_graph
|
|
13
|
+
outputs = result.outputs.to_h
|
|
14
|
+
state_values = result.state.to_h
|
|
15
|
+
|
|
16
|
+
Snapshot.new(
|
|
17
|
+
graph: graph_name(compiled_graph),
|
|
18
|
+
profile_fingerprint: result.profile_fingerprint,
|
|
19
|
+
events: build_events(compiled_graph, state_values: state_values, outputs: outputs),
|
|
20
|
+
states: build_states(compiled_graph, state_values: state_values, outputs: outputs),
|
|
21
|
+
children: build_children(state_values),
|
|
22
|
+
output_names: compiled_graph.operations.select(&:output?).map(&:name)
|
|
23
|
+
)
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def unwrap_result(target)
|
|
27
|
+
return target.execution_result if target.respond_to?(:execution_result)
|
|
28
|
+
|
|
29
|
+
target
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def graph_name(compiled_graph)
|
|
33
|
+
operation_names = compiled_graph.operations.reject(&:output?).map(&:name)
|
|
34
|
+
return "contracts_graph" if operation_names.empty?
|
|
35
|
+
|
|
36
|
+
"contracts_graph(#{operation_names.join(",")})"
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def build_events(compiled_graph, state_values:, outputs:)
|
|
40
|
+
compiled_graph.operations.each_with_index.map do |operation, index|
|
|
41
|
+
Event.new(
|
|
42
|
+
event_id: "#{operation.kind}:#{operation.name}:#{index}",
|
|
43
|
+
type: event_type_for(operation),
|
|
44
|
+
node_name: operation.name,
|
|
45
|
+
path: [operation.name],
|
|
46
|
+
status: status_for(operation, state_values: state_values, outputs: outputs),
|
|
47
|
+
payload: payload_for(operation, state_values: state_values, outputs: outputs)
|
|
48
|
+
)
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def build_states(compiled_graph, state_values:, outputs:)
|
|
53
|
+
compiled_graph.operations.each_with_object({}) do |operation, memo|
|
|
54
|
+
next if operation.output?
|
|
55
|
+
|
|
56
|
+
value = operation.output? ? outputs[operation.name] : state_values[operation.name]
|
|
57
|
+
memo[operation.name] = {
|
|
58
|
+
path: [operation.name],
|
|
59
|
+
kind: operation.kind,
|
|
60
|
+
status: status_for(operation, state_values: state_values, outputs: outputs),
|
|
61
|
+
value: serialize_value(value),
|
|
62
|
+
dependencies: dependency_names_for(operation)
|
|
63
|
+
}
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def build_children(state_values)
|
|
68
|
+
state_values.each_with_object([]) do |(name, value), memo|
|
|
69
|
+
next unless nested_execution_result?(value)
|
|
70
|
+
|
|
71
|
+
memo << {
|
|
72
|
+
node_name: name,
|
|
73
|
+
snapshot: build(value).to_h
|
|
74
|
+
}
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def event_type_for(operation)
|
|
79
|
+
return :output_observed if operation.output?
|
|
80
|
+
|
|
81
|
+
:"#{operation.kind}_observed"
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
def status_for(operation, state_values:, outputs:)
|
|
85
|
+
collection = operation.output? ? outputs : state_values
|
|
86
|
+
collection.key?(operation.name) ? :succeeded : :missing
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def payload_for(operation, state_values:, outputs:)
|
|
90
|
+
value = operation.output? ? outputs[operation.name] : state_values[operation.name]
|
|
91
|
+
payload = {
|
|
92
|
+
kind: operation.kind,
|
|
93
|
+
value: serialize_value(value)
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
dependencies = dependency_names_for(operation)
|
|
97
|
+
payload[:dependencies] = dependencies if dependencies.any?
|
|
98
|
+
payload
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
def dependency_names_for(operation)
|
|
102
|
+
names = []
|
|
103
|
+
names.concat(Array(operation.attributes[:depends_on])) if operation.attribute?(:depends_on)
|
|
104
|
+
names << operation.attributes[:from] if operation.attribute?(:from)
|
|
105
|
+
names.map(&:to_sym).uniq
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
def serialize_value(value)
|
|
109
|
+
case value
|
|
110
|
+
when Igniter::Contracts::ExecutionResult
|
|
111
|
+
{
|
|
112
|
+
type: :execution_result,
|
|
113
|
+
profile_fingerprint: value.profile_fingerprint,
|
|
114
|
+
outputs: value.outputs.to_h
|
|
115
|
+
}
|
|
116
|
+
when Array
|
|
117
|
+
value.map { |item| serialize_value(item) }
|
|
118
|
+
when Hash
|
|
119
|
+
value.transform_keys(&:to_sym).transform_values { |item| serialize_value(item) }
|
|
120
|
+
else
|
|
121
|
+
value
|
|
122
|
+
end
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
def nested_execution_result?(value)
|
|
126
|
+
value.is_a?(Igniter::Contracts::ExecutionResult)
|
|
127
|
+
end
|
|
128
|
+
end
|
|
129
|
+
end
|
|
130
|
+
end
|
|
131
|
+
end
|
|
132
|
+
end
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Igniter
|
|
4
|
+
module Extensions
|
|
5
|
+
module Contracts
|
|
6
|
+
module Audit
|
|
7
|
+
class Event
|
|
8
|
+
attr_reader :event_id, :type, :node_name, :path, :status, :payload
|
|
9
|
+
|
|
10
|
+
def initialize(event_id:, type:, node_name:, path:, status:, payload: {})
|
|
11
|
+
@event_id = event_id.to_s
|
|
12
|
+
@type = type.to_sym
|
|
13
|
+
@node_name = node_name.to_sym
|
|
14
|
+
@path = Array(path).map(&:to_sym).freeze
|
|
15
|
+
@status = status.to_sym
|
|
16
|
+
@payload = payload.freeze
|
|
17
|
+
freeze
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def to_h
|
|
21
|
+
{
|
|
22
|
+
event_id: event_id,
|
|
23
|
+
type: type,
|
|
24
|
+
node_name: node_name,
|
|
25
|
+
path: path,
|
|
26
|
+
status: status,
|
|
27
|
+
payload: payload
|
|
28
|
+
}
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Igniter
|
|
4
|
+
module Extensions
|
|
5
|
+
module Contracts
|
|
6
|
+
module Audit
|
|
7
|
+
class Snapshot
|
|
8
|
+
attr_reader :graph, :profile_fingerprint, :event_count, :events, :states, :children, :output_names
|
|
9
|
+
|
|
10
|
+
def initialize(graph:, profile_fingerprint:, events:, states:, children:, output_names:)
|
|
11
|
+
@graph = graph.to_s
|
|
12
|
+
@profile_fingerprint = profile_fingerprint
|
|
13
|
+
@events = events.freeze
|
|
14
|
+
@states = states.transform_keys(&:to_sym).freeze
|
|
15
|
+
@children = children.freeze
|
|
16
|
+
@output_names = output_names.map(&:to_sym).freeze
|
|
17
|
+
@event_count = @events.length
|
|
18
|
+
freeze
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def event_types
|
|
22
|
+
events.map(&:type).uniq
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def state(name)
|
|
26
|
+
states.fetch(name.to_sym)
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def to_h
|
|
30
|
+
{
|
|
31
|
+
graph: graph,
|
|
32
|
+
profile_fingerprint: profile_fingerprint,
|
|
33
|
+
event_count: event_count,
|
|
34
|
+
output_names: output_names,
|
|
35
|
+
events: events.map(&:to_h),
|
|
36
|
+
states: states,
|
|
37
|
+
children: children
|
|
38
|
+
}
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|