Words available inside bluebook do ... end.
The tables on this page are generated from the language’s own
aggregate-local syntax tables (lib/hecks/language/**/*.bluebook)
by hecks language_run.project_reference — do not edit inside the markers. The prose
between them is hand-written and survives regeneration.
Most of these run against the corpus — examples/banking is core and
carries every structural word; examples/pizzas is supporting.
generic and formerly_known_as are declared by nothing that ships, so
they get a chapter each:
```ruby boot Hecks::Adapters::Folder.new.load_bluebooks(File.join(InMemoryDomain::ROOT, “examples/banking/bluebook”)) Kernel.load(File.join(InMemoryDomain::ROOT, “examples/pizzas/bluebook/pizzas.bluebook”))
Hecks.hecksagon(“Banking”) do attaches “Governance” Banking::Customer.persisted_by(“Memory”) Banking::Account.persisted_by(“Memory”) end Hecks.hecksagon(“Governance”) do Governance::RoleAssignment.persisted_by(“Memory”) Governance::RoleTransition.persisted_by(“Memory”) end
Hecks.hecksagon(“Pizzas”) do attaches “Governance” Pizzas::Order.persisted_by(“Memory”) end
```ruby bluebook
Hecks.bluebook "BluebookReference" do
vision "Nothing anybody competes on — the kind of thing you would buy if you could."
generic
attaches_to "Query", "ReadModel"
aggregate "Postcode" do
attribute :code, Code
identified_by :code
value_object("Code") { attribute :value, String }
command "Record" do
sets :code
emits "PostcodeRecorded"
end
end
end
```ruby bluebook Hecks.bluebook “Ledgering” do vision “The same domain, under the name it answers to now.” # THE OLD NAME, KEPT — a chapter that is renamed still has to be # findable by whatever wrote records under the name it used to have. formerly_known_as “Bookkeeping”
aggregate “Folio” do attribute :number, Number
identified_by :number
value_object("Number") { attribute :value, String }
command "OpenFolio" do
sets :number
emits "FolioOpened"
end end end ```
```ruby boot Hecks.hecksagon(“BluebookReference”) { BluebookReference::Postcode.persisted_by(“Memory”) } Hecks.hecksagon(“Ledgering”) { Ledgering::Folio.persisted_by(“Memory”) }
## vision
<!-- generated:begin word=vision -->
`vision vision` — fills `vision`
| argument | kind | required | fills |
|---|---|---|---|
| positional 1 | text | true | vision |
<!-- generated:end -->
A one-line statement of what the domain is for. Optional, but not empty if given — the same value-object invariant fires here as on any string field, enforced by the meta-domain that judges the bluebook itself. Stored on the chapter and readable back after boot as `Chapter.vision`.
```ruby
runtime.registry.bluebook("Pizzas").vision # => "Put toppings on a pizza and sell it to a customer."
formerly_known_as formerly_known_as — fills formerly_known_as
| argument | kind | required | fills | |—|—|—|—| | positional 1 | text | true | formerly_known_as |
A domain’s own declared identity can change. Naming what it used to be lets the storage layer recognize its own history under the old name instead of minting a brand-new lineage from nothing — a rename, once applied, bridges the journal, the sequence, every era partition, and the domain-keyed rows in hecks_eras/hecks_era_texts/hecks_approvals/hecks_attestations onto the new name, in one transaction, the first time the domain boots under it. Optional, but not empty if given — the same value-object invariant fires here as on any string field. Stored on the chapter and readable back after boot as Chapter.formerly_known_as; safe to leave declared permanently, since every later boot after the bridge falls through to the ordinary already-held path.
The chapter answers to its current name and remembers the old one — the old name is a fact about this chapter, not a second chapter:
runtime.registry.bluebook("Ledgering").formerly_known_as # => "Bookkeeping"
runtime.registry.bluebook("Bookkeeping") # => nil
Nothing about the domain’s own shape depends on it:
Ledgering::Folio.open_folio!(number: { value: "f-1" }).number.value # => "f-1"
Written exemption (ADR 0025 principle 4) — no bluebook in THIS
repository’s own corpus renames itself, so the doctest above is
necessarily synthetic. The word is real, and used for real, outside
it: a downstream client project’s bluebook (a separate repository,
outside this one) declares formerly_known_as with its previous
domain name on its own chapter, bridging real production journal/
era/approval rows onto the renamed domain the day it deployed under
the new name, zero data loss, real Member rows confirmed intact.
namespace namespace — fills namespace
| argument | kind | required | fills | |—|—|—|—| | positional 1 | text | true | namespace |
Names the Ruby module the constants of a chapter install under, instead of its own name. Without it, a chapter called Pizzas installs as ::Pizzas and its aggregates as Pizzas::Order. With namespace "Tools::Workbench", the chapter installs as Tools::Workbench and its aggregates inside it, with no top-level shortcuts. The Hecks domain (ADR 0080) uses it: a chapter named Hecks would otherwise install into the gem module of the same name, where aggregates such as Release and Kernel collide with modules hecks already defines. A chapter whose constants would land in Hecks itself refuses at build.
The chapter keeps its own name; only where Ruby installs it changes:
Hecks::Bluebook::Chapter.new(name: "Workbench", namespace: "Tools::Workbench").namespace # => "Tools::Workbench"
attaches_to attaches_to — fills attaches_to
| argument | kind | required | fills | |—|—|—|—| | positional 1 | text | true | attaches_to |
ADR 0026’s own seam for a sub-language chapter: the core grammar does
not name its extension points, so a chapter that extends one names
ITSELF onto it instead. attaches_to "Query", "ReadModel" names the
core contexts this chapter’s own Syntax aggregate contributes
Keyword/Argument rows for — read at boot by SyntaxBoot’s own
generic discovery (MetaValidator::ATTACHED_GRAMMAR_DIR), which merges
every attached chapter’s rows into the same grammar table the core’s
own rows populate, tagged by nothing more than having been found.
Variadic — accumulates across calls the same reason identified_by/
group_by do. Stored on the chapter and readable back after boot as
Chapter.attaches_to. Most chapters attach to nothing, and ABSENT IS
NOT EMPTY, the same reading version/formerly_known_as give.
runtime.registry.bluebook("BluebookReference").attaches_to # => ["Query", "ReadModel"]
runtime.registry.bluebook("Ledgering").attaches_to # => []
The real, load-bearing use (not synthetic, unlike the fixture
above): lib/hecks/language/bluebook/attaches/paging.bluebook
declares attaches_to "Query", "ReadModel" for real — the Paging
sub-language, whose own Syntax aggregate holds the seed rows for
limit/offset/cursor/nulls (see docs/implemented/reference/query.md/
read_model.md’s own sections on each). Every boot merges them in;
ParserTable/this doc generator/every conformance spec reads the
merged table and cannot tell a core row from an attached one.
provides provides, assignments:, grant:, transitions:, admit:, people:, register:, link:, resolve:, subscribe:, add_name:, confirm:, unsubscribe:, awaiting_confirmation:, receives_issues:, left:, confirm_window:, unsubscribe_window:, webhook_tolerance:, session_hold:, initiate:, succeeded:, failed:, lapse_reason:, holds_seat:, send_issue:, record_delivery:, schedule:, request:, registered_at:, frees_seat:, connect:, reconnect:, disconnect:, suspend:, resume:, enable:, disable:, mark_sensitive:, markings_for:, key_for:, shred: — fills provides
| argument | kind | required | fills |
|—|—|—|—|
| positional 1 | text | true | provides |
| assignments: | text | false | provides |
| grant: | text | false | provides |
| transitions: | text | false | provides |
| admit: | text | false | provides |
| people: | text | false | provides |
| register: | text | false | provides |
| link: | text | false | provides |
| resolve: | text | false | provides |
| subscribe: | text | false | provides |
| add_name: | text | false | provides |
| confirm: | text | false | provides |
| unsubscribe: | text | false | provides |
| awaiting_confirmation: | text | false | provides |
| receives_issues: | text | false | provides |
| left: | text | false | provides |
| confirm_window: | text | false | provides |
| unsubscribe_window: | text | false | provides |
| webhook_tolerance: | text | false | provides |
| session_hold: | text | false | provides |
| initiate: | text | false | provides |
| succeeded: | text | false | provides |
| failed: | text | false | provides |
| lapse_reason: | text | false | provides |
| holds_seat: | text | false | provides |
| send_issue: | text | false | provides |
| record_delivery: | text | false | provides |
| schedule: | text | false | provides |
| request: | text | false | provides |
| registered_at: | text | false | provides |
| frees_seat: | text | false | provides |
| connect: | text | false | provides |
| reconnect: | text | false | provides |
| disconnect: | text | false | provides |
| suspend: | text | false | provides |
| resume: | text | false | provides |
| enable: | text | false | provides |
| disable: | text | false | provides |
| mark_sensitive: | text | false | provides |
| markings_for: | text | false | provides |
| key_for: | text | false | provides |
| shred: | text | false | provides |
Declares a capability this chapter answers for every domain that attaches
it, so nothing downstream has to recognise the chapter by its name. The
one capability the language knows today is authorization, and it needs
exactly three verbs, each one of this chapter’s own:
assignments: — a query answering every role an actor holdsgrant: — the command that grants an actor a roletransitions: — a query answering whether one role may act as anotherThe Governance framework member is the real use
(lib/hecks/framework/bluebook/governance.bluebook). The role check at
dispatch, boot’s refusal of a role in a domain with no provider, the
authorization adapter and the fuzzer all read these verbs. None of them
looks for the name “Governance”. A provides with an unknown capability,
a missing or extra key, or a verb that names no command or query of the
right kind is refused when the chapter is built. Each key becomes one
row, in the order written:
runtime.registry.bluebook("Governance").provision("authorization")[:grant] # => "RoleAssignment.Assign"
runtime.registry.bluebook("Governance").provided_verb("authorization", :assignments) # => "Governance::RoleAssignment.AssignmentsForActor"
runtime.registry.authorization_provider_for("Banking").name # => "Governance"
runtime.registry.bluebook("Ledgering").provides # => []
A capability can also name a quantity the host needs (ADR 0098). A :duration
entry is spelled Aggregate.attribute and resolves to the whole-seconds
default: of that attribute, so the number is declared once, in the bluebook.
provides "newsletter" takes optional confirm_window: and unsubscribe_window:
(the lifetimes of the emailed links), and provides "checkout" takes optional
webhook_tolerance: and session_hold::
value_object "Seconds" do
attribute :value, Integer
end
attribute :hold, Seconds, default: { value: 1800 } # 30 minutes
provides "checkout", session_hold: "CheckoutSession.hold"
A chapter that names an attribute that does not exist, or whose default is not a positive whole number of seconds, is refused when it is built.
Two more optional kinds name a word and a field (ADR 0099). A :text entry
(provides "payments", lapse_reason: "Payment.lapse_reason") resolves to the string
default: of that attribute, and an :attribute entry
(provides "registrations", registered_at: "Registration.requested_at") resolves to the
attribute’s own name. Both are refused when the attribute is missing, and a :text entry
when its default is not a non-empty string.
The Privacy chapter declares two more, so the runtime never looks for its name:
provides "privacy" (mark_sensitive: the command that flags an attribute, markings_for:
the query of one domain’s markings) is what boot’s seeding of mark_sensitive facts and a
handle’s masking of a read dispatch through, and provides "subject_keys" (key_for: and
shred:) is what Ports::KeyVault.shred! dispatches through.
core — fills classification
Marks this chapter a core subdomain, in the DDD sense (core/supporting/generic). core, supporting, and generic all just set the one classification field, so calling more than one leaves whichever ran last — and, verified against the runtime, nothing else currently reads that field back. It documents intent; it gates nothing.
runtime.registry.bluebook("Banking").classification # => "core"
“Gates nothing” is checkable rather than promised — a core chapter
dispatches exactly like any other:
Banking::Account.open!(customer: "bb-1", number: { value: "bb-a1" }, kind: { name: "current" }, daily_limit: { cents: 1 }) # ~> NotFound: bb-1
supporting — fills classification
Marks this chapter a supporting subdomain. Same field, same mutual exclusivity, as core.
runtime.registry.bluebook("Pizzas").classification # => "supporting"
generic — fills classification
Marks this chapter a generic subdomain — undifferentiated, off-the-shelf territory in the DDD sense. Same field as core.
runtime.registry.bluebook("BluebookReference").classification # => "generic"
aggregate name do ... end — opens a Aggregate body
| argument | kind | required | fills | |—|—|—|—| | positional 1 | text | true | name |
Opens the thing with identity in this domain — identified_by, its attributes, commands, and lifecycle. See the Aggregate reference page for the full vocabulary inside.
Every aggregate a chapter opens becomes an entry point of its own, named after the chapter:
runtime.registry.bluebook("Banking").aggregates.map(&:hecks_name).first(3) # => ["Customer", "Account", "OnboardingCase"]
BluebookReference::Postcode.record!(code: { value: "N1" }).code.value # => "N1"
read_model name do ... end — opens a ReadModel body, was report
| argument | kind | required | fills | |—|—|—|—| | positional 1 | text | true | name |
Opens a read that gathers heads from several aggregates around one spine — declared at the chapter’s own top level, not under any single aggregate, because no one aggregate owns it. See the ReadModel reference page for reference_to/include and the rest. report (ADR 0025 reverts it — the IR construct, the registry API, and the docs filename all said read_model the whole time) stays answered only for frozen era text still parsed by the legacy grammar; live source refuses it, naming this word.
Banking declares ten, and they sit on the chapter rather than under any aggregate — which is the whole reason the word exists at this level:
runtime.registry.bluebook("Banking").read_models.map(&:hecks_name) # => ["CustomerPortfolio", "ComplianceDashboard", "DisputedPaymentCount", "DisputedPaymentMedian", "DisputedPaymentTotal", "DisputedPaymentAverage", "DisputedPaymentSmallest", "DisputedPaymentLargest", "DisputedPaymentP95", "AccountsByKind"]
policy name do ... end — opens a Policy body
| argument | kind | required | fills | |—|—|—|—| | positional 1 | text | true | name |
The same word as inside an aggregate — a reaction to an event, on/trigger — but written here at the chapter’s top level for a policy that isn’t one aggregate’s own business. Pizzas’ OnPizzaPaymentReceived (examples/pizzas/bluebook/pizzas.bluebook) is a real example: it triggers Order.Purchase but is declared beside the aggregate, not inside it. See the Policy reference page.
Banking declares four here and two inside aggregates, and they all land in the same place — where a policy was WRITTEN is a readability decision, not a structural one:
runtime.registry.bluebook("Banking").policies.map(&:name) # => ["ReviewOnFreeze", "RetryOnPaymentFailure", "FreezeAccountsOnSuspension", "NotifyOnClosure", "ReviewOnBoxSurrender", "FlagKeyReturn"]
The first two of those are Account’s and ScheduledPayment’s own,
declared inside them; the rest are the chapter’s.
process_manager name do ... end — opens a ProcessManager body
| argument | kind | required | fills | |—|—|—|—| | positional 1 | text | true | name |
Opens a stateful saga spanning several events and commands — correlates_by, starts_on/ends_on, its handlers. Chapter-level only, like report and top-level policy, since it belongs to no single aggregate. See the ProcessManager reference page.
runtime.registry.bluebook("Banking").process_managers.map(&:name) # => ["Onboarding", "Settlement", "ExternalSettlement"]