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,133 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "digest"
4
+
5
+ module Igniter
6
+ module Contracts
7
+ module Assembly
8
+ class Profile
9
+ attr_reader :nodes,
10
+ :dsl_keywords,
11
+ :validators,
12
+ :normalizers,
13
+ :runtime_handlers,
14
+ :diagnostics_contributors,
15
+ :pack_manifests,
16
+ :effects,
17
+ :executors,
18
+ :fingerprint
19
+
20
+ def self.build_from(kernel)
21
+ payload = {
22
+ nodes: kernel.nodes.to_h.freeze,
23
+ dsl_keywords: kernel.dsl_keywords.to_h.freeze,
24
+ validators: kernel.validators.entries.freeze,
25
+ normalizers: kernel.normalizers.entries.freeze,
26
+ runtime_handlers: kernel.runtime_handlers.to_h.freeze,
27
+ diagnostics_contributors: kernel.diagnostics_contributors.entries.freeze,
28
+ pack_manifests: kernel.pack_manifests.dup.freeze,
29
+ effects: kernel.effects.to_h.freeze,
30
+ executors: kernel.executors.to_h.freeze
31
+ }
32
+
33
+ new(**payload, fingerprint: fingerprint_for(payload))
34
+ end
35
+
36
+ def self.fingerprint_for(payload)
37
+ normalized = payload.map do |key, value|
38
+ serialized = serialize_for_fingerprint(value)
39
+ [key.to_s, serialized]
40
+ end
41
+
42
+ Digest::SHA256.hexdigest(normalized.inspect)
43
+ end
44
+
45
+ def self.serialize_for_fingerprint(value)
46
+ case value
47
+ when Hash
48
+ value.map { |entry_key, entry_value| [entry_key.to_s, entry_value.inspect] }
49
+ when Array
50
+ value.map do |entry|
51
+ if entry.respond_to?(:key) && entry.respond_to?(:value)
52
+ [entry.key.to_s, entry.value.inspect]
53
+ else
54
+ entry.inspect
55
+ end
56
+ end
57
+ else
58
+ value.inspect
59
+ end
60
+ end
61
+
62
+ def initialize(nodes:, dsl_keywords:, validators:, normalizers:, runtime_handlers:, diagnostics_contributors:,
63
+ pack_manifests:, effects:, executors:, fingerprint:)
64
+ @nodes = nodes
65
+ @dsl_keywords = dsl_keywords
66
+ @validators = validators
67
+ @normalizers = normalizers
68
+ @runtime_handlers = runtime_handlers
69
+ @diagnostics_contributors = diagnostics_contributors
70
+ @pack_manifests = pack_manifests
71
+ @effects = effects
72
+ @executors = executors
73
+ @fingerprint = fingerprint
74
+ freeze
75
+ end
76
+
77
+ def node_class(kind)
78
+ nodes.fetch(kind.to_sym)
79
+ end
80
+
81
+ def dsl_keyword(name)
82
+ dsl_keywords.fetch(name.to_sym)
83
+ end
84
+
85
+ def runtime_handler(kind)
86
+ runtime_handlers.fetch(kind.to_sym)
87
+ end
88
+
89
+ def effect(name)
90
+ effects.fetch(name.to_sym)
91
+ end
92
+
93
+ def executor(name)
94
+ executors.fetch(name.to_sym)
95
+ end
96
+
97
+ def supports_node_kind?(kind)
98
+ nodes.key?(kind.to_sym)
99
+ end
100
+
101
+ def supports_effect?(name)
102
+ effects.key?(name.to_sym)
103
+ end
104
+
105
+ def supports_executor?(name)
106
+ executors.key?(name.to_sym)
107
+ end
108
+
109
+ def pack_names
110
+ pack_manifests.map(&:name)
111
+ end
112
+
113
+ def pack_manifest(name)
114
+ pack_manifests.find { |manifest| manifest.name == name.to_sym }
115
+ end
116
+
117
+ def provided_capabilities
118
+ pack_manifests.flat_map(&:provides_capabilities).uniq
119
+ end
120
+
121
+ def required_capabilities
122
+ pack_manifests.flat_map(&:requires_capabilities).uniq
123
+ end
124
+
125
+ def declared_registry_keys(registry)
126
+ pack_manifests
127
+ .flat_map { |manifest| manifest.declared_keys_for(registry) }
128
+ .uniq
129
+ end
130
+ end
131
+ end
132
+ end
133
+ end
@@ -0,0 +1,42 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Igniter
4
+ module Contracts
5
+ module Assembly
6
+ module ProjectPack
7
+ module_function
8
+
9
+ def manifest
10
+ PackManifest.new(
11
+ name: :project,
12
+ registry_contracts: [PackManifest.dsl_keyword(:project)]
13
+ )
14
+ end
15
+
16
+ def install_into(kernel)
17
+ kernel.dsl_keywords.register(:project, DslKeyword.new(:project) do |name, from:, builder:, key: nil, dig: nil, default: PathAccess::NO_DEFAULT|
18
+ source_name = from.to_sym
19
+ path = PathAccess.normalize_path(keyword_name: :project, key: key, dig: dig)
20
+
21
+ builder.add_operation(
22
+ kind: :compute,
23
+ name: name,
24
+ depends_on: [source_name],
25
+ callable: lambda do |**values|
26
+ source = values.fetch(source_name)
27
+ PathAccess.fetch_path(
28
+ source,
29
+ path,
30
+ source_name: source_name,
31
+ keyword_name: :project,
32
+ default: default
33
+ )
34
+ end
35
+ )
36
+ end)
37
+ kernel
38
+ end
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,57 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Igniter
4
+ module Contracts
5
+ module Assembly
6
+ class Registry
7
+ def initialize(name:)
8
+ @name = name
9
+ @entries = {}
10
+ @frozen = false
11
+ end
12
+
13
+ def register(key, value)
14
+ raise FrozenRegistryError, "#{@name} is frozen" if frozen?
15
+
16
+ normalized_key = normalize_key(key)
17
+ raise DuplicateRegistrationError, "#{@name} already has #{normalized_key}" if @entries.key?(normalized_key)
18
+
19
+ @entries[normalized_key] = value
20
+ value
21
+ end
22
+
23
+ def fetch(key)
24
+ @entries.fetch(normalize_key(key))
25
+ end
26
+
27
+ def registered?(key)
28
+ @entries.key?(normalize_key(key))
29
+ end
30
+
31
+ def entries
32
+ @entries.dup
33
+ end
34
+
35
+ def to_h
36
+ entries
37
+ end
38
+
39
+ def freeze!
40
+ @frozen = true
41
+ @entries.freeze
42
+ self
43
+ end
44
+
45
+ def frozen?
46
+ @frozen
47
+ end
48
+
49
+ private
50
+
51
+ def normalize_key(key)
52
+ key.to_sym
53
+ end
54
+ end
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,42 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Igniter
4
+ module Contracts
5
+ module Assembly
6
+ module StepResultPack
7
+ module_function
8
+
9
+ def manifest
10
+ PackManifest.new(
11
+ name: :step_result,
12
+ node_contracts: [PackManifest.node(:step)],
13
+ registry_contracts: [
14
+ PackManifest.validator(:step_dependencies),
15
+ PackManifest.validator(:step_callables),
16
+ PackManifest.diagnostic(:step_trace)
17
+ ]
18
+ )
19
+ end
20
+
21
+ def install_into(kernel)
22
+ kernel.nodes.register(:step, NodeType.new(kind: :step, metadata: { category: :pipeline }))
23
+ kernel.dsl_keywords.register(:step, step_keyword)
24
+ kernel.validators.register(:step_dependencies, Execution::StepResultValidators.method(:validate_step_dependencies))
25
+ kernel.validators.register(:step_callables, Execution::StepResultValidators.method(:validate_step_callables))
26
+ kernel.runtime_handlers.register(:step, Execution::StepResultRuntime.method(:handle_step))
27
+ kernel.diagnostics_contributors.register(:step_trace, Execution::StepResultDiagnostics)
28
+ kernel
29
+ end
30
+
31
+ def step_keyword
32
+ DslKeyword.new(:step, lambda { |name, builder:, **attributes, &block|
33
+ normalized_attributes = attributes.dup
34
+ normalized_attributes[:callable] = normalized_attributes.delete(:call) if normalized_attributes.key?(:call)
35
+ normalized_attributes[:callable] = block if block
36
+ builder.add_operation(kind: :step, name: name, **normalized_attributes)
37
+ })
38
+ end
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "assembly/registry"
4
+ require_relative "assembly/ordered_registry"
5
+ require_relative "assembly/pack"
6
+ require_relative "assembly/pack_manifest"
7
+ require_relative "assembly/node_type"
8
+ require_relative "assembly/dsl_keyword"
9
+ require_relative "assembly/path_access"
10
+ require_relative "assembly/hook_result_policies"
11
+ require_relative "assembly/hook_spec"
12
+ require_relative "assembly/hook_specs"
13
+ require_relative "assembly/profile"
14
+ require_relative "assembly/kernel"
15
+ require_relative "assembly/baseline_pack"
16
+ require_relative "assembly/const_pack"
17
+ require_relative "assembly/project_pack"
18
+ require_relative "assembly/step_result_pack"
@@ -0,0 +1,135 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Igniter
4
+ class Contract
5
+ class ResultReader
6
+ def initialize(outputs)
7
+ @outputs = outputs
8
+ freeze
9
+ end
10
+
11
+ def output(name)
12
+ @outputs.fetch(name.to_sym)
13
+ rescue KeyError
14
+ raise KeyError, "unknown contract output #{name}"
15
+ end
16
+
17
+ def to_h
18
+ @outputs.to_h
19
+ end
20
+
21
+ def method_missing(name, *args, &block)
22
+ return output(name) if args.empty? && block.nil? && @outputs.key?(name)
23
+ raise KeyError, "unknown contract output #{name}" if args.empty? && block.nil?
24
+
25
+ super
26
+ end
27
+
28
+ def respond_to_missing?(name, include_private = false)
29
+ @outputs.key?(name) || super
30
+ end
31
+ end
32
+
33
+ class << self
34
+ attr_writer :profile
35
+
36
+ def inherited(subclass)
37
+ super
38
+ subclass.profile = profile
39
+ end
40
+
41
+ def define(&block)
42
+ raise ArgumentError, "contract definition requires a block" unless block
43
+
44
+ @definition_block = block
45
+ @compiled_graphs = {}
46
+ self
47
+ end
48
+
49
+ def definition_block
50
+ @definition_block || raise(Contracts::Error, "#{name || self} does not define a contract")
51
+ end
52
+
53
+ def profile
54
+ @profile || Contracts.default_profile
55
+ end
56
+
57
+ def compile(profile: self.profile)
58
+ compiled_graphs.fetch(profile.fingerprint) do
59
+ compiled_graphs[profile.fingerprint] = Contracts.compile(profile: profile, &definition_block)
60
+ end
61
+ end
62
+
63
+ def call(inputs = nil, profile: self.profile, **keyword_inputs)
64
+ new(inputs, profile: profile, **keyword_inputs)
65
+ end
66
+
67
+ def execute(inputs = nil, profile: self.profile, **keyword_inputs)
68
+ contract = new(inputs, profile: profile, **keyword_inputs)
69
+ contract.execution_result
70
+ end
71
+
72
+ private
73
+
74
+ def compiled_graphs
75
+ @compiled_graphs ||= {}
76
+ end
77
+ end
78
+
79
+ attr_reader :inputs, :profile, :execution_result
80
+
81
+ def initialize(inputs = nil, profile: self.class.profile, **keyword_inputs)
82
+ @profile = profile
83
+ @inputs = normalize_inputs(inputs, keyword_inputs)
84
+ run!
85
+ end
86
+
87
+ def result
88
+ ResultReader.new(execution_result.outputs)
89
+ end
90
+
91
+ def outputs
92
+ execution_result.outputs
93
+ end
94
+
95
+ def output(name)
96
+ result.output(name)
97
+ end
98
+
99
+ def success?
100
+ true
101
+ end
102
+
103
+ def failure?
104
+ !success?
105
+ end
106
+
107
+ def update_inputs(inputs = nil, **keyword_inputs)
108
+ @inputs = @inputs.merge(normalize_inputs(inputs, keyword_inputs))
109
+ run!
110
+ self
111
+ end
112
+
113
+ def to_h
114
+ {
115
+ contract: self.class.name,
116
+ inputs: inputs.dup,
117
+ outputs: outputs.to_h,
118
+ success: success?
119
+ }
120
+ end
121
+
122
+ private
123
+
124
+ def run!
125
+ @execution_result = Contracts.execute(self.class.compile(profile: profile), inputs: inputs, profile: profile)
126
+ end
127
+
128
+ def normalize_inputs(inputs, keyword_inputs)
129
+ normalized_inputs = {}
130
+ normalized_inputs.merge!(inputs) if inputs
131
+ normalized_inputs.merge!(keyword_inputs)
132
+ normalized_inputs.transform_keys(&:to_sym)
133
+ end
134
+ end
135
+ end