Class: Hecks::Bluebook::MetaValidator::Plan

Inherits:
Object
  • Object
show all
Defined in:
lib/hecks/bluebook/meta_validator/plan.rb

Overview

What the language says about ITSELF, read back as something walkable.

The judge used to carry one hand-written branch per category, and the reason given for keeping it that way was that "which append command belongs to which list is not derivable from a name". True — and beside the point. It is derivable from the language's own IR, because every append command DECLARES its target:

command "Argument" do
  reference_to Command
  then_set :arguments, append: { name: :name, type: :type, ... }
end

That one line says both things a walk needs: Command.Argument is the appender for the arguments list, and the map binds each value-object field to the command argument that fills it. Nothing is matched by name.

The same reading recovers the containment tree. A command with NO self-reference is the creating one, and the *_id argument it carries names the parent — so Bluebook -> Aggregate -> Command / ValueObject / Query / Entity, ValueObject -> Member, ProcessManager -> Handler -> Dispatch all fall out of the declarations rather than being restated here.

Usage:

plan = Plan.for(MetaValidator.grammar_registry)
plan.category("Command").parent            # => "Aggregate"
plan.category("Command").appends["rules"]  # => Append(verb: "Rule", map: {...})
plan.verbs                                 # every Bluebook:: verb declared

This reads the language. It does not read a bluebook and it dispatches nothing — that is the judge's job.

Defined Under Namespace

Classes: Append, Category, Setter

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(meta) ⇒ Plan

Returns a new instance of Plan.



73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
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
117
118
# File 'lib/hecks/bluebook/meta_validator/plan.rb', line 73

def initialize(meta)
  @categories = {}
  meta.aggregates.each do |aggregate|
    # Vocabulary declares no commands — it is static declaration read from
    # the IR by spec/vocabulary_conformance_spec, never dispatched. It needs
    # no special case: a category with nothing to offer contributes nothing.
    next if aggregate.commands.empty?

    @categories[aggregate.hecks_name] = read(aggregate)

    # S17, ADR 0026 — Member/Handler/Dispatch are ENTITIES now
    # (`entity "Member" do ... end`, nested under `ValueObject`/
    # `ProcessManager`/`Handler`), not their own top-level
    # aggregates — so `meta.aggregates` alone no longer finds
    # them the way it always found a standalone `aggregate
    # "Member"`. Each STILL needs its own named category here:
    # `Assembly::Contracts` keeps a bespoke entry for each
    # (Member's own open-map `pairs`, Dispatch's own open-map
    # `with_spec`), distinct from the single generic "Entity"
    # category every ORDINARY real-corpus entity (LedgerEntry,
    # Withdrawal, ...) is described through instead. Safe to
    # walk EVERY meta-domain aggregate's own `.entities`
    # unconditionally — `Plan` only ever reads the META-
    # DOMAIN'S OWN self-description (`Plan.for(MetaValidator.
    # grammar_registry)`), which never declares a generic,
    # nameless entity of its own the way a REAL domain's
    # `LedgerEntry` is; the only entities the meta-domain
    # itself ever declares are exactly the three this ADR
    # names. `entity_owned: true` records WHY this is a
    # category at all — the real runtime has no top-level
    # aggregate named "Member" to dispatch a BARE verb into
    # any more, so the judge has to build a DOTTED one instead
    # (see `Judge#verb_for`).
    #
    # RECURSES — `Dispatch` nests inside `Handler`, which nests
    # inside `ProcessManager`, two levels deep, not one. Each
    # nested entity's own `parent` names the DIRECT owner it was
    # actually found under (Dispatch's is "Handler", not
    # "ProcessManager") — `read`'s own `owner:` argument already
    # takes whichever name is passed, so walking one level deeper
    # each recursive call is the whole change; nothing about
    # `read` itself needed to know how deep it was called from.
    add_nested_entities(aggregate)
  end
  @categories.freeze
end

Instance Attribute Details

#categoriesObject (readonly)

Returns the value of attribute categories.



71
72
73
# File 'lib/hecks/bluebook/meta_validator/plan.rb', line 71

def categories
  @categories
end

Class Method Details

.for(registry) ⇒ Object



67
68
69
# File 'lib/hecks/bluebook/meta_validator/plan.rb', line 67

def self.for(registry)
  new(registry.bluebook("Bluebook"))
end

Instance Method Details

#category(name) ⇒ Object



120
# File 'lib/hecks/bluebook/meta_validator/plan.rb', line 120

def category(name) = @categories[name.to_s]

#namesObject



121
# File 'lib/hecks/bluebook/meta_validator/plan.rb', line 121

def names          = @categories.keys

#verbsObject

Every verb the language declares, spelled as the judge would dispatch it. S17, ADR 0026 — an ENTITY-OWNED category (its own entity_owned flag) has no real top-level aggregate the runtime can route a BARE verb into any more, so it is spelled DOTTED here too — Bluebook::ValueObject.Member. Declare, Bluebook::ProcessManager.Handler.Dispatch.Bind — matching Judge#verb_for/#dotted_prefix's own build exactly (recursing the same way, for the same reason: an entity-owned category's own parent may itself be entity- owned), so this stays what it already is: the judge's own coverage promise, not a second guess at it.



134
135
136
137
138
# File 'lib/hecks/bluebook/meta_validator/plan.rb', line 134

def verbs
  @categories.flat_map do |name, category|
    category.verbs.map { |verb| "Bluebook::#{dotted_prefix(name)}.#{verb}" }
  end
end