Module: Hecks::Runtime::EraCheck

Defined in:
lib/hecks/ports/persistence/plugins/era/era_check.rb

Overview

The boot-time era gate, run for the adapters that HAVE eras — the lineage-capable ones. An era is a fact about stored data that some adapter can carry across a shape change; an adapter that cannot translate has no era to hold, and holding one for it would record history about data nothing can act on.

The whole domain is handed to the capable adapter's own era check (Postgres: hold, recognize, mint, or refuse toward the scaffold), which keeps its era facts as rows BESIDE its data. That co-location is load-bearing — a watermark only means something against the journal it was cut from, and an approval recorded anywhere but the reviewed database binds to nothing.

The capability is asked of the adapter, never of its name: a second adapter that grows an era story declares lineage_capable? and era_check! and needs no change here.

Detection and identity are separate jobs: this check never hashes anything — era names come from the store, already minted (by the scaffold) or absent. Refusal wordings are contract, pinned by the corpus.

Class Method Summary collapse

Class Method Details

.adapter_for(registry, domain, aggregate) ⇒ Object



153
154
155
# File 'lib/hecks/ports/persistence/plugins/era/era_check.rb', line 153

def adapter_for(registry, domain, aggregate)
  Ports::Persistence::BindingPolicy.resolve(registry, domain, aggregate).adapter
end

.check!(registry, directory) ⇒ Object

