Module: Hecks::Adapters::PostgresEra::LineageManager::CoverageCheck

Included in:
Hecks::Adapters::PostgresEra::LineageManager
Defined in:
lib/hecks/adapters/driven/postgres_era/lineage_manager/coverage_check.rb

Overview

What a mint must prove before it may happen: the edge covers the whole diff, no identity path was re-keyed, and the audit's first two layers pass over the LIVE compiled chain.

Instance Method Summary collapse

Instance Method Details

#audit!(bluebook, lineage, chain, ordinal, edge) ⇒ Object

Layers 1 and 2 of the audit, over the LIVE compiled chain — before anything is minted, so a refusal leaves no half-born era. (A convert meeting an unmapped value raises inside the preview query itself: same rollback-shaped outcome.)



63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
# File 'lib/hecks/adapters/driven/postgres_era/lineage_manager/coverage_check.rb', line 63

def audit!(bluebook, lineage, chain, ordinal, edge)
  violations = []
  bluebook.aggregates.each do |aggregate|
    declared = edge.for_aggregate(aggregate.name)
    after = begin
      lineage.translated_latest(aggregate, ordinal, chain)
    rescue PG::Error => error
      raise Runtime::WiringError, "cannot mint era #{ordinal} of #{bluebook.name}: #{error.message.strip}"
    end
    before = if chain.size > 1
               lineage.translated_latest(aggregate, ordinal, chain[0..-2])
             else
               lineage.ancestor_latest(aggregate, ordinal, chain)
             end
    verdict = Translation::Audit.check(aggregate: aggregate, declared: declared, before: before, after: after)
    violations.concat(verdict.violations)
  end
  return if violations.empty?

  raise Runtime::WiringError,
        "cannot mint era #{ordinal} of #{bluebook.name}: the audit refused —\n  - #{violations.join("\n  - ")}"
end

#check_coverage!(registry, bluebook, held_bluebook, edge) ⇒ Object

Layer 1 against THIS edge specifically: every vanished or retyped path in the held→current diff must be explained, and every held aggregate must still be claimed. The refusal is EraGuard's own, byte for byte.



19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
# File 'lib/hecks/adapters/driven/postgres_era/lineage_manager/coverage_check.rb', line 19

def check_coverage!(registry, bluebook, held_bluebook, edge)
  bluebook.aggregates.each do |aggregate|
    rules = Ports::Persistence::Lineage.from_declared(edge.for_aggregate(aggregate.name), aggregate.name)
    held_aggregate = held_bluebook.aggregate(rules&.ancestor_name || aggregate.name)
    next unless held_aggregate

    check_identity_unchanged!(bluebook, aggregate, held_aggregate, rules)
    uncovered = Runtime::EraGuard.uncovered_attributes(aggregate, held_aggregate, rules)
    Runtime::EraGuard.refuse_uncovered!(bluebook, aggregate, uncovered) unless uncovered.empty?

    unsafe = Runtime::EraGuard.unsafe_additions(aggregate, held_aggregate, rules)
    Runtime::EraGuard.refuse_unsafe_addition!(bluebook, aggregate, unsafe) unless unsafe.empty?
  end
  Runtime::EraGuard.check_vanished_aggregates!(registry, bluebook, held_bluebook)
end

#check_identity_unchanged!(bluebook, aggregate, held_aggregate, rules) ⇒ Object

An identity-path change is a RE-KEYING, not an ordinary translation — stored ids were fixed at write time under the old key, so this refuses UNLESS the edge declares a rekey for this aggregate covering exactly that. rules.rekey? (see Ports::Persistence::Lineage's own comment) is the single source of truth every consumer of this fact asks — this is not a second, independent check of declared.rekeys.



42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
# File 'lib/hecks/adapters/driven/postgres_era/lineage_manager/coverage_check.rb', line 42

def check_identity_unchanged!(bluebook, aggregate, held_aggregate, rules)
  # The FULL declared path lists, in declaration order — never the
  # single-head shortcut, which is nil for every composite identity
  # and so would let two different composites compare as unchanged.
  return if held_aggregate.identity_paths == aggregate.identity_paths
  return if rules&.rekey?

  held_identity = Runtime::Identity.reading(held_aggregate)
  current_identity = Runtime::Identity.reading(aggregate)
  raise Runtime::WiringError,
        "cannot mint an era for #{bluebook.name}::#{aggregate.name}: its identity path changed " \
        "(#{held_identity}#{current_identity}), and that is a re-keying, not a translation — " \
        "stored ids were minted under #{held_identity}, and no rule declares rows the same " \
        "entity under a new key. Keep the identity path, declare a rekey rule, or migrate the " \
        "data explicitly"
end