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.
Files changed (100) hide show
  1. checksums.yaml +7 -0
  2. data/README.md +381 -0
  3. data/lib/igniter/extensions/contracts/aggregate_pack.rb +103 -0
  4. data/lib/igniter/extensions/contracts/audit/builder.rb +132 -0
  5. data/lib/igniter/extensions/contracts/audit/event.rb +34 -0
  6. data/lib/igniter/extensions/contracts/audit/snapshot.rb +44 -0
  7. data/lib/igniter/extensions/contracts/audit_pack.rb +60 -0
  8. data/lib/igniter/extensions/contracts/branch_pack.rb +199 -0
  9. data/lib/igniter/extensions/contracts/capabilities/declaration.rb +31 -0
  10. data/lib/igniter/extensions/contracts/capabilities/error.rb +35 -0
  11. data/lib/igniter/extensions/contracts/capabilities/policy.rb +20 -0
  12. data/lib/igniter/extensions/contracts/capabilities/report.rb +47 -0
  13. data/lib/igniter/extensions/contracts/capabilities/violation.rb +30 -0
  14. data/lib/igniter/extensions/contracts/capabilities_pack.rb +146 -0
  15. data/lib/igniter/extensions/contracts/collection_pack.rb +212 -0
  16. data/lib/igniter/extensions/contracts/commerce_pack.rb +91 -0
  17. data/lib/igniter/extensions/contracts/compose_pack.rb +213 -0
  18. data/lib/igniter/extensions/contracts/content_addressing/cache.rb +59 -0
  19. data/lib/igniter/extensions/contracts/content_addressing/content_key.rb +63 -0
  20. data/lib/igniter/extensions/contracts/content_addressing/declaration.rb +47 -0
  21. data/lib/igniter/extensions/contracts/content_addressing_pack.rb +90 -0
  22. data/lib/igniter/extensions/contracts/creator/profile.rb +196 -0
  23. data/lib/igniter/extensions/contracts/creator/report.rb +85 -0
  24. data/lib/igniter/extensions/contracts/creator/scaffold.rb +461 -0
  25. data/lib/igniter/extensions/contracts/creator/scope.rb +79 -0
  26. data/lib/igniter/extensions/contracts/creator/wizard.rb +269 -0
  27. data/lib/igniter/extensions/contracts/creator/workflow.rb +189 -0
  28. data/lib/igniter/extensions/contracts/creator/workflow_step.rb +51 -0
  29. data/lib/igniter/extensions/contracts/creator/write_result.rb +48 -0
  30. data/lib/igniter/extensions/contracts/creator/write_step.rb +63 -0
  31. data/lib/igniter/extensions/contracts/creator/writer.rb +131 -0
  32. data/lib/igniter/extensions/contracts/creator_pack.rb +128 -0
  33. data/lib/igniter/extensions/contracts/dataflow/aggregate_operators.rb +119 -0
  34. data/lib/igniter/extensions/contracts/dataflow/aggregate_state.rb +60 -0
  35. data/lib/igniter/extensions/contracts/dataflow/builder.rb +66 -0
  36. data/lib/igniter/extensions/contracts/dataflow/collection_result.rb +70 -0
  37. data/lib/igniter/extensions/contracts/dataflow/diff.rb +37 -0
  38. data/lib/igniter/extensions/contracts/dataflow/item_result.rb +44 -0
  39. data/lib/igniter/extensions/contracts/dataflow/result.rb +58 -0
  40. data/lib/igniter/extensions/contracts/dataflow/session.rb +173 -0
  41. data/lib/igniter/extensions/contracts/dataflow/window_filter.rb +49 -0
  42. data/lib/igniter/extensions/contracts/dataflow_pack.rb +66 -0
  43. data/lib/igniter/extensions/contracts/debug/pack_audit.rb +181 -0
  44. data/lib/igniter/extensions/contracts/debug/pack_snapshot.rb +46 -0
  45. data/lib/igniter/extensions/contracts/debug/profile_snapshot.rb +50 -0
  46. data/lib/igniter/extensions/contracts/debug/report.rb +50 -0
  47. data/lib/igniter/extensions/contracts/debug_pack.rb +115 -0
  48. data/lib/igniter/extensions/contracts/differential/divergence.rb +37 -0
  49. data/lib/igniter/extensions/contracts/differential/formatter.rb +85 -0
  50. data/lib/igniter/extensions/contracts/differential/report.rb +83 -0
  51. data/lib/igniter/extensions/contracts/differential/runner.rb +136 -0
  52. data/lib/igniter/extensions/contracts/differential_pack.rb +61 -0
  53. data/lib/igniter/extensions/contracts/execution_report_pack.rb +38 -0
  54. data/lib/igniter/extensions/contracts/incremental/formatter.rb +60 -0
  55. data/lib/igniter/extensions/contracts/incremental/node_state.rb +30 -0
  56. data/lib/igniter/extensions/contracts/incremental/result.rb +65 -0
  57. data/lib/igniter/extensions/contracts/incremental/session.rb +146 -0
  58. data/lib/igniter/extensions/contracts/incremental_pack.rb +40 -0
  59. data/lib/igniter/extensions/contracts/invariants/builder.rb +27 -0
  60. data/lib/igniter/extensions/contracts/invariants/cases_report.rb +47 -0
  61. data/lib/igniter/extensions/contracts/invariants/error.rb +34 -0
  62. data/lib/igniter/extensions/contracts/invariants/invariant.rb +30 -0
  63. data/lib/igniter/extensions/contracts/invariants/report.rb +45 -0
  64. data/lib/igniter/extensions/contracts/invariants/suite.rb +36 -0
  65. data/lib/igniter/extensions/contracts/invariants/violation.rb +39 -0
  66. data/lib/igniter/extensions/contracts/invariants_pack.rb +88 -0
  67. data/lib/igniter/extensions/contracts/journal_pack.rb +55 -0
  68. data/lib/igniter/extensions/contracts/language/formula_pack.rb +185 -0
  69. data/lib/igniter/extensions/contracts/language/piecewise_pack.rb +166 -0
  70. data/lib/igniter/extensions/contracts/language/scale_pack.rb +147 -0
  71. data/lib/igniter/extensions/contracts/lookup_pack.rb +50 -0
  72. data/lib/igniter/extensions/contracts/mcp/creator_session.rb +105 -0
  73. data/lib/igniter/extensions/contracts/mcp/tool_argument.rb +35 -0
  74. data/lib/igniter/extensions/contracts/mcp/tool_definition.rb +33 -0
  75. data/lib/igniter/extensions/contracts/mcp/tool_result.rb +28 -0
  76. data/lib/igniter/extensions/contracts/mcp_pack.rb +335 -0
  77. data/lib/igniter/extensions/contracts/provenance/builder.rb +80 -0
  78. data/lib/igniter/extensions/contracts/provenance/lineage.rb +59 -0
  79. data/lib/igniter/extensions/contracts/provenance/node_trace.rb +53 -0
  80. data/lib/igniter/extensions/contracts/provenance/text_formatter.rb +62 -0
  81. data/lib/igniter/extensions/contracts/provenance_pack.rb +52 -0
  82. data/lib/igniter/extensions/contracts/reactive/builder.rb +43 -0
  83. data/lib/igniter/extensions/contracts/reactive/dispatch_result.rb +59 -0
  84. data/lib/igniter/extensions/contracts/reactive/engine.rb +79 -0
  85. data/lib/igniter/extensions/contracts/reactive/event.rb +36 -0
  86. data/lib/igniter/extensions/contracts/reactive/matcher.rb +20 -0
  87. data/lib/igniter/extensions/contracts/reactive/plan.rb +58 -0
  88. data/lib/igniter/extensions/contracts/reactive/subscription.rb +29 -0
  89. data/lib/igniter/extensions/contracts/reactive_pack.rb +169 -0
  90. data/lib/igniter/extensions/contracts/saga/compensation.rb +25 -0
  91. data/lib/igniter/extensions/contracts/saga/compensation_record.rb +28 -0
  92. data/lib/igniter/extensions/contracts/saga/compensation_set.rb +47 -0
  93. data/lib/igniter/extensions/contracts/saga/formatter.rb +39 -0
  94. data/lib/igniter/extensions/contracts/saga/result.rb +56 -0
  95. data/lib/igniter/extensions/contracts/saga/runner.rb +124 -0
  96. data/lib/igniter/extensions/contracts/saga_pack.rb +56 -0
  97. data/lib/igniter/extensions/contracts.rb +445 -0
  98. data/lib/igniter/extensions.rb +6 -0
  99. data/lib/igniter-extensions.rb +3 -0
  100. metadata +152 -0