EACH BLUEBOOK'S OWN SOURCE, not one file read once and reused for every bluebook in the registry — true as long as a domain directory only ever held exactly one, and silently wrong the moment uses_framework made a second, differently-sourced bluebook (Governance, Identity — lib/hecks/framework/bluebook/, not the domain's own directory) share a boot with the first. Caught the hard way: three domains booted together, one real source text (the domain's own), and every OTHER domain's era-1 held THAT text under its own name — a shadow-parse of it later reconstructs a completely different shape, and every boot after the first refuses toward a scaffold that was never the real drift.



46
47
48
49
# File 'lib/hecks/ports/persistence/plugins/era/era_check.rb', line 46

def check!(registry, directory)
  check_compute_rules_for_registry!(registry)
  check_lineage!(registry, directory)
end

.check_bluebook!(registry, bluebook, current_text, directory: nil) ⇒ Object



117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
# File 'lib/hecks/ports/persistence/plugins/era/era_check.rb', line 117

def check_bluebook!(registry, bluebook, current_text, directory: nil)
  first = bluebook.aggregates.first
  return unless first

  adapter_name = adapter_for(registry, bluebook.name, first)
  return unless lineage_capable?(registry, adapter_name)

  unless current_text
    raise WiringError,
          "cannot boot #{bluebook.name}: bound to a lineage-capable adapter, but no source file for " \
          "it could be found (checked #{directory.inspect} and the framework registry)"
  end

  settings = registry.world(bluebook.name)&.for_binding(Ports::Persistence::VERB, adapter_name) || {}
  registry.adapter_class(adapter_name).era_check!(
    registry: registry, bluebook: bluebook, current_text: current_text, settings: settings,
    directory: directory
  )
end

.check_compute_rules!(registry, bluebook) ⇒ Object

The per-rule capability gate — NOT an era fact, and so it survives on every adapter: a compute rule's SQL is its only implementation, so an aggregate carrying one cannot boot anywhere but Postgres, whatever any shape comparison would say.



141
142
143
144
145
146
147
148
149
150
151
# File 'lib/hecks/ports/persistence/plugins/era/era_check.rb', line 141

def check_compute_rules!(registry, bluebook)
  bluebook.aggregates.each do |aggregate|
    lineage = Ports::Persistence::Lineage.for(registry, bluebook.name, aggregate)
    next unless lineage&.computes?

    adapter = adapter_for(registry, bluebook.name, aggregate)
    next if lineage_capable?(registry, adapter)

    raise WiringError, "compute rules require the Postgres adapter; #{aggregate.name} is bound to #{adapter}"
  end
end

.check_compute_rules_for_registry!(registry) ⇒ Object

The domain-agnostic half, split out for ADR 0031's boot-gate registry: a compute rule requires Postgres whatever adapter is actually bound, so this must run for EVERY registry, the same way registry.verify! does — it is not conditional on any adapter being lineage-capable, and must never be skipped by check_lineage!'s own capability gate below.



57
58
59
# File 'lib/hecks/ports/persistence/plugins/era/era_check.rb', line 57

def check_compute_rules_for_registry!(registry)
  registry.bluebooks.each_value { |bluebook| check_compute_rules!(registry, bluebook) }
end

.check_lineage!(registry, directory) ⇒ Object

The capability-gated half — ADR 0031's registered :era_check gate. Registration is conditional on lineage_capable_registry?; check_bluebook! below still carries its own per-bluebook lineage_capable? return-early, unchanged, for a registry with a mix of lineage-capable and plain-adapter bluebooks.



66
67
68
69
70
# File 'lib/hecks/ports/persistence/plugins/era/era_check.rb', line 66

def check_lineage!(registry, directory)
  registry.bluebooks.each_value do |bluebook|
    check_bluebook!(registry, bluebook, source_text_for(bluebook, directory), directory: directory)
  end
end

.lineage_capable?(registry, adapter_name) ⇒ Boolean

The capability idiom: an adapter CLASS that answers lineage_capable? with true carries eras and may act on drift (translate, fork, merge). Postgres alone does today; the seam is what lets a second one arrive without touching this file.

Returns:

  • (Boolean)


161
162
163
164
165
166
# File 'lib/hecks/ports/persistence/plugins/era/era_check.rb', line 161

def lineage_capable?(registry, adapter_name)
  adapter_class = registry.adapters[adapter_name] && registry.adapter_class(adapter_name)
  adapter_class.respond_to?(:lineage_capable?) && adapter_class.lineage_capable?
rescue StandardError
  false
end

.lineage_capable_registry?(registry) ⇒ Boolean

The :era_check gate's own registration predicate: true iff at least one bluebook's own anchor (first) aggregate resolves to a lineage-capable adapter — mirrors check_bluebook!'s existing per-bluebook anchor check, just asked once, up front, of the whole registry, so a registry with nothing lineage-capable bound anywhere never registers the gate at all.

Returns:

  • (Boolean)


78
79
80
81
82
83
84
85
# File 'lib/hecks/ports/persistence/plugins/era/era_check.rb', line 78

def lineage_capable_registry?(registry)
  registry.bluebooks.each_value.any? do |bluebook|
    first = bluebook.aggregates.first
    next false unless first

    lineage_capable?(registry, adapter_for(registry, bluebook.name, first))
  end
end

.source_text_for(bluebook, directory) ⇒ Object

The domain's own directory first, matched by name — a real app's directory may hold more than one file once uses_framework exists, so ".first" alone can no longer be trusted, the exact way it silently wasn't the day this was found: three domains booted together, one real source text read once (the domain's own, ".first"'d), and every OTHER domain's era-1 held THAT text under its own name — a later shadow-parse of it reconstructs a completely different shape, and every boot after the first refuses toward a scaffold that was never the real drift.

A single-file directory whose one file names something ELSE falls back to it anyway (a fixture may legitimately name its file differently from the Hecks.bluebook it declares) — UNLESS this bluebook is a known framework member, in which case that one file is certainly some OTHER domain's, not this one's, and the framework registry is asked instead — the only other place a bluebook in this registry could have come from, per uses_framework.



105
106
107
108
109
110
111
112
113
114
115
# File 'lib/hecks/ports/persistence/plugins/era/era_check.rb', line 105

def source_text_for(bluebook, directory)
  domain_files = Dir[File.join(directory, "*.bluebook")]
  own = domain_files.select do |path|
    File.foreach(path, encoding: "UTF-8").any? { |line| line.match?(/\A\s*Hecks\.bluebook\s+#{Regexp.escape(bluebook.name.inspect)}/) }
  end
  own = domain_files if own.empty? && domain_files.size == 1 && !Framework.members.key?(bluebook.name)
  own = [Framework.members[bluebook.name]].compact if own.empty?
  return if own.empty?

  own.map { |path| File.read(path, encoding: "UTF-8") }.join("\n")
end