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,115 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "debug/profile_snapshot"
4
+ require_relative "debug/pack_audit"
5
+ require_relative "debug/report"
6
+
7
+ module Igniter
8
+ module Extensions
9
+ module Contracts
10
+ module DebugPack
11
+ module_function
12
+
13
+ REPORT_CONTRIBUTOR = Module.new do
14
+ module_function
15
+
16
+ def augment(report:, result:, profile:)
17
+ snapshot = DebugPack.profile_snapshot(profile)
18
+ report.add_section(:debug, {
19
+ profile_fingerprint: profile.fingerprint,
20
+ pack_names: snapshot.pack_names,
21
+ registry_keys: snapshot.registry_keys,
22
+ output_names: result.outputs.keys.sort,
23
+ state_keys: result.state.keys.sort,
24
+ operation_count: result.compiled_graph.operations.length
25
+ })
26
+ end
27
+ end
28
+
29
+ def manifest
30
+ Igniter::Contracts::PackManifest.new(
31
+ name: :extensions_debug,
32
+ requires_packs: [ExecutionReportPack, ProvenancePack],
33
+ registry_contracts: [Igniter::Contracts::PackManifest.diagnostic(:debug)],
34
+ metadata: { category: :developer }
35
+ )
36
+ end
37
+
38
+ def install_into(kernel)
39
+ kernel.diagnostics_contributors.register(:debug, REPORT_CONTRIBUTOR)
40
+ kernel
41
+ end
42
+
43
+ def profile_snapshot(profile)
44
+ Debug::ProfileSnapshot.new(profile: profile)
45
+ end
46
+
47
+ def pack_snapshot(pack_or_name, profile:)
48
+ manifest =
49
+ case pack_or_name
50
+ when Symbol, String
51
+ profile.pack_manifest(pack_or_name)
52
+ else
53
+ pack_or_name.respond_to?(:manifest) ? pack_or_name.manifest : profile.pack_manifest(pack_or_name)
54
+ end
55
+ raise ArgumentError, "unknown pack #{pack_or_name}" unless manifest
56
+
57
+ Debug::PackSnapshot.new(manifest)
58
+ end
59
+
60
+ def audit(pack, profile: nil)
61
+ Debug::PackAudit.build(pack, profile: profile)
62
+ end
63
+
64
+ def snapshot(result, profile:)
65
+ diagnostics = Igniter::Contracts.diagnose(result, profile: profile)
66
+ Debug::Report.new(
67
+ profile_snapshot: profile_snapshot(profile),
68
+ execution_result: result,
69
+ diagnostics_report: diagnostics,
70
+ provenance_summary: provenance_summary(result)
71
+ )
72
+ end
73
+
74
+ def report(environment, inputs: nil, compiled_graph: nil, &block)
75
+ compilation =
76
+ if block
77
+ environment.compilation_report(&block)
78
+ elsif compiled_graph
79
+ nil
80
+ else
81
+ raise ArgumentError, "debug_report requires a block or compiled_graph"
82
+ end
83
+
84
+ graph = compiled_graph || compilation&.compiled_graph
85
+ if inputs.nil? || compilation&.invalid?
86
+ return Debug::Report.new(profile_snapshot: profile_snapshot(environment.profile),
87
+ compilation_report: compilation)
88
+ end
89
+
90
+ result = environment.execute(graph, inputs: inputs)
91
+ diagnostics = environment.diagnose(result)
92
+
93
+ Debug::Report.new(
94
+ profile_snapshot: profile_snapshot(environment.profile),
95
+ compilation_report: compilation,
96
+ execution_result: result,
97
+ diagnostics_report: diagnostics,
98
+ provenance_summary: provenance_summary(result)
99
+ )
100
+ end
101
+
102
+ def provenance_summary(result)
103
+ result.outputs.keys.sort.each_with_object({}) do |output_name, memo|
104
+ lineage = ProvenancePack.lineage(result, output_name)
105
+ memo[output_name] = {
106
+ value: lineage.value,
107
+ contributing_inputs: lineage.contributing_inputs,
108
+ trace: lineage.to_h
109
+ }
110
+ end
111
+ end
112
+ end
113
+ end
114
+ end
115
+ end
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Igniter
4
+ module Extensions
5
+ module Contracts
6
+ module Differential
7
+ class Divergence
8
+ attr_reader :output_name, :primary_value, :candidate_value, :kind
9
+
10
+ def initialize(output_name:, primary_value:, candidate_value:, kind:)
11
+ @output_name = output_name.to_sym
12
+ @primary_value = primary_value
13
+ @candidate_value = candidate_value
14
+ @kind = kind.to_sym
15
+ freeze
16
+ end
17
+
18
+ def delta
19
+ return nil unless primary_value.is_a?(Numeric) && candidate_value.is_a?(Numeric)
20
+
21
+ candidate_value - primary_value
22
+ end
23
+
24
+ def to_h
25
+ {
26
+ output_name: output_name,
27
+ primary_value: primary_value,
28
+ candidate_value: candidate_value,
29
+ kind: kind,
30
+ delta: delta
31
+ }
32
+ end
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,85 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Igniter
4
+ module Extensions
5
+ module Contracts
6
+ module Differential
7
+ module Formatter
8
+ module_function
9
+
10
+ VALUE_MAX = 60
11
+
12
+ def format(report)
13
+ lines = []
14
+ lines << "Primary: #{report.primary_name}"
15
+ lines << "Candidate: #{report.candidate_name}"
16
+ lines << "Match: #{report.match? ? "YES" : "NO"}"
17
+
18
+ if report.primary_error
19
+ lines << ""
20
+ lines << "PRIMARY ERROR: #{report.primary_error.fetch(:message)}"
21
+ return lines.join("\n")
22
+ end
23
+
24
+ if report.candidate_error
25
+ lines << ""
26
+ lines << "CANDIDATE ERROR: #{report.candidate_error.fetch(:message)}"
27
+ end
28
+
29
+ lines << ""
30
+
31
+ if report.divergences.empty? && report.primary_only.empty? && report.candidate_only.empty?
32
+ lines << "All shared outputs match."
33
+ else
34
+ append_divergences(report, lines)
35
+ append_only_section("CANDIDATE ONLY", report.candidate_only, lines)
36
+ append_only_section("PRIMARY ONLY", report.primary_only, lines)
37
+ end
38
+
39
+ lines.join("\n")
40
+ end
41
+
42
+ def append_divergences(report, lines)
43
+ return if report.divergences.empty?
44
+
45
+ lines << "DIVERGENCES (#{report.divergences.size}):"
46
+ report.divergences.each do |divergence|
47
+ lines << " :#{divergence.output_name}"
48
+ lines << " primary: #{fmt(divergence.primary_value)}"
49
+ lines << " candidate: #{fmt(divergence.candidate_value)}"
50
+ next if divergence.delta.nil?
51
+
52
+ delta = divergence.delta
53
+ lines << " delta: #{delta >= 0 ? "+#{delta}" : delta}"
54
+ end
55
+ lines << ""
56
+ end
57
+
58
+ def append_only_section(label, values, lines)
59
+ return if values.empty?
60
+
61
+ lines << "#{label} (#{values.size}):"
62
+ values.each do |name, value|
63
+ lines << " :#{name} = #{fmt(value)}"
64
+ end
65
+ lines << ""
66
+ end
67
+
68
+ def fmt(value)
69
+ string =
70
+ case value
71
+ when nil then "nil"
72
+ when String, Symbol then value.inspect
73
+ when Hash then "{#{value.map { |key, item| "#{key}: #{item.inspect}" }.join(", ")}}"
74
+ when Array then "[#{value.map(&:inspect).join(", ")}]"
75
+ else
76
+ value.inspect
77
+ end
78
+
79
+ string.length > VALUE_MAX ? "#{string[0, VALUE_MAX - 3]}..." : string
80
+ end
81
+ end
82
+ end
83
+ end
84
+ end
85
+ end
@@ -0,0 +1,83 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Igniter
4
+ module Extensions
5
+ module Contracts
6
+ module Differential
7
+ class Report
8
+ attr_reader :primary_name, :candidate_name, :inputs,
9
+ :primary_outputs, :candidate_outputs,
10
+ :divergences, :primary_only, :candidate_only,
11
+ :primary_error, :candidate_error
12
+
13
+ def initialize(
14
+ primary_name:,
15
+ candidate_name:,
16
+ inputs:,
17
+ primary_outputs:,
18
+ candidate_outputs:,
19
+ divergences:,
20
+ primary_only:,
21
+ candidate_only:,
22
+ primary_error: nil,
23
+ candidate_error: nil
24
+ )
25
+ @primary_name = primary_name.to_s
26
+ @candidate_name = candidate_name.to_s
27
+ @inputs = inputs.transform_keys(&:to_sym).freeze
28
+ @primary_outputs = primary_outputs.transform_keys(&:to_sym).freeze
29
+ @candidate_outputs = candidate_outputs.transform_keys(&:to_sym).freeze
30
+ @divergences = divergences.freeze
31
+ @primary_only = primary_only.transform_keys(&:to_sym).freeze
32
+ @candidate_only = candidate_only.transform_keys(&:to_sym).freeze
33
+ @primary_error = primary_error&.freeze
34
+ @candidate_error = candidate_error&.freeze
35
+ freeze
36
+ end
37
+
38
+ def match?
39
+ divergences.empty? &&
40
+ primary_only.empty? &&
41
+ candidate_only.empty? &&
42
+ primary_error.nil? &&
43
+ candidate_error.nil?
44
+ end
45
+
46
+ def summary
47
+ return "match" if match?
48
+
49
+ parts = []
50
+ parts << "#{divergences.size} value(s) differ" if divergences.any?
51
+ parts << "#{primary_only.size} output(s) only in primary" if primary_only.any?
52
+ parts << "#{candidate_only.size} output(s) only in candidate" if candidate_only.any?
53
+ parts << "candidate error: #{candidate_error.fetch(:message)}" if candidate_error
54
+ parts << "primary error: #{primary_error.fetch(:message)}" if primary_error
55
+ "diverged - #{parts.join(", ")}"
56
+ end
57
+
58
+ def explain
59
+ Formatter.format(self)
60
+ end
61
+
62
+ alias to_s explain
63
+
64
+ def to_h
65
+ {
66
+ primary: primary_name,
67
+ candidate: candidate_name,
68
+ inputs: inputs,
69
+ match: match?,
70
+ primary_outputs: primary_outputs,
71
+ candidate_outputs: candidate_outputs,
72
+ divergences: divergences.map(&:to_h),
73
+ primary_only: primary_only,
74
+ candidate_only: candidate_only,
75
+ primary_error: primary_error,
76
+ candidate_error: candidate_error
77
+ }
78
+ end
79
+ end
80
+ end
81
+ end
82
+ end
83
+ end
@@ -0,0 +1,136 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Igniter
4
+ module Extensions
5
+ module Contracts
6
+ module Differential
7
+ class Runner
8
+ def initialize(primary_name:, candidate_name:, tolerance: nil)
9
+ @primary_name = primary_name
10
+ @candidate_name = candidate_name
11
+ @tolerance = tolerance
12
+ end
13
+
14
+ def compare(
15
+ inputs:,
16
+ primary_environment: nil,
17
+ primary_compiled_graph: nil,
18
+ primary_result: nil,
19
+ candidate_environment: nil,
20
+ candidate_compiled_graph: nil,
21
+ candidate_result: nil
22
+ )
23
+ primary_result, primary_error = resolve_execution(
24
+ result: primary_result,
25
+ environment: primary_environment,
26
+ compiled_graph: primary_compiled_graph,
27
+ inputs: inputs,
28
+ label: @primary_name
29
+ )
30
+ candidate_result, candidate_error = resolve_execution(
31
+ result: candidate_result,
32
+ environment: candidate_environment,
33
+ compiled_graph: candidate_compiled_graph,
34
+ inputs: inputs,
35
+ label: @candidate_name
36
+ )
37
+
38
+ primary_outputs = primary_result ? primary_result.outputs.to_h : {}
39
+ candidate_outputs = candidate_result ? candidate_result.outputs.to_h : {}
40
+
41
+ build_report(
42
+ inputs: inputs,
43
+ primary_outputs: primary_outputs,
44
+ candidate_outputs: candidate_outputs,
45
+ primary_error: primary_error,
46
+ candidate_error: candidate_error
47
+ )
48
+ end
49
+
50
+ private
51
+
52
+ def resolve_execution(result:, environment:, compiled_graph:, inputs:, label:)
53
+ return [result, nil] if result
54
+
55
+ if environment.nil? || compiled_graph.nil?
56
+ raise ArgumentError,
57
+ "#{label} comparison requires either an execution result or environment + compiled_graph"
58
+ end
59
+
60
+ begin
61
+ [environment.execute(compiled_graph, inputs: inputs), nil]
62
+ rescue StandardError => e
63
+ [nil, serialize_error(e)]
64
+ end
65
+ end
66
+
67
+ def build_report(inputs:, primary_outputs:, candidate_outputs:, primary_error:, candidate_error:)
68
+ common = primary_outputs.keys & candidate_outputs.keys
69
+ divergences = compare_common(primary_outputs, candidate_outputs, common)
70
+
71
+ Report.new(
72
+ primary_name: @primary_name,
73
+ candidate_name: @candidate_name,
74
+ inputs: inputs,
75
+ primary_outputs: primary_outputs,
76
+ candidate_outputs: candidate_outputs,
77
+ divergences: divergences,
78
+ primary_only: slice_missing(primary_outputs, candidate_outputs),
79
+ candidate_only: slice_missing(candidate_outputs, primary_outputs),
80
+ primary_error: primary_error,
81
+ candidate_error: candidate_error
82
+ )
83
+ end
84
+
85
+ def compare_common(primary_outputs, candidate_outputs, names)
86
+ names.filter_map do |name|
87
+ primary_value = primary_outputs.fetch(name)
88
+ candidate_value = candidate_outputs.fetch(name)
89
+ next if values_match?(primary_value, candidate_value)
90
+
91
+ Divergence.new(
92
+ output_name: name,
93
+ primary_value: primary_value,
94
+ candidate_value: candidate_value,
95
+ kind: divergence_kind(primary_value, candidate_value)
96
+ )
97
+ end
98
+ end
99
+
100
+ def slice_missing(source, other)
101
+ (source.keys - other.keys).each_with_object({}) do |name, memo|
102
+ memo[name] = source.fetch(name)
103
+ end
104
+ end
105
+
106
+ def values_match?(left, right)
107
+ return true if left == right
108
+ return false unless @tolerance
109
+ return false unless left.is_a?(Numeric) && right.is_a?(Numeric)
110
+
111
+ (left - right).abs <= @tolerance
112
+ end
113
+
114
+ def divergence_kind(left, right)
115
+ left.instance_of?(right.class) ? :value_mismatch : :type_mismatch
116
+ end
117
+
118
+ def serialize_error(error)
119
+ payload =
120
+ if error.respond_to?(:to_h)
121
+ error.to_h
122
+ else
123
+ {}
124
+ end
125
+
126
+ {
127
+ type: error.class.name,
128
+ message: error.message,
129
+ details: payload
130
+ }
131
+ end
132
+ end
133
+ end
134
+ end
135
+ end
136
+ end
@@ -0,0 +1,61 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "differential/divergence"
4
+ require_relative "differential/formatter"
5
+ require_relative "differential/report"
6
+ require_relative "differential/runner"
7
+
8
+ module Igniter
9
+ module Extensions
10
+ module Contracts
11
+ module DifferentialPack
12
+ module_function
13
+
14
+ def manifest
15
+ Igniter::Contracts::PackManifest.new(
16
+ name: :extensions_differential,
17
+ metadata: { category: :developer }
18
+ )
19
+ end
20
+
21
+ def install_into(kernel)
22
+ kernel
23
+ end
24
+
25
+ def compare(
26
+ inputs:,
27
+ primary_environment: nil,
28
+ primary_compiled_graph: nil,
29
+ primary_result: nil,
30
+ candidate_environment: nil,
31
+ candidate_compiled_graph: nil,
32
+ candidate_result: nil,
33
+ tolerance: nil,
34
+ primary_name: "primary",
35
+ candidate_name: "candidate"
36
+ )
37
+ Differential::Runner.new(
38
+ primary_name: primary_name,
39
+ candidate_name: candidate_name,
40
+ tolerance: tolerance
41
+ ).compare(
42
+ inputs: inputs,
43
+ primary_environment: primary_environment,
44
+ primary_compiled_graph: primary_compiled_graph,
45
+ primary_result: primary_result,
46
+ candidate_environment: candidate_environment,
47
+ candidate_compiled_graph: candidate_compiled_graph,
48
+ candidate_result: candidate_result
49
+ )
50
+ end
51
+
52
+ def shadow(**arguments)
53
+ on_divergence = arguments.delete(:on_divergence)
54
+ report = compare(**arguments)
55
+ on_divergence.call(report) if on_divergence && !report.match?
56
+ report
57
+ end
58
+ end
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Igniter
4
+ module Extensions
5
+ module Contracts
6
+ module ExecutionReportPack
7
+ module_function
8
+
9
+ REPORT_CONTRIBUTOR = Module.new do
10
+ module_function
11
+
12
+ def augment(report:, result:, profile:)
13
+ report.add_section(:execution_report, {
14
+ profile_fingerprint: profile.fingerprint,
15
+ pack_names: profile.pack_names.sort,
16
+ output_count: result.outputs.length,
17
+ state_count: result.state.length,
18
+ outputs: result.outputs.to_h,
19
+ state_keys: result.state.keys.sort
20
+ })
21
+ end
22
+ end
23
+
24
+ def manifest
25
+ Igniter::Contracts::PackManifest.new(
26
+ name: :extensions_execution_report,
27
+ registry_contracts: [Igniter::Contracts::PackManifest.diagnostic(:execution_report)]
28
+ )
29
+ end
30
+
31
+ def install_into(kernel)
32
+ kernel.diagnostics_contributors.register(:execution_report, REPORT_CONTRIBUTOR)
33
+ kernel
34
+ end
35
+ end
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,60 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Igniter
4
+ module Extensions
5
+ module Contracts
6
+ module Incremental
7
+ module Formatter
8
+ module_function
9
+
10
+ LINE = "─" * 42
11
+ VALUE_MAX = 60
12
+
13
+ def format(result)
14
+ lines = []
15
+ lines << "Contracts Incremental Report"
16
+ lines << LINE
17
+ lines << "Recomputed: #{result.recomputed_count} node(s)"
18
+ lines << "Skipped: #{result.skipped_nodes.length} node(s)"
19
+ lines << "Backdated: #{result.backdated_nodes.length} node(s)"
20
+ lines << ""
21
+
22
+ if result.changed_outputs.any?
23
+ lines << "CHANGED OUTPUTS (#{result.changed_outputs.length}):"
24
+ result.changed_outputs.each do |name, diff|
25
+ lines << " :#{name} #{fmt(diff[:from])} -> #{fmt(diff[:to])}"
26
+ end
27
+ else
28
+ lines << "No output values changed."
29
+ end
30
+
31
+ lines << ""
32
+ if result.skipped_nodes.any?
33
+ lines << "SKIPPED: #{result.skipped_nodes.map do |name|
34
+ ":#{name}"
35
+ end.join(" ")}"
36
+ end
37
+ if result.backdated_nodes.any?
38
+ lines << "BACKDATED: #{result.backdated_nodes.map do |name|
39
+ ":#{name}"
40
+ end.join(" ")}"
41
+ end
42
+ if result.changed_nodes.any?
43
+ lines << "CHANGED: #{result.changed_nodes.map do |name|
44
+ ":#{name}"
45
+ end.join(" ")}"
46
+ end
47
+ lines.compact.join("\n")
48
+ end
49
+
50
+ def fmt(value)
51
+ rendered = value.inspect
52
+ return rendered if rendered.length <= VALUE_MAX
53
+
54
+ "#{rendered[0, VALUE_MAX - 3]}..."
55
+ end
56
+ end
57
+ end
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Igniter
4
+ module Extensions
5
+ module Contracts
6
+ module Incremental
7
+ class NodeState
8
+ attr_reader :name, :value, :value_version, :dep_snapshot
9
+
10
+ def initialize(name:, value:, value_version:, dep_snapshot: {})
11
+ @name = name.to_sym
12
+ @value = value
13
+ @value_version = Integer(value_version)
14
+ @dep_snapshot = dep_snapshot.transform_keys(&:to_sym).freeze
15
+ freeze
16
+ end
17
+
18
+ def to_h
19
+ {
20
+ name: name,
21
+ value: value,
22
+ value_version: value_version,
23
+ dep_snapshot: dep_snapshot
24
+ }
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end