igniter-contracts 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 (64) hide show
  1. checksums.yaml +7 -0
  2. data/README.md +239 -0
  3. data/lib/igniter/contracts/api.rb +92 -0
  4. data/lib/igniter/contracts/assembly/baseline_pack.rb +141 -0
  5. data/lib/igniter/contracts/assembly/const_pack.rb +29 -0
  6. data/lib/igniter/contracts/assembly/dsl_keyword.rb +21 -0
  7. data/lib/igniter/contracts/assembly/hook_result_policies.rb +47 -0
  8. data/lib/igniter/contracts/assembly/hook_spec.rb +73 -0
  9. data/lib/igniter/contracts/assembly/hook_specs.rb +74 -0
  10. data/lib/igniter/contracts/assembly/kernel.rb +220 -0
  11. data/lib/igniter/contracts/assembly/node_type.rb +26 -0
  12. data/lib/igniter/contracts/assembly/ordered_registry.rb +55 -0
  13. data/lib/igniter/contracts/assembly/pack.rb +13 -0
  14. data/lib/igniter/contracts/assembly/pack_manifest.rb +131 -0
  15. data/lib/igniter/contracts/assembly/path_access.rb +76 -0
  16. data/lib/igniter/contracts/assembly/profile.rb +133 -0
  17. data/lib/igniter/contracts/assembly/project_pack.rb +42 -0
  18. data/lib/igniter/contracts/assembly/registry.rb +57 -0
  19. data/lib/igniter/contracts/assembly/step_result_pack.rb +42 -0
  20. data/lib/igniter/contracts/assembly.rb +18 -0
  21. data/lib/igniter/contracts/contract.rb +135 -0
  22. data/lib/igniter/contracts/contractable.rb +288 -0
  23. data/lib/igniter/contracts/environment.rb +51 -0
  24. data/lib/igniter/contracts/errors.rb +47 -0
  25. data/lib/igniter/contracts/execution/baseline_normalizers.rb +23 -0
  26. data/lib/igniter/contracts/execution/baseline_runtime.rb +55 -0
  27. data/lib/igniter/contracts/execution/baseline_validators.rb +113 -0
  28. data/lib/igniter/contracts/execution/builder.rb +43 -0
  29. data/lib/igniter/contracts/execution/compilation_report.rb +46 -0
  30. data/lib/igniter/contracts/execution/compiled_graph.rb +21 -0
  31. data/lib/igniter/contracts/execution/compiler.rb +66 -0
  32. data/lib/igniter/contracts/execution/const_runtime.rb +15 -0
  33. data/lib/igniter/contracts/execution/diagnostics.rb +24 -0
  34. data/lib/igniter/contracts/execution/diagnostics_report.rb +40 -0
  35. data/lib/igniter/contracts/execution/diagnostics_section.rb +37 -0
  36. data/lib/igniter/contracts/execution/effect_invocation.rb +26 -0
  37. data/lib/igniter/contracts/execution/execution_request.rb +28 -0
  38. data/lib/igniter/contracts/execution/execution_result.rb +32 -0
  39. data/lib/igniter/contracts/execution/inline_executor.rb +19 -0
  40. data/lib/igniter/contracts/execution/mutable_named_values.rb +52 -0
  41. data/lib/igniter/contracts/execution/named_values.rb +48 -0
  42. data/lib/igniter/contracts/execution/operation.rb +42 -0
  43. data/lib/igniter/contracts/execution/runtime.rb +43 -0
  44. data/lib/igniter/contracts/execution/step_result.rb +51 -0
  45. data/lib/igniter/contracts/execution/step_result_diagnostics.rb +35 -0
  46. data/lib/igniter/contracts/execution/step_result_runtime.rb +51 -0
  47. data/lib/igniter/contracts/execution/step_result_validators.rb +44 -0
  48. data/lib/igniter/contracts/execution/structured_dump.rb +49 -0
  49. data/lib/igniter/contracts/execution/validation_finding.rb +28 -0
  50. data/lib/igniter/contracts/execution/validation_report.rb +46 -0
  51. data/lib/igniter/contracts/execution.rb +28 -0
  52. data/lib/igniter/contracts.rb +54 -0
  53. data/lib/igniter/lang/backend.rb +19 -0
  54. data/lib/igniter/lang/backends/ruby.rb +42 -0
  55. data/lib/igniter/lang/diagnostic_payload.rb +174 -0
  56. data/lib/igniter/lang/metadata_carrier_manifest.rb +112 -0
  57. data/lib/igniter/lang/metadata_manifest.rb +128 -0
  58. data/lib/igniter/lang/receipt_payload.rb +152 -0
  59. data/lib/igniter/lang/schema_compatibility_diagnostic.rb +300 -0
  60. data/lib/igniter/lang/types.rb +84 -0
  61. data/lib/igniter/lang/verification_report.rb +226 -0
  62. data/lib/igniter/lang.rb +27 -0
  63. data/lib/igniter-contracts.rb +3 -0
  64. metadata +103 -0
