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,66 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Igniter
4
+ module Contracts
5
+ module Execution
6
+ class Compiler
7
+ class << self
8
+ def compile(profile:, &block)
9
+ compilation_report(profile: profile, &block).to_compiled_graph
10
+ end
11
+
12
+ def compilation_report(profile:, &block)
13
+ builder = Builder.build(profile: profile, &block)
14
+ operations = normalize(builder.operations, profile: profile)
15
+ findings = validate(operations, profile: profile)
16
+ validation_report = ValidationReport.new(
17
+ operations: operations,
18
+ findings: findings,
19
+ profile_fingerprint: profile.fingerprint
20
+ )
21
+ compiled_graph = if validation_report.ok?
22
+ CompiledGraph.new(operations: operations,
23
+ profile_fingerprint: profile.fingerprint)
24
+ end
25
+
26
+ CompilationReport.new(
27
+ operations: operations,
28
+ validation_report: validation_report,
29
+ compiled_graph: compiled_graph,
30
+ profile_fingerprint: profile.fingerprint
31
+ )
32
+ end
33
+
34
+ def validation_report(profile:, &block)
35
+ compilation_report(profile: profile, &block).validation_report
36
+ end
37
+
38
+ private
39
+
40
+ def normalize(operations, profile:)
41
+ hook_spec = Assembly::HookSpecs.fetch(:normalizers)
42
+
43
+ profile.normalizers.each do |entry|
44
+ operations = entry.value.call(operations: operations, profile: profile)
45
+ operations = hook_spec.validate_result!(entry.key, operations)
46
+ end
47
+ operations
48
+ end
49
+
50
+ def validate(operations, profile:)
51
+ hook_spec = Assembly::HookSpecs.fetch(:validators)
52
+ findings = []
53
+
54
+ profile.validators.each do |entry|
55
+ validator_findings = entry.value.call(operations: operations, profile: profile)
56
+ validated_findings = hook_spec.validate_result!(entry.key, validator_findings)
57
+ findings.concat(validated_findings || [])
58
+ end
59
+
60
+ findings
61
+ end
62
+ end
63
+ end
64
+ end
65
+ end
66
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Igniter
4
+ module Contracts
5
+ module Execution
6
+ module ConstRuntime
7
+ module_function
8
+
9
+ def handle_const(operation:, **)
10
+ operation.attributes[:value]
11
+ end
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Igniter
4
+ module Contracts
5
+ module Execution
6
+ module Diagnostics
7
+ module_function
8
+
9
+ def build_report(result:, profile:)
10
+ report = DiagnosticsReport.new
11
+
12
+ profile.diagnostics_contributors.each do |entry|
13
+ contributor = entry.value
14
+ next unless contributor.respond_to?(:augment)
15
+
16
+ contributor.augment(report: report, result: result, profile: profile)
17
+ end
18
+
19
+ report
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,40 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Igniter
4
+ module Contracts
5
+ module Execution
6
+ class DiagnosticsReport
7
+ attr_reader :sections
8
+
9
+ def initialize
10
+ @sections = {}
11
+ end
12
+
13
+ def add_section(name, value)
14
+ section = DiagnosticsSection.new(name: name, value: value)
15
+ sections[section.name] = section
16
+ section
17
+ end
18
+
19
+ def section(name)
20
+ value = section_object(name).value
21
+ value.is_a?(NamedValues) ? value.to_h : value
22
+ end
23
+
24
+ def section_object(name)
25
+ sections.fetch(name.to_sym)
26
+ end
27
+
28
+ def section_names
29
+ sections.keys
30
+ end
31
+
32
+ def to_h
33
+ {
34
+ sections: sections.transform_values(&:to_h)
35
+ }
36
+ end
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Igniter
4
+ module Contracts
5
+ module Execution
6
+ class DiagnosticsSection
7
+ attr_reader :name, :value
8
+
9
+ def initialize(name:, value:)
10
+ @name = name.to_sym
11
+ @value = normalize_value(value)
12
+ freeze
13
+ end
14
+
15
+ def to_h
16
+ {
17
+ name: name,
18
+ value: StructuredDump.dump(value)
19
+ }
20
+ end
21
+
22
+ private
23
+
24
+ def normalize_value(value)
25
+ case value
26
+ when NamedValues
27
+ value
28
+ when Hash
29
+ NamedValues.new(value)
30
+ else
31
+ value
32
+ end
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Igniter
4
+ module Contracts
5
+ module Execution
6
+ class EffectInvocation
7
+ attr_reader :payload, :context, :profile
8
+
9
+ def initialize(payload:, context:, profile:)
10
+ @payload = payload
11
+ @context = context.is_a?(NamedValues) ? context : NamedValues.new(context)
12
+ @profile = profile
13
+ freeze
14
+ end
15
+
16
+ def to_h
17
+ {
18
+ payload: StructuredDump.dump(payload),
19
+ context: context.to_h,
20
+ profile_fingerprint: profile.fingerprint
21
+ }
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Igniter
4
+ module Contracts
5
+ module Execution
6
+ class ExecutionRequest
7
+ attr_reader :compiled_graph, :inputs, :profile, :runtime
8
+
9
+ def initialize(compiled_graph:, inputs:, profile:, runtime:)
10
+ @compiled_graph = compiled_graph
11
+ @inputs = inputs.is_a?(NamedValues) ? inputs : NamedValues.new(inputs)
12
+ @profile = profile
13
+ @runtime = runtime
14
+ freeze
15
+ end
16
+
17
+ def to_h
18
+ {
19
+ compiled_graph: StructuredDump.dump(compiled_graph),
20
+ inputs: inputs.to_h,
21
+ profile_fingerprint: profile.fingerprint,
22
+ runtime: runtime.name
23
+ }
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Igniter
4
+ module Contracts
5
+ module Execution
6
+ class ExecutionResult
7
+ attr_reader :state, :outputs, :profile_fingerprint, :compiled_graph
8
+
9
+ def initialize(state:, outputs:, profile_fingerprint:, compiled_graph:)
10
+ @state = state
11
+ @outputs = outputs
12
+ @profile_fingerprint = profile_fingerprint
13
+ @compiled_graph = compiled_graph
14
+ freeze
15
+ end
16
+
17
+ def output(name)
18
+ outputs.fetch(name.to_sym)
19
+ end
20
+
21
+ def to_h
22
+ {
23
+ state: state.to_h,
24
+ outputs: outputs.to_h,
25
+ profile_fingerprint: profile_fingerprint,
26
+ compiled_graph: compiled_graph.to_h
27
+ }
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Igniter
4
+ module Contracts
5
+ module Execution
6
+ module InlineExecutor
7
+ module_function
8
+
9
+ def call(invocation:)
10
+ invocation.runtime.execute(
11
+ invocation.compiled_graph,
12
+ inputs: invocation.inputs,
13
+ profile: invocation.profile
14
+ )
15
+ end
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,52 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Igniter
4
+ module Contracts
5
+ module Execution
6
+ class MutableNamedValues
7
+ def initialize(values = {})
8
+ normalized_values =
9
+ case values
10
+ when NamedValues
11
+ values.to_h
12
+ when MutableNamedValues
13
+ values.snapshot.to_h
14
+ else
15
+ values
16
+ end
17
+
18
+ @values = normalized_values.transform_keys(&:to_sym)
19
+ end
20
+
21
+ def write(name, value)
22
+ @values[name.to_sym] = value
23
+ value
24
+ end
25
+
26
+ def fetch(name)
27
+ @values.fetch(name.to_sym)
28
+ end
29
+
30
+ def [](name)
31
+ @values[name.to_sym]
32
+ end
33
+
34
+ def key?(name)
35
+ @values.key?(name.to_sym)
36
+ end
37
+
38
+ def keys
39
+ @values.keys
40
+ end
41
+
42
+ def length
43
+ @values.length
44
+ end
45
+
46
+ def snapshot
47
+ NamedValues.new(@values)
48
+ end
49
+ end
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,48 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Igniter
4
+ module Contracts
5
+ module Execution
6
+ class NamedValues
7
+ def initialize(values = {})
8
+ normalized_values =
9
+ case values
10
+ when NamedValues
11
+ values.to_h
12
+ when MutableNamedValues
13
+ values.snapshot.to_h
14
+ else
15
+ values
16
+ end
17
+
18
+ @values = normalized_values.transform_keys(&:to_sym).freeze
19
+ freeze
20
+ end
21
+
22
+ def fetch(name)
23
+ @values.fetch(name.to_sym)
24
+ end
25
+
26
+ def [](name)
27
+ @values[name.to_sym]
28
+ end
29
+
30
+ def key?(name)
31
+ @values.key?(name.to_sym)
32
+ end
33
+
34
+ def keys
35
+ @values.keys
36
+ end
37
+
38
+ def length
39
+ @values.length
40
+ end
41
+
42
+ def to_h
43
+ @values.to_h { |key, value| [key, StructuredDump.dump(value)] }
44
+ end
45
+ end
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,42 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Igniter
4
+ module Contracts
5
+ module Execution
6
+ class Operation
7
+ attr_reader :kind, :name, :attributes
8
+
9
+ def initialize(kind:, name:, attributes: {})
10
+ @kind = kind.to_sym
11
+ @name = name.to_sym
12
+ @attributes = attributes.transform_keys(&:to_sym).freeze
13
+ freeze
14
+ end
15
+
16
+ def attribute(key)
17
+ attributes.fetch(key.to_sym)
18
+ end
19
+
20
+ def attribute?(key)
21
+ attributes.key?(key.to_sym)
22
+ end
23
+
24
+ def with_attributes(updated_attributes)
25
+ self.class.new(kind: kind, name: name, attributes: updated_attributes)
26
+ end
27
+
28
+ def output?
29
+ kind == :output
30
+ end
31
+
32
+ def to_h
33
+ {
34
+ kind: kind,
35
+ name: name,
36
+ attributes: StructuredDump.dump(attributes)
37
+ }
38
+ end
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,43 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Igniter
4
+ module Contracts
5
+ module Execution
6
+ class Runtime
7
+ class << self
8
+ def execute(compiled_graph, inputs:, profile:)
9
+ validate_profile!(compiled_graph, profile: profile)
10
+
11
+ normalized_inputs = NamedValues.new(inputs)
12
+ state = MutableNamedValues.new
13
+ outputs = MutableNamedValues.new
14
+
15
+ compiled_graph.operations.each do |operation|
16
+ handler = profile.runtime_handler(operation.kind)
17
+ value = handler.call(operation: operation, state: state, outputs: outputs, inputs: normalized_inputs,
18
+ profile: profile)
19
+ state.write(operation.name, value) unless operation.output?
20
+ outputs.write(operation.name, value) if operation.output?
21
+ end
22
+
23
+ ExecutionResult.new(
24
+ state: state.snapshot,
25
+ outputs: outputs.snapshot,
26
+ profile_fingerprint: profile.fingerprint,
27
+ compiled_graph: compiled_graph
28
+ )
29
+ end
30
+
31
+ private
32
+
33
+ def validate_profile!(compiled_graph, profile:)
34
+ return if compiled_graph.profile_fingerprint == profile.fingerprint
35
+
36
+ raise ProfileMismatchError,
37
+ "compiled graph fingerprint #{compiled_graph.profile_fingerprint} does not match profile #{profile.fingerprint}"
38
+ end
39
+ end
40
+ end
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,51 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Igniter
4
+ module Contracts
5
+ module Execution
6
+ class StepResult
7
+ attr_reader :value, :failure, :metadata
8
+
9
+ def self.success(value, metadata: {})
10
+ new(value: value, failure: nil, metadata: metadata)
11
+ end
12
+
13
+ def self.failure(code:, message:, details: {}, metadata: {})
14
+ new(
15
+ value: nil,
16
+ failure: {
17
+ code: code.to_sym,
18
+ message: message,
19
+ details: details
20
+ },
21
+ metadata: metadata
22
+ )
23
+ end
24
+
25
+ def initialize(value:, failure:, metadata: {})
26
+ @value = value
27
+ @failure = failure&.transform_keys(&:to_sym)
28
+ @metadata = metadata.transform_keys(&:to_sym).freeze
29
+ freeze
30
+ end
31
+
32
+ def success?
33
+ failure.nil?
34
+ end
35
+
36
+ def failure?
37
+ !success?
38
+ end
39
+
40
+ def to_h
41
+ {
42
+ success: success?,
43
+ value: StructuredDump.dump(value),
44
+ failure: StructuredDump.dump(failure),
45
+ metadata: StructuredDump.dump(metadata)
46
+ }
47
+ end
48
+ end
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Igniter
4
+ module Contracts
5
+ module Execution
6
+ module StepResultDiagnostics
7
+ module_function
8
+
9
+ def augment(report:, result:, profile:) # rubocop:disable Lint/UnusedMethodArgument
10
+ report.add_section(:step_trace, step_trace(result))
11
+ end
12
+
13
+ def step_trace(result)
14
+ result.compiled_graph.operations.filter_map do |operation|
15
+ next unless operation.kind == :step
16
+
17
+ step_result = result.state[operation.name]
18
+ next unless step_result.is_a?(StepResult)
19
+
20
+ trace_entry(operation, step_result)
21
+ end
22
+ end
23
+
24
+ def trace_entry(operation, step_result)
25
+ {
26
+ name: operation.name,
27
+ status: step_result.success? ? :success : :failed,
28
+ dependencies: Array(operation.attributes[:depends_on]).map(&:to_sym),
29
+ failure: StructuredDump.dump(step_result.failure)
30
+ }
31
+ end
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,51 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Igniter
4
+ module Contracts
5
+ module Execution
6
+ module StepResultRuntime
7
+ module_function
8
+
9
+ def handle_step(operation:, state:, **)
10
+ failed_dependency = failed_step_dependency(operation, state: state)
11
+ return halted_dependency_result(operation, failed_dependency) if failed_dependency
12
+
13
+ callable = operation.attributes[:callable]
14
+ normalize_step_result(callable.call(**step_dependency_values(operation, state: state)))
15
+ end
16
+
17
+ def failed_step_dependency(operation, state:)
18
+ Array(operation.attributes[:depends_on]).filter_map do |dependency|
19
+ dependency_name = dependency.to_sym
20
+ value = state.fetch(dependency_name)
21
+
22
+ { name: dependency_name, result: value } if value.is_a?(StepResult) && value.failure?
23
+ end.first
24
+ end
25
+
26
+ def halted_dependency_result(operation, dependency)
27
+ StepResult.failure(
28
+ code: :halted_dependency,
29
+ message: "step #{operation.name} halted because dependency #{dependency.fetch(:name)} failed",
30
+ details: {
31
+ dependency: dependency.fetch(:name),
32
+ failure: dependency.fetch(:result).failure
33
+ }
34
+ )
35
+ end
36
+
37
+ def step_dependency_values(operation, state:)
38
+ Array(operation.attributes[:depends_on]).each_with_object({}) do |dependency, memo|
39
+ dependency_name = dependency.to_sym
40
+ value = state.fetch(dependency_name)
41
+ memo[dependency_name] = value.is_a?(StepResult) ? value.value : value
42
+ end
43
+ end
44
+
45
+ def normalize_step_result(value)
46
+ value.is_a?(StepResult) ? value : StepResult.success(value)
47
+ end
48
+ end
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,44 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Igniter
4
+ module Contracts
5
+ module Execution
6
+ module StepResultValidators
7
+ module_function
8
+
9
+ def validate_step_dependencies(operations:, profile: nil) # rubocop:disable Lint/UnusedMethodArgument
10
+ available = operations.reject(&:output?).map(&:name)
11
+ missing = step_operations(operations)
12
+ .flat_map { |operation| Array(operation.attributes[:depends_on]) }
13
+ .map(&:to_sym)
14
+ .reject { |name| available.include?(name) }
15
+ .uniq
16
+ return [] if missing.empty?
17
+
18
+ [ValidationFinding.new(
19
+ code: :missing_step_dependencies,
20
+ message: "step dependencies are not defined: #{missing.map(&:to_s).join(", ")}",
21
+ subjects: missing
22
+ )]
23
+ end
24
+
25
+ def validate_step_callables(operations:, profile: nil) # rubocop:disable Lint/UnusedMethodArgument
26
+ missing = step_operations(operations)
27
+ .reject { |operation| operation.attributes[:callable].respond_to?(:call) }
28
+ .map(&:name)
29
+ return [] if missing.empty?
30
+
31
+ [ValidationFinding.new(
32
+ code: :missing_step_callable,
33
+ message: "step nodes require a callable: #{missing.map(&:to_s).join(", ")}",
34
+ subjects: missing
35
+ )]
36
+ end
37
+
38
+ def step_operations(operations)
39
+ operations.select { |operation| operation.kind == :step }
40
+ end
41
+ end
42
+ end
43
+ end
44
+ end