hecks

What 1.0 means, and what it covers

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.

The gate

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:

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.)

What “the 1.0 DSL surface” will mean, once frozen

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.

What else has to be true first

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:

  1. ADR 0025 lands, corpus migrated, docs regenerated. Resolved, 2026-08-28. The gate. All of S0a/S0b and S1–S13 are DONE per 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.
  2. The property fuzzer’s real-adapter mode covers what it currently doesn’t. Resolved, 2026-08-27. 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.
  3. The 8 confirmed-real open GitHub issues Resolved, 2026-08-27. 4 of 8 (#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.
  4. Reconciliation Phase 1b/2/3 — Resolved, 2026-08-28. 18 non-guard-shaped 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.
  5. M1–M19, L1–L24, and the Rust-parity divergence list — Resolved, 2026-08-28. Re-verified by three independent, parallel investigations reading current file:line content and existing spec coverage, not the audit doc’s stale line numbers: 53 of 55 individually-tracked items are fixed or no longer applicable. Two carry an honestly-tracked caveat rather than a clean close (M10’s chapter-level @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.

Known gaps at 1.0

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:

  1. Cross-tenant boot isolation was unwired at the 1.0.0 tag itself. Resolved in 1.0.1. 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.
  2. Read models have no cross-engine agreement gate. 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.
  3. ADR 0037 Findings 3–5 are still pending:, including a dangling-reference gap where SafeDepositBox.Rent refuses in Ruby but succeeds in Rust. Resolved. All three closed per ADR 0037’s 2026-09-08 status update: Findings 3 and 4 by reordering generated dispatch in both generators (missing-argument and query-argument checks now run up front); Finding 5 re-verified live and found moot — the one corpus command that triggered it (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.

What a release number promises

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.

  1. A major version means a breaking DSL or runtime change, and always carries a Breaking: entry in the CHANGELOG.
  2. A minor version may carry a 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.
  3. Deploys pin exactly. ~> floats only in development.
  4. A break that reaches an installed client site gets one release of warning where a warning is possible. The 1.4 to 1.5 removal of loose-keyword dispatch is the model: a named removal version, a warning, and a codemod.
  5. The warning is not required where the break is a boot refusal a warning cannot soften, as with 2.0.0. The CHANGELOG entry still says exactly what now refuses and what to change.
  6. A slower release train or a pre-release channel is revisited when a first outside adopter appears. No outside adopter is visible today.

Why this doc exists

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.