@@ -0,0 +1,288 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Igniter
4
+ module Contracts
5
+ module Contractable
6
+ Observation = Struct.new(:name, :value, :metadata, keyword_init: true) do
7
+ def initialize(name:, value:, metadata: {})
8
+ super(name: name.to_sym, value: value, metadata: metadata.dup.freeze)
9
+ freeze
10
+ end
11
+
12
+ def to_h
13
+ {
14
+ name: name,
15
+ value: value,
16
+ metadata: metadata.dup
17
+ }
18
+ end
19
+ end
20
+
21
+ Result = Struct.new(:status, :outputs, :observations, :error, :metadata, keyword_init: true) do
22
+ def initialize(status:, outputs: {}, observations: [], error: nil, metadata: {})
23
+ super(
24
+ status: status.to_sym,
25
+ outputs: outputs.transform_keys(&:to_sym).freeze,
26
+ observations: observations.map { |observation| normalize_observation(observation) }.freeze,
27
+ error: error,
28
+ metadata: metadata.dup.freeze
29
+ )
30
+ freeze
31
+ end
32
+
33
+ def success?
34
+ status == :success
35
+ end
36
+
37
+ def failure?
38
+ !success?
39
+ end
40
+
41
+ def to_h
42
+ {
43
+ status: status,
44
+ success: success?,
45
+ outputs: outputs,
46
+ observations: observations.map(&:to_h),
47
+ error: error,
48
+ metadata: metadata.dup
49
+ }.compact
50
+ end
51
+
52
+ private
53
+
54
+ def normalize_observation(observation)
55
+ return observation if observation.is_a?(Observation)
56
+
57
+ Observation.new(**observation.to_h)
58
+ end
59
+ end
60
+
61
+ Definition = Struct.new(:method_name, :inputs, :outputs, :role, :stage, :metadata, keyword_init: true) do
62
+ def initialize(method_name:, inputs: [], outputs: [], role: nil, stage: nil, metadata: {})
63
+ super(
64
+ method_name: method_name.to_sym,
65
+ inputs: inputs.map(&:to_sym).freeze,
66
+ outputs: outputs.map(&:to_sym).freeze,
67
+ role: role&.to_sym,
68
+ stage: stage&.to_sym,
69
+ metadata: metadata.dup.freeze
70
+ )
71
+ freeze
72
+ end
73
+
74
+ def to_h
75
+ {
76
+ method_name: method_name,
77
+ inputs: inputs,
78
+ outputs: outputs,
79
+ role: role,
80
+ stage: stage,
81
+ metadata: metadata.dup
82
+ }.compact
83
+ end
84
+ end
85
+
86
+ class DefinitionBuilder
87
+ attr_reader :method_name, :inputs, :outputs, :metadata, :role_value, :stage_value
88
+
89
+ def initialize(method_name)
90
+ @method_name = method_name.to_sym
91
+ @inputs = []
92
+ @outputs = []
93
+ @metadata = {}
94
+ @role_value = nil
95
+ @stage_value = nil
96
+ end
97
+
98
+ def input(name, **)
99
+ inputs << name.to_sym
100
+ end
101
+
102
+ def output(name, **)
103
+ outputs << name.to_sym
104
+ end
105
+
106
+ def role(value)
107
+ @role_value = value.to_sym
108
+ end
109
+
110
+ def stage(value)
111
+ @stage_value = value.to_sym
112
+ end
113
+
114
+ def meta(key, value)
115
+ metadata[key.to_sym] = value
116
+ end
117
+
118
+ def build
119
+ Definition.new(
120
+ method_name: method_name,
121
+ inputs: inputs,
122
+ outputs: outputs,
123
+ role: role_value,
124
+ stage: stage_value,
125
+ metadata: metadata
126
+ )
127
+ end
128
+ end
129
+
130
+ class ExecutionContext
131
+ attr_reader :observations
132
+
133
+ def initialize
134
+ @observations = []
135
+ end
136
+
137
+ def observe(name, metadata: {})
138
+ value = yield
139
+ observations << Observation.new(name: name, value: value, metadata: metadata)
140
+ value
141
+ end
142
+ end
143
+
144
+ module InstanceMethods
145
+ def observe(name, metadata: {}, &block)
146
+ raise Error, "observe requires a block" unless block
147
+ raise Error, "observe can only be used during a contractable call" unless @__igniter_contractable_context
148
+
149
+ @__igniter_contractable_context.observe(name, metadata: metadata, &block)
150
+ end
151
+
152
+ def success(**outputs)
153
+ Result.new(status: :success, outputs: outputs, observations: current_observations)
154
+ end
155
+
156
+ def failure(code:, message:, details: {})
157
+ Result.new(
158
+ status: :failure,
159
+ outputs: {},
160
+ observations: current_observations,
161
+ error: { code: code.to_sym, message: message, details: details }
162
+ )
163
+ end
164
+
165
+ private
166
+
167
+ def current_observations
168
+ @__igniter_contractable_context&.observations || []
169
+ end
170
+ end
171
+
172
+ module ClassMethods
173
+ def contractable(method_name, &block)
174
+ builder = DefinitionBuilder.new(method_name)
175
+ builder.instance_eval(&block) if block
176
+ @__igniter_contractable_definition = builder.build
177
+ end
178
+
179
+ def contractable_definition
180
+ @__igniter_contractable_definition || Definition.new(method_name: :call)
181
+ end
182
+ end
183
+
184
+ class << self
185
+ def included(base)
186
+ base.extend(ClassMethods)
187
+ base.include(InstanceMethods)
188
+ end
189
+
190
+ def invoke(target, **inputs)
191
+ instance = target.is_a?(Class) ? target.new : target
192
+ definition = instance.class.contractable_definition
193
+ input_error = validate_inputs(definition, inputs)
194
+ return input_error if input_error
195
+
196
+ context = ExecutionContext.new
197
+ instance.instance_variable_set(:@__igniter_contractable_context, context)
198
+ raw = instance.public_send(definition.method_name, **inputs)
199
+ result = normalize_result(raw, observations: context.observations)
200
+ result = with_definition_metadata(result, definition)
201
+ validate_outputs(definition, result)
202
+ rescue StandardError => e
203
+ Result.new(
204
+ status: :failure,
205
+ outputs: {},
206
+ observations: context&.observations || [],
207
+ metadata: definition ? definition_metadata(definition) : {},
208
+ error: { code: :contractable_error, message: e.message, class: e.class.name }
209
+ )
210
+ ensure
211
+ instance&.remove_instance_variable(:@__igniter_contractable_context) if instance&.instance_variable_defined?(:@__igniter_contractable_context)
212
+ end
213
+
214
+ def contractable?(target)
215
+ klass = target.is_a?(Class) ? target : target.class
216
+ klass.respond_to?(:contractable_definition)
217
+ end
218
+
219
+ private
220
+
221
+ def validate_inputs(definition, inputs)
222
+ missing_inputs = definition.inputs - inputs.keys.map(&:to_sym)
223
+ return if missing_inputs.empty?
224
+
225
+ Result.new(
226
+ status: :failure,
227
+ metadata: definition_metadata(definition),
228
+ error: {
229
+ code: :contractable_missing_inputs,
230
+ message: "Missing contractable inputs: #{missing_inputs.join(", ")}",
231
+ inputs: missing_inputs
232
+ }
233
+ )
234
+ end
235
+
236
+ def validate_outputs(definition, result)
237
+ return result unless result.success?
238
+ return result if definition.outputs.empty?
239
+
240
+ missing_outputs = definition.outputs - result.outputs.keys
241
+ return result if missing_outputs.empty?
242
+
243
+ Result.new(
244
+ status: :failure,
245
+ outputs: result.outputs,
246
+ observations: result.observations,
247
+ metadata: result.metadata.merge(definition_metadata(definition)),
248
+ error: {
249
+ code: :contractable_missing_outputs,
250
+ message: "Missing contractable outputs: #{missing_outputs.join(", ")}",
251
+ outputs: missing_outputs
252
+ }
253
+ )
254
+ end
255
+
256
+ def with_definition_metadata(result, definition)
257
+ metadata = definition_metadata(definition)
258
+ return result if metadata.empty?
259
+
260
+ Result.new(
261
+ status: result.status,
262
+ outputs: result.outputs,
263
+ observations: result.observations,
264
+ error: result.error,
265
+ metadata: result.metadata.merge(metadata)
266
+ )
267
+ end
268
+
269
+ def definition_metadata(definition)
270
+ metadata = definition.metadata.dup
271
+ metadata[:role] = definition.role if definition.role
272
+ metadata[:stage] = definition.stage if definition.stage
273
+ metadata
274
+ end
275
+
276
+ def normalize_result(value, observations:)
277
+ return value if value.is_a?(Result)
278
+
279
+ if value.respond_to?(:to_h)
280
+ Result.new(status: :success, outputs: value.to_h, observations: observations)
281
+ else
282
+ Result.new(status: :success, outputs: { value: value }, observations: observations)
283
+ end
284
+ end
285
+ end
286
+ end
287
+ end
288
+ end
@@ -0,0 +1,51 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Igniter
4
+ module Contracts
5
+ class Environment
6
+ attr_reader :profile
7
+
8
+ def initialize(profile:)
9
+ @profile = profile
10
+ end
11
+
12
+ def compile(&block)
13
+ Contracts.compile(profile: profile, &block)
14
+ end
15
+
16
+ def validation_report(&block)
17
+ Contracts.validation_report(profile: profile, &block)
18
+ end
19
+
20
+ def compilation_report(&block)
21
+ Contracts.compilation_report(profile: profile, &block)
22
+ end
23
+
24
+ def execute(compiled_graph, inputs:)
25
+ Contracts.execute(compiled_graph, inputs: inputs, profile: profile)
26
+ end
27
+
28
+ def execute_with(executor_name, compiled_graph, inputs:, runtime: Execution::Runtime)
29
+ Contracts.execute_with(
30
+ executor_name,
31
+ compiled_graph,
32
+ inputs: inputs,
33
+ profile: profile,
34
+ runtime: runtime
35
+ )
36
+ end
37
+
38
+ def run(inputs:, &block)
39
+ execute(compile(&block), inputs: inputs)
40
+ end
41
+
42
+ def diagnose(result)
43
+ Contracts.diagnose(result, profile: profile)
44
+ end
45
+
46
+ def apply_effect(effect_name, payload:, context: {})
47
+ Contracts.apply_effect(effect_name, payload: payload, context: context, profile: profile)
48
+ end
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,47 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Igniter
4
+ module Contracts
5
+ Error = Class.new(StandardError)
6
+
7
+ class ValidationError < Error
8
+ attr_reader :findings
9
+
10
+ def initialize(message = nil, findings: [])
11
+ @findings = Array(findings).freeze
12
+ super(message || default_message)
13
+ end
14
+
15
+ private
16
+
17
+ def default_message
18
+ return "validation failed" if findings.empty?
19
+
20
+ findings.map(&:message).join("; ")
21
+ end
22
+
23
+ public
24
+
25
+ def to_h
26
+ {
27
+ message: message,
28
+ findings: findings.map(&:to_h)
29
+ }
30
+ end
31
+ end
32
+
33
+ FrozenKernelError = Class.new(Error)
34
+ FrozenRegistryError = Class.new(Error)
35
+ DuplicateRegistrationError = Class.new(Error)
36
+ UnknownDslKeywordError = Class.new(Error)
37
+ UnknownNodeKindError = Class.new(Error)
38
+ UnknownEffectError = Class.new(Error)
39
+ UnknownExecutorError = Class.new(Error)
40
+ ProfileMismatchError = Class.new(Error)
41
+ IncompletePackError = Class.new(Error)
42
+ UnknownPackDependencyError = Class.new(Error)
43
+ CircularPackDependencyError = Class.new(Error)
44
+ InvalidHookImplementationError = Class.new(Error)
45
+ InvalidHookResultError = Class.new(Error)
46
+ end
47
+ end
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Igniter
4
+ module Contracts
5
+ module Execution
6
+ module BaselineNormalizers
7
+ module_function
8
+
9
+ def normalize_operation_attributes(operations:, profile: nil) # rubocop:disable Lint/UnusedMethodArgument
10
+ operations.map do |operation|
11
+ attributes = operation.attributes
12
+ normalized_attributes = attributes.dup
13
+
14
+ next operation unless normalized_attributes.key?(:depends_on)
15
+
16
+ normalized_attributes[:depends_on] = Array(normalized_attributes[:depends_on]).map(&:to_sym)
17
+ operation.with_attributes(normalized_attributes)
18
+ end
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,55 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Igniter
4
+ module Contracts
5
+ module Execution
6
+ module BaselineRuntime
7
+ module_function
8
+
9
+ def handle_input(operation:, inputs:, **)
10
+ inputs.fetch(operation.name)
11
+ end
12
+
13
+ def handle_compute(operation:, state:, **)
14
+ callable = operation.attributes[:callable]
15
+ kwargs = resolve_dependency_values(operation, state: state)
16
+ callable.call(**kwargs)
17
+ end
18
+
19
+ def handle_effect(operation:, state:, profile:, **)
20
+ callable = operation.attributes[:callable]
21
+ effect_name = operation.attributes.fetch(:using).to_sym
22
+ dependency_values = resolve_dependency_values(operation, state: state)
23
+ payload = callable.call(**dependency_values)
24
+ invocation = EffectInvocation.new(
25
+ payload: payload,
26
+ context: {
27
+ node_name: operation.name,
28
+ effect_name: effect_name,
29
+ dependencies: dependency_values
30
+ },
31
+ profile: profile
32
+ )
33
+
34
+ profile.effect(effect_name).call(invocation: invocation)
35
+ end
36
+
37
+ def handle_output(operation:, state:, **)
38
+ state.fetch(operation.name)
39
+ end
40
+
41
+ def unsupported(kind)
42
+ lambda do |**|
43
+ raise NotImplementedError, "#{kind} runtime handler is not implemented in the baseline runtime yet"
44
+ end
45
+ end
46
+
47
+ def resolve_dependency_values(operation, state:)
48
+ Array(operation.attributes[:depends_on]).each_with_object({}) do |dependency, memo|
49
+ memo[dependency.to_sym] = state.fetch(dependency.to_sym)
50
+ end
51
+ end
52
+ end
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,113 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Igniter
4
+ module Contracts
5
+ module Execution
6
+ module BaselineValidators
7
+ module_function
8
+
9
+ def validate_uniqueness(operations:, profile: nil) # rubocop:disable Lint/UnusedMethodArgument
10
+ names = operations.reject(&:output?).map(&:name)
11
+ duplicates = names.group_by(&:itself).select { |_name, entries| entries.length > 1 }.keys
12
+ return [] if duplicates.empty?
13
+
14
+ [ValidationFinding.new(
15
+ code: :duplicate_node_names,
16
+ message: "duplicate node names: #{duplicates.map(&:to_s).join(", ")}",
17
+ subjects: duplicates
18
+ )]
19
+ end
20
+
21
+ def validate_outputs(operations:, profile: nil) # rubocop:disable Lint/UnusedMethodArgument
22
+ available = operations.reject(&:output?).map(&:name)
23
+ missing = operations.select(&:output?)
24
+ .map(&:name)
25
+ .reject { |name| available.include?(name) }
26
+ return [] if missing.empty?
27
+
28
+ [ValidationFinding.new(
29
+ code: :missing_output_targets,
30
+ message: "output targets are not defined: #{missing.map(&:to_s).join(", ")}",
31
+ subjects: missing
32
+ )]
33
+ end
34
+
35
+ def validate_dependencies(operations:, profile: nil) # rubocop:disable Lint/UnusedMethodArgument
36
+ available = operations.reject(&:output?).map(&:name)
37
+ missing = operations.select { |operation| operation.kind == :compute }
38
+ .flat_map { |operation| Array(operation.attributes[:depends_on]) }
39
+ .map(&:to_sym)
40
+ .reject { |name| available.include?(name) }
41
+ .uniq
42
+ return [] if missing.empty?
43
+
44
+ [ValidationFinding.new(
45
+ code: :missing_compute_dependencies,
46
+ message: "compute dependencies are not defined: #{missing.map(&:to_s).join(", ")}",
47
+ subjects: missing
48
+ )]
49
+ end
50
+
51
+ def validate_callables(operations:, profile: nil) # rubocop:disable Lint/UnusedMethodArgument
52
+ missing = operations.select { |operation| operation.kind == :compute }
53
+ .reject { |operation| operation.attributes[:callable].respond_to?(:call) }
54
+ .map(&:name)
55
+ return [] if missing.empty?
56
+
57
+ [ValidationFinding.new(
58
+ code: :missing_compute_callable,
59
+ message: "compute nodes require a callable: #{missing.map(&:to_s).join(", ")}",
60
+ subjects: missing
61
+ )]
62
+ end
63
+
64
+ def validate_effect_dependencies(operations:, profile: nil) # rubocop:disable Lint/UnusedMethodArgument
65
+ available = operations.reject(&:output?).map(&:name)
66
+ missing = operations.select { |operation| operation.kind == :effect }
67
+ .flat_map { |operation| Array(operation.attributes[:depends_on]) }
68
+ .map(&:to_sym)
69
+ .reject { |name| available.include?(name) }
70
+ .uniq
71
+ return [] if missing.empty?
72
+
73
+ [ValidationFinding.new(
74
+ code: :missing_effect_dependencies,
75
+ message: "effect dependencies are not defined: #{missing.map(&:to_s).join(", ")}",
76
+ subjects: missing
77
+ )]
78
+ end
79
+
80
+ def validate_effect_payload_builders(operations:, profile: nil) # rubocop:disable Lint/UnusedMethodArgument
81
+ missing = operations.select { |operation| operation.kind == :effect }
82
+ .reject { |operation| operation.attributes[:callable].respond_to?(:call) }
83
+ .map(&:name)
84
+ return [] if missing.empty?
85
+
86
+ [ValidationFinding.new(
87
+ code: :missing_effect_payload_builder,
88
+ message: "effect nodes require a payload callable: #{missing.map(&:to_s).join(", ")}",
89
+ subjects: missing
90
+ )]
91
+ end
92
+
93
+ def validate_effect_adapters(operations:, profile:)
94
+ missing = operations.select { |operation| operation.kind == :effect }
95
+ .map { |operation| operation.attributes[:using] }
96
+ .reject { |effect_name| profile.supports_effect?(effect_name) }
97
+ .uniq
98
+ return [] if missing.empty?
99
+
100
+ [ValidationFinding.new(
101
+ code: :unknown_effect_adapters,
102
+ message: "effect adapters are not registered in profile: #{missing.map(&:to_s).join(", ")}",
103
+ subjects: missing
104
+ )]
105
+ end
106
+
107
+ def validate_types(operations:, profile: nil) # rubocop:disable Lint/UnusedMethodArgument
108
+ []
109
+ end
110
+ end
111
+ end
112
+ end
113
+ end
@@ -0,0 +1,43 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Igniter
4
+ module Contracts
5
+ module Execution
6
+ class Builder
7
+ def self.build(profile:, &block)
8
+ builder = new(profile: profile)
9
+ builder.instance_eval(&block)
10
+ builder
11
+ end
12
+
13
+ attr_reader :profile, :operations
14
+
15
+ def initialize(profile:)
16
+ @profile = profile
17
+ @operations = []
18
+ end
19
+
20
+ def add_operation(kind:, name:, **attributes)
21
+ normalized_kind = kind.to_sym
22
+ unless profile.supports_node_kind?(normalized_kind)
23
+ raise UnknownNodeKindError,
24
+ "unknown node kind #{normalized_kind}"
25
+ end
26
+
27
+ operations << Operation.new(kind: normalized_kind, name: name, attributes: attributes)
28
+ end
29
+
30
+ def method_missing(name, *args, **kwargs, &block)
31
+ keyword = profile.dsl_keyword(name)
32
+ keyword.call(*args, builder: self, **kwargs, &block)
33
+ rescue KeyError
34
+ raise UnknownDslKeywordError, "unknown DSL keyword #{name}"
35
+ end
36
+
37
+ def respond_to_missing?(name, include_private = false)
38
+ profile.dsl_keywords.key?(name.to_sym) || super
39
+ end
40
+ end
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,46 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Igniter
4
+ module Contracts
5
+ module Execution
6
+ class CompilationReport
7
+ attr_reader :operations, :validation_report, :compiled_graph, :profile_fingerprint
8
+
9
+ def initialize(operations:, validation_report:, compiled_graph:, profile_fingerprint:)
10
+ @operations = operations.freeze
11
+ @validation_report = validation_report
12
+ @compiled_graph = compiled_graph
13
+ @profile_fingerprint = profile_fingerprint
14
+ freeze
15
+ end
16
+
17
+ def ok?
18
+ validation_report.ok?
19
+ end
20
+
21
+ def invalid?
22
+ validation_report.invalid?
23
+ end
24
+
25
+ def findings
26
+ validation_report.findings
27
+ end
28
+
29
+ def to_compiled_graph
30
+ validation_report.raise_if_invalid!
31
+ compiled_graph
32
+ end
33
+
34
+ def to_h
35
+ {
36
+ operations: StructuredDump.dump(operations),
37
+ validation_report: validation_report.to_h,
38
+ compiled_graph: StructuredDump.dump(compiled_graph),
39
+ profile_fingerprint: profile_fingerprint,
40
+ ok: ok?
41
+ }
42
+ end
43
+ end
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Igniter
4
+ module Contracts
5
+ module Execution
6
+ CompiledGraph = Struct.new(:operations, :profile_fingerprint, keyword_init: true) do
7
+ def initialize(operations:, profile_fingerprint:)
8
+ frozen_operations = operations.freeze
9
+ super(operations: frozen_operations, profile_fingerprint: profile_fingerprint)
10
+ end
11
+
12
+ def to_h
13
+ {
14
+ operations: StructuredDump.dump(operations),
15
+ profile_fingerprint: profile_fingerprint
16
+ }
17
+ end
18
+ end
19
+ end
20
+ end
21
+ end