Module: Textus::Manifest::Schema::Semantics::Invariants

Included in:
Textus::Manifest::Schema::Semantics
Defined in:
lib/textus/manifest/schema/semantics/invariants.rb

Instance Method Summary collapse

Instance Method Details

#check_entries!(entries) ⇒ Object



58
59
60
61
62
63
64
65
# File 'lib/textus/manifest/schema/semantics/invariants.rb', line 58

def check_entries!(entries)
  Array(entries).each_with_index do |e, i|
    path = "$.entries[#{i}]"
    walk(e, ENTRY_KEYS, path)
    check_publish_block!(e, path)
    walk(e["source"], SOURCE_KEYS, "#{path}.source") if e.is_a?(Hash) && e["source"].is_a?(Hash)
  end
end

#check_invariants!(raw) ⇒ Object



6
7
8
9
10
11
12
13
14
# File 'lib/textus/manifest/schema/semantics/invariants.rb', line 6

def check_invariants!(raw)
  check_roles!(raw["roles"])
  check_lanes!(raw["lanes"])
  check_entries!(raw["entries"])
  check_rules!(raw["rules"])
  check_single_queue!(raw)
  check_single_machine!(raw)
  walk(raw["audit"], AUDIT_KEYS, "$.audit") if raw["audit"].is_a?(Hash)
end

#check_lanes!(lanes) ⇒ Object



46
47
48
49
50
51
52
53
54
55
56
# File 'lib/textus/manifest/schema/semantics/invariants.rb', line 46

def check_lanes!(lanes)
  Array(lanes).each_with_index do |z, i|
    walk(z, LANE_KEYS, "$.lanes[#{i}]")
    next unless %w[quarantine derived].include?(z["kind"])

    raise BadManifest.new(
      "lane kind '#{z["kind"]}' at '$.lanes[#{i}]' was folded into 'machine' (ADR 0091) — " \
      "use `kind: machine`",
    )
  end
end

#check_publish_block!(entry, path) ⇒ Object

Raises:



80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
# File 'lib/textus/manifest/schema/semantics/invariants.rb', line 80

def check_publish_block!(entry, path)
  return unless entry.is_a?(Hash) && entry.key?("publish")

  block = entry["publish"]
  if block.is_a?(Hash)
    raise BadManifest.new(
      "publish: at '#{path}.publish' must be a list of targets (ADR 0094); the map form was retired.",
    )
  end
  raise BadManifest.new("publish: must be a list of targets at '#{path}.publish'") unless block.is_a?(Array)

  block.each_with_index do |t, i|
    raise BadManifest.new("publish target ##{i} must be a mapping at '#{path}.publish'") unless t.is_a?(Hash)

    walk(t, %w[to tree template inject_boot], "#{path}.publish[#{i}]")
  end
end

#check_roles!(roles) ⇒ Object

Raises:



16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
# File 'lib/textus/manifest/schema/semantics/invariants.rb', line 16

def check_roles!(roles)
  return if roles.nil?

  roles.each_with_index do |r, i|
    path = "$.roles[#{i}]"
    name = r["name"]
    unless Textus::Value::Role::NAMES.include?(name)
      raise BadManifest.new(
        "unknown role name '#{name}' at '#{path}' (allowed: #{Textus::Value::Role::NAMES.join(", ")})",
      )
    end
    Array(r["can"]).each do |verb|
      next if CAPABILITIES.include?(verb)

      hint = %w[ingest fetch].include?(verb) ? " — the quarantine capability folded into 'converge' (ADR 0090)" : ""
      raise BadManifest.new(
        "unknown capability '#{verb}' for role '#{name}' at '#{path}' " \
        "(known: #{CAPABILITIES.join(", ")})#{hint}",
      )
    end
  end

  author_holders = roles.count { |r| Array(r["can"]).include?("author") }
  return if author_holders <= 1

  raise BadManifest.new(
    "manifest declares #{author_holders} roles with the author capability; at most one is allowed",
  )
end

#check_rules!(rules) ⇒ Object



67
68
69
70
71
72
73
74
75
76
77
78
# File 'lib/textus/manifest/schema/semantics/invariants.rb', line 67

def check_rules!(rules)
  Array(rules).each_with_index do |r, i|
    path = "$.rules[#{i}]"
    walk(r, RULE_KEYS, path)
    FIELD_REGISTRY.each_value do |meta|
      next unless meta[:sub_keys]

      value = r.is_a?(Hash) ? r[meta[:yaml_key]] : nil
      walk(value, meta[:sub_keys], "#{path}.#{meta[:yaml_key]}") if value.is_a?(Hash)
    end
  end
end

#check_single_machine!(raw) ⇒ Object

Raises:



105
106
107
108
109
110
# File 'lib/textus/manifest/schema/semantics/invariants.rb', line 105

def check_single_machine!(raw)
  machines = Array(raw["lanes"]).select { |z| z["kind"] == "machine" }.map { |z| z["name"] }
  return if machines.size <= 1

  raise BadManifest.new("at most one lane may declare kind: machine (found: #{machines.join(", ")})")
end

#check_single_queue!(raw) ⇒ Object

Raises:



98
99
100
101
102
103
# File 'lib/textus/manifest/schema/semantics/invariants.rb', line 98

def check_single_queue!(raw)
  queues = Array(raw["lanes"]).select { |z| z["kind"] == "queue" }.map { |z| z["name"] }
  return if queues.size <= 1

  raise BadManifest.new("at most one lane may declare kind: queue (found: #{queues.join(", ")})")
end

#walk(hash, allowed, path) ⇒ Object



112
113
114
115
116
117
118
119
120
# File 'lib/textus/manifest/schema/semantics/invariants.rb', line 112

def walk(hash, allowed, path)
  return unless hash.is_a?(Hash)

  hash.each_key do |k|
    next if allowed.include?(k)

    raise BadManifest.new("unknown key '#{k}' at '#{path}'")
  end
end