Module: IgniterLang::CompilerProfileContractValidator

Defined in:
lib/igniter_lang/compiler_profile_contract_validator.rb

Constant Summary collapse

RESULT_KIND =
"compiler_profile_contract_validation_result"
FORMAT_VERSION =
"0.1.0"
DEFAULT_DIGEST_REFERENCE_POLICY =
:prop038_24_plus
REQUIRED_SLOTS =
%w[core oof_registry fragment_registry escape_boundary].freeze
OPTIONAL_SLOTS =
%w[
  contract_modifiers temporal stream olap invariant assumptions evidence_observation pipeline
].freeze
ALL_SLOTS =
(REQUIRED_SLOTS + OPTIONAL_SLOTS).freeze
DESCRIPTOR_DIGEST_PATTERN =
/\Acompiler_profile_descriptor\/sha256:[0-9a-f]{24,}\z/
FINALIZATION_PAYLOAD_DIGEST_PATTERN =
/\Asha256:[0-9a-f]{64}\z/
CONTRACT_DIGEST_PREFIX =
"compiler_profile_contract/sha256:"
CONTRACT_DIGEST_PATTERN =
/\Acompiler_profile_contract\/sha256:[0-9a-f]{24,}\z/
SUPPORTED_DIGEST_REFERENCE_POLICIES =
%w[prop038_24_plus].freeze
CANONICAL_CONTRACT_FIELDS =
%w[
  kind
  format_version
  profile_namespace
  profile_kind
  compiler_profile_id
  descriptor_digest
  finalization_payload_digest
  required_slot_schema
  slot_order
  slot_assignments
  strict_registries
  ordered_rule_graph
  non_authority
].freeze

Class Method Summary collapse

Class Method Details

.validate(contract, digest_reference_policy: DEFAULT_DIGEST_REFERENCE_POLICY) ⇒ Object



40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
# File 'lib/igniter_lang/compiler_profile_contract_validator.rb', line 40

def self.validate(contract, digest_reference_policy: DEFAULT_DIGEST_REFERENCE_POLICY)
  policy = digest_reference_policy.to_s
  diagnostics = []

  unless contract.is_a?(Hash)
    diagnostics << diagnostic("wrong_kind", "expected compiler_profile_contract", "kind")
    return result(diagnostics, policy)
  end

  diagnostics << diagnostic("wrong_kind", "expected compiler_profile_contract", "kind") unless contract["kind"] == "compiler_profile_contract"
  diagnostics << diagnostic("unsupported_format_version", "expected format_version 0.1.0", "format_version") unless contract["format_version"] == FORMAT_VERSION
  diagnostics << diagnostic("descriptor_digest_invalid", "descriptor_digest must be compiler_profile_descriptor/sha256:<hex>", "descriptor_digest") unless contract["descriptor_digest"].to_s.match?(DESCRIPTOR_DIGEST_PATTERN)
  diagnostics << diagnostic("finalization_payload_digest_invalid", "finalization_payload_digest must be sha256:<64 hex>", "finalization_payload_digest") unless contract["finalization_payload_digest"].to_s.match?(FINALIZATION_PAYLOAD_DIGEST_PATTERN)

  contract_digest_recomputable = validate_contract_digest_shape(diagnostics, contract, policy)

  slot_order = Array(contract["slot_order"])
  slot_assignments = contract["slot_assignments"] || {}
  Array(contract.dig("required_slot_schema", "required_slots")).each do |slot|
    unless slot_order.include?(slot) && slot_assignments.key?(slot)
      diagnostics << diagnostic("missing_required_slot", "required slot #{slot.inspect} is missing from slot_order or slot_assignments", "slot_assignments.#{slot}")
    end
  end

  strict_registries = contract["strict_registries"] || {}
  strict_registries.each do |registry_name, entries|
    seen = {}
    Array(entries).each do |entry|
      key = entry["key"]
      if seen.key?(key)
        diagnostics << diagnostic("duplicate_strict_key", "strict registry #{registry_name} has duplicate key #{key.inspect}", "strict_registries.#{registry_name}.#{key}")
      end
      seen[key] = true
    end
  end

  rules = Array(contract.dig("ordered_rule_graph", "rules"))
  rule_ids = rules.map { |rule| rule["rule_id"] }
  rules.each do |rule|
    (Array(rule["before"]) + Array(rule["after"])).each do |ref|
      unless rule_ids.include?(ref)
        diagnostics << diagnostic("missing_rule_reference", "ordered rule #{rule["rule_id"]} references missing rule #{ref.inspect}", "ordered_rule_graph.rules.#{rule["rule_id"]}")
      end
    end
  end

  cycle = find_rule_cycle(rules)
  diagnostics << diagnostic("rule_cycle", "ordered rule graph contains cycle: #{cycle.join(" -> ")}", "ordered_rule_graph.rules") if cycle

  non_authority = contract["non_authority"] || {}
  diagnostics << diagnostic("runtime_authority_forbidden", "compiler profile contract cannot grant runtime authority", "non_authority.runtime_authority_granted") if non_authority["runtime_authority_granted"]
  diagnostics << diagnostic("dispatch_migration_forbidden", "compiler profile contract cannot authorize dispatch migration", "non_authority.dispatch_migration_authorized") if non_authority["dispatch_migration_authorized"]

  validate_contract_digest_match(diagnostics, contract) if contract_digest_recomputable

  result(diagnostics, policy)
end