Skip to content

Release 2.0.0

Choose a tag to compare

@reid-spencer reid-spencer released this 27 Aug 20:20
· 90 commits to main since this release

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.