Back to Blog

RIDDL 2.0.0 Released

RIDDL 2.0.0 is now available.

release riddl compiler

RIDDL 2.0.0 is now available. This release of the RIDDL compiler and language tooling includes the following changes:

What’s New

RIDDL 2.0.0 is the first major release since 1.x and the largest single body of work in the project’s history: 833 commits over six weeks, adding roughly 110,000 lines and nearly doubling the codebase. It is a language release — 2.0 exists to make RIDDL models precise enough that correct code can be generated from them.

This release contains breaking changes. Deprecated 1.x spellings still parse and now emit deprecations; several previously-silent model defects are now errors. See Breaking Changes below.

What’s New

The language

  • Unified processor model — every Processor (context, entity, projector, repository, adaptor, and the new generic processor) can declare inlets and outlets. Shape is either ascribed (context C as sink is {…}) or derived from port arity. The old source/sink/flow/merge/split/router keywords become deprecated aliases.
  • Intentions are grammar, not options — 14 keywords across three families, written before the definition: entity role and consistency and persistence (aggregate, consistent/available, event-sourced/persistent/transient), context intent (application/external/gateway/service), and connector durability and delivery (persistent, at-least-once/at-most-once/exactly-once). They were options in 1.x, but the Computational Model calls options advisory, and a hard error keyed off advisory metadata is a category error.
  • Processor instance identity — Id(P) names an instance of any processor, self is the instance a handler is executing as, initiate creates one and yields its id, and terminate ends one. Before this, RIDDL could describe processors but not instances of them, so Id(P) was a type with no producer. tell may now address an instance, not only a named processor.
  • forward — delegation, and the only statement that discharges a yields/replies obligation by passing it on. Legal only where there is an obligation to delegate: you cannot forward an event or a result.
  • Typed holes — prompt("…") as T ascribes a checkable type to an AI-computed value: the seam between RIDDL’s deterministic tier and its AI tier. The ascription restates the position’s type and never overrides it.
  • Correlations in projectors — keyed accumulation of several events into one command, with a mandatory timeout block.
  • Numeric literals — count > 5 now parses. Literal text is stored exactly as written, so 1.50 and 007 survive a round trip.
  • empty (and its synonym none) — the minimum-cardinality inhabitant of a type, legal wherever minimum cardinality is zero.
  • On-clause message binding — on foo: command Foo { … } binds the handled message to a local name; on other as x binds the residual message’s envelope.
  • not and ! are synonymous everywhere, building the identical AST node.
  • Multi-line do and prompt — do { "a" "b" }, using the block spelling RIDDL already had for prose.
  • A predefined Riddl standard module, available with no import: BottomlessPit and ForeverEmpty terminators, Envelope (CloudEvents v1.0 context attributes), and GeneratorError.

riddlc

  • riddlc find — a query engine over your model with 42 predicates and actions, plus -exec, -replace and -delete for model-wide edits. Nothing is written until every script has run, no two spans overlap, and the result has been re-parsed and re-validated; a model that stops parsing or merely gains errors is fully restored.
  • Every diagnostic names its rule — 375 rule ids in rustc’s style: [error] [msg-target-crosses-boundary] file(12:5). Suppress with --no-msg-ids.
  • validate --json emits one object per diagnostic for tooling, and validate --fix / --fix-rule <id> applies the codemod a rule carries, through the same verify-or-restore gate as find -replace.
  • JSON is now a fully reflective surface alongside parse, prettify and the binary AST — root → JSON → root recovers the exact AST, including the order of definitions within their parent.

Validation

Many classes of model defect that used to pass silently are now reported, including: a message sent to a portlet whose declared type cannot carry it; duplicate field names; a message delivered where nothing can receive it; a cross-context connector or send reaching past a context boundary into its contents; an entity’s own inlet and outlet obligations; a queried repository with no index; and unreachable statements after error or terminate.

Breaking Changes

  • What discharges a yields/replies obligation narrowed. Only yield/reply, error/require and forward settle a path — a send or tell no longer does.
  • A cross-context connector must terminate on the context’s own portlet, and a tell/send/forward from outside a context must address the context itself, never something it contains. There is no adaptor exemption.
  • set and get from state require something that owns state — legal in an entity or projector, an error elsewhere.
  • Every field of a constructor must be supplied, and constructor arguments are now type-checked.
  • error and terminate are terminal in their statement block.
  • Deprecated but still parsing: the shape keywords, option-spelled intentions, type X is command {…}, state X is <record>, quoted numeric constants, and Abstract (now Anything).

Internal

  • Scala 3.9.0, sbt 2.0.6 and sbt-ossuminc 3.0.3, on a projectMatrix build with a flat cross-platform source layout. JDK 25.
  • Binary AST format advanced 17 revisions (6 → 23).
  • Test suite grew from 173 to 427 files; 2.0.0 was certified from a genuinely cold cache across all 19 module legs on three platforms — JVM 3,083 / JS 1,005 / Native 3,041 tests, zero failures — and validated against a 191-model, 1,017-file corpus.
  • A Scala.js performance fix took Definition.hashCode from 384,016ns to 217ns (1,770×), bringing browser-side validation to parity with the JVM.