Status: dated roadmap, last reconciled 2026-08-07. This is a plan, not a record of what is built. Phases it marks as new or research may have shipped, changed shape or never started since. It stays in
docs/because the README and CONTRIBUTING link it as the roadmap. For the current state read the README’s “Project status” section, the commit history,docs/implemented/andfuture-features.md.
This document is the canonical engineering specification for Hecks. It combines architectural invariants, execution model, implementation guidance, and the feature roadmap into a single reference intended for humans and coding agents.
Restored 2026-08-07, corrected same day. This document was drafted, committed (
ddb1ce2), and then reset away — all within the same session — before any of §8’s work actually started; a differently-architected Rust effort began fresh afterward onfeat/rust-projectionand is now substantially complete. §8/§9 below are rewritten to describe what was actually built, which diverges from this document’s own original plan in ways worth reading (it does the opposite of one of §8’s stated non-goals). Everything else in this document — Phases 1, 3–10 — was never started in the interim and is restored as originally written; spot-checked against the current codebase, not exhaustively re-audited section by section.docs/decisions/000{1,2,7,8,9,10}andhecks check_conformance, both named throughout, are restored alongside this file and confirmed still running against the current codebase.
Read the sections in order:
Hecks models organizational intent rather than software implementation. Bluebooks are the canonical description of why an organization behaves the way it does. Runtimes, APIs, databases, user interfaces, documentation, reports, AI assistants, and language projections are all derived artifacts. Every new feature should first ask whether it belongs in the intent layer before introducing implementation-specific concepts.
Every command should move through an explicit deterministic pipeline:
Each stage should document: - Inputs - Outputs - Replay behavior - Determinism guarantees - Failure semantics
Represents the canonical Bluebook independent of Ruby syntax. This is the durable semantic representation consumed by projectors.
Represents a fully bound command ready for execution after enrichment. It contains resolved identities, caller context, generated identifiers, timestamps, resolved references, and effective role information.
Represents the outcome of execution. It records accepted/refused decisions, evaluated grounds, authority chain, mutations, emitted events, adapter interactions, evidence hashes, and provenance.
These three IRs should remain independent.
The runtime should expose a graph of required capabilities and their fulfillments.
Examples:
Fulfillments may come from Bluebooks or adapters. The graph should power deployment analysis, onboarding, dependency visualization, and AI reasoning.
Canonical IR should expose a navigable semantic graph.
Examples:
Command -> Role Command -> Event Event -> Policy Policy -> Command Command -> Aggregate Aggregate -> Record Record -> Value Object
AI, projectors, verification, documentation, and visualization should operate on this graph instead of parsing Ruby.
Rules are constraints rather than simple validations.
The runtime should eventually explain blocked work by traversing dependency chains and identifying the next satisfiable organizational action.
Every runtime decision should produce an explanation tree.
Examples:
Accepted because… Refused because… Blocked because…
Different projections should render explanations for developers, auditors, regulators, and business users without changing evidence.
Add a verification subsystem capable of identifying:
Target command:
hecks verify
Status note (2026-08-07 restore): substantially real already, under a
different name — hecks model_check (built before this document was
first drafted, survived the reset that took the rest of this plan)
covers unreachable lifecycle states, transitions nothing can ever
fire, saga states no handler chain reaches, unreachable compensation
from_states, dispatches to nonexistent commands, and handlers
listening for events nothing emits — all as static analysis over the
same canonical IR the runtime dispatches against, the lightweight
formal-methods leg this section describes. given/ensures (Design
by Contract pre/postconditions) also exist and are load-bearing
throughout the corpus and the Rust projection (§8) both. Not yet
covered here specifically: dead commands/events/policies, duplicate
rules, unused roles/value objects — the dead-code-shaped half of this
list, distinct from model_check’s reachability/consistency half.
Generate diagrams directly from canonical IR:
No hand-maintained diagrams should be required.
AI should operate as an assistant rather than an execution engine.
Responsibilities include:
Runtime behavior always remains deterministic.
Every architectural invariant should be captured as an ADR. The implementation plan and ADRs together become the durable engineering memory of Hecks.
These sections should be merged into the existing HECKS_IMPLEMENTATION_PLAN.md. They elevate the execution model, semantic graph, verification, and explainability to first-class architectural concepts while preserving the small core language.
Agent-ready engineering briefs for the next phase of Hecks.
This document is intentionally implementation-oriented. Each feature states the current Hecks correlation, the target behavior, likely code seams, Ruby DSL examples, acceptance criteria, dependencies, and explicit non-goals. The goal is that an engineering agent can pick up one section and implement it without re-deriving the architecture from the original design conversation.
Scope note (2026-08-07): this roadmap is pruned to unimplemented work only —
supportingBluebooks (formerly a separate item) is removed as already implemented. The Rust/WASM/host-language projection sections were briefly removed as obsolete after the Rust runtime retirement (docs/implemented/rust-experiment.md), then restored, then narrowed by decision: there is one Rust runtime target in this roadmap (§8, plus its WASM compile output in§9), not a family of separate host-language projections — the Ruby-hosts-Rust, Python, Go, and Java sections that used to describe a second, generic, IR-interpreting Rust core were removed rather than kept as speculative alternatives.§8itself is a code generator, not an interpreter: it consumes canonical IR at build time and emits native Rust source, one module per held era as an initial implementation (a scaling concern, not a permanent architecture — see§8). The governing invariant is that no canonical IR is parsed or interpreted at runtime; generated static metadata for introspection/diagnostics is fine as long as it’s read, never interpreted as instructions.§8starts early (Phase 2, right after core hardening) rather than waiting for later IR-shaping work to finish — perdocs/decisions/0010-ruby-is-the-reference-implementation.md, Ruby serves as both the implementer’s guide and, through a continuously-run differential harness, the correctness oracle, so IR/behavior changes from later phases become fast-following, harness-verified extensions rather than a reason to have waited. This is explicitly framed as a second attempt that must not repeat the retired architecture (a hand-written second interpreter maintained through differential parity). Sections are renumbered sequentially; numbers no longer match any prior draft or external references to this document.
to_h / Bluebook::Assembly.lib/hecks/language/ and the self-hosted language declarations as load-bearing specifications, not documentation.Status: Partially present
Priority: P0
Complexity: S
Commands already carry a single role field through Bluebook::DSL::CommandBuilder and Bluebook::IR::Command. Runtime authorization already compares the current caller role against that field in Runtime::CommandRules::Authorization. The current language still allows commands with no role, and comments in the authorization code note that a meaningful portion of Banking currently declares none.
Likely files:
lib/hecks/bluebook/dsl/command_builder.rblib/hecks/bluebook/ir/command.rblib/hecks/runtime/command_rules/authorization.rblib/hecks/language/bluebook/behavior.bluebookspec/runtime/authorization_spec.rbspec/corpus/*.jsonA command may never declare multiple roles. The DSL already structurally satisfies this because role is a scalar assignment, but the invariant should become explicit in the language model and documentation. Whether every externally invokable command must eventually have a role should be a separate migration, because current role-less commands may represent system reactions or intentionally unguarded operations.
The immediate feature is therefore: role cardinality is zero-or-one, with a roadmap toward exactly one for externally proposed commands.
command "Withdraw" do
role "Teller"
goal "Give cash to an account holder"
reference_to Account
attribute :amount, Money
given("the account has enough available funds") {
balance.cents >= amount.cents
}
then_set :balance, decrement: :amount
emits "CashWithdrawn"
end
Do not add this:
command "Withdraw" do
role "Teller"
role "Branch manager" # invalid modeling; second assignment must not mean OR
end
Add a language-level statement to the self-hosted behavior grammar expressing that a command has at most one role. If the builder currently silently overwrites a second role call, make duplicate calls raise Malformed so authoring mistakes cannot disappear.
A simple builder guard is sufficient:
def role(value)
raise Malformed, "#{@name} declares role twice" if defined?(@role) && @role
@role = value
end
Keep IR shape scalar:
role: "Teller"
Do not change it to an array.
role declarations is refused at declaration time.to_h still emits a scalar role.Bluebook::Assembly reconstructs the scalar exactly.act_as authority transitionStatus: Done (2026-08-07) — on a smaller shape than this section
originally sketched, and deliberately so. Re-reading the “Runtime execution”
sketch below closely showed Governance.authorize_transition! called by
APPLICATION code, before Runtime::Caller.as(role:) wraps a nested
dispatch — not woven into CommandInterpreter/Dispatcher/
CommandRules::Authorization at all. So none of those three changed.
lib/hecks/framework/bluebook/governance.bluebook gained a second aggregate,
RoleTransition (from_role/to_role, Grant/Revoke, an Allowed
query) — the fact “role X may act as role Y” that RoleAssignment alone
could not answer. spec/act_as_spec.rb demonstrates the full pattern
against two separate registries (Governance’s own, and a real Pizzas
boot): an application checks Allowed, then wraps the downstream dispatch
in Hecks.as_caller(role: ...) ; the unauthorized-refusal path (no
Allowed row → the app-level check itself refuses, before any dispatch)
and the role-restoration proof (a THIRD dispatch, right after the nested
one, still authorized under the ORIGINAL role) are both covered. Also
confirmed and left alone on purpose: “replay does not invoke live
Governance decisions” was already true before this section existed —
Fuzzing::Replay never binds a Caller, and refuse_role_mismatch’s
first line is return unless caller. A facade-level calls "...", as:
"..." sugar (this section’s own DSL sketch below) is real, later work —
see “Non-goals”.
Priority: P0
Complexity: M
Runtime::Caller already provides:
Hecks::Runtime::Caller.as(role: "Teller") { ... }
and authorization reads that ambient role. Dispatcher reactions explicitly clear caller context because system reactions should not inherit the initiating human’s authority.
That is very close to the execution primitive required for act_as, but today it is merely a runtime caller binding rather than a governed, declared cross-command transition.
Likely files:
lib/hecks/runtime/caller.rblib/hecks/runtime/dispatcher.rblib/hecks/runtime/command_interpreter.rblib/hecks/runtime/command_rules/authorization.rblib/hecks/bluebook/ir/command.rblib/hecks/language/bluebook/behavior.bluebookspec/runtime/authorization_spec.rbspec/runtime/act_as_spec.rbWhen command A invokes command B across a domain or aggregate boundary, A may declare that the downstream invocation is performed as B’s required role. Governance decides whether that transition is permitted.
This preserves the invariant that each command has one role while supporting workflows where the initiating actor’s organizational role differs from the downstream domain role.
Prefer declaration on the cross-command call rather than global role inheritance.
Illustrative syntax:
command "RegisterCustomerAccount" do
role "Customer administrator"
goal "Open the customer's operating relationship"
calls "Customers::Customer.Register",
as: "Customer registrar"
end
Or if cross-command execution remains policy-oriented:
policy "RegisterCustomerAfterIdentity" do
on "IdentityRegistered"
trigger "Customers::Customer.Register",
act_as: "Customer registrar"
end
The exact surface spelling can be chosen after inspecting the existing trigger / orchestration DSL. The IR should care about the semantics, not the spelling.
A call edge should contain:
{
command: "Customers::Customer.Register",
act_as: "Customer registrar"
}
Do not change the downstream command:
command "Register" do
role "Customer registrar"
end
Conceptually:
Governance.authorize_transition!(
actor: current_actor,
from_command: "Identity::RegisterCustomerAccount",
from_role: "Customer administrator",
to_command: "Customers::Customer.Register",
to_role: "Customer registrar"
)
Runtime::Caller.as(role: "Customer registrar") do
dispatcher.dispatch_flat("Customers::Customer.Register", **args)
end
The existing Caller.as should be reused rather than replaced.
Preserve:
{
actor: "identity-123",
originating_role: "Customer administrator",
effective_role: "Customer registrar",
originating_command: "Identity::RegisterCustomerAccount",
command: "Customers::Customer.Register"
}
The domain event itself may remain business-focused; the execution/evidence envelope can retain the authority chain.
act_as changes effective role only for the nested dispatch.act_as a general role hierarchy.Status: Done (2026-08-07) — lib/hecks/framework/bluebook/governance.bluebook
(+ .hecksagon, Memory-persisted). RoleAssignment aggregate: composite
identity (actor_id, role_name, starts_at) rather than the sketch’s
synthetic assignment_id — no UUID adapter exists yet (§5), and a real
assignment event is genuinely unique by actor+role+when it started, so
none is needed. Assign/Revoke commands (Revoke sets ends_at,
never deletes), one query (AssignmentsForActor, does not filter by
ends_at — a caller decision, not baked in). The original sketch’s
Hecks.port "Authorization" do operation "CanActAs" ... returns ... end
doesn’t match the real port DSL (PortOperation has no returns — it’s
attributes + required emits, an anti-corruption boundary, not a
query); AssignmentsForActor is an ordinary query, the construct that
actually answers “who holds what” — no new DSL surface needed anywhere
in this build. spec/corpus_spec.rb/spec/model_check_spec.rb extended
with a FRAMEWORK_MEMBERS glob (mirrors GRAMMAR_CHAPTERS’s flat-file
pattern) so lib/hecks/framework/bluebook/*.bluebook gets the same
must-load/must-have-a-corpus-script/no-unnamed-model-check-finding
guarantees examples/* domains get. spec/governance_spec.rb (4
examples) + spec/corpus/governance.json, full suite green (1004
examples). Explicitly NOT done here: §2 (act_as wired to a real
Governance.authorize_transition! — there is still no hook point in
CommandRules::Authorization for this at all, confirmed by reading it;
refuse_role_mismatch is a hard-coded ==), any port/query surface for
OTHER domains to actually call into Governance at dispatch time, and
§4/§5 (Identities, UUID adapter) — IdentityId here is Governance’s
own owned value object, not a cross-domain reference.
Priority: P0
Complexity: L
Create a canonical governance.bluebook that models organizational authority independently of business domains.
Governance should answer questions such as:
Hecks.bluebook "Governance" do
vision "Organizational authority is explicit, reviewable, and historically explainable."
supporting
aggregate "RoleAssignment" do
identified_by { assignment_id.value }
attribute :assignment_id, AssignmentId
attribute :actor_id, IdentityId
attribute :role_name, RoleName
attribute :scope, Scope
attribute :starts_at, Timestamp
attribute :ends_at, Timestamp, optional: true
command "Assign" do
role "Governance administrator"
goal "Authorize an actor to perform a responsibility"
attribute :assignment_id, AssignmentId
attribute :actor_id, IdentityId
attribute :role_name, RoleName
attribute :scope, Scope
attribute :starts_at, Timestamp
attribute :ends_at, Timestamp, optional: true
emits "RoleAssigned"
end
command "Revoke" do
role "Governance administrator"
goal "Remove organizational authority"
reference_to RoleAssignment
emits "RoleRevoked"
end
end
query "AssignmentsForActor" do
where(actor_id: :actor_id)
end
end
The exact modeling should be driven by current Bluebook capabilities; avoid adding language solely for Governance.
Build Governance first as an ordinary Bluebook using current records/aggregates, commands, queries, lifecycles, policies, and ports. Only promote something into the language if Governance cannot express a universal concept without distortion.
Expose Governance to runtime authorization through a port, not a direct dependency on the Ruby implementation.
Example runtime port concept:
Hecks.port "Authorization" do
operation "CanActAs" do
attribute :actor_id, String
attribute :from_role, String
attribute :to_role, String
attribute :command, String
end
end
act_as) decisions.Status: Done (2026-08-07) — lib/hecks/framework/bluebook/identity.bluebook
(+ .hecksagon, Memory-persisted; chapter renamed from Identities to
Identity on 2026-08-08, matching Governance’s own singular naming —
the aggregate inside is ALSO named Identity, so its FQN is the
slightly repetitive but unambiguous Identity::Identity). Attached to
Banking for real via attaches "Identity" in
examples/banking/bluebook/banking.hecksagon, the same mechanism
Governance uses — see §31’s own note on Hecks::Framework.
Identity (identity_id minted via §5’s
Ports::IdentityGeneration.uuid — no natural key exists for “a newly
recognized identity,” and this is the first real consumer of that
port) and ExternalIdentifier (issuer/subject pair, natural key
key.value). One correction to the sketch: ExternalIdentifier uses a
real reference_to Identity rather than a bare owned value object —
both aggregates share one bluebook (unlike Governance’s actor_id,
which deliberately avoided a cross-domain reference to this exact
domain before it existed), so there’s no reason to give up
resolve_references’s real existence-checking; spec/identity_spec.rb
confirms Link refuses against a nonexistent identity, not just that
it succeeds against a real one. key’s derivation (SHA256 of
issuer+subject, or any other scheme) stays a caller-side concern, per
the original sketch’s own framing — no new port for it.
spec/corpus_spec.rb/spec/model_check_spec.rb needed no changes —
§3’s FRAMEWORK_MEMBERS glob picked this domain up automatically.
Full suite green (1019 examples). Explicitly not done: any OIDC
verification (§11); Governance’s actor_id was not retrofitted to
reference this domain.
Priority: P0
Complexity: M
Create identities.bluebook as the domain that relates authenticated identifiers to stable organizational identities. It should not become a junk-drawer User model.
The domain should support external identities such as OIDC issuer/subject pairs and locally minted identity IDs where there is no derivable identity.
Hecks.bluebook "Identities" do
vision "Authentication proves an identifier; the organization decides what that identity means."
supporting
aggregate "Identity" do
identified_by { identity_id.value }
attribute :identity_id, IdentityId
value_object "IdentityId" do
attribute :value, String
invariant("an identity id is present") { !value.to_s.empty? }
end
command "Register" do
role "Identity registrar"
goal "Recognize an identity"
attribute :identity_id, IdentityId
emits "IdentityRegistered"
end
end
aggregate "ExternalIdentifier" do
identified_by { key.value }
attribute :key, ExternalIdentifierKey
attribute :identity_id, IdentityId
attribute :issuer, Issuer
attribute :subject, Subject
command "Link" do
role "Identity registrar"
goal "Associate an external authenticated subject with an identity"
attribute :key, ExternalIdentifierKey
attribute :identity_id, IdentityId
attribute :issuer, Issuer
attribute :subject, Subject
emits "ExternalIdentifierLinked"
end
end
end
For OIDC, the external identifier key can be derived deterministically from (issuer, subject).
Example conceptual adapter-side derivation:
key = Digest::SHA256.hexdigest("#{issuer}\0#{subject}")
The Bluebook need not know how it is derived if the adapter supplies it.
Status: Done (2026-08-07) — lib/hecks/ports/identity_generation.{port,rb},
modeled on Ports::Extraction (one adapter registry-wide implements the
port; zero/many both refuse), not on Persistence’s heavier per-aggregate
binding — different aggregates have no real reason to want different
id-generation strategies. Two adapters: SecureRandomIdentity (real)
and SequentialIdentity (deterministic, for specs — satisfies the
“replaceable by deterministic test adapter” criterion directly). The
sketch’s operation "UUID" do returns String end doesn’t match the
real port DSL (no returns anywhere — same correction §3 already
needed for its own port sketch); the real precedent for “call something
and get a plain value back” is extraction.port’s shape, not
DomainPort/PortOperation’s. The “replay never invokes it” half
needed no new mechanism at all — confirmed by reading the real replay
path (Fuzzing::Replay re-dispatches recorded args through the
identical CommandInterpreter pipeline; nothing ever adds a key to
args between a caller and hydrate), so a UUID minted for a creating
command’s identity is just an ordinary string baked into that step’s
own recorded args from the moment it’s generated — replaying those
args never touches the adapter again, for the same reason
Event#occurred_at’s own Time.now call (the one existing
“environmental fact” precedent) needs no suppression either. Verified
directly, not just architecturally: spec/ports/identity_generation_spec.rb
mints an id, dispatches a real Pizzas::Order.CreatePizza with it,
then replays the same recorded args through a fresh boot and confirms
both the identical id and that SequentialIdentity’s own counter did
not advance a second time. No CommandInterpreter/Dispatcher
changes, no new .hecksagon binding syntax. Not done: facade-level
automatic enrichment (omitting an identity argument and having a
generated command method fill it in) — demonstrated as an explicit
caller-side call, deliberately not built as new AggregateDoor codegen;
nothing in the existing corpus was retrofitted to use this (natural
keys remain preferred wherever one exists).
Priority: P0
Complexity: S–M
Hecks already has named ports and adapters and a deterministic replay model. DeclarationSnapshot demonstrates the strong separation between authoring and replay: structured IR can be reconstructed without rerunning Ruby source.
When a creating command has no natural/derived reference, the runtime obtains a UUID through an adapter on first execution and freezes it into durable history. Replay never invokes the UUID adapter.
Bluebook remains declarative. UUID generation is an imperative environmental fact.
Do not add a create keyword:
command "Register" do
role "Identity registrar"
goal "Recognize a new identity"
attribute :identity_id, IdentityId
emits "IdentityRegistered"
end
A façade may allow omission:
identities.identity.register
and the runtime enriches the command:
dispatcher.dispatch_flat(
"Identities::Identity.Register",
identity_id: { value: uuid_adapter.generate }
)
Hecks.port "IdentityGeneration" do
operation "UUID" do
returns String
end
end
If the existing port DSL does not support returns, use the existing operation-output convention instead of expanding syntax.
first execution:
intent → enrich imperative facts → execute → record facts/events
replay:
recorded facts/events → rebuild state
report as business-facing read modelStatus: Existing query + read_model; naming change only
Priority: P0
Complexity: XS–S
Hecks already has both query and read_model. The query/read-model guide explicitly describes read models as report-like cross-aggregate projections.
Introduce report as the SME-facing spelling for read_model, preserving read_model as a permanent compatibility alias.
Do not replace query. A query is a request/question; a report is a declared view.
Current:
read_model "ComplianceDashboard" do
reference_to Account
include Account
include CardPayment
where(status: "disputed")
order_by :amount, :desc
limit 5
end
Preferred:
report "ComplianceDashboard" do
description "One account and the disputed payments requiring review."
reference_to Account
include Account
include CardPayment
where(status: "disputed")
order_by :amount, :desc
limit 5
end
Keep:
query "SuspendedCustomers" do
where(status: "suspended")
end
Make the DSL builder dispatch both words to the same IR type initially. Update self-hosted syntax vocabulary using the existing rename/was: mechanism if appropriate so old eras remain valid.
report and read_model produce identical IR.report as primary business term.Status: Done (2026-08-07) — IR::Bluebook::IR_VERSION (currently 1) is now the first key to_h emits, distinct from a domain’s own version:; golden IR (all 9 fixtures) and running-a-runtime.md updated; Assembly needed no change (an unrecognized key is simply not read back). Otherwise strongly present already through to_h, Projector::Exporter, DeclarationSnapshot, Assembly.
Priority: P0
Complexity: M
Projector::Exporter serializes booted Bluebooks to hashes/JSON. Runtime::DeclarationSnapshot reconstructs a real Bluebook from JSON through Bluebook::Assembly. Era replay explicitly no longer depends on reparsing Ruby source.
This is the foundation for language-independent projections.
Formalize canonical IR as a versioned contract that can be consumed without Ruby authoring. Ruby remains a high-quality authoring surface, but the IR is sufficient to reconstruct and project a Bluebook.
registry = Hecks.registry
json = Hecks::Projector::Exporter.json(registry)
File.write("payments.ir.json", json)
original = registry.bluebook("Payments")
json = Hecks::Runtime::DeclarationSnapshot.dump(original)
restored = Hecks::Runtime::DeclarationSnapshot.load(json)
expect(restored.to_h).to eq(original.to_h)
Add an explicit top-level IR metadata envelope if one does not already exist:
{
ir_version: 1,
bluebook: { ... }
}
Avoid changing the semantic tree merely to satisfy a projection language. Language projection layers should transform canonical IR into target-specific IR.
Status: Substantially done for Pizzas and Banking (zero generation skips on either, as of feat/rust-projection’s current tip), on a DIFFERENT architecture than this section originally specified — see “What actually got built” below before reading anything past it as current. No differential-harness integration, no era support, no WASM.
Priority: P0 (was; largely satisfied)
Complexity: XL (was)
Recorded formally as docs/implemented/decisions/0011-rust-compiles-types-interprets-dispatch.md, which supersedes 0007’s central claim — read it for the fuller argument and consequences; this section is the summary.
Everything below this point in §8, up through the original “Non-goals for milestone 1,” was written before work started and describes a design that was not the one implemented. The as-built system inverts one of that design’s own stated non-goals — worth reading as a real correction, not a footnote:
Location. rust/ at the repo root (a Cargo crate — renamed twice in review, first from the pre-existing rust_runner/, then settled here specifically so Ruby generator code doesn’t live under lib/hecks/) holds both the Rust crate (rust/src/) and its Ruby generator (rust/project.rb + rust/project/*.rb, module RustProjection, no Hecks:: namespace — it’s tooling for hecks, not part of the library). hecks project_rust <domain> is the thin driver.
Architecture actually built: compile types, interpret dispatch DATA. rust/src/kernel/{expr.rs,dispatch.rs} is hand-written once, and is a small, fully generic interpreter: interpret(expr, ctx) walks an Expr AST (a direct structural port of Evaluator/Resolver’s node kinds — Or/And/Not/Compare/Include/Lookup/arithmetic/sign-test/etc.), and dispatch(...) walks the real DISPATCH_ORDER steps generically, driven by whatever GivenSpec/EnsuresSpec/mutation-closure a generated function hands it. This is the opposite of the non-goal this section originally stated (“No generic kernel that parses or interprets canonical IR at runtime”) — given/ensures/invariant text genuinely is walked as data, by one hand-written interpreter, at the compiled binary’s own runtime. What IS compiled ahead of time, by hecks project_rust, is narrower than originally planned: just the type shapes — real Rust structs/enums per value object/entity/record, plus a Fielded trait impl per type so the generic interpreter can do name-based field lookup ("balance.cents") against otherwise fully static, typed data. The generator itself parses real IR objects via Ruby’s own Evaluator.parse, then emits Expr::Compare { ... }-shaped Rust data literals, not compiled boolean expressions.
Why this diverged: mid-build, generating one bespoke Rust function per command shape (a plain creator vs. an acting command with a given vs. one with a lifecycle transition vs. one with ensures…) didn’t converge — every new shape needed a new hand-written case, the same failure mode this section already correctly diagnosed for the retired Rust runtime, just one level down (per-shape codegen instead of a whole second interpreter). Interpreting dispatch data generically, once, was the fix — see docs/implemented/guides/running-a-runtime.md’s “Interpret data, don’t compile source” section for the fuller argument.
What generates correctly today, for both Pizzas and Banking, zero skips: every then_set op (set/append/increment/decrement, including cross-VO-type coercion by matching field names — Value::Coercion#fields_for’s real rule, not a guess), given/ensures (any expression the real grammar admits — no Unsupported case, because an interpreter needs none of the static machinery a compiler would), lifecycle transitions, closed sets (including multi-field ones, as a data table rather than a tag enum), composite identity (dotted, bare-declared, and bare-undeclared components), and entities — a capability this section didn’t originally scope at all: list_of(Entity) attributes (Account.ledger, a Vec<LedgerEntry>) are a valid append target, with the entity’s own identity and lifecycle field auto-filled at append time the way MutationApplier#entity_element fills them. The self-hosted grammar itself (lib/hecks/language/bluebook/) also compiles through the identical pipeline, alongside whichever business domain is the build target — another capability not in the original plan.
What’s still real and unbuilt, same as before: an entity’s OWN commands (LedgerEntry.Amend/.Reverse — addressing one list element by identity, a materially different and larger feature) — done (0013); role checking — done (0019); reference-existence checking (resolve_references) — done (0017); attribute-level admits:/pattern: constraint checks — done (0018); the CardPayment.tags nil-vs-[] list representation gap — done (0020, full corpus parity: 35/35 matching instances); era selection / historical-era support — split, partially done: reading a lineage-capable aggregate’s current, already-translated state and writing a new mutation from Rust — done (rust/host’s journal::read_lineage_head_all/_by_id/append_lineage_mutation, generic over any aggregate Projector::Exporter.lineage names, tracked per domain by hecks rust_coverage’s own lineage_aggregate findings); minting an era, detecting shape drift, or compiling a translation rule to SQL from Rust — still not done, and not planned, by architecture rather than by gap (that machinery parses bluebook/translation-rule DSL source at runtime, which ADR 0007 rules out for any Rust target permanently — rust/project.rb’s own header carries the full, current account, including why this generator’s own WASM kernel deliberately still never blends a lineage-capable aggregate into its InMemoryRepository replay path); WASM (§9, fully unstarted, and now depends on a different underlying design than §9 assumes — see §9’s own note).
Correction (2026-08-10): “building a Rust Postgres adapter FIRST” is now stale — one exists, built afterward (
rust/host/src/journal.rs,rust/host/src/main.rs), and is live in production (the deployed pizzas Lambda). It’s real, tested against a genuine Postgres instance including Ruby’s own RLS fence (journal::lineage_tests::a_stale_era_write_is_refused_by_postgres_rls_not_this_crate, using real RubyLineageManagermint code, not a stand-in), and writes each accepted command’s mutations into Ruby’s OWN per-aggregate, era-partitioned schema (hecks_journal_<domain>,<aggregate>_head_snapshot_<era>— same table names Ruby’spostgres.rbuses) so a human or Ruby tooling reading that schema sees Rust’s writes exactly the way it sees Ruby’s own. As of the boot-gate hardening in this same investigation,rust/hostalso refuses to boot against a stale era (one that exists inhecks_erasbut has been superseded by a later mint), not merely a never-minted one — closing the gap where a bare existence check would have let a stale checkout start and rely on RLS alone to catch it, and only for the subset of commands touching a lineage-capable aggregate. What’s still genuinely, entirely missing — the actual remaining scope of this line item — is everything on the reading and transition side:rust/hostwrites into exactly the ONE era it’s deployed against and never reads the era-partitioned tables back (its own command-replay state comes from a separate, flat, era-oblivious journal); there is no Rust equivalent ofLineageManager’s edge-chain resolution, shadow-parsing, shape-hashing, translation-rule execution, mint transaction, tamper-evidence, or coverage/approval checks (lib/hecks/adapters/driven/postgres/lineage_manager/*.rb,lineage/*.rb) — all of that remains Ruby-only, deliberately, per ADR 0007 (Rust doesn’t carry the bluebook parser or translation DSL). So: narrower than this section originally claimed in the “Postgres adapter” dimension (done), unchanged in the “shape-diff/migration/multi-era-read subsystem” dimension (still not started, still genuinely large — see the era/lineage investigation’s own scoping notes for the phased plan a full port would need).
Verification, as actually done: hecks check_conformance (restored 2026-08-07 alongside this document, confirmed still running against the current codebase — spec/corpus/pizzas.json and banking.json both still work) was not wired into the Rust generator’s workflow at any point. Everything above was verified instead by direct cargo run smoke tests against real scenarios (Account.Open → Credit → Debit → over-limit refusal, ScheduledPayment.Schedule → Fail → Retry ×3 → exhausted, the full Pizzas walkthrough), diffed by eye against known-correct Ruby behavior, plus spec/port_shape_coverage_spec.rb (a Ruby-side gate ensuring Banking keeps exercising every IR shape the generator branches on) and full-suite regression on the Ruby side after every change. Wiring hecks check_conformance into an automated per-change check against the Rust output remains real, valuable, un-started work — see “If this is picked up again,” below.
Everything from here to the end of §8 is the original plan, kept verbatim as a record of the intended design — read it as history, not as a spec to implement, except where the notes above say otherwise.
Rust is a projection, not an interpreter, and specifically a code generator, not a data embedder: a build-time tool reads canonical IR and emits native Rust source — types for records/value objects, a dispatch table, and per-command argument gates, role checks, given evaluation, mutation application, and event emission, all as real generated Rust code. That generated code is what gets compiled. A small hand-written kernel supplies only genuinely domain-independent infrastructure (the repository trait, evaluator primitives the generated given code calls into, the dispatch entry point) and is compiled alongside the generated code. Ruby is not required at runtime, and neither is the IR file; both are authoring/build-time-only.
The governing invariant is narrower than “nothing IR-shaped exists in the binary”: no canonical IR is parsed or interpreted at runtime — runtime behavior is compiled into native code. That leaves room for the generator to also emit compact static metadata tables (for introspection, evidence, diagnostics, docs, or explaining a refusal) without reopening the interpreter architecture, as long as those tables are consulted for description, not walked by a generic kernel to decide behavior. Behavior comes from generated code paths; metadata is only ever read, never interpreted.
The semantic requirement is that the compiled artifact can execute or query any held era, not a snapshot of current semantics only — matching Ruby’s era-replay capability (§25), not a reduced “current era only” subset. That requirement is separate from how eras get generated, and shouldn’t be read as committing to a specific implementation forever.
Initial implementation: one generated module per held era. The codegen step reads every held declaration for the Bluebook (the same lineage DeclarationSnapshot/era_check machinery Ruby’s history tooling already uses) and generates a module per era — its own types, dispatch table, and command logic — with a single dispatch entry point that takes an era selector (defaulting to the latest) and routes into the matching module. This is the simplest thing that satisfies the semantic requirement, and it’s an explicit scaling concern, not a permanent architecture: a long-lived domain with many eras where most rules are unchanged from one era to the next would generate and compile a full duplicate module per era regardless. Don’t let the wording here — or the milestone-1 acceptance criteria below — foreclose a later optimization that deduplicates unchanged behavior across eras (e.g. sharing generated functions/types where two eras’ declarations produce identical output, and only forking where they diverge); that’s a code-generator-internals improvement, not a semantic change, and shouldn’t require touching this section’s acceptance criteria to land.
The real constraint regardless of implementation: minting a new era still requires rerunning codegen and recompiling — the compiled artifact does not ingest a new era at runtime, by the same no-runtime-IR design as everything else in this section.
Minimum path:
Ruby DSL (authoring only)
↓
canonical Bluebook IR for every held era (JSON, build-time artifact — consumed once, then discarded)
↓
Rust projection (code generator; initial implementation: one module per held era)
↓
generated Rust source — native types & per-command logic derived from the IR, per era
↓
compiled together with a small hand-written shared kernel + an era-dispatching entry point
↓
native binary / library / WASM — every held era's semantics compiled in as code, not carried as embedded IR data
Hecks.bluebook "Payments" do
aggregate "Payment" do
command "Open" do
role "Merchant"
goal "Start tracking a payment"
emits "PaymentOpened"
end
end
end
No Rust syntax appears in the Bluebook.
Real, scoped next increments, roughly in order of how self-contained each is: (1) hecks check_conformance’s native/<path.wasm> subprocess mode (0012) is now wired into CI as an automated regression gate (spec/rust_conformance_spec.rb, run by the same bundle exec rspec CI already runs) — done; (2) entity commands (Amend/Reverse-shaped — addressing one list element by its own identity) — done (0013), along with policies and process managers/sagas, neither of which this section originally scoped at all; (3) the JSON-boundary refuse_unknown_arguments/refuse_absent_arguments equivalent 0013 flagged — done (0014) — and the optional: true/Option<T> modeling gap 0014 found and deferred — done (0016, skips loudly the one shape it can’t honestly represent: an optional argument feeding a non-optional VO/entity field); reference-existence checking — done (0017); investigating it surfaced three distinct, previously-masked gaps — a closed-set-subset admits: membership check and a scalar attribute’s pattern: regex constraint, both done (0018, the pattern: half needing a hand-rolled zero-Cargo-dependency regex matcher bounded to exactly the dialect PatternSubset admits), and an Integer-typed JSON value arriving as a non-numeric string, which turned out to be a real bug in 0014’s own default: fallback rather than a missing check — also done, fixed directly. Role checking — done (0019, needed a wire-contract extension first — a step’s optional role: key — since Ruby’s own caller is ambient thread-local state never reachable through the existing corpus-script format at all). 0014’s original two-item pair is now fully closed; the CardPayment.tags nil-vs-[] representation gap 0014 also flagged — done (0020: a list-typed record field is Option-wrapped exactly when a creating command’s own :set mutation can leave it genuinely unset, traced to Instance.defaults’/apply_mutations’ real mechanics rather than guessed at). Full corpus parity reached: 35/35 matching instances — every corpus-level mismatch this arc has been closing since 0013 is now resolved. The two remaining COSMETIC gaps 0012/0013 named (event payload default-fill, GivenNotMet wording) — also done (0021; spec/rust_conformance_spec.rb now compares events and full refusal wording, not just instances/verbs). Running the FULL corpus through the differential harness during that pass found a much longer tail of OTHER refusal-wording templates that still don’t match Ruby byte-for-byte (LifecycleRefused, general VO-invariant messages, one_of membership, entity-element-missing) — none affect instances, all deliberately left open as a separately-scoped future slice, not silently skipped; (4) era support — investigated and confirmed genuinely out of scope for this arc: not a parity bug in current output but a missing subsystem requiring a Rust Postgres adapter before any lineage/shape-diff work could even start; update (2026-08-10): that adapter now exists (rust/host, see the correction on this same era-support sentence just above) — single-era read/write against Ruby’s own schema, boot-gated on era currency, RLS-verified — but the shape-diff/translation/multi-era-read subsystem it was gating stays exactly as out-of-scope as described, since rust/host never grew the bluebook parser or translation DSL that subsystem needs; (5) §9 WASM — a first, non-browser slice shipped (0012, stdin/stdout over WASI, not the JS API), plus a browser wasm-bindgen slice (0015); the original browser-facing design in §9 remains only partially scoped against the as-built (no-era) architecture.
Status: A first WASM slice is built, on a DIFFERENT shape than this section originally specified — see decision 0012 and “What actually shipped” below. Everything else in this section (the browser JS API, per-era dispatch, the host-adapter import boundary) remains unstarted and was written against the ORIGINAL §8 design; re-scope against the as-built architecture before picking it up, particularly the per-era claims in the acceptance criteria.
Priority: P1
Complexity: L
Not the browser JS API below — a stdin/stdout JSON CLI contract ({"steps": [...]} in, {"instances", "events", "refusals"} out — hecks check_conformance’s own pre-existing shape), compiled unchanged for both native (aarch64-apple-darwin) and wasm32-wasip1 (WASI preview 1). hecks build_wasm <domain> regenerates and cross-compiles; hecks check_conformance <domain> <script.json> native|<path.wasm> runs either artifact as a real subprocess and diffs it against the Ruby oracle, finally wiring the subprocess hook that tool’s own header comment used to say didn’t exist. Verified on Banking (10 aggregates, composite identities, closed-set data tables): instances matches byte-for-byte across Ruby, native Rust, and WASM-under-wasmtime. Two pre-existing, unrelated fidelity gaps surfaced by actually running the harness live rather than fixed by this slice — events[].payload’s debug-string-vs-structured-JSON shape, and a refusal-wording prefix Ruby adds and Rust doesn’t — see 0012’s own Consequences for both.
No era selection (the generator still has no era concept — §8’s own still-open gap, untouched here), no browser JS API, no host-adapter import boundary. The rest of this section — Target through Non-goals — is the ORIGINAL, still-unbuilt browser-facing design; 0012’s “Rejected alternatives” explains why it wasn’t what got built first, and that a browser layer remains a plausible follow-on built ON TOP of the WASI-stdio contract, not a replacement for it.
Compile the artifact §8 generates to WASM so browser applications can call the domain directly as their internal command bus. This is a compile target of §8’s single runtime, not a separate implementation.
The Bluebook’s command logic — every held era of it — is already compiled into parcelpro.wasm by §8’s code generator, so loading the module takes no separate IR argument — there is nothing left to load into it:
const domain = await Hecks.load("parcelpro.wasm")
await domain.dispatch("LandPlanning::Road.MoveVertex", {
road_id: "r-1",
vertex: 3,
x: 12.2,
y: 4.8
})
// domain.dispatch(name, args, { era: 12 }) — optional, defaults to latest
const report = await domain.report("LandPlanning::ProjectView", {
project_id: "p-1"
})
command "MoveVertex" do
role "Designer"
goal "Move a road vertex"
reference_to Road
attribute :vertex, Integer
attribute :x, Float
attribute :y, Float
emits "RoadVertexMoved"
end
Browser-only capabilities remain outside WASM:
Define narrow imported host operations rather than embedding JS application code in the runtime.
Status: Persistence projections already exist implicitly in adapters; full runtime is research
Priority: P2
Complexity: L–XL
Separate schema/migration generation conceptually from persistence execution.
Bluebook IR → Postgres DDL / SQLite DDL
Research compiling transactional command semantics into stored procedures, constraints, triggers, and views.
Ruby Bluebook:
command "CloseAccount" do
role "Branch clerk"
reference_to Account
given("the balance is zero") { balance.cents == 0 }
emits "AccountClosed"
end
Possible generated PostgreSQL surface:
select * from banking_close_account(
actor_id => 'a-1',
account_number => '1234'
);
Use outbox/ports for those.
given refusal wording remains equivalent.Status: Integration layer done (2026-08-08) — the real provider-
facing half (redirect, code exchange, JWT/JWKS signature verification
against a live provider) is deliberately NOT built: security-critical,
needs real external dependencies and provider credentials neither
available nor meaningfully testable here, scoped out on purpose rather
than stubbed. What IS built and proven against real Banking: a new
identity_resolution port (lib/hecks/ports/identity_resolution.{port,rb}),
resolved zero/one/many exactly like the authorization port
Ports::Authorization built for §2, fulfilled by Hecks::Adapters::IdentityRegistry
(lib/hecks/adapters/driven/identity_registry.rb) — a same-registry
dispatch against Identity::ExternalIdentifier.ResolvedBy, mirroring
GovernanceAuthorization’s own shape exactly. spec/oidc_projection_spec.rb
composes it with Ports::Authorization.holds_role? and Hecks.as_caller
— the plan’s own sketch, just spelled out — booted through the real
Hecks.boot("examples/banking") path (both Governance and Identity are
already attached there via attaches, see §4/§5’s own notes).
Every acceptance criterion below is proven, not merely asserted: no
password field exists on Identity/ExternalIdentifier (checked
against the real declared attributes), (issuer, subject) uniquely
resolves (checked against Link’s own natural key), two different
external identifiers link to one identity and both authenticate
(dispatched for real, against real Banking commands), and the
unauthorized/unresolved paths refuse BEFORE any dispatch — command
authorization stays exactly Governance’s job, identity_resolution
never touches CommandInterpreter/Dispatcher/CommandRules::Authorization.
Priority: P1
Complexity: M
Allow applications to authenticate through Google/Microsoft/etc. as OIDC clients while mapping (issuer, subject) into identities.bluebook.
OIDC provider
→ verified issuer/subject
→ identities.bluebook lookup/link
→ stable Identity
→ Governance roles
→ command dispatch
identity = identities.lookup_external(
issuer: claims["iss"],
subject: claims["sub"]
)
Hecks.as_caller(role: governance.role_for(identity)) do
runtime.dispatch_flat("Billing::Invoice.Refund", **args)
end
This is illustrative; final runtime should carry actor separately from role.
(issuer, subject) uniquely identifies external login.Status: New executable protocol projection
Priority: P2
Complexity: XL
Project Identity + Governance into a deployable OIDC provider that exposes standard endpoints while Bluebook remains the organizational source.
Expected generated/hosted surfaces:
/.well-known/openid-configuration
/authorize
/token
/jwks.json
/userinfo
Hecks.bluebook "IdentityProvider" do
# business configuration represented with normal Bluebook concepts
end
Protocol machinery should be projection/runtime code, not DSL keywords.
Issuer URL, signing keys, upstream providers, registered clients, redirect URIs, token lifetimes, and storage remain deployment configuration/adapters.
Status: New auth adapter
Priority: P1
Complexity: M
Provide passwordless email sign-in as an alternative authenticator feeding identities.bluebook.
command "RequestLoginCode" do
role "Anonymous"
goal "Prove control of an email address"
attribute :email, EmailAddress
emits "LoginCodeRequested"
end
The actual random code generation and email send are adapters. The durable domain should store only appropriate digests/state required to verify the challenge.
Status: Canonical corpus design
Priority: P1
Complexity: M
Keep technical identity separate from business identity.
Potential canonical Bluebooks:
identities.bluebook — who can be authenticated
people.bluebook — people/teams/employment
customers.bluebook — commercial relationships
users.bluebook — app-specific preferences, only if needed
governance.bluebook — authority
Hecks.bluebook "Customers" do
aggregate "Customer" do
identified_by { customer_id.value }
reference_to "Identities::Identity", as: :identity_id, optional: true
attribute :customer_id, CustomerId
attribute :identity_id, IdentityId, optional: true
end
end
Use the actual cross-domain reference mechanism supported by Hecks; the syntax above is illustrative if FQN references require adjustment.
Status: New projection class
Priority: P0
Complexity: L
Adapt a canonical Bluebook into an organization’s ubiquitous language while preserving executable semantics.
Example canonical source:
command "Refund" do
role "Refund agent"
end
UL mapping:
Hecks.ul_projection "Acme Payments" do
role "Refund agent", as: "Customer success representative"
command "Refund", as: "Reverse charge"
end
Projected Bluebook:
command "Reverse charge" do
role "Customer success representative"
end
The exact mapping DSL can live in a supporting Bluebook or projection configuration rather than core Bluebook grammar.
The projection changes vocabulary, not behavior.
given, ensures, lifecycle transitions, mutation semantics, types, and event relationships remain equivalent unless onboarding explicitly records a semantic customization.
Start by operating directly on canonical IR hashes:
projected = ULProjector.call(ir, mapping)
Do not parse/rewrite Ruby source.
Then render projected IR back to Bluebook source if human-editable output is desired.
Bluebook::Assembly.Status: New durable supporting artifact
Priority: P0
Complexity: M
Keep the UL projection used during onboarding so future canonical releases can be adapted again without repeating all mappings.
Example recipe:
Hecks.ul_projection "Acme Payments" do
from "Registry::Payments", version: "1.2"
role "Refund agent", as: "Customer success representative"
role "Payment processor", as: "Finance administrator"
command "Cancel refund" do
role "Finance manager"
end
end
This recipe is not required at runtime.
Status: New supporting Bluebook
Priority: P0
Complexity: L
Model ontology adoption as a business process using Hecks itself.
Suggested aggregates:
Suggested commands:
Suggested reports:
Hecks.bluebook "Onboarding" do
supporting
aggregate "Adoption" do
identified_by { adoption_id.value }
command "MapRole" do
role "Domain owner"
goal "Express a canonical responsibility in our language"
reference_to Adoption
attribute :canonical_role, String
attribute :organization_role, String
emits "RoleMapped"
end
end
report "UnmappedRoles" do
# driven from stored adoption state
end
end
Onboarding must inspect external Bluebook IR, so define a projection/catalog port rather than letting the Bluebook read files directly.
Status: New onboarding feature
Priority: P0
Complexity: M
When the organization maps a canonical role, show every command that will inherit the mapping before writing the projected Bluebook.
Example:
Refund agent → Customer success representative
Affected:
- Refund payment
- Review refund
- Cancel refund
- View refund history
SME may then override one:
Cancel refund → Finance manager
Store mapping at two levels:
role_mapping "Refund agent" => "Customer success representative"
command_override "Payments::Refund.Cancel",
role: "Finance manager"
Status: New onboarding projection/query
Priority: P1
Complexity: M
Onboarding should inspect organization Bluebooks and offer existing roles as mapping choices.
Potential sources:
[
"Customer success representative",
"Finance administrator",
"Controller",
"Operations manager"
]
Status: New AI-assisted tool; optional
Priority: P2
Complexity: L
Rank likely organization-role matches based on command/responsibility shape rather than string similarity alone.
Example:
Canonical Refund Agent:
Refund payment
Review refund
View payment
Matches:
Customer Success Representative 0.92
Finance Administrator 0.71
Create semantic feature vectors from canonical IR:
AI proposes; SME approves.
Never silently apply authority mappings based on AI confidence.
Status: Existing era/translation/diff machinery is a foundation; broaden scope
Priority: P1
Complexity: L
Hecks already has:
Define drift as disagreement between semantic representations, not textual differences.
Drift pairs:
Canonical Bluebook ↔ Organization Bluebook
ISO mapping ↔ Bluebook
Bluebook ↔ projected artifacts
Era N ↔ Era N+1
diff = Hecks::SemanticDiff.call(old_ir, new_ir)
diff.commands.changed
diff.roles.renamed
diff.rules.changed
diff.references.changed
Status: New workflow built on UL Projection + drift
Priority: P1
Complexity: XL
Upgrade an organization from canonical v1 to canonical v2 without overwriting organization-specific evolution.
Conceptual three-way merge:
canonical v1
↓ UL recipe
projected org baseline v1
↓ org edits
organization current
canonical v2
↓ same UL recipe
projected org candidate v2
semantic merge(candidate v2, organization current, baseline v1)
Status: New adjacent workflow
Priority: P2
Complexity: XL
Use ISO standards as discovery inputs for executable quality/compliance Bluebooks. Initially humans interpret requirements into Bluebook; later an ISO-facing projection provides clause coverage and documentation.
Do not make ISO a new Bluebook dialect.
Hecks.bluebook "QualityManagement" do
aggregate "Nonconformity" do
command "Report" do
role "Quality representative"
goal "Record a failure to meet a requirement"
emits "NonconformityReported"
end
end
aggregate "CorrectiveAction" do
command "VerifyEffectiveness" do
role "Quality manager"
goal "Confirm the corrective action solved the problem"
emits "CorrectiveActionVerified"
end
end
end
Prefer external mapping initially:
map "ISO9001:2015/10.2",
to: [
"QualityManagement::Nonconformity.Report",
"QualityManagement::CorrectiveAction.VerifyEffectiveness"
]
Status: New
Priority: P2
Complexity: L
If a Bluebook changes in a way that invalidates its ISO mapping, create a reconciliation task instead of silently generating stale documentation.
Example:
Changed:
QualityManagement::ReleaseProduct
Potentially affected:
ISO 9001 §8.6
Reason:
approval evidence removed
Resolution required:
- restore behavior
- update mapping
- record justified deviation
Status: Much of the storage foundation already exists
Priority: P1
Complexity: M
The codebase already stores held era declarations, structured declaration snapshots, resolved era ordinals, shape checks, translations, and replay tooling.
Expose a simple first-class API to boot/query a historical Era without Git or source checkout.
Ruby target:
history = Hecks.at(era: 12, domain: "Banking")
history.query(
"Banking::Customer.Suspended"
)
or:
Hecks.query(
"Banking::Customer.Suspended",
era: 12
)
Use held structured declarations (DeclarationSnapshot) and the lineage adapter’s era data directly. Do not parse historical Ruby source unless a repair/reattest tool explicitly requires it.
Status: New surface over existing era storage + query/read_model
Priority: P1
Complexity: M
Let analysts run reports against the semantics of the era in which data was produced, or intentionally restate through current semantics.
Two explicit modes:
report.run(era: 12, interpretation: :historical)
report.run(era: 12, interpretation: :current)
Historical means “what did this mean then?” Current means “translate old facts forward and answer using today’s definition.”
Status: Existing query/read-model infrastructure is foundation
Priority: P2
Complexity: L
Treat analytical artifacts as projections of Bluebook reports instead of standalone warehouse semantics.
Potential targets:
Ruby source:
report "MonthlyRevenue" do
include Invoice
where(status: "paid")
order_by :paid_at
end
Projection command:
hecks refresh_projections MonthlyRevenue --target postgres-view
Status: New product layer; existing spec/corpus proves corpus tooling patterns
Priority: P1
Complexity: Ongoing
Build a curated repository of canonical Bluebooks that capture reusable organizational knowledge.
Initial candidates:
A canonical Bluebook should include:
Do not canonicalize arbitrary application implementation details.
Status: Research/product tooling
Priority: P3
Complexity: XL
Use public source/documentation as evidence for discovering recurring business patterns, then author canonical Bluebooks from those findings.
Pipeline:
sources
→ pattern extraction
→ semantic clustering
→ candidate ontology
→ human/AI review
→ canonical Bluebook
→ verification
Do not mechanically translate source code line-by-line.
Status: Done (2026-08-08) — Hecks::Projector.register(name, projector) /
.call(name, bluebook:, options:) now exist for real (lib/hecks/projector.rb),
proven against a genuine registered target, :ir
(lib/hecks/projector/ir_projector.rb), whose output matches
spec/golden/ir/Pizzas.json byte-for-byte once sorted and carries
ir_version: as target-version metadata for free (§7). Exporter
itself is UNTOUCHED and still exactly what hecks ir, hecks project_rust,
and translation/audit’s approval digest read — it operates on a whole
registry (many bluebooks), a different unit than this framework’s
per-bluebook call, so it was deliberately not folded in rather than
forced to fit. Retrofitting the real existing projectors — :rust
(hecks project_rust/rust/project.rb, a whole separate toolchain under
a confusingly-identical RustProjection::Projector name) and the
not-yet-built :ul/:openid — is real, separate work this pass didn’t
attempt; spec/projector_spec.rb covers register/call/re-register/
unknown-target/golden-determinism.
Priority: P0
Complexity: M
Turn Hecks::Projector into a stable projection framework rather than one exporter module.
Suggested interface:
Hecks::Projector.register(:rust, RustProjector)
Hecks::Projector.register(:ul, ULProjector)
Hecks::Projector.register(:openid, OIDCProjector)
Hecks::Projector.call(
:rust,
bluebook: ir,
options: {}
)
RustProjector here is §8’s code generator: this .call is the build-time step that emits the native Rust source described there (one module per held era), not a runtime call — its output is source files for the Rust toolchain to compile, the same way :ul’s output is a projected Bluebook and :openid’s is provider configuration.
Canonical IR
→ target normalization
→ target IR
→ renderer/artifact emitter
This prevents target formatting logic from polluting canonical semantics.
Status: Done (2026-08-07) — lib/hecks/runtime/capability_graph.rb,
Hecks::Runtime::CapabilityGraph, exposing exactly the sketched
registry.capability_graph.{fulfillments,unfulfilled,cycles}. Built off
what the registry already holds: registry.ports/registry.adapters, and
the same adapter.port == port.name match Ports::Extraction and
Ports::IdentityGeneration already make for themselves, one port at a
time — this asks it once, for every port at once. cycles answers []
always, and honestly: nothing in this port model lets one port depend on
another, so there is no edge for a cycle to be made of. Registry
#capability_graph is a memoized wrapper, matching #repository’s own
pattern one line above it. spec/runtime/capability_graph_spec.rb proves
fulfilled/unfulfilled/multi-adapter cases against a real registry (Memory
on persistence, Prism on extraction, identity_generation declared and
left unbound on purpose, to exercise the gap). The onboarding-UI framing
above (“Found: ✓ Acme Governance ○ …”) is not built — this section only
covers the data the UI would read.
Priority: P1
Complexity: M
Expose the dependency tree that already emerges from port fulfillment.
Example:
Payments
├── Identity → Identities.bluebook
├── Governance → Governance.bluebook
└── PaymentGateway → Stripe.adapter
graph = registry.capability_graph
graph.unfulfilled
graph.fulfillments
graph.cycles
When canonical Payments requires a port:
Governance capability required.
Found:
✓ Acme Governance
○ Core Governance
○ External adapter
Status: Audited (2026-08-07) against the current public keyword list (spec/dsl_coverage_spec.rb’s own maintained inventory). Conclusion: read_model → report (§6) was the one concrete, ready-to-execute rename, and it’s done. Everything else this section names is a “retain” or an explicitly open “consider,” not a decided rename — aggregate in particular is real DDD jargon an SME wouldn’t naturally reach for, but renaming it would be structural (it’s the core keyword, not a leaf verb like report was) and the plan itself only ever says “consider,” not “do” — left alone on purpose, per “do not rename merely for novelty,” not overlooked. sets/then_set (Command level) already went through this exact same treatment earlier, via the LOP roadmap work, before this section was written.
Priority: P2
Complexity: M
Review every public DSL keyword against the rule:
Would an SME plausibly understand this word without learning a software architecture pattern?
Likely candidates:
read_model → report (keep alias)aggregate remains author-facing or becomes mostly structural documentationcommand, role, goal, policy, event, query, lifecycle, given if SMEs find them naturalDo not rename merely for novelty.
Status: Done (2026-08-07), through the full self-hosted grammar — not
skipped the way §7’s ir_version was (that one is a computed constant;
provenance is author-facing DSL, so it belongs in the load-bearing
language description). provenance from: { source:, source_id:,
source_version: } on both aggregate and command, captured as a raw
Hash untouched — the exact shape sketched below, one level up from
attribute ..., default: { ... }’s own literal-Hash precedent, which it
reuses end to end: AggregateBuilder#provenance/CommandBuilder
#provenance, IR::Aggregate#provenance/IR::Command#provenance (in
to_h), new rows in syntax.bluebook, and — the part that makes it real
rather than decorative — aggregate.bluebook/behavior.bluebook (the
language’s OWN grammar) gained a matching attribute :provenance,
LiteralText/CommandText on both the root record and the Declare
command, Assembly::CONTRACTS/Shapes#provenance/Reconstruction#
aggregate round-trip it, and all 9 golden IR fixtures were regenerated.
One real usage: Banking’s Account aggregate now carries provenance
from: { source: "HecksCanonical", source_id: "aggregate:account",
source_version: "1.0" }, proving the path against the real corpus, not
just a synthetic fixture — spec/round_trip_spec.rb catches any future
drift here directly. Confirmed and left alone on purpose:
readings.rb needed no change (the judge’s generic field_value
fallback already offers any field with no shape-differing exception);
description was confirmed NOT a usable precedent for Command
specifically (CommandBuilder has no description at all — the
asymmetry is real). Provenance never touches dispatch or identity, exactly
as the acceptance criteria below require.
Priority: P1
Complexity: M
IR::Command comments explicitly state that identity is (KIND, FQN), because names may collide across construct kinds.
UL projection and ontology upgrades complicate name-based identity because vocabulary intentionally changes.
Introduce provenance identifiers for adopted canonical concepts without replacing Hecks runtime identity.
For example:
{
kind: "command",
fqn: "AcmePayments::Payment.ReverseCharge",
provenance: {
source: "Registry::Payments",
source_id: "command:payment.refund",
source_version: "1.2"
}
}
The organization FQN remains runtime identity. Provenance supports upgrades and semantic correspondence.
Status: Started — docs/decisions/0001, 0002, 0007, 0008, 0009, 0010 exist (restored 2026-08-07 alongside this document); 0011-rust-compiles-types-interprets-dispatch.md (new, same day) records the real architecture §8 was actually built on and formally supersedes 0007’s central claim (0007’s own Status line now points at it). 0003–0006 below were never written. 0009/0010 hold up as written — read 0007 as historical record of the original reasoning, same as §8’s own superseded sections, not current architecture.
Priority: P0
Complexity: XS
Record irreversible or expensive design decisions in docs/decisions/.
Initial ADRs:
0001-canonical-ir-is-runtime-independent.md
0002-one-command-one-role.md
0003-act-as-cross-domain-authority.md
0004-bluebooks-never-own-entropy.md
0005-ul-projection-preserves-ubiquitous-language.md
0006-ports-not-packages-drive-composition.md
0007-rust-generates-code-not-ruby-source.md
0008-reports-are-business-facing-read-models.md
# Decision
## Context
...
## Decision
...
## Consequences
...
## Rejected alternatives
...
An agent implementing a feature can find the relevant decision without re-reading chat history.
Reordered 2026-08-07 from a dependency/risk analysis of all 34 sections. The Rust/WASM/host-language sections were briefly pruned as obsolete after the Rust retirement, then restored at the user’s request, then narrowed to a single runtime target: Rust (
§8) plus its WASM compile output (§9) are the only runtime-projection sections in this roadmap — Ruby-hosts-Rust, Python, Go, and Java host bindings were removed rather than kept as separate speculative sections; OIDC and the other non-runtime projections were untouched by that decision and remain as before.supportingBluebooks stays removed as already implemented. Each phase either has no unmet dependency on later work, or exists specifically to unblock the phase after it. Section numbers below (§N) refer to the numbered headings above.Rust/WASM moved to Phase 2 (2026-08-07, second revision) per
docs/decisions/0010-ruby-is-the-reference-implementation.md: it no longer waits for authority/ontology/canonical-domain work to finish. Ruby serves as both the implementer’s guide and, through a continuously-run differential harness (§8’s Milestone 0), the correctness oracle — so IR/behavior changes from later phases become fast-following, harness-verified extensions to the generator and kernel, not a reason to have waited. See §8 and 0010/0007 for the full reasoning; 0007’s original “wait for Phases 2–4” entry criterion is superseded, kept in that ADR only as the reasoning this project moved past.Phases 1, 3, 4, 5 still read as one arc for the authority/ontology track specifically: harden the language (1), establish authority/identity semantics (3), establish ontology/provenance semantics (4), prove those semantics against real domains and a real corpus (5). Phase 2 (Rust/WASM) runs concurrently alongside that whole arc rather than waiting at its end, chasing each phase’s IR/behavior additions as they land. Phase 6 onward project the same settled semantics outward into external ecosystems (auth protocols, history/analytics, persistence, standards, research).
§1 duplicate-role guard · §6 report alias · §32 business-language DSL audit · §7 IR version envelope · §34 ADRs (start now, apply continuously)
All five are small, have zero dependencies on anything else in this plan, and directly reduce risk for every later phase: §1 closes an authoring footgun before more commands get written against Governance/Identities; §6+§32 fix vocabulary before it’s baked into onboarding-facing tooling; §7 gives every later projector (§8, §10, §11, §15, §27) a stable contract to target instead of an implicit one; §34 should be adopted as a habit starting here, not treated as a single deliverable — every phase below changes an architectural invariant somewhere.
Status note (2026-08-07 restore): none of §1, §6, §7, §32 were implemented — verified directly against the current codebase, not assumed. §8 (Phase 2) proceeded anyway, out of the order this section recommends, and reached substantial completion without any of Phase 1 landing first; nothing about §8’s actual build depended on core hardening happening first. Worth noting as evidence either way: Phase 1 may matter less as a hard prerequisite than this ordering implied, or §8’s architecture change (interpret dispatch data, rather than the originally-planned pure codegen) is precisely what let it route around needing §7’s IR-version contract, §1’s role-cardinality guard, etc. Re-evaluate whether Phase 1 still belongs first before resuming this roadmap, rather than restarting it by default because it’s numbered first.
§8 Rust projection, second attempt · §9 WASM embedded Bluebook
Status note (2026-08-07 restore): §8 is done, substantially, per its own “What actually got built” section — but not via this paragraph’s plan. Read the note there before treating anything below as current.
Starts right after core hardening, not after authority/ontology/canonical-domain work — see the scope note above and docs/decisions/0010-ruby-is-the-reference-implementation.md. §8’s Milestone 0 (the differential harness against Ruby) is the actual gate from here on, not a phase boundary: build the harness first, then generate against Pizzas and Banking as they exist today. As Phase 3 (Governance, act_as), Phase 4 (UL projection/provenance), and Phase 5 (canonical domain expansion) land, each extends canonical IR and sometimes dispatch-pipeline behavior — §8’s “chase Ruby” section is the standing obligation that follows: extend the generator/kernel, re-run the harness, done when it’s green, not accumulated as debt. §9 is simply §8’s output compiled to a WASM target and depends directly on §8’s generator existing — there is exactly one runtime here, not a family of host bindings. Hold to §8’s own non-goals throughout: no second hand-written parser, no ported DSL builder, no full parity corpus, and no generic IR-interpreting kernel — Rust code is generated from canonical IR at build time, and no canonical IR is parsed or interpreted at runtime.
§3 Governance Bluebook · §4 Identities Bluebook · §5 UUID adapter · §2 act_as (wired to real Governance.authorize_transition!) · §31 port fulfillment graph · §33 semantic identity/provenance
Status note (2026-08-07): Every task in this phase is done — §3, §5, §4, §2, §31, and §33 — see their own Status lines. §2 landed with no dispatch-pipeline change at all (CommandInterpreter/Dispatcher/CommandRules::Authorization untouched) — the “Runtime execution” sketch below turned out to describe an application-level pattern composing Governance.RoleTransition + Runtime::Caller.as, not new pipeline plumbing, so the “first workload for Phase 2’s chase-Ruby obligation” framing two paragraphs down did not end up applying to §2 — nothing here changed Rust-relevant dispatch behavior.
This is the load-bearing phase for the authority/ontology arc: almost everything in Phase 4/5 assumes Governance and Identities exist. It’s also the first real workload for Phase 2’s “chase Ruby” obligation — act_as in particular adds dispatch-pipeline behavior, not just IR shape, so it’s the first place the Rust kernel (not just the generator) needs a follow-up change. Order within the phase matters — §5 (UUID adapter) should land before or alongside §4, since Identity.Register needs a UUID source for identity IDs with no natural key; §2 can only be fully accepted (not just stubbed) once §3 exists to make the authorization decision. §31 and §33 are cheap additions best made here, while the object model is still small: §31 becomes meaningful the moment Governance/Identities exist as real fulfillments to graph, and §33 (provenance on IR nodes) is far cheaper to add now than to retrofit once Onboarding (§17) and ontology upgrades (§22) depend on it.
At the end of this phase, command-level authorization is organizational rather than merely ambient-role matching.
§30 Projector framework · §15 UL Projection · §16 UL adoption recipe · §17 Onboarding Bluebook · §18 role mapping review · §19 existing-role discovery · §21 semantic drift detection · §22 canonical ontology upgrades · §20 semantic mapping suggestions (AI, optional tail)
Build §30 first — it’s the general registration framework, and §15 is its first real target, so building the framework in a vacuum before this phase would be guessing at an interface. §15 → §16 → §17 → §18 → §19 follow the natural onboarding flow (project a canonical Bluebook into org vocabulary → keep the recipe → give onboarding a home → let SMEs review the mapping → let them discover existing roles to map onto). §21 (drift detection) should land before §22 (ontology upgrades), since upgrades are explicitly a three-way merge built on top of drift detection. §20 is genuinely optional and lowest-value here — AI-ranked suggestions only matter once there’s a real mapping backlog to rank, so it can trail or slip to a later pass without blocking anything.
Status note (2026-08-08): §30 is done — see its own Status line. Nothing else in this phase has started; §15 (UL Projection) is next, now that the framework it registers against is real.
At the end of this phase, a canonical Bluebook can be shipped and a customer can adopt it into its own language. UL projection/onboarding should not be allowed to destabilize runtime IR any more than necessary: vocabulary and provenance changes should land as a clean, bounded addition to the IR contract, not as ongoing churn — Phase 2’s Rust generator extends to cover whatever this phase adds, verified by its harness, rather than treating onboarding as something Rust needs to wait on or stay in lockstep with.
§14 People/Customers/Users decomposition · §28 canonical corpus (ongoing)
§14 depends on Identities/Governance (Phase 3) and is the first real test of the UL/onboarding machinery from Phase 4. §28 isn’t a single task — it’s an ongoing practice of writing more canonical domains at the Phase 4 quality bar — but it can’t produce anything credible until Governance, Identities, and People/Customers exist as its first real examples.
This phase still doubles as a stress test for the IR’s expressiveness — writing real domains against Phase 3/4’s semantics is how gaps in them surface — but per 0010, Rust (Phase 2) no longer waits for that proof before starting. Instead, whatever this phase finds and fixes about the IR flows into Phase 2 through the same “chase Ruby” mechanism as everything else, verified by the harness as it lands rather than gating Rust’s start.
§11 OIDC client projection · §13 email OTP adapter · §12 OIDC provider projection
Both §11 and §13 only need Identities (Phase 3) and can run in parallel — neither depends on Phase 2’s Rust runtime. §12 is ordered last and is by far the biggest lift (XL, full protocol surface) — it should wait until the client-side flow (§11) has validated the (issuer, subject) → Identity mapping in practice.
§25 era-addressable query API · §26 historical reports · §27 data-engineering projections from reports
§25 is mostly API surface over storage that already exists (DeclarationSnapshot, era_check, era_tamper) and unblocks §26’s historical/current interpretation modes. §27 depends on both §6 (the report keyword) and §30 (the projector framework), both already in place by this point.
§10 SQL projection vs SQL runtime
Deliberately isolated as its own phase: Target A (DDL-as-artifact) is a moderate, low-risk win, but Target B (transactional command semantics as stored procedures) is research-grade and shouldn’t gate anything else. Slot Target A wherever bandwidth allows once §30 exists; treat Target B as optional.
§23 ISO discovery and projection · §24 standards conformance drift
Lower priority (P2) and largely independent of the rest of the roadmap, but §24 explicitly depends on §23 existing first (a mapping has to exist before its drift can be detected), and both benefit from §21’s drift-detection groundwork and §33’s provenance edges.
§29 GitHub/documentation pattern discovery
Lowest priority (P3) and least defined. Needs the canonical corpus (§28) to already be a going concern before a discovery pipeline for growing it is worth building.
Status note (2026-08-20): this is not one of §1–§34, and it does not belong to a phase above. It is an out-of-band track: an agent-run implementation of docs/value-object-identity-and-relationships-plan.md and its ADR (0029), which supersede the Identity and References halves of ADR 0025 rather than any roadmap section. It is recorded here because it is the largest uncommitted change this checkout has ever carried — ~291 modified tracked files and ~48 new untracked ones, nothing committed — and because a reader arriving at “Recommended implementation order” would otherwise pick up a phase whose ground has already shifted under it. Everything below was measured against the working tree, not read off the implementing agent’s own summary: the suite, hecks model_check, hecks measure_doc_coverage, rubocop, and the Rust crates were all run on 2026-08-20.
Four language/runtime changes landed together, because each one is the other three’s prerequisite:
identified_by AccountNumber, as: :number, an inline identified_by do attribute … end block that synthesizes a deterministic value object, and identified_by :branch_code, :box_number as an explicit two-or-more compound key. The wire shape is unchanged — still identified_by: [path, …], still joined by Naming.identity — so no storage rewrite was needed.has_many, has_one, and belongs_to are live again, with has_many a real reference list rather than the scalar it used to be under a plural name. Relationship kind survives into IR so a projector can tell belongs_to from a neutral reference_to.to:; with: carries only new domain facts. Runtime::Routing, Runtime::ReactionInvocation, and Facade::CommandRequest are the new seams; policies, process managers, handles, CLI, Forms, and the JSON door all consume the same envelope, and reference_to inside a command/query/policy/port operation is now a hard Malformed refusal.Runtime::DependencyPlanning::Analyzer derives a command’s read set, write set, and completeness from its own semantics, and CommandInterpreter#step_hydrate picks an execution strategy from it (ATOMIC_PUT vs. load/apply/validate/store) instead of branching on Command#creates?. Ports::Persistence::Execution/Outcome are the adapter contract; Memory is the oracle, SQLite the pilot, D1/Postgres/PostgresEra implement it.Two structural reorganizations rode along and account for most of the file count: finished material moved from docs/{decisions,guides,reference,resolution-rules}/ into docs/implemented/… (moves plus link fixes only — no decision text was rewritten), and the monolithic sources split into concept files — examples/banking/bluebook/banking.bluebook into eight business-concept bluebooks, language/bluebook/{behavior,reaction}.bluebook into command/policy/process_manager/query, and syntax.bluebook’s 1083 lines of central tables distributed into each aggregate’s own file. Runtime::EraCheck.source_text_for was widened to digest a chapter spread across several files, which is what made that split safe.
Status note (2026-08-28): the “109” figure below, and “What’s next” item 1
right after it, both predate a fix that has since landed — legacy_
implicit_creation? already dropped plan.write_set.empty? by the time this
note was written (ctx.route.nil? && ctx.command.creates?, unchanged since).
Separately, and later: a full corpus audit (DependencyPlanning::Analyzer
against every creates?-true command in all 5 example domains) found and
fixed 4 commands whose plan was genuinely incomplete (Statement.Generate,
CreatePizza, Chess::Game.Start, Roster.Open — each declared an
attribute and never sets it) — real progress on item 5’s own recipe-by-
recipe migration, from a different angle. CommandInterpreter#hydrate_
legacy_creation (the fallback this whole passage is about) is NOT
deleted, though a first attempt tried to, on the strength of that one
audit — running the full suite afterward (not merely trusting the audit’s
own scope) found 218 unrelated failures: dozens of separate spec fixtures
across the suite, well beyond the 5 example domains, declare their own
creates?-true commands the same incomplete way and depend on this exact
fallback to create anything at all. See command_interpreter.rb’s own
current comment on legacy_implicit_creation? for the corrected account.
Read the rest of this section, unedited below, as the historical record it
is — the specific counts and named specs no longer describe the current
tree, but the fallback itself is still live and still load-bearing.
The plan’s own Wave 0–6 checkpoints are substantially real; Wave 7 (corpus and prose migration) barely started, and Wave 8 (regeneration, then the first full-suite run the plan permits) never ran at all. The implementing agent stopped mid-Wave-6 on an external usage limit, not at a checkpoint — so the tree is a genuine mid-wave snapshot, not an abandoned one.
Measured, with the ordinary fuzzing/io exclusions:
| Gate | Result |
|---|---|
bundle exec rspec |
1691 examples, 146 failures |
cargo test — parser / codegen / build / root lib |
green (65+10, 3, 21, 22+4); rust/host will not build — the aws-* crates want rustc 1.94.1 and this machine has 1.94.0, unrelated to anything here |
hecks model_check |
clean |
hecks measure_doc_coverage |
fails — reference pages stale, and Entity’s has_many/has_one/belongs_to carry no prose and no running example |
bundle exec rubocop |
257 offenses across 43 files, every one of them a file this change touched; 249 autocorrectable, 199 of those a single Layout/HashAlignment pass |
The 146 failures are not 146 problems. They are five causes:
CommandInterpreter#legacy_implicit_creation? reads route.nil? && plan.write_set.empty? && command.creates?. Every un-migrated creating command in the corpus (Account.Open, Wire.Ask, the Engagement/Team/Light fixtures) has a non-empty write set — sets :number, sets :kind — so it misses that fallback; it is not complete_state? either, because its belongs_to is still spelled as a command-level reference_to; so it falls through to hydrate_existing, which demands a record that does not exist yet. Hecks::Runtime::NotFound: no Account with number.value "a1", 73 times for Account alone. The four in spec/runtime/relationship_semantics_spec.rb are in this count: that is the plan’s own Wave 2B relationship exemplar, green when written and broken afterwards by the Wave 3 planner.reference_to refused where the corpus still spells it. Five doc pages (writing-an-adapter, wiring, port_operation, domain_port, hecksagon) plus two spec/dsl_spec.rb port-operation examples.belongs_to and Entity’s relationship words. Wave 8 work, correctly deferred.Worth reading as evidence rather than noise: docs/implemented/guides/aggregates-and-value-objects.md:101 fails because a second SafeDepositBox.rent! now succeeds where the guide says it refuses. That is not a stale doc. hydrate_complete_state mints a fresh Instance and hands it to atomic_put without consulting repository.find, so the AlreadyExists refusal — whose removed comment records a real shipped bug, “the tenant a box was rented to, gone without a refusal” — now exists only on the legacy path.
In order. The first two are gates on everything after them.
plan.write_set.empty? from legacy_implicit_creation? so command.creates? alone carries un-routed legacy source. The plan’s own wave rule — “Do not leave the main branch in a state where the grammar refuses source that has not yet migrated” — makes this the fix, not the corpus sweep; step 5 is what eventually deletes the fallback. Expect the 109 to close, the Wave 2B exemplar among them. Check Bluebook::Entity.Seal and Bluebook::Aggregate.Seal alongside it: those two reach the legacy branch and then refuse for a different reason — no identity facts to derive — and may want the same decision.hydrate comment says a silent replace has already cost this project real data. Either re-introduce an existence check before atomic_put for the creating case, or amend docs/implemented/guides/aggregates-and-value-objects.md and ADR 0029 to say the refusal is gone on purpose. Do not leave it as an accident of which hydration branch a command happens to take.ReactionInvocation.resolve_target assumes registry.bluebook(domain) answers #aggregate, and raises UnknownVerb when the target aggregate is not loaded. spec/runtime/reaction_defects_spec.rb catches both consequences: a domain refusal (“a cross-domain target not loaded in this deployment” — that spec’s own words) is now recorded as a runtime defect, which is exactly the distinction that spec exists to hold.spec/oidc_manifest_spec.rb fails with NameError: undefined local variable or method 'name' for ValueObjectBuilder out of WordGate#method_missing while shadow-parsing a held era. Frozen history must stay readable; this is the compatibility boundary the plan calls non-negotiable.safe_deposit_boxes.bluebook is the resolved exemplar; deposit_accounts (13), transfers_and_payments (21), payment_cards (18), new_customer_onboarding (5), customer_and_compliance_views (5), customer_relationships (3), statements (3), and pizzas (2) still carry reference_to. The self-hosted language is down from 89 to 55, with aggregate (13), command (10), and query (8) the remaining bulk.spec/dsl_spec.rb expects the old one-symbol identity refusals; note that identified_by :name, as: :other now raises nothing at all, which is a real hole rather than only a stale expectation. spec/evolve_spec.rb expects a deprecated keyword row and there are none left — the lifecycle machinery has lost its live example. spec/word_coverage_spec.rb wants the now-outgrown belongs_to (Aggregate) exemption deleted. spec/runtime/saga_durability_spec.rb’s saga_mutex race gets born_count 0 and should be re-checked after step 1 rather than assumed collateral.has_many, has_one, and belongs_to need a section and a running example each; hecks measure_doc_coverage names them precisely. Then Wave 7D’s remaining guide/ADR prose.rubocop -a over the 43 touched files — 249 of the 257 offenses are autocorrectable and 199 are one hash-alignment pass — then hand-fix the residue (Lint/StructNewOverride, Metrics/*, Naming/Predicate*).rust/host is actually in that matrix.Nothing here needs re-deriving: the wave structure, exemplar-first rule, and per-wave checkpoints in docs/value-object-identity-and-relationships-plan.md still hold, and its own “Implementation checkpoint — 2026-08-18” list is an accurate account of what was resolved before the work stopped.
A feature is not complete merely because the Ruby DSL accepts it.
An agent should check every applicable box:
to_h includes it.Bluebook::Assembly reconstructs it.Hecks should remain a small language for executable organizational knowledge. The canonical IR is the durable semantic representation. Ruby is an excellent authoring/runtime environment but not a mandatory deployment dependency — a second hand-written interpreter maintained through differential parity is a cost this project has already paid once and retired (see docs/implemented/rust-experiment.md), so any non-Ruby runtime must consume canonical IR through a thin projection with a small handwritten kernel, not reimplement semantics from scratch. Ports define required capabilities; Bluebooks or adapters fulfill them. Imperative facts such as UUIDs, time, authentication results, and external API responses enter through adapters and become durable history. Commands carry a single responsibility role; Governance maps real organizational authority onto those roles and controls act_as transitions. Reports expose the analytical face of the same organizational model. Canonical Bluebooks can be adopted through UL Projection so organizations gain reusable ontology without giving up their own language. Eras preserve both facts and the interpreter that gave those facts meaning. Projections then carry the same organizational knowledge into Rust, WASM, OIDC, SQL, documentation, standards, and future ecosystems without forcing the core language to grow.
The roadmap should evolve alongside the codebase.
When implementing any feature:
This document is intended to become the long-term architectural memory of Hecks.