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,58 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Igniter
4
+ module Extensions
5
+ module Contracts
6
+ module Dataflow
7
+ class Result
8
+ attr_reader :processed, :diff, :aggregates, :inputs
9
+
10
+ def initialize(processed:, aggregates:, inputs:)
11
+ @processed = processed
12
+ @diff = processed.diff
13
+ @aggregates = aggregates.transform_keys(&:to_sym).freeze
14
+ @inputs = inputs
15
+ freeze
16
+ end
17
+
18
+ alias collection processed
19
+
20
+ def aggregate(name)
21
+ aggregates.fetch(name.to_sym)
22
+ end
23
+
24
+ def output(name)
25
+ aggregate(name)
26
+ end
27
+
28
+ def summary
29
+ {
30
+ diff: diff.to_h,
31
+ aggregates: aggregates
32
+ }
33
+ end
34
+
35
+ def to_h
36
+ {
37
+ processed: processed.to_h,
38
+ aggregates: aggregates,
39
+ inputs: inputs.to_h
40
+ }
41
+ end
42
+
43
+ def method_missing(name, *args)
44
+ return super unless args.empty?
45
+ return processed if name.to_sym == :processed
46
+ return aggregate(name) if aggregates.key?(name.to_sym)
47
+
48
+ super
49
+ end
50
+
51
+ def respond_to_missing?(name, include_private = false)
52
+ name.to_sym == :processed || aggregates.key?(name.to_sym) || super
53
+ end
54
+ end
55
+ end
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,173 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Igniter
4
+ module Extensions
5
+ module Contracts
6
+ module Dataflow
7
+ class Session
8
+ attr_reader :environment, :compiled_graph, :source, :key_name, :window, :context, :last_result
9
+
10
+ def initialize(environment:, compiled_graph:, source:, key:, window: nil, context: [],
11
+ aggregate_operators: {})
12
+ @environment = environment
13
+ @compiled_graph = compiled_graph
14
+ @source = source.to_sym
15
+ @key_name = key.to_sym
16
+ @window = window
17
+ @context = Array(context).map(&:to_sym).freeze
18
+ @aggregate_states = aggregate_operators.transform_values { |operator| AggregateState.new(operator) }
19
+ @item_sessions = {}
20
+ @snapshots = {}
21
+ @cached_items = {}
22
+ @last_inputs = nil
23
+ @last_result = nil
24
+ end
25
+
26
+ def run(inputs:)
27
+ normalized_inputs = Igniter::Contracts::NamedValues.new(inputs)
28
+ items = filtered_items(normalized_inputs.fetch(source))
29
+ diff = compute_diff(items)
30
+ processed_items = build_processed_items(items, normalized_inputs, diff)
31
+ delete_removed_sessions(diff.removed)
32
+
33
+ collection_result = CollectionResult.new(items: processed_items, diff: diff)
34
+ update_aggregate_states(collection_result)
35
+
36
+ @snapshots = snapshot_items(items)
37
+ @cached_items = processed_items.dup
38
+ @last_inputs = normalized_inputs
39
+ @last_result = Result.new(
40
+ processed: collection_result,
41
+ aggregates: @aggregate_states.transform_values(&:value),
42
+ inputs: normalized_inputs
43
+ )
44
+ end
45
+
46
+ def feed_diff(add: [], remove: [], update: [], inputs: {})
47
+ current_inputs = @last_inputs.to_h
48
+ current_items = Array(current_inputs.fetch(source, []))
49
+ merged_items = apply_diff(current_items, add: add, remove: remove, update: update)
50
+
51
+ run(inputs: current_inputs.merge(inputs).merge(source => merged_items))
52
+ end
53
+
54
+ def collection_diff
55
+ @last_result&.diff
56
+ end
57
+
58
+ private
59
+
60
+ def filtered_items(items)
61
+ normalized_items = Array(items).map { |item| normalize_item(item) }
62
+ WindowFilter.new(window).apply(normalized_items)
63
+ end
64
+
65
+ def normalize_item(item)
66
+ raise TypeError, "dataflow items must be Hash-like" unless item.is_a?(Hash)
67
+
68
+ item.transform_keys(&:to_sym)
69
+ end
70
+
71
+ def build_processed_items(items, normalized_inputs, diff)
72
+ items.each_with_object({}) do |item, memo|
73
+ key = extract_key(item)
74
+ memo[key] =
75
+ if diff.unchanged.include?(key)
76
+ @cached_items.fetch(key)
77
+ else
78
+ resolve_item(item, normalized_inputs)
79
+ end
80
+ end
81
+ end
82
+
83
+ def resolve_item(item, normalized_inputs)
84
+ key = extract_key(item)
85
+ session = @item_sessions[key] ||= Igniter::Extensions::Contracts::IncrementalPack.session(
86
+ environment,
87
+ compiled_graph: compiled_graph
88
+ )
89
+ item_inputs = context.each_with_object({}) do |name, memo|
90
+ memo[name] = normalized_inputs.fetch(name)
91
+ end.merge(item)
92
+ incremental_result = session.run(inputs: item_inputs)
93
+
94
+ ItemResult.new(
95
+ key: key,
96
+ inputs: item_inputs,
97
+ execution_result: incremental_result.execution_result,
98
+ incremental_result: incremental_result
99
+ )
100
+ end
101
+
102
+ def compute_diff(items)
103
+ current_keys = items.to_h { |item| [extract_key(item), item] }
104
+ added = []
105
+ changed = []
106
+ unchanged = []
107
+
108
+ items.each do |item|
109
+ key = extract_key(item)
110
+ fingerprint = fingerprint(item)
111
+
112
+ if !@snapshots.key?(key)
113
+ added << key
114
+ elsif @snapshots.fetch(key) != fingerprint
115
+ changed << key
116
+ else
117
+ unchanged << key
118
+ end
119
+ end
120
+
121
+ removed = @snapshots.keys.reject { |key| current_keys.key?(key) }
122
+
123
+ Diff.new(added: added, removed: removed, changed: changed, unchanged: unchanged)
124
+ end
125
+
126
+ def snapshot_items(items)
127
+ items.to_h { |item| [extract_key(item), fingerprint(item)] }
128
+ end
129
+
130
+ def fingerprint(item)
131
+ item.sort_by { |name, _| name.to_s }
132
+ .map { |name, value| "#{name}:#{value.inspect}" }
133
+ .hash
134
+ .to_s
135
+ end
136
+
137
+ def extract_key(item)
138
+ item.fetch(key_name)
139
+ end
140
+
141
+ def delete_removed_sessions(removed_keys)
142
+ removed_keys.each do |key|
143
+ @item_sessions.delete(key)
144
+ end
145
+ end
146
+
147
+ def update_aggregate_states(collection_result)
148
+ @aggregate_states.each_value do |state|
149
+ state.apply_diff!(collection_result.diff, collection_result)
150
+ end
151
+ end
152
+
153
+ def apply_diff(items, add:, remove:, update:)
154
+ remove_keys = Array(remove).map do |entry|
155
+ entry.is_a?(Hash) ? normalize_item(entry).fetch(key_name) : entry
156
+ end
157
+ result = items.map { |item| normalize_item(item) }
158
+ .reject { |item| remove_keys.include?(item.fetch(key_name)) }
159
+
160
+ Array(update).each do |entry|
161
+ updated = normalize_item(entry)
162
+ key = updated.fetch(key_name)
163
+ index = result.index { |item| item.fetch(key_name) == key }
164
+ index ? result[index] = updated : result << updated
165
+ end
166
+
167
+ result.concat(Array(add).map { |entry| normalize_item(entry) })
168
+ end
169
+ end
170
+ end
171
+ end
172
+ end
173
+ end
@@ -0,0 +1,49 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Igniter
4
+ module Extensions
5
+ module Contracts
6
+ module Dataflow
7
+ class WindowFilter
8
+ def initialize(options)
9
+ @options = options
10
+ validate!
11
+ end
12
+
13
+ def apply(items)
14
+ return items unless @options
15
+
16
+ if @options.key?(:last)
17
+ items.last(@options.fetch(:last))
18
+ else
19
+ cutoff = Time.now - @options.fetch(:seconds)
20
+ field = @options.fetch(:field).to_sym
21
+ items.select { |item| item.fetch(field) >= cutoff }
22
+ end
23
+ end
24
+
25
+ private
26
+
27
+ def validate!
28
+ return unless @options
29
+ raise ArgumentError, "window must be a Hash" unless @options.is_a?(Hash)
30
+
31
+ if @options.key?(:last)
32
+ return if @options.fetch(:last).is_a?(Integer) && @options.fetch(:last).positive?
33
+
34
+ raise ArgumentError, "window { last: } must be a positive Integer"
35
+ end
36
+
37
+ if @options.key?(:seconds)
38
+ raise ArgumentError, "window { seconds: } requires a :field key" unless @options.key?(:field)
39
+
40
+ return
41
+ end
42
+
43
+ raise ArgumentError, "window must use :last or :seconds"
44
+ end
45
+ end
46
+ end
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,66 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "incremental_pack"
4
+ require_relative "dataflow/aggregate_operators"
5
+ require_relative "dataflow/aggregate_state"
6
+ require_relative "dataflow/builder"
7
+ require_relative "dataflow/collection_result"
8
+ require_relative "dataflow/diff"
9
+ require_relative "dataflow/item_result"
10
+ require_relative "dataflow/result"
11
+ require_relative "dataflow/session"
12
+ require_relative "dataflow/window_filter"
13
+
14
+ module Igniter
15
+ module Extensions
16
+ module Contracts
17
+ module DataflowPack
18
+ module_function
19
+
20
+ def manifest
21
+ Igniter::Contracts::PackManifest.new(
22
+ name: :extensions_dataflow,
23
+ metadata: { category: :orchestration }
24
+ )
25
+ end
26
+
27
+ def install_into(kernel)
28
+ kernel
29
+ end
30
+
31
+ def session(environment, source:, key:, window: nil, context: [], compiled_graph: nil, &block)
32
+ ensure_installed!(environment.profile)
33
+ Igniter::Extensions::Contracts::IncrementalPack.ensure_installed!(environment.profile)
34
+
35
+ item_graph, aggregate_operators =
36
+ if compiled_graph
37
+ raise ArgumentError, "DataflowPack.session accepts either compiled_graph: or a block, not both" if block
38
+
39
+ [compiled_graph, {}]
40
+ else
41
+ builder = Dataflow::Builder.new(source: source, key: key, window: window, context: context)
42
+ builder.instance_eval(&block) if block
43
+ builder.build!(environment)
44
+ end
45
+
46
+ Dataflow::Session.new(
47
+ environment: environment,
48
+ compiled_graph: item_graph,
49
+ source: source,
50
+ key: key,
51
+ window: window,
52
+ context: context,
53
+ aggregate_operators: aggregate_operators
54
+ )
55
+ end
56
+
57
+ def ensure_installed!(profile)
58
+ return if profile.pack_names.include?(:extensions_dataflow)
59
+
60
+ raise Igniter::Contracts::Error,
61
+ "DataflowPack is not installed in profile #{profile.fingerprint}; add Igniter::Extensions::Contracts::DataflowPack"
62
+ end
63
+ end
64
+ end
65
+ end
66
+ end
@@ -0,0 +1,181 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "pack_snapshot"
4
+
5
+ module Igniter
6
+ module Extensions
7
+ module Contracts
8
+ module Debug
9
+ class PackAudit
10
+ REGISTRIES = {
11
+ node_kinds: ->(kernel) { kernel.nodes.to_h.keys.sort },
12
+ dsl_keywords: ->(kernel) { kernel.dsl_keywords.to_h.keys.sort },
13
+ validators: ->(kernel) { kernel.validators.entries.map(&:key).sort },
14
+ normalizers: ->(kernel) { kernel.normalizers.entries.map(&:key).sort },
15
+ runtime_handlers: ->(kernel) { kernel.runtime_handlers.to_h.keys.sort },
16
+ diagnostics_contributors: ->(kernel) { kernel.diagnostics_contributors.entries.map(&:key).sort },
17
+ effects: ->(kernel) { kernel.effects.to_h.keys.sort },
18
+ executors: ->(kernel) { kernel.executors.to_h.keys.sort }
19
+ }.freeze
20
+
21
+ attr_reader :pack_snapshot,
22
+ :installed_in_target_profile,
23
+ :target_profile_fingerprint,
24
+ :draft_registered_keys,
25
+ :missing_node_definitions,
26
+ :missing_dsl_keywords,
27
+ :missing_runtime_handlers,
28
+ :missing_registry_contracts,
29
+ :install_error,
30
+ :finalize_error
31
+
32
+ def self.build(pack, profile: nil)
33
+ manifest = pack.manifest
34
+ baseline_snapshot = registry_snapshot(Igniter::Contracts.build_kernel)
35
+ draft_snapshot, install_error = install_snapshot(pack)
36
+
37
+ new(
38
+ pack_snapshot: PackSnapshot.new(manifest),
39
+ installed_in_target_profile: profile ? profile.pack_names.include?(manifest.name) : false,
40
+ target_profile_fingerprint: profile&.fingerprint,
41
+ draft_registered_keys: registry_delta(baseline_snapshot, draft_snapshot),
42
+ missing_node_definitions: missing_node_definitions(manifest, draft_snapshot),
43
+ missing_dsl_keywords: missing_dsl_keywords(manifest, draft_snapshot),
44
+ missing_runtime_handlers: missing_runtime_handlers(manifest, draft_snapshot),
45
+ missing_registry_contracts: missing_registry_contracts(manifest, draft_snapshot),
46
+ install_error: install_error,
47
+ finalize_error: finalize_error_for(pack)
48
+ )
49
+ end
50
+
51
+ def self.install_snapshot(pack)
52
+ kernel = Igniter::Contracts.build_kernel
53
+ error = nil
54
+
55
+ begin
56
+ kernel.install(pack)
57
+ rescue StandardError => e
58
+ error = "#{e.class}: #{e.message}"
59
+ end
60
+
61
+ [registry_snapshot(kernel), error]
62
+ end
63
+
64
+ def self.finalize_error_for(pack)
65
+ kernel = Igniter::Contracts.build_kernel
66
+ kernel.install(pack)
67
+ kernel.finalize
68
+ nil
69
+ rescue StandardError => e
70
+ "#{e.class}: #{e.message}"
71
+ end
72
+
73
+ def self.registry_snapshot(kernel)
74
+ REGISTRIES.to_h { |name, reader| [name, reader.call(kernel)] }
75
+ end
76
+
77
+ def self.registry_delta(before, after)
78
+ REGISTRIES.keys.to_h do |name|
79
+ [name, after.fetch(name) - before.fetch(name)]
80
+ end
81
+ end
82
+
83
+ def self.missing_node_definitions(manifest, snapshot)
84
+ manifest.node_contracts.map(&:kind).reject { |kind| snapshot.fetch(:node_kinds).include?(kind) }
85
+ end
86
+
87
+ def self.missing_dsl_keywords(manifest, snapshot)
88
+ required = manifest.node_contracts.select(&:requires_dsl).map(&:kind)
89
+ required.reject { |kind| snapshot.fetch(:dsl_keywords).include?(kind) }
90
+ end
91
+
92
+ def self.missing_runtime_handlers(manifest, snapshot)
93
+ required = manifest.node_contracts.select(&:requires_runtime).map(&:kind)
94
+ required.reject { |kind| snapshot.fetch(:runtime_handlers).include?(kind) }
95
+ end
96
+
97
+ def self.missing_registry_contracts(manifest, snapshot)
98
+ manifest.registry_contracts.each_with_object({}) do |contract, memo|
99
+ registry = normalize_registry(contract.registry)
100
+ available = snapshot.fetch(registry)
101
+ next if available.include?(contract.key)
102
+
103
+ memo[registry] ||= []
104
+ memo[registry] << contract.key
105
+ end.transform_values(&:sort)
106
+ end
107
+
108
+ def self.normalize_registry(name)
109
+ case name.to_sym
110
+ when :nodes
111
+ :node_kinds
112
+ else
113
+ name.to_sym
114
+ end
115
+ end
116
+
117
+ def initialize(pack_snapshot:, installed_in_target_profile:, target_profile_fingerprint:,
118
+ draft_registered_keys:, missing_node_definitions:, missing_dsl_keywords:, missing_runtime_handlers:, missing_registry_contracts:, install_error:, finalize_error:)
119
+ @pack_snapshot = pack_snapshot
120
+ @installed_in_target_profile = installed_in_target_profile
121
+ @target_profile_fingerprint = target_profile_fingerprint
122
+ @draft_registered_keys = draft_registered_keys
123
+ @missing_node_definitions = missing_node_definitions.freeze
124
+ @missing_dsl_keywords = missing_dsl_keywords.freeze
125
+ @missing_runtime_handlers = missing_runtime_handlers.freeze
126
+ @missing_registry_contracts = missing_registry_contracts.transform_values(&:freeze).freeze
127
+ @install_error = install_error
128
+ @finalize_error = finalize_error
129
+ freeze
130
+ end
131
+
132
+ def name
133
+ pack_snapshot.name
134
+ end
135
+
136
+ def ok?
137
+ install_error.nil? &&
138
+ finalize_error.nil? &&
139
+ missing_node_definitions.empty? &&
140
+ missing_dsl_keywords.empty? &&
141
+ missing_runtime_handlers.empty? &&
142
+ missing_registry_contracts.empty?
143
+ end
144
+
145
+ def to_h
146
+ {
147
+ pack: pack_snapshot.to_h,
148
+ installed_in_target_profile: installed_in_target_profile,
149
+ target_profile_fingerprint: target_profile_fingerprint,
150
+ draft_registered_keys: draft_registered_keys,
151
+ missing: {
152
+ node_definitions: missing_node_definitions,
153
+ dsl_keywords: missing_dsl_keywords,
154
+ runtime_handlers: missing_runtime_handlers,
155
+ registry_contracts: missing_registry_contracts
156
+ },
157
+ install_error: install_error,
158
+ finalize_error: finalize_error,
159
+ ok: ok?
160
+ }
161
+ end
162
+
163
+ def explain
164
+ return "#{name} looks complete" if ok?
165
+
166
+ parts = []
167
+ parts << "install_error=#{install_error}" if install_error
168
+ parts << "missing node definitions: #{missing_node_definitions.join(", ")}" unless missing_node_definitions.empty?
169
+ parts << "missing DSL keywords: #{missing_dsl_keywords.join(", ")}" unless missing_dsl_keywords.empty?
170
+ parts << "missing runtime handlers: #{missing_runtime_handlers.join(", ")}" unless missing_runtime_handlers.empty?
171
+ missing_registry_contracts.each do |registry, keys|
172
+ parts << "missing #{registry}: #{keys.join(", ")}"
173
+ end
174
+ parts << "finalize_error=#{finalize_error}" if finalize_error
175
+ parts.join("; ")
176
+ end
177
+ end
178
+ end
179
+ end
180
+ end
181
+ end
@@ -0,0 +1,46 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Igniter
4
+ module Extensions
5
+ module Contracts
6
+ module Debug
7
+ class PackSnapshot
8
+ attr_reader :manifest
9
+
10
+ def initialize(manifest)
11
+ @manifest = manifest
12
+ freeze
13
+ end
14
+
15
+ def name
16
+ manifest.name
17
+ end
18
+
19
+ def metadata
20
+ manifest.metadata
21
+ end
22
+
23
+ def node_kinds
24
+ manifest.node_contracts.map(&:kind)
25
+ end
26
+
27
+ def registry_contracts
28
+ manifest.registry_contracts.each_with_object({}) do |contract, memo|
29
+ memo[contract.registry] ||= []
30
+ memo[contract.registry] << contract.key
31
+ end.transform_values(&:sort)
32
+ end
33
+
34
+ def to_h
35
+ {
36
+ name: name,
37
+ metadata: metadata,
38
+ node_kinds: node_kinds,
39
+ registry_contracts: registry_contracts
40
+ }
41
+ end
42
+ end
43
+ end
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,50 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "pack_snapshot"
4
+
5
+ module Igniter
6
+ module Extensions
7
+ module Contracts
8
+ module Debug
9
+ class ProfileSnapshot
10
+ attr_reader :profile
11
+
12
+ def initialize(profile:)
13
+ @profile = profile
14
+ freeze
15
+ end
16
+
17
+ def pack_names
18
+ profile.pack_names.sort
19
+ end
20
+
21
+ def packs
22
+ profile.pack_manifests.map { |manifest| PackSnapshot.new(manifest) }
23
+ end
24
+
25
+ def registry_keys
26
+ {
27
+ node_kinds: profile.nodes.keys.sort,
28
+ dsl_keywords: profile.dsl_keywords.keys.sort,
29
+ validators: profile.validators.map(&:key).sort,
30
+ normalizers: profile.normalizers.map(&:key).sort,
31
+ runtime_handlers: profile.runtime_handlers.keys.sort,
32
+ diagnostics_contributors: profile.diagnostics_contributors.map(&:key).sort,
33
+ effects: profile.effects.keys.sort,
34
+ executors: profile.executors.keys.sort
35
+ }
36
+ end
37
+
38
+ def to_h
39
+ {
40
+ fingerprint: profile.fingerprint,
41
+ pack_names: pack_names,
42
+ registry_keys: registry_keys,
43
+ packs: packs.map(&:to_h)
44
+ }
45
+ end
46
+ end
47
+ end
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,50 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "profile_snapshot"
4
+
5
+ module Igniter
6
+ module Extensions
7
+ module Contracts
8
+ module Debug
9
+ class Report
10
+ attr_reader :profile_snapshot,
11
+ :compilation_report,
12
+ :execution_result,
13
+ :diagnostics_report,
14
+ :provenance_summary
15
+
16
+ def initialize(profile_snapshot:, compilation_report: nil, execution_result: nil, diagnostics_report: nil,
17
+ provenance_summary: nil)
18
+ @profile_snapshot = profile_snapshot
19
+ @compilation_report = compilation_report
20
+ @execution_result = execution_result
21
+ @diagnostics_report = diagnostics_report
22
+ @provenance_summary = provenance_summary
23
+ freeze
24
+ end
25
+
26
+ def ok?
27
+ compilation_report.nil? || compilation_report.ok?
28
+ end
29
+
30
+ def invalid?
31
+ !ok?
32
+ end
33
+
34
+ def to_h
35
+ payload = {
36
+ profile: profile_snapshot.to_h,
37
+ ok: ok?
38
+ }
39
+
40
+ payload[:compilation] = compilation_report.to_h if compilation_report
41
+ payload[:execution] = execution_result.to_h if execution_result
42
+ payload[:diagnostics] = diagnostics_report.to_h if diagnostics_report
43
+ payload[:provenance] = provenance_summary if provenance_summary
44
+ payload
45
+ end
46
+ end
47
+ end
48
+ end
49
+ end
50
+ end