Module: Hecks::Projector::Exporter
- Defined in:
- lib/hecks/projector/exporter.rb
Class Method Summary collapse
- .call(registry) ⇒ Object
-
.compiled_translation_aggregate(aggregate) ⇒ Object
THE EXPORT SHAPE —
translation_aggregate's own digest-relevant fields, plus the precompiled SQL (compiled_state_expression/compiled_id_expression) a consumer embedding this JSON (rust/host's own boot-time mint) needs to execute the edge without compiling SQL itself. - .compiled_translation_hash(translation) ⇒ Object
- .json(registry) ⇒ Object
-
.lineage(registry, domain_name) ⇒ Object
A BINDING fact, deliberately NOT folded into
call/bluebook.to_habove — the canonical IR is runtime-independent by design (ADR 0001: it describes what a bluebook DECLARES, never which adapter a deployment happens to bind it to), and "is this aggregate bound to a lineage-capable adapter" is exactly the kind of fact that answer can change per-deployment without the bluebook's own shape changing at all. - .translation_aggregate(aggregate) ⇒ Object
-
.translation_hash(translation) ⇒ Object
THE DIGEST-RELEVANT SHAPE —
ApprovalDigest.edge_digesthashes EXACTLY this, and only this, for exactly the reasoncompiled_ translation_aggregatebelow must never be used for that purpose: a digest bound to the COMPILED SQL, not just the DECLARED rules, would invalidate an existing human approval the momentTranslation::RuleCompiler's own output format changed for any reason — a compiler refactor, a cosmetic SQL-formatting change — even when the declared rules an approver actually reviewed never changed at all. -
.translations(registry) ⇒ Object
Translation IR, always as an array, WITH each aggregate's precompiled SQL attached (
compiled_translation_aggregate) — this is the export a consumer embeds (ir.json'stranslationskey), never the bare digest-relevant shapeedge_digesthashes (see that method's own header for why the two must stay separate). - .translations_json(registry) ⇒ Object
Class Method Details
.call(registry) ⇒ Object
10 11 12 |
# File 'lib/hecks/projector/exporter.rb', line 10 def call(registry) registry.bluebooks.transform_values(&:to_h) end |
.compiled_translation_aggregate(aggregate) ⇒ Object
THE EXPORT SHAPE — translation_aggregate's own digest-relevant
fields, plus the precompiled SQL (compiled_state_expression/
compiled_id_expression) a consumer embedding this JSON
(rust/host's own boot-time mint) needs to execute the edge
without compiling SQL itself. The SAME call head_compiler.rb's
own compile_rules(declared)/id_case(guard, declared) make at
mint time, run here once at build/export time instead —
Translation::RuleCompiler is the ONE place this expression is
built, called from both here and from head_compiler.rb's real
per-mint assembly, so a consumer gets Ruby's own compiler's
output verbatim, never a second, independently-authored SQL
compiler that could drift from this one. compiled_id_ expression is nil unless this edge rekeys — the bare
aggregate_id passthrough head_compiler.rb itself falls back to
for the overwhelming common case.
133 134 135 136 137 138 |
# File 'lib/hecks/projector/exporter.rb', line 133 def compiled_translation_aggregate(aggregate) translation_aggregate(aggregate).merge( compiled_state_expression: Translation::RuleCompiler.compile_rules(aggregate), compiled_id_expression: Translation::RuleCompiler.rekeyed?(aggregate) ? Translation::RuleCompiler.compile_id_expression(aggregate) : nil ) end |
.compiled_translation_hash(translation) ⇒ Object
55 56 57 58 59 60 61 62 63 |
# File 'lib/hecks/projector/exporter.rb', line 55 def compiled_translation_hash(translation) { domain: translation.domain, from: translation.from, to: translation.to, retired: translation.retired, aggregates: translation.aggregates.map { |aggregate| compiled_translation_aggregate(aggregate) } } end |
.json(registry) ⇒ Object
14 15 16 |
# File 'lib/hecks/projector/exporter.rb', line 14 def json(registry) JSON.pretty_generate(call(registry)) end |
.lineage(registry, domain_name) ⇒ Object
A BINDING fact, deliberately NOT folded into call/bluebook.to_h
above — the canonical IR is runtime-independent by design (ADR
0001: it describes what a bluebook DECLARES, never which adapter
a deployment happens to bind it to), and "is this aggregate bound
to a lineage-capable adapter" is exactly the kind of fact that
answer can change per-deployment without the bluebook's own shape
changing at all. Consumers that need it (bin/project_rust's own
ir.json sidecar, rust/host's runtime era-aware seed overlay —
dispatch.rs) merge this in as a SEPARATE top-level key, the same
way translations already sits beside call's output rather than
inside it.
Reuses Runtime::EraCheck's own capability predicates rather than
re-deriving them — the boot-time gate and this export must never
answer differently for the same aggregate.
33 34 35 36 37 38 39 40 41 |
# File 'lib/hecks/projector/exporter.rb', line 33 def lineage(registry, domain_name) bluebook = registry.bluebooks.fetch(domain_name) capable = bluebook.aggregates.select do |aggregate| adapter_name = Runtime::EraCheck.adapter_for(registry, domain_name, aggregate) Runtime::EraCheck.lineage_capable?(registry, adapter_name) end { capable_aggregates: capable.map { |aggregate| { name: aggregate.name, storage_name: aggregate.storage_name } } } end |
.translation_aggregate(aggregate) ⇒ Object
90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 |
# File 'lib/hecks/projector/exporter.rb', line 90 def translation_aggregate(aggregate) { name: aggregate.name, was: aggregate.was, renames: aggregate.renames.transform_keys(&:to_s).transform_values(&:to_s), moves: aggregate.moves.map { |move| { from: move.from, to: move.to } }, converts: aggregate.converts.map do |convert| { from: convert.from, to: convert.to, values: convert.values.map { |key, value| [key, value] } } end, drops: aggregate.drops.map(&:to_s), retypes: aggregate.retypes.map { |retype| { from: retype.from, to: retype.to } }, computes: aggregate.computes.map { |compute| { from: compute.from, to: compute.to, sql: compute.sql } }, # PREVIOUSLY MISSING — found live while planning Rust-side mint # support. An edge carrying only a rekey (no compute) had its # approval bind to nothing rekey-specific at all: any two # rekey edges with otherwise-identical renames/moves/converts/ # drops/retypes/computes produced the SAME digest regardless # of what their `rekey sql:` actually said, and a rekey's own # SQL could change without invalidating an existing approval. # Same bug shape for `backfills` (present, just never # exported). Fixing this CHANGES every existing rekey/ # backfill edge's digest — any approval already recorded for # one is invalidated by this fix and must be re-reviewed. rekeys: aggregate.rekeys.map { |rekey| { sql: rekey.sql } }, backfills: aggregate.backfills.map { |backfill| { name: backfill.name.to_s, default: backfill.default } } } end |
.translation_hash(translation) ⇒ Object
THE DIGEST-RELEVANT SHAPE — ApprovalDigest.edge_digest hashes
EXACTLY this, and only this, for exactly the reason compiled_ translation_aggregate below must never be used for that
purpose: a digest bound to the COMPILED SQL, not just the
DECLARED rules, would invalidate an existing human approval the
moment Translation::RuleCompiler's own output format changed
for any reason — a compiler refactor, a cosmetic SQL-formatting
change — even when the declared rules an approver actually
reviewed never changed at all. The approval binds to WHAT WAS
DECLARED, not to what a particular compiler build happened to
emit from it.
80 81 82 83 84 85 86 87 88 |
# File 'lib/hecks/projector/exporter.rb', line 80 def translation_hash(translation) { domain: translation.domain, from: translation.from, to: translation.to, retired: translation.retired, aggregates: translation.aggregates.map { |aggregate| translation_aggregate(aggregate) } } end |
.translations(registry) ⇒ Object
Translation IR, always as an array, WITH each aggregate's
precompiled SQL attached (compiled_translation_aggregate) —
this is the export a consumer embeds (ir.json's translations
key), never the bare digest-relevant shape edge_digest hashes
(see that method's own header for why the two must stay
separate). values: tables serialize as [key, value] pairs,
never an object, because JSON object keys are always strings and
a convert's keys are typed.
51 52 53 |
# File 'lib/hecks/projector/exporter.rb', line 51 def translations(registry) registry.translations.map { |translation| compiled_translation_hash(translation) } end |
.translations_json(registry) ⇒ Object
65 66 67 |
# File 'lib/hecks/projector/exporter.rb', line 65 def translations_json(registry) JSON.pretty_generate(translations(registry)) end |