Status: shipped — ADR 0025’s gate was satisfied and 1.0.0 was tagged on
2026-08-28. The sections below keep the tense of the pre-tag checklist
they began as; the README’s “Project status” section states the current
release and what is and isn’t stable today. This doc
exists so “when can we ship 1.0” has one place to answer it, instead of an
inference from scattered status lines across the README, future-features.md,
and the decision log.
A 1.0.0 tag is a semver promise: the DSL and runtime API described in
the DSL reference won’t change in a
breaking way without a major-version bump. That promise could not be made
honestly until
ADR 0025
landed — a real, scoped, breaking redesign of exactly the surface a 1.0 tag
freezes:
has_many / has_one / belongs_to are deleted outright (all three
compile to reference_to today; nothing replaces them).identified_by’s three current forms (bare field, ValueObject, as:,
block) collapse to one./ operator, splitting what . does
today between “hop to another record” and “walk a field.”String forms — a bare constant becomes the only spelling.attribute
keyword (one_of:) for the single-field case.Every bluebook in the corpus used several of these before the migration. Shipping 1.0 before this landed would have frozen syntax the project had already decided to remove — the exact failure mode semver exists to prevent, on day one of using it.
So: 1.0 ships now that ADR 0025’s sequenced plan has landed and the corpus
is migrated to the post-cleanup surface. docs/dsl-work-slices.md tracks
every individual slice (S0a/S0b through S13, the full ADR 0025 scope); all
are DONE as of 2026-08-28, most recently S6 (events first-class) and S7
(reactions), which closed with the corpus’s last ~128 emits/policy on/
saga-transition/starts_on/ends_on sites migrated from quoted strings to
bare constants. (S14–S18 are ADR 0026 scope, a separate decision record —
not part of this gate; S18 in particular is explicitly scoped but not
started, and does not block this tag.)
The 22 documented DSL contexts,
generated from the aggregate-local tables under lib/hecks/language/ and
held equal to them by spec/reference_golden_spec.rb — post-ADR-0025. Every
construct in that reference, demonstrated in the example/framework corpus,
with a passing doctest. Nothing broader: a construct that exists in code but
has no corpus use and no doctest doesn’t earn a stability promise just by
compiling (this is literally ADR 0025’s own fourth principle — “a word
earns its place by being used” — applied to the release process, not just
the language).
Explicitly not covered by the eventual 1.0 promise, even once it ships,
without a separate note when it happens: anything in this README’s
“Experimental or partial” or “Planned or research only” lists at the time
of the 1.0 tag. Concretely, as of 2026-08-27: query DSL aggregation
(none then; read_model has since gained count, median and group_by —
ADRs 0050 and 0052 — but sum/avg/min/max still don’t exist), Rust
codegen’s read_model gap (since narrowed to a proven subset that runs and
a refusal for the rest), Rails integration, Drivers,
a durable outbox (since shipped — ADR 0053, Runtime::Outbox — but the
adapter-host protocol and standalone relay it leaves open stay outside the
promise), and whatever ADR 0025 leaves for a later slice if it ships
in stages rather than all at once.
Independent of ADR 0025, tracked here so this doc is the one place that says “not yet, and here is the whole list,” not just the DSL-syntax part:
docs/dsl-work-slices.md, live-verified rather than trusted. Corpus is
fully migrated to the post-cleanup surface — the last quoted-string
emits/policy on/saga-transition/starts_on/ends_on sites (128 of
them) moved to bare constants this pass; the 2 sites still quoted
(PortOperation#emits in pizzas’ PaymentGateway port) are a
genuinely different, deliberately-text-kind grammar context — a
boundary translation, not an aggregate event reference — not part of
this gate’s scope. docs/implemented/reference/*.md regenerated and
held equal to the live grammar by spec/reference_golden_spec.rb.
Full gate green: the whole rspec suite, model_check (0 errors),
doc_coverage clean, rubocop clean, parser/codegen parity, and a real
cargo build + cargo test --lib. rust_conformance_spec was failing
part of its pinned set (the pre-existing Rust-runtime-projection-gap
baseline) when this item was first verified; PR #433, merged the same
day into this same release, closed that gap for real (ProjectedFieldSpec/
seeded_projections/SetProjectedField, rust/src/kernel/reference_lookup.rs
— projects fields are now seeded at dispatch time in Rust, not just
Ruby) — rust_conformance_spec passes in full, zero failures, as of the
actual 1.0.0 tag.hecks fuzz --adapter sqlite|postgres
landed the same day (PRD 02); the follow-up question — whether a
reference-hop query field (owner/field) can be queried, not just
indexed, against a SQL adapter — turned out not to be a gap at all:
Runtime::ReferenceHop.apply folds a hop where-clause into a synthetic
local in: clause before any adapter ever sees it, for every engine
alike, and order_by on a hop is refused at DSL-seal time regardless of
adapter. Live-verified against real Postgres and Sqlite,
spec/adapters/query_hop_agreement_spec.rb. See
docs/prds/02-fuzzer-real-adapters.md for the full account.#310, #257, #293, #229) were already fixed. 3 (#308,
#305, #301 — destination validation on Transfer.Request/Reject/
Debited) turned out NOT to be real bugs: adding the checks broke a
deliberate, doc-tested compensation mechanism (docs/implemented/guides/
policies-and-process-managers.md’s “Compensation — the refused leg” —
Debited has to be able to succeed against a bad destination so the
saga’s own reversal path is reachable). The 8th (#278,
SafeDepositBox.Rent missing a customer-status guard) is fixed: the
initial diagnosis (a belongs_to dereference bug) was wrong — the real
cause was Rent redundantly redeclaring attribute :customer,
CustomerNumber, a plain value-object type that shadowed the
aggregate’s own belongs_to Customer reference with an incompatible
type. Removed the redundant redeclaration, added the guard, closed.
All 8 done.qa-legacy issues, the 38-issue legacy-fork
epic, and 7 misc issues all closed at 0 open; fresh independent re-run
of bundle exec rspec / hecks fuzz / hecks model_check / cargo test
against main tip confirmed the audit sweep’s self-reported numbers.
See docs/audits/2026-08-28-reconciliation-phase-1b-2-3-closeout.md.@ports round-trip gap,
deliberately deferred as its own epic; and R2, whose specific mechanism
is gone but whose underlying invariant is still actively tracked in ADR
0037). Full table: docs/audits/2026-08-28-m1-m19-l1-l24-rust-parity-reverify.md.None of these blocked ADR 0025 work from starting, and all five are now resolved — nothing on this list blocks the tag any longer.
Passing the ADR 0025 gate is not the same claim as “no gaps exist.” Three are known, named here on purpose rather than left for someone else to find later — see “Why this doc exists” below for why that matters more here than in most projects:
Runtime::TenantCheck
.refuse_unless_tenant_capable! existed and was directly tested
(spec/tenant_isolation_spec.rb), but nothing called it — a second
Hecks.boot of the same directory for a second tenant, bound to a
non-tenant_capable? adapter (plain Postgres, no schema story), was
not refused; both boots shared tables. Closed by wiring the check
into Bluebook::ProjectRegister#register, the actual point where
more than one tenant boot of a directory converges into one shared
route table: a directory’s SECOND registration is refused — its
first never is, so an ordinary single-tenant deploy on a plain
adapter is unaffected — before that second boot’s routes ever merge
in, so a leaking tenant’s requests are never reachable through
Router#resolve at all.ReadModelInterpreter#project still silently forks between native
SQL pushdown and an in-process Ruby loop, switched by declaration
details (e.g. group_by) rather than checked for agreement. This is
the layer that produces dashboard/query numbers; a divergence here
doesn’t refuse or crash, it just renders a wrong number.pending:, including a
dangling-reference gap where SafeDepositBox.Rent refuses in Ruby
but succeeds in Rust.SafeDepositBox.Rent) no longer locally redeclares a
non-Reference customer attribute, so the already-ported
command-level reference check now catches it on both engines.
ADR 0037’s own “honest addendum” from that same verification pass
catalogues two OTHER, later-discovered Ruby/Rust divergence
classes that remain genuinely open — a one_of closed set given
a non-scalar shape, and an entity command’s argument-invariant-vs.
element-lookup ordering — neither of which is Findings 3–5; see
ADR 0037 for the full detail.The release pace stays as it is, and the promise is stated in two tiers (ADR 0068). The 1.0 promise above says only that the DSL and runtime API won’t break without a major-version bump. This section says the rest.
Breaking: entry in the CHANGELOG.Behavior change entry, as 2.4.0 did
for deployed domains. A minor is not a promise of no behavior change, so
read the Behavior change entries before bumping a running system.~> floats only in development.1.4 to 1.5 removal of
loose-keyword dispatch is the model: a named removal version, a
warning, and a codemod.2.0.0. The CHANGELOG entry still says
exactly what now refuses and what to change.Because the pattern that produced it — a status doc calling something fixed that wasn’t (the fuzzer/Memory-adapter gap), sitting next to a different finding (H1, entity argument gate) that stayed silently broken past a point its own audit narrative implied otherwise — is exactly the failure mode a project whose whole thesis is “specification drift should be mechanically impossible” can least afford to ship 1.0 with. One doc, updated when the gate state actually changes, beats inferring readiness from five files that each drift independently.