Module: Hecks::Bluebook::MetaValidator
- Defined in:
- lib/hecks/bluebook/meta_validator.rb,
lib/hecks/bluebook/meta_validator/plan.rb,
lib/hecks/bluebook/meta_validator/judge.rb,
lib/hecks/bluebook/meta_validator/shapes.rb,
lib/hecks/bluebook/meta_validator/readings.rb,
lib/hecks/bluebook/meta_validator/port_judge.rb,
lib/hecks/bluebook/meta_validator/syntax_boot.rb,
lib/hecks/bluebook/meta_validator/world_judge.rb,
lib/hecks/bluebook/meta_validator/adapter_judge.rb,
lib/hecks/bluebook/meta_validator/reconstruction.rb,
lib/hecks/bluebook/meta_validator/translation_judge.rb
Overview
Judges a bluebook by DISPATCHING it into the language declared in itself.
lib/hecks/language/bluebook/ declares what a bluebook IS —
Chapter, Root, Verb, Shape, Ask, Piece, and the rest, split across files
by domain concept and merged into one chapter at load time (see
GRAMMAR_FILES below). This replays a built IR into that domain and turns
any refusal into a Malformed, so the meta-domain is what actually judges
rather than a description sitting beside the code — for whatever rules
it carries. spec/meta_rules_spec.rb's own header names the plan: port
the language's rules OUT of builder raise Malformed calls and INTO
given/invariant here, where they are declarations any reader of the
meta-domain can consume instead of behavior buried in a builder.
THIS MIGRATION IS PARTIAL, NOT DONE. As of this writing the meta-domain
declares 62 given/invariant/ensures rules (Hecks::QueryIR.collect_rules
against grammar_registry.bluebook("Bluebook") enumerates them) —
spec/meta_rule_reachability_spec.rb is what proves, per declaration,
not per verb, that most of them still lack a spec exercising the
refusal at all (see that file's own KNOWN_GAPS for the current count).
Meanwhile lib/hecks/bluebook/dsl/ still carries well over a hundred
raise Malformed calls of its own — some are genuinely pre-IR
construction errors (arity, argument shape) that cannot become
meta-domain rules, and some are exactly the semantic kind this file
claims to have moved (see aggregate_builder.rb's seal_* passes:
a mutation into a field the aggregate never declares, a lifecycle guard
on an aggregate with no lifecycle). No doc currently inventories which
is which, or tracks migrating the latter — that inventory is the actual
next step, not a "delete the folder" thought experiment.
So: "delete language/bluebook/ and validation stops" is true for the 62
rules actually declared here, and false for whatever a builder's own
raise Malformed still checks — the language does not yet own its own
enforcement end to end, and this comment used to claim it already did.
The self-hosting mechanism itself is real and is the point worth
keeping : a self-description that only describes is indistinguishable
from enforcement, and the first version of this file was deleted for
exactly that reason. What is not yet real is that self-hosting being
the WHOLE of validation.
The meta-domain is loaded ONCE and its registry reused ; each bluebook is judged in a fresh in-memory store so no domain can see another's records.
Defined Under Namespace
Modules: Readings, Shapes, SyntaxBoot Classes: AdapterJudge, Judge, Plan, PortJudge, Reconstruction, TranslationJudge, WorldJudge
Constant Summary collapse
- GRAMMAR_DIR =
THE FOLDER IS THE CHAPTER. Files are grouped by the domain concept they describe and every one reopens the same
Hecks.bluebook "Bluebook".BluebookBuilder.buildkeeps one builder open per chapter name across calls, so the sorted folder accumulates one domain. Adding or renaming a concept file requires no second catalog here; deterministic filename order is the source order exported to IR. File.("../language/bluebook", __dir__).freeze
- GRAMMAR_FILES =
Dir.glob(File.join(GRAMMAR_DIR, "*.bluebook")).sort.freeze
- WORLD_GRAMMAR =
Sibling artifact languages use the same folder-is-the-chapter rule. Their arrays are discovered, sorted source sets—not filename catalogs.
Dir.glob(File.("../language/world/*.bluebook", __dir__)).sort.freeze
- HECKSAGON_GRAMMAR =
Dir.glob(File.("../language/hecksagon/*.bluebook", __dir__)).sort.freeze
- PORT_GRAMMAR =
so is a port — whole-project table-unification survey, item #13's remaining builders. Backs the new PortJudge door the same way WORLD_GRAMMAR backs WorldJudge.
File.("../language/port.bluebook", __dir__).freeze
- ADAPTER_GRAMMAR =
so is an adapter — same reasoning, one file over. Backs AdapterJudge.
File.("../language/adapter.bluebook", __dir__).freeze
- TRANSLATION_GRAMMAR =
so is a translation — same reasoning, one file over. Backs TranslationJudge.
Dir.glob(File.("../language/translation/*.bluebook", __dir__)).sort.freeze
- ATTACHED_GRAMMAR_DIR =
ADR 0026's OWN SEAM: THE CORE DOES NOT NAME ITS EXTENSION POINTS.
A sub-language chapter (Paging, so far the only one) is an ORDINARY bluebook — declared with the same
aggregate/value_object/attaches_towords every domain has, judged through the language the normal way, not bootstrapped raw the way GRAMMAR_FILES is. What makes it special is only where it LIVES: any file in this directory is discovered and loaded here, by the directory's own existence, never by a name this file would have to know. Add a chapter here and it is attached ; nothing in this file changes. File.("../language/bluebook/attaches", __dir__).freeze
- LANGUAGE_CHAPTERS =
The chapters that ARE the language — loaded raw during bootstrap, then judged through themselves and replaced by their own assembled graphs (see grammar_registry). Each is named after its file : Bluebook describes bluebooks (language/bluebook/) ; World describes worlds (world.bluebook), and backs the WorldJudge door ; Hecksagon describes hecksagons (hecksagon.bluebook) — declared for the same self-description reasons as World, but WITHOUT a judge door of its own : nothing dispatches a real .hecksagon file through it yet, so HecksagonBuilder's own behavior is unchanged. What this buys is what syntax.bluebook needed — a real shape
subscribe'sfills: "subscriptions"can point at — not new validation on top of the corpus's existing .hecksagon files. %w[Bluebook World Hecksagon].freeze
Class Method Summary collapse
-
.bootstrapping? ⇒ Boolean
The meta-domain is itself a bluebook.
-
.call(bluebook) ⇒ Object
THE LANGUAGE HANDS THE GRAPH BACK.
-
.call_adapter(adapter) ⇒ Object
An adapter is not a bluebook either — same door shape, one more artifact over.
-
.call_port(port) ⇒ Object
A port is not a bluebook either — same door shape as call_world, one artifact over.
-
.call_translation(translation) ⇒ Object
A translation is not a bluebook either — same door shape, one more artifact over (a
translations/*.bluebookedge —data_translation's own real, established convention; there is no separate extension). -
.call_world(world) ⇒ Object
A world is not a bluebook, so it gets its own door.
- .defer ⇒ Object
- .deferred_chapters ⇒ Object
-
.deferring? ⇒ Boolean
A CHAPTER MAY BE SPLIT ACROSS FILES, so it cannot be judged until every file has been read.
-
.disabled? ⇒ Boolean
&& !@forcing_fixpoint— seewhile_forcing_fixpointbelow, whose own window must win even while a growth spec'swhile_disabledis open, for the reason recorded there. -
.fresh_runtime ⇒ Object
A FRESH STORE per bluebook.
- .grammar_registry ⇒ Object
-
.grammar_registry_ready? ⇒ Boolean
A REENTRANT CALL DURING THE FIXPOINT/ATTACH WINDOW ABOVE gets a real, correctly-mutating registry object back — no infinite loop, no wrong data for THAT caller's own purposes.
-
.hold(bluebook) ⇒ Object
Dispatch it in and READ IT BACK.
- .judge_deferred!(registry) ⇒ Object
-
.load_attached_grammar_into(registry) ⇒ Object
ATTACHED CHAPTERS LOAD AFTER THE FIXPOINT, NOT DURING BOOTSTRAP — they are declared IN the language the language just finished judging itself through, so they are ordinary bluebooks, judged the ordinary way (
Hecks.bluebook→BluebookBuilder#build→MetaValidator.call,bootstrapping?already false). -
.load_grammar_into(registry) ⇒ Object
THE ONE PLACE THE GRAMMAR'S OWN BOOT SEQUENCE IS SPELLED — ports, the memory/prism adapters, the (now nine-file) chapter itself, then the sibling world grammar.
-
.shadow_parsing? ⇒ Boolean
ADR 0025's own prerequisite (docs/dsl-work-slices.md, S0a): a word a later slice removes from the LIVE grammar must still parse FROZEN ERA TEXT —
EraGuard.shadow_parse(runtime/era_guard.rb) is a plainKernel.evalof stored source, run at boot, at mint, and during tamper detection, against whatever grammar is live TODAY, not whatever grammar was live when that text was written. -
.verdicts ⇒ Object
The same bluebook judged twice gets the same verdict, and a suite reloads its fixtures constantly — banking alone is ~200 dispatches per build.
-
.while_disabled ⇒ Object
THE SAME STACK-RESTORE SHAPE
while_shadow_parsing/while_forcing_ fixpointUSE. -
.while_forcing_fixpoint ⇒ Object
THE SAME STACK-RESTORE SHAPE
while_shadow_parsingUSES, for the same reason: whatever this wraps must never seedisabled?answer true, however a test elsewhere has the env toggle set at that exact moment. - .while_shadow_parsing ⇒ Object
Class Method Details
.bootstrapping? ⇒ Boolean
The meta-domain is itself a bluebook. Judging it while loading it would recurse, so the load path marks the bootstrap and skips — but the skip is only the FIRST pass. Once every grammar file is loaded and merged, grammar_registry judges the language through itself and keeps the assembled result (the fixpoint, made load-bearing).
100 |
# File 'lib/hecks/bluebook/meta_validator.rb', line 100 def self.bootstrapping? = @bootstrapping |
.call(bluebook) ⇒ Object
THE LANGUAGE HANDS THE GRAPH BACK.
This used to return the bluebook it was given — dispatch every declaration in, collect refusals, throw the records away — which is all JUDGING needs and exactly why the language could only validate. It returns what the meta-domain HOLDS instead, assembled into the graph the runtime runs. The builder's own object graph now exists only to be dispatched; nothing keeps it.
Hecks.bluebook registers whatever comes back from here, so this one line
is the difference between a language that checks a domain and a language
that is the source of one.
What is CACHED is the declarations, not the graph. A hash carries no Ruby
classes, so a second load of the same chapter re-assembles fresh ones —
which is the behaviour Namespace.install and spec/construct_spec both
expect. Caching the graph would hand two boots the same classes.
THE LANGUAGE IS THE SOURCE. This is the line that makes it one.
Hecks.bluebook registers whatever comes back from here, so returning the
assembled graph rather than the bluebook it was handed is the whole swap: the
runtime runs what the meta-domain HOLDS. The builder's own graph exists only
to be dispatched in ; nothing keeps it.
It stayed unlanded for one wrong belief, worth naming because it looked so
much like a wall: that the language may only hold what to_h carries.
ReadModel#to_h omitted a read model's filters until 2026-08-11, so
read-model filtering seemed impossible to read back —
and hoisted policies lost which head declared them for the same reason.
But to_h is a PROJECTION and the language is the
SOURCE. They must agree about everything to_h spells ; they need not be the
same size. Both were held even before the wire format carried them, as
declarations the wire format didn't yet see.
UPDATE, 2026-08-11: the wire format DID move, on purpose, for a reason
unrelated to this file — a Rust-codegen task needed wheres/
order_by/limit on the wire to compile a read model's real declared
filtering, and the boundary described above was never load-bearing for
THIS mechanism (option_rows/filter_options in meta_validator/
readings.rb read node.wheres/node.order_by/node.limit off the
live object directly, never off to_h), so extending to_h changed
nothing here. ReadModel#to_h now spells all three explicitly, the
same mechanism Query#to_h already used — purely additive, still
agreeing with the language about everything it spells.
What is CACHED is the declarations, not the graph. A hash carries no Ruby
classes, so a second load of the same chapter assembles fresh ones — which is
what Namespace.install and spec/construct_spec both expect. Caching the
graph would hand two boots the same classes.
389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 |
# File 'lib/hecks/bluebook/meta_validator.rb', line 389 def self.call(bluebook) return bluebook if disabled? || bootstrapping? || shadow_parsing? if deferring? deferred_chapters << bluebook.hecks_name return bluebook end key = Digest::SHA256.hexdigest(JSON.generate(bluebook.to_h)) held = verdicts[key] ||= hold(bluebook) unless held[:refusals].empty? raise DSL::Malformed, "#{bluebook.hecks_name} is not a well-formed bluebook; #{held[:refusals].join('; ')}" end Assembly.call(held[:declaration]) end |
.call_adapter(adapter) ⇒ Object
An adapter is not a bluebook either — same door shape, one more artifact over. Whole-project table-unification survey, item #13's remaining builders.
298 299 300 301 302 303 304 305 306 307 |
# File 'lib/hecks/bluebook/meta_validator.rb', line 298 def self.call_adapter(adapter) return adapter if disabled? || bootstrapping? || shadow_parsing? key = Digest::SHA256.hexdigest(JSON.generate([adapter.name, adapter.port, adapter.fields, adapter.secrets])) refusals = verdicts[key] ||= AdapterJudge.new(adapter).refusals return adapter if refusals.empty? raise DSL::Malformed, "#{adapter.name}'s adapter is not well formed; #{refusals.join('; ')}" end |
.call_port(port) ⇒ Object
A port is not a bluebook either — same door shape as call_world, one artifact over. Whole-project table-unification survey, item #13's remaining builders.
284 285 286 287 288 289 290 291 292 293 |
# File 'lib/hecks/bluebook/meta_validator.rb', line 284 def self.call_port(port) return port if disabled? || bootstrapping? || shadow_parsing? key = Digest::SHA256.hexdigest(JSON.generate([port.name, port.verb, port.signal])) refusals = verdicts[key] ||= PortJudge.new(port).refusals return port if refusals.empty? raise DSL::Malformed, "#{port.name}'s port is not well formed; #{refusals.join('; ')}" end |
.call_translation(translation) ⇒ Object
A translation is not a bluebook either — same door shape, one more
artifact over (a translations/*.bluebook edge — data_translation's
own real, established convention; there is no separate extension).
TranslationJudge walks the WHOLE built translation (every nested
aggregate's own rule table) in one pass — only
TranslationBuilder's own top-level build calls this;
TranslationAggregateBuilder#build stays a plain struct constructor,
the same way WorldJudge judges every Wiring a .world declares
in ONE pass over World::World.Declare's own caller, not from a
separate door per binding. Whole-project table-unification survey,
item #13's remaining builders.
320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 |
# File 'lib/hecks/bluebook/meta_validator.rb', line 320 def self.call_translation(translation) return translation if disabled? || bootstrapping? || shadow_parsing? # `Translation`/`TranslationAggregate` are plain classes (attr_ # reader, not Struct — lib/hecks/bluebook/translation.rb), # so neither carries a `.to_h`; `.inspect` is JSON-safe (a plain # string) and, unlike a Struct's own memoization-friendly `.to_h`, # doesn't need one — the small correctness cost is that its # embedded object-id prefix makes two structurally-identical # translations hash differently, so `verdicts` simply never # cache-hits here (an efficiency loss, not a correctness one — # judged fresh every time instead of memoized). key = Digest::SHA256.hexdigest(translation.inspect) refusals = verdicts[key] ||= TranslationJudge.new(translation).refusals return translation if refusals.empty? raise DSL::Malformed, "#{translation.domain}'s translation is not well formed; #{refusals.join('; ')}" end |
.call_world(world) ⇒ Object
A world is not a bluebook, so it gets its own door. Same judge, same meta-domain registry — a different artifact and a different language file.
270 271 272 273 274 275 276 277 278 279 |
# File 'lib/hecks/bluebook/meta_validator.rb', line 270 def self.call_world(world) return world if disabled? || bootstrapping? || shadow_parsing? key = Digest::SHA256.hexdigest(JSON.generate([world.domain, world.realm, world.latest, world.settings])) refusals = verdicts[key] ||= WorldJudge.new(world).refusals return world if refusals.empty? raise DSL::Malformed, "#{world.domain}'s world is not well formed; #{refusals.join('; ')}" end |
.defer ⇒ Object
128 129 130 131 132 133 134 |
# File 'lib/hecks/bluebook/meta_validator.rb', line 128 def self.defer previous = @deferring @deferring = true yield ensure @deferring = previous end |
.deferred_chapters ⇒ Object
136 |
# File 'lib/hecks/bluebook/meta_validator.rb', line 136 def self.deferred_chapters = @deferred_chapters ||= [] |
.deferring? ⇒ Boolean
A CHAPTER MAY BE SPLIT ACROSS FILES, so it cannot be judged until every file has been read.
BluebookBuilder.build already MERGES — registry.bluebook_builder (name) memoises one builder per chapter name, so nine files each
saying Hecks.bluebook "Bluebook" accumulate into one. What it
also does is call MetaValidator.call once PER FILE, judging a
chapter that is still eight files short: Aggregate's reference
to Bluebook dangles because Bluebook has not been declared
yet, and the load dies.
The language's own grammar has always needed this and got it
privately, through @bootstrapping (see load_grammar_into) —
which is exactly why the language could not boot the way the
domains it describes boot. This is that same two-phase load,
available to anything: Folder#load_domain reads every
*.bluebook inside defer, then judges each composed chapter
once, before hecksagons and worlds load (DOMAIN_ORDER already
puts every chapter ahead of those).
Only chapters DECLARED INSIDE the window are judged afterwards.
Re-judging one already assembled — a framework member pulled in
earlier by uses_framework, say — would re-run Assembly and hand
out a second set of classes for a graph something already holds.
126 |
# File 'lib/hecks/bluebook/meta_validator.rb', line 126 def self.deferring? = @deferring |
.disabled? ⇒ Boolean
&& !@forcing_fixpoint — see while_forcing_fixpoint below, whose own
window must win even while a growth spec's while_disabled is open,
for the reason recorded there. Otherwise the SAME stack-restore shape
while_shadow_parsing/while_forcing_fixpoint use, not a bare env
toggle any more — it used to be exactly that (ENV["HECKS_META_ VALIDATION"] == "off", read directly, with no previous/ensure of
its own), and the gap between "bare toggle" and "stack-restore" was
not cosmetic: a test's temporary window could reach code it was never
meant to touch. If grammar_registry's ONE-TIME lazy build (below)
happened to land inside that window, EVERY language chapter got
cached in its raw, never-judged form for the rest of the process —
unmark_scalar's String->Integer/Boolean fix (assembly/marks.rb)
never ran, so a Command's own required: true stayed
required: "true" forever after, permanently memoized. Found live:
an intermittent, parallel_rspec-only ir_golden_spec.rb failure,
order-dependent on whether identifier_numeric_coercion_growth_spec.rb's
disabled-validation window raced the ONE lazy build in its own worker
process — reproduced in isolation by disabling validation before the
first grammar_registry call. && !@forcing_fixpoint was the first
fix and is kept ; converting @disabled itself to this shape closes
the gap for every OTHER caller of while_disabled, not just the one
race that was actually observed — nothing outside this file reads
ENV["HECKS_META_VALIDATION"] any more (confirmed: every one of the
dozen growth specs that used to hand-roll previous = ENV[...] ; ENV[...] = "off" ; ... ; ensure ENV[...] = previous now calls
while_disabled instead), so there is no bare global left to race.
205 |
# File 'lib/hecks/bluebook/meta_validator.rb', line 205 def self.disabled? = @disabled && !@forcing_fixpoint |
.fresh_runtime ⇒ Object
A FRESH STORE per bluebook. The registry memoises repositories, so
reusing it let every bluebook see the records of every bluebook judged
before it. The parsed grammar is reused ; only the records are cleared.
(During grammar_registry's own fixpoint judge this clear runs on the
singleton mid-memoisation — harmless for the same reason : the grammar
is what is kept, the records were never meant to survive a judging.)
No bind_runtime here : judging dispatches by FQN and never opens the
door, and binding would re-install the language's own facade constants
once per judged chapter.
535 536 537 538 539 |
# File 'lib/hecks/bluebook/meta_validator.rb', line 535 def self.fresh_runtime registry = grammar_registry registry.instance_variable_set(:@repositories, {}) Runtime::Dispatcher.new(registry) end |
.grammar_registry ⇒ Object
418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 |
# File 'lib/hecks/bluebook/meta_validator.rb', line 418 def self.grammar_registry @grammar_registry ||= begin registry = load_grammar_into(Runtime::Registry.new) # Assigned BEFORE the fixpoint judge below: judging re-enters # grammar_registry through fresh_runtime (judge.rb) and Plan.for # (judge.rb, reconstruction.rb) — a bare ||= would still be nil # while its right-hand side evaluates, and recurse forever. That # reentrancy window is real: a caller landing here BEFORE the # fixpoint/attach below have run sees the SAME registry object, # correctly, but one still missing the attached chapters (Paging's # `attaches_to` among them) — see grammar_registry_ready? below. @grammar_registry = registry # THE FIXPOINT MADE LOAD-BEARING. The bootstrap loaded the language # raw ; now the language judges itself, its records are read back, # and the ASSEMBLED graph replaces the raw one — so every bluebook # judged from here on is judged by the language the language itself # produced. Outside load_grammar_into on purpose : its ensure clears # @bootstrapping, and call() must see bootstrapping? == false to do # anything at all. `while_forcing_fixpoint`-wrapped so a growth # spec's own `while_disabled` window can never leave this # ONE-TIME build cached in its raw, never-judged form — see # `disabled?`'s own comment. while_forcing_fixpoint do LANGUAGE_CHAPTERS.each { |name| registry.add_bluebook(call(registry.bluebook(name))) } load_attached_grammar_into(registry) end # Stamped LAST, keyed by this registry's own identity rather than # a bare boolean — a manual reset (fixpoint_spec.rb's own # `@grammar_registry = nil`) makes @grammar_registry not equal # this object_id again until a fresh build finishes, so a stale # "ready" from the PREVIOUS cycle can never leak into the next. @grammar_ready_for = registry.object_id registry end end |
.grammar_registry_ready? ⇒ Boolean
A REENTRANT CALL DURING THE FIXPOINT/ATTACH WINDOW ABOVE gets a real, correctly-mutating registry object back — no infinite loop, no wrong data for THAT caller's own purposes. But anything that MEMOIZES a snapshot derived from it must not lock that snapshot in forever : this is one signal such a cache can check. Found live — SyntaxBoot.call had cached a Query keyword list missing every Paging-attached word (limit/offset/cursor/nulls) because something called it inside this exact window.
SyntaxBoot.call NO LONGER USES THIS. Gating its cache on "the
whole registry is finished" meant nothing was cached for the
entire window, and the window is not narrow — every word routed
through word_gate_dispatch while the language judged itself
re-ran the ~284-dispatch syntax boot (42 times, 32 seconds, per
process — see SyntaxBoot.call's own comment). It keys its cache
on the registry's chapter set instead, which is the actual input
the snapshot is a function of. Kept here for any other derived
cache that genuinely needs "is the build finished" rather than
"have my inputs changed".
473 474 475 |
# File 'lib/hecks/bluebook/meta_validator.rb', line 473 def self.grammar_registry_ready? @grammar_registry && @grammar_ready_for == @grammar_registry.object_id end |
.hold(bluebook) ⇒ Object
Dispatch it in and READ IT BACK. A refused chapter has no declarations to read — the records are half-written by definition — so it carries refusals and nothing else.
411 412 413 414 415 416 |
# File 'lib/hecks/bluebook/meta_validator.rb', line 411 def self.hold(bluebook) judge = Judge.new(bluebook) return { refusals: judge.refusals } unless judge.refusals.empty? { refusals: [], declaration: Reconstruction.of(judge.runtime, bluebook.hecks_name) } end |
.judge_deferred!(registry) ⇒ Object
138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 |
# File 'lib/hecks/bluebook/meta_validator.rb', line 138 def self.judge_deferred!(registry) pending = deferred_chapters.uniq @deferred_chapters = [] return unless registry pending.each do |name| chapter = registry.bluebook(name) next unless chapter # A bare chapter-given left PENDING by any file of this # chapter (`AggregateBuilder#pending_chapter_given`) resolves # first — before anything below reads a `Given`'s fields. # `registry.bluebook_builder(name)` is the SAME instance every # one of this chapter's own files built onto (`#self.build`'s # own comment); it is guaranteed already open here, since it # is what produced `chapter` in the first place — the block is # dead code, never actually invoked. builder = registry.bluebook_builder(name) { raise "internal: no open builder for #{name}" } builder.resolve_pending_chapter_givens! # THE ENTITY-SCOPED ANALOGUE, one level down — same reason, # same timing: a bare entity-level given left PENDING by any # file of this chapter (`EntityBuilder#pending_chapter_entity_ # given`) must resolve before anything below reads a piece's # own `Given` fields too. builder.resolve_pending_chapter_entity_givens! # `BluebookBuilder#build` skipped its own whole-chapter battery # (hops, projected fields, correlation keys, event shapes, # `with:` projections) for every file of THIS chapter while # `deferring?` was true, the same reason `call` below queued # instead of judging — each of those checks needs every file # loaded first (see `BluebookBuilder.validate_assembled!`'s own # comment). `chapter` here is exactly that: whatever the LAST # file's own `add_bluebook` left in the registry, which by now # holds every aggregate/policy/process_manager the whole # chapter declares. Run once, here, instead of once per file. DSL::BluebookBuilder.validate_assembled!(chapter) registry.add_bluebook(call(chapter)) end end |
.load_attached_grammar_into(registry) ⇒ Object
ATTACHED CHAPTERS LOAD AFTER THE FIXPOINT, NOT DURING BOOTSTRAP —
they are declared IN the language the language just finished
judging itself through, so they are ordinary bluebooks, judged the
ordinary way (Hecks.bluebook → BluebookBuilder#build →
MetaValidator.call, bootstrapping? already false). A directory
with nothing in it loads nothing ; this is a no-op until a chapter
is added there.
484 485 486 487 488 |
# File 'lib/hecks/bluebook/meta_validator.rb', line 484 def self.load_attached_grammar_into(registry) Hecks.with_registry(registry) do Dir.glob(File.join(ATTACHED_GRAMMAR_DIR, "*.bluebook")).sort.each { |file| Kernel.load(file) } end end |
.load_grammar_into(registry) ⇒ Object
THE ONE PLACE THE GRAMMAR'S OWN BOOT SEQUENCE IS SPELLED — ports, the
memory/prism adapters, the (now nine-file) chapter itself, then the
sibling world grammar. grammar_registry uses this for its memoised
singleton ; anything that needs an ISOLATED registry (a spec wanting a
fresh store per example, say) calls this directly instead of hand-
copying the sequence.
THE BOOTSTRAP GUARD LIVES HERE, not just around the singleton. Splitting
the chapter into several files means each file's own Hecks.bluebook "Bluebook" call now runs BluebookBuilder#build once per file — and
MetaValidator.call judges whatever it is handed unless bootstrapping?
is true. A caller that loaded the grammar files by hand into its own
registry, without this guard, would get each file DISPATCHED AND JUDGED
ALONE the moment it loaded — and a lone file like aggregate.bluebook
refuses immediately, since Aggregate.Attribute references ValueObject
and Aggregate.Holds references Entity, both declared in later files.
Every caller of the grammar must go through here for exactly that reason.
507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 |
# File 'lib/hecks/bluebook/meta_validator.rb', line 507 def self.load_grammar_into(registry) @bootstrapping = true Hecks.with_registry(registry) do Kernel.load(File.("../ports/persistence.port", __dir__)) Kernel.load(File.("../ports/extraction.port", __dir__)) Kernel.load(File.("../adapters/driven/memory.adapter", __dir__)) Kernel.load(File.("../adapters/driven/prism.adapter", __dir__)) GRAMMAR_FILES.each { |file| Kernel.load(file) } WORLD_GRAMMAR.each { |file| Kernel.load(file) } HECKSAGON_GRAMMAR.each { |file| Kernel.load(file) } Kernel.load(PORT_GRAMMAR) Kernel.load(ADAPTER_GRAMMAR) TRANSLATION_GRAMMAR.each { |file| Kernel.load(file) } end registry ensure @bootstrapping = false end |
.shadow_parsing? ⇒ Boolean
ADR 0025's own prerequisite (docs/dsl-work-slices.md, S0a): a word
a later slice removes from the LIVE grammar must still parse
FROZEN ERA TEXT — EraGuard.shadow_parse (runtime/era_guard.rb)
is a plain Kernel.eval of stored source, run at boot, at mint,
and during tamper detection, against whatever grammar is live
TODAY, not whatever grammar was live when that text was written.
Judging it again here would refuse history the day a spelling it
used is removed — proved with a rule that already lives ONLY in
the meta-domain, never duplicated as a builder's own raise Malformed (vision's own comment: "moved to the language").
Mirrors defer's own stack-restore shape, not disabled?'s bare
env toggle — this must never leak past the one shadow-parse call
that set it, the same reason ConstShim.with/.active?
(bluebook/dsl/const_shim.rb) restores in an ensure rather than
being flipped and left.
238 |
# File 'lib/hecks/bluebook/meta_validator.rb', line 238 def self.shadow_parsing? = @shadow_parsing |
.verdicts ⇒ Object
The same bluebook judged twice gets the same verdict, and a suite reloads its fixtures constantly — banking alone is ~200 dispatches per build. Keyed on the IR itself, so a CHANGED bluebook is always re-judged.
266 |
# File 'lib/hecks/bluebook/meta_validator.rb', line 266 def self.verdicts = @verdicts ||= {} |
.while_disabled ⇒ Object
THE SAME STACK-RESTORE SHAPE while_shadow_parsing/while_forcing_ fixpoint USE. This toggle's real, intended use is a growth spec
that boots a scratch bluebook from a tempfile and wants the runtime
behaviour without the validation overhead ; that is always a single
bounded window around one boot, never a flag meant to survive past
it, so the flag itself is scoped in the same previous/ensure
shape rather than a plain assignment a caller could forget to undo.
214 215 216 217 218 219 220 |
# File 'lib/hecks/bluebook/meta_validator.rb', line 214 def self.while_disabled previous = @disabled @disabled = true yield ensure @disabled = previous end |
.while_forcing_fixpoint ⇒ Object
THE SAME STACK-RESTORE SHAPE while_shadow_parsing USES, for the
same reason: whatever this wraps must never see disabled? answer
true, however a test elsewhere has the env toggle set at that
exact moment. Only grammar_registry's own one-time build (below)
wraps itself in this — nothing else needs it, and nothing else
should reach for it just to dodge disabled? for a domain
bluebook, which is precisely the toggle's real, intended use.
255 256 257 258 259 260 261 |
# File 'lib/hecks/bluebook/meta_validator.rb', line 255 def self.while_forcing_fixpoint previous = @forcing_fixpoint @forcing_fixpoint = true yield ensure @forcing_fixpoint = previous end |
.while_shadow_parsing ⇒ Object
240 241 242 243 244 245 246 |
# File 'lib/hecks/bluebook/meta_validator.rb', line 240 def self.while_shadow_parsing previous = @shadow_parsing @shadow_parsing = true yield ensure @shadow_parsing = previous end |