@@ -0,0 +1,65 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Igniter
4
+ module Extensions
5
+ module Contracts
6
+ module Incremental
7
+ class Result
8
+ attr_reader :execution_result, :changed_nodes, :skipped_nodes, :backdated_nodes,
9
+ :changed_outputs, :recomputed_count
10
+
11
+ def initialize(execution_result:, changed_nodes:, skipped_nodes:, backdated_nodes:, changed_outputs:,
12
+ recomputed_count:)
13
+ @execution_result = execution_result
14
+ @changed_nodes = changed_nodes.freeze
15
+ @skipped_nodes = skipped_nodes.freeze
16
+ @backdated_nodes = backdated_nodes.freeze
17
+ @changed_outputs = changed_outputs.freeze
18
+ @recomputed_count = recomputed_count
19
+ freeze
20
+ end
21
+
22
+ def outputs_changed?
23
+ changed_outputs.any?
24
+ end
25
+
26
+ def fully_memoized?
27
+ recomputed_count.zero?
28
+ end
29
+
30
+ def summary
31
+ parts = []
32
+ parts << "#{changed_nodes.length} changed" if changed_nodes.any?
33
+ parts << "#{skipped_nodes.length} skipped" if skipped_nodes.any?
34
+ parts << "#{backdated_nodes.length} backdated" if backdated_nodes.any?
35
+ parts << "#{recomputed_count} recomputed"
36
+ parts.join(", ")
37
+ end
38
+
39
+ def explain
40
+ Formatter.format(self)
41
+ end
42
+
43
+ alias to_s explain
44
+
45
+ def output(name)
46
+ execution_result.output(name)
47
+ end
48
+
49
+ def to_h
50
+ {
51
+ changed_nodes: changed_nodes,
52
+ skipped_nodes: skipped_nodes,
53
+ backdated_nodes: backdated_nodes,
54
+ changed_outputs: changed_outputs,
55
+ recomputed_count: recomputed_count,
56
+ outputs_changed: outputs_changed?,
57
+ fully_memoized: fully_memoized?,
58
+ execution_result: execution_result.to_h
59
+ }
60
+ end
61
+ end
62
+ end
63
+ end
64
+ end
65
+ end
@@ -0,0 +1,146 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Igniter
4
+ module Extensions
5
+ module Contracts
6
+ module Incremental
7
+ class Session
8
+ attr_reader :compiled_graph, :profile
9
+
10
+ def initialize(compiled_graph:, profile:)
11
+ @compiled_graph = compiled_graph
12
+ @profile = profile
13
+ @node_states = {}
14
+ @last_result = nil
15
+ end
16
+
17
+ def run(inputs:)
18
+ normalized_inputs = Igniter::Contracts::NamedValues.new(inputs)
19
+ current_values = Igniter::Contracts::MutableNamedValues.new
20
+ current_outputs = Igniter::Contracts::MutableNamedValues.new
21
+ current_states = {}
22
+
23
+ changed_nodes = []
24
+ skipped_nodes = []
25
+ backdated_nodes = []
26
+ recomputed_count = 0
27
+
28
+ compiled_graph.operations.each do |operation|
29
+ if operation.output?
30
+ current_outputs.write(operation.name, current_values.fetch(operation.name))
31
+ next
32
+ end
33
+
34
+ if operation.kind == :input
35
+ state = resolve_input_state(operation, normalized_inputs)
36
+ current_states[operation.name] = state
37
+ current_values.write(operation.name, state.value)
38
+ changed_nodes << operation.name if changed_value?(operation.name, state.value)
39
+ next
40
+ end
41
+
42
+ dependency_versions = dependency_names_for(operation).each_with_object({}) do |dependency_name, memo|
43
+ if current_states.key?(dependency_name)
44
+ memo[dependency_name] =
45
+ current_states.fetch(dependency_name).value_version
46
+ end
47
+ end
48
+
49
+ previous = @node_states[operation.name]
50
+
51
+ if previous && previous.dep_snapshot == dependency_versions
52
+ current_states[operation.name] = previous
53
+ current_values.write(operation.name, previous.value)
54
+ skipped_nodes << operation.name
55
+ next
56
+ end
57
+
58
+ handler = profile.runtime_handler(operation.kind)
59
+ value = handler.call(
60
+ operation: operation,
61
+ state: current_values,
62
+ outputs: current_outputs,
63
+ inputs: normalized_inputs,
64
+ profile: profile
65
+ )
66
+
67
+ value_version =
68
+ if previous && previous.value == value
69
+ backdated_nodes << operation.name
70
+ previous.value_version
71
+ else
72
+ changed_nodes << operation.name if changed_value?(operation.name, value)
73
+ previous ? previous.value_version + 1 : 1
74
+ end
75
+
76
+ current_states[operation.name] = NodeState.new(
77
+ name: operation.name,
78
+ value: value,
79
+ value_version: value_version,
80
+ dep_snapshot: dependency_versions
81
+ )
82
+ current_values.write(operation.name, value)
83
+ recomputed_count += 1
84
+ end
85
+
86
+ execution_result = Igniter::Contracts::ExecutionResult.new(
87
+ state: current_values.snapshot,
88
+ outputs: current_outputs.snapshot,
89
+ profile_fingerprint: profile.fingerprint,
90
+ compiled_graph: compiled_graph
91
+ )
92
+
93
+ result = Result.new(
94
+ execution_result: execution_result,
95
+ changed_nodes: changed_nodes.uniq,
96
+ skipped_nodes: skipped_nodes.uniq,
97
+ backdated_nodes: backdated_nodes.uniq,
98
+ changed_outputs: changed_outputs_for(execution_result),
99
+ recomputed_count: recomputed_count
100
+ )
101
+
102
+ @node_states = current_states.freeze
103
+ @last_result = result
104
+ end
105
+
106
+ private
107
+
108
+ def resolve_input_state(operation, normalized_inputs)
109
+ value = normalized_inputs.fetch(operation.name)
110
+ previous = @node_states[operation.name]
111
+ value_version = previous && previous.value == value ? previous.value_version : (previous&.value_version || 0) + 1
112
+
113
+ NodeState.new(
114
+ name: operation.name,
115
+ value: value,
116
+ value_version: value_version
117
+ )
118
+ end
119
+
120
+ def dependency_names_for(operation)
121
+ names = []
122
+ names.concat(Array(operation.attributes[:depends_on])) if operation.attribute?(:depends_on)
123
+ names << operation.attributes[:from] if operation.attribute?(:from)
124
+ names.map(&:to_sym).uniq
125
+ end
126
+
127
+ def changed_outputs_for(execution_result)
128
+ previous_outputs = @last_result&.execution_result&.outputs
129
+ execution_result.outputs.keys.each_with_object({}) do |name, memo|
130
+ previous = previous_outputs&.[](name)
131
+ current = execution_result.outputs[name]
132
+ next if previous == current
133
+
134
+ memo[name] = { from: previous, to: current }
135
+ end
136
+ end
137
+
138
+ def changed_value?(name, value)
139
+ previous = @node_states[name]
140
+ previous.nil? || previous.value != value
141
+ end
142
+ end
143
+ end
144
+ end
145
+ end
146
+ end
@@ -0,0 +1,40 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "incremental/node_state"
4
+ require_relative "incremental/formatter"
5
+ require_relative "incremental/result"
6
+ require_relative "incremental/session"
7
+
8
+ module Igniter
9
+ module Extensions
10
+ module Contracts
11
+ module IncrementalPack
12
+ module_function
13
+
14
+ def manifest
15
+ Igniter::Contracts::PackManifest.new(
16
+ name: :extensions_incremental,
17
+ metadata: { category: :orchestration }
18
+ )
19
+ end
20
+
21
+ def install_into(kernel)
22
+ kernel
23
+ end
24
+
25
+ def session(environment, compiled_graph: nil, &block)
26
+ graph = compiled_graph || environment.compile(&block)
27
+ ensure_installed!(environment.profile)
28
+ Incremental::Session.new(compiled_graph: graph, profile: environment.profile)
29
+ end
30
+
31
+ def ensure_installed!(profile)
32
+ return if profile.pack_names.include?(:extensions_incremental)
33
+
34
+ raise Igniter::Contracts::Error,
35
+ "IncrementalPack is not installed in profile #{profile.fingerprint}; add Igniter::Extensions::Contracts::IncrementalPack"
36
+ end
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Igniter
4
+ module Extensions
5
+ module Contracts
6
+ module Invariants
7
+ class Builder
8
+ attr_reader :suite
9
+
10
+ def initialize
11
+ @suite = Suite.new(invariants: [])
12
+ end
13
+
14
+ def invariant(name, &block)
15
+ @suite = suite.invariant(name, &block)
16
+ end
17
+
18
+ def self.build(&block)
19
+ builder = new
20
+ builder.instance_eval(&block) if block
21
+ builder.suite
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,47 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Igniter
4
+ module Extensions
5
+ module Contracts
6
+ module Invariants
7
+ class CasesReport
8
+ attr_reader :reports
9
+
10
+ def initialize(reports:)
11
+ @reports = reports.freeze
12
+ freeze
13
+ end
14
+
15
+ def valid?
16
+ reports.all?(&:valid?)
17
+ end
18
+
19
+ def invalid_cases
20
+ reports.each_with_index.filter_map do |report, index|
21
+ next if report.valid?
22
+
23
+ {
24
+ index: index,
25
+ report: report.to_h
26
+ }
27
+ end
28
+ end
29
+
30
+ def summary
31
+ return "all cases valid" if valid?
32
+
33
+ "#{invalid_cases.length} invalid case(s)"
34
+ end
35
+
36
+ def to_h
37
+ {
38
+ valid: valid?,
39
+ case_count: reports.length,
40
+ invalid_cases: invalid_cases
41
+ }
42
+ end
43
+ end
44
+ end
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Igniter
4
+ module Extensions
5
+ module Contracts
6
+ module Invariants
7
+ class Error < StandardError; end
8
+
9
+ class InvariantError < Error
10
+ attr_reader :violations
11
+
12
+ def initialize(message = nil, violations: [])
13
+ @violations = Array(violations).freeze
14
+ super(message || default_message)
15
+ end
16
+
17
+ def to_h
18
+ {
19
+ message: message,
20
+ violations: violations.map(&:to_h)
21
+ }
22
+ end
23
+
24
+ private
25
+
26
+ def default_message
27
+ names = violations.map { |violation| ":#{violation.name}" }.join(", ")
28
+ "#{violations.length} invariant(s) violated: #{names}"
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Igniter
4
+ module Extensions
5
+ module Contracts
6
+ module Invariants
7
+ class Invariant
8
+ attr_reader :name, :block
9
+
10
+ def initialize(name, &block)
11
+ raise ArgumentError, "invariant #{name.inspect} requires a block" unless block
12
+
13
+ @name = name.to_sym
14
+ @block = block
15
+ freeze
16
+ end
17
+
18
+ def check(outputs)
19
+ passed = block.call(**outputs)
20
+ return nil if passed
21
+
22
+ Violation.new(name: name, outputs: outputs)
23
+ rescue StandardError => e
24
+ Violation.new(name: name, outputs: outputs, error: e)
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,45 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Igniter
4
+ module Extensions
5
+ module Contracts
6
+ module Invariants
7
+ class Report
8
+ attr_reader :suite, :outputs, :violations, :execution_result
9
+
10
+ def initialize(suite:, outputs:, violations:, execution_result: nil)
11
+ @suite = suite
12
+ @outputs = outputs.transform_keys(&:to_sym).freeze
13
+ @violations = Array(violations).freeze
14
+ @execution_result = execution_result
15
+ freeze
16
+ end
17
+
18
+ def valid?
19
+ violations.empty?
20
+ end
21
+
22
+ def invalid?
23
+ !valid?
24
+ end
25
+
26
+ def summary
27
+ return "valid" if valid?
28
+
29
+ "invalid - #{violations.length} invariant(s) violated: #{violations.map(&:name).join(", ")}"
30
+ end
31
+
32
+ def to_h
33
+ {
34
+ valid: valid?,
35
+ outputs: outputs,
36
+ invariants: suite.names,
37
+ violations: violations.map(&:to_h),
38
+ execution_result: execution_result&.to_h
39
+ }
40
+ end
41
+ end
42
+ end
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Igniter
4
+ module Extensions
5
+ module Contracts
6
+ module Invariants
7
+ class Suite
8
+ attr_reader :invariants
9
+
10
+ def initialize(invariants:)
11
+ @invariants = invariants.freeze
12
+ freeze
13
+ end
14
+
15
+ def invariant(name, &block)
16
+ self.class.new(invariants: invariants + [Invariant.new(name, &block)])
17
+ end
18
+
19
+ def empty?
20
+ invariants.empty?
21
+ end
22
+
23
+ def names
24
+ invariants.map(&:name)
25
+ end
26
+
27
+ def to_h
28
+ {
29
+ invariants: names
30
+ }
31
+ end
32
+ end
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,39 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Igniter
4
+ module Extensions
5
+ module Contracts
6
+ module Invariants
7
+ class Violation
8
+ attr_reader :name, :outputs, :error
9
+
10
+ def initialize(name:, outputs:, error: nil)
11
+ @name = name.to_sym
12
+ @outputs = outputs.transform_keys(&:to_sym).freeze
13
+ @error = error
14
+ freeze
15
+ end
16
+
17
+ def passed?
18
+ false
19
+ end
20
+
21
+ def failed?
22
+ true
23
+ end
24
+
25
+ def to_h
26
+ {
27
+ name: name,
28
+ outputs: outputs,
29
+ error: error && {
30
+ type: error.class.name,
31
+ message: error.message
32
+ }
33
+ }
34
+ end
35
+ end
36
+ end
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,88 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "invariants/error"
4
+ require_relative "invariants/invariant"
5
+ require_relative "invariants/violation"
6
+ require_relative "invariants/suite"
7
+ require_relative "invariants/builder"
8
+ require_relative "invariants/report"
9
+ require_relative "invariants/cases_report"
10
+
11
+ module Igniter
12
+ module Extensions
13
+ module Contracts
14
+ module InvariantsPack
15
+ module_function
16
+
17
+ def manifest
18
+ Igniter::Contracts::PackManifest.new(
19
+ name: :extensions_invariants,
20
+ metadata: { category: :validation }
21
+ )
22
+ end
23
+
24
+ def install_into(kernel)
25
+ kernel
26
+ end
27
+
28
+ def build(&block)
29
+ Invariants::Builder.build(&block)
30
+ end
31
+
32
+ def check(target, invariants:)
33
+ execution_result = unwrap_execution_result(target)
34
+ outputs = execution_result.outputs.to_h
35
+ violations = invariants.invariants.filter_map { |invariant| invariant.check(outputs) }
36
+
37
+ Invariants::Report.new(
38
+ suite: invariants,
39
+ outputs: outputs,
40
+ violations: violations,
41
+ execution_result: execution_result
42
+ )
43
+ end
44
+
45
+ def validate!(target, invariants:)
46
+ report = check(target, invariants: invariants)
47
+ raise Invariants::InvariantError.new(nil, violations: report.violations) if report.invalid?
48
+
49
+ report
50
+ end
51
+
52
+ def run(environment, inputs:, invariants:, compiled_graph: nil, &block)
53
+ graph =
54
+ if block
55
+ environment.compile(&block)
56
+ else
57
+ compiled_graph || raise(ArgumentError, "invariant run requires a block or compiled_graph")
58
+ end
59
+
60
+ result = environment.execute(graph, inputs: inputs)
61
+ check(result, invariants: invariants)
62
+ end
63
+
64
+ def verify_cases(environment, cases:, invariants:, compiled_graph: nil, &block)
65
+ graph =
66
+ if block
67
+ environment.compile(&block)
68
+ else
69
+ compiled_graph || raise(ArgumentError, "verify_cases requires a block or compiled_graph")
70
+ end
71
+
72
+ reports = Array(cases).map do |inputs|
73
+ result = environment.execute(graph, inputs: inputs)
74
+ check(result, invariants: invariants)
75
+ end
76
+
77
+ Invariants::CasesReport.new(reports: reports)
78
+ end
79
+
80
+ def unwrap_execution_result(target)
81
+ return target.execution_result if target.respond_to?(:execution_result)
82
+
83
+ target
84
+ end
85
+ end
86
+ end
87
+ end
88
+ end
@@ -0,0 +1,55 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Igniter
4
+ module Extensions
5
+ module Contracts
6
+ module JournalPack
7
+ class << self
8
+ def manifest
9
+ Igniter::Contracts::PackManifest.new(
10
+ name: :extensions_journal,
11
+ registry_contracts: [
12
+ Igniter::Contracts::PackManifest.effect(:journal),
13
+ Igniter::Contracts::PackManifest.executor(:journaled_inline)
14
+ ]
15
+ )
16
+ end
17
+
18
+ def install_into(kernel)
19
+ kernel.effects.register(:journal, method(:apply_journal_effect))
20
+ kernel.executors.register(:journaled_inline, method(:execute_journaled_inline))
21
+ kernel
22
+ end
23
+
24
+ def journal
25
+ @journal ||= {
26
+ effects: [],
27
+ executions: [],
28
+ results: []
29
+ }
30
+ end
31
+
32
+ def reset_journal!
33
+ journal.each_value(&:clear)
34
+ end
35
+
36
+ def apply_journal_effect(invocation:)
37
+ journal[:effects] << invocation.to_h
38
+ invocation.payload
39
+ end
40
+
41
+ def execute_journaled_inline(invocation:)
42
+ journal[:executions] << invocation.to_h
43
+ result = invocation.runtime.execute(
44
+ invocation.compiled_graph,
45
+ inputs: invocation.inputs,
46
+ profile: invocation.profile
47
+ )
48
+ journal[:results] << result.to_h
49
+ result
50
+ end
51
+ end
52
+ end
53
+ end
54
+ end
55
+ end