RIDDL 2.2.0 Released
RIDDL 2.2.0 is now available.
release riddl compiler
RIDDL 2.2.0 is now available. This release of the RIDDL compiler and language tooling includes the following changes:
What’s New
What’s New
RIDDL 2.2.0 is a language release: three new pieces of syntax, a new message kind, and a
substantial tightening of what riddlc can say about the streaming and messaging structure of a
model. Every model that parsed on 2.1.1 still parses; the changes below are additive syntax and
new or corrected diagnostics. The BAST binary format moved to revision 25, so .bast files
written by 2.1.x must be regenerated with riddlc bastify.
Features
on quiescence <window>— a handler clause that fires when nothing has arrived at a processor instance within the window (on quiescence 30 minutes is { … }). The clock restarts on every handled message; inside a State’s handler it is armed only while that state is active. Legal on any handler-bearing processor, at most one per handler. Before this, “no payment within 30 minutes, cancel the order” had to live in a projector correlation, and riddl-models counted 18 time-caused events raised by a command nothing could send.send <message> to <portlet> at <instant>— a delivery scheduled for a time.atstates an instant (aTimeStamp,DateTimeorZonedDateTimevalue), not a mechanism; the timer, delay queue or scheduler is the generator’s choice. There is no cancellation construct — the idiom is to schedule to yourself and decide at fire time, and that loop connector is legal.appendandremove— collection statements. An event-sourced entity’s fold can now say what it appends to and removes from a collection field of its state:append added.item to field Cart.Data.items,remove tag from field Cart.Data.tags(every element equal to the value), and the keyed formremove from field Cart.Data.items where itemId == removed.itemId(every element whose named field matches). The target must be a collection field; the value is typed against the element type; both statements obey every rulesetobeys (scope, refusals-first, pure functions, event-sourcing). Arithmetic deliberately gets no syntax — RIDDL does none, andset field S.balance to prompt("balance + points")is the intended spelling.Advisory— a new message kind. An advisory reports a structural fact that is consistent with the model as written and inconsistent with what such a declaration usually means; it never blocks code generation and never names a fix as required — the modeller may dismiss it by design. Severity 1 (with Style), not a warning: its own--show-advisoriesflag andshow-advisoriesconfig key, default on, independent of-w.adaptor-direction-advisoryhad been a plain Warning that blocked generation; it is the first rule on the new kind.- Four counts about what an entity does with its journal. riddlc cannot judge whether an
entity ought to be
event-sourced, but it can count:entity-event-sourced-unread-history(one state, no transitions, no other processor reads its events — the journal has no reader),entity-crud-with-transitions-consumed(its inverse),entity-event-sourced-snapshot-events(every event carries the whole state and every fold copies it back) — all three advisories — andentity-event-sourced-prose-folds(Completeness: a fold written only as prose has no replay semantics). Asetof a stated field from aprompt(…)counts as a stated fold.
Language rulings now enforced
- Implied adaptor ports are abolished. A port is declared or absent, for every processor
kind. A processor whose handlers receive messages but declares no inlet, or transmit but
declares no outlet, is incomplete — a Missing warning naming the types
(
stream-processor-no-inlet/-no-outlet; an Entity keepsentity-no-inlet/-no-outlet) — and every rule that would read the missing side abstains until it exists, exactly as rules abstain from a???body. A connector endpoint that names an adaptor rather than one of its portlets isref-wrong-kindagain. Adaptors take whatever shape their ports give them (as mergeon two inlets is legal). - The adaptor is the boundary for its pair, in both directions. A cross-context connector
may terminate on an adaptor’s declared portlet at either end, so an asking adaptor owns both
legs of its
ask— the request outlet and the reply inlet — and the reply has a legal path. - An
askneeds a modelled path both ways (msg-ask-target-unreachable,msg-ask-reply-unreachable): the question leg and the reply leg must each be carried by connectors. The mechanism of the reply is the generator’s; the path is the model’s. on otheriscase _. It fires for exactly the message types noon <message>clause handles, and it receives whatever its body — an error-only body is a refusal of those messages, which is business logic, not non-reception. A Context or Projector with an inlet and no handler at all is now reported (stream-inlet-not-received), the error-sink inlet excepted.- A
tellinto an unrelated domain is a modelling error (msg-tell-crosses-unrelated-domains) whose remedy is to restructure — put both domains under a common parent — since no connector between unrelated domains is legal and “add a connector” was advice that could not be followed. stream-graph-cycleforbids an infinite message loop, not a connector ring: a message of type X that travels the network and re-enters the clause that transmits it. The schedule-to-yourself idiom is no longer flagged.- A handler-less processor is a stream tail whatever its shape — an opaque processor lets no rule assert what it does with a message.
- A saga step that tells nothing is an Error (
saga-step-no-tell): a step that effects nothing yet promises to compensate it is a self-contradiction.saga-no-timeoutstays a Completeness warning but no longer blocks code generation. - A handler may declare at most one of each special on-clause (
on other,on init,on term,on activate,on passivate,on quiescence) — two catch-alls with no rule for which runs used to validate clean.
Bug Fixes
- Prettify wrote
URL("https")asURL"https", which does not parse — a second copy of the type’s ownformathad drifted. Fixed; both spellings round-trip. find -type on-quiescencerefused a kind the language produces;find -type append-statement/remove-statementlikewise, caught by the release gate.adaptor-direction-advisorynow counts a far-context reference anywhere in the adaptor (handled types, transmitted operands,letascriptions, portlet types), and resolves a qualified referent correctly; it used to reward the unmigrated shape.Root2JsonCorpusTeststripped only one spelling of an embedded location, so the first corpus error carrying afile(line:col->col)read as a round-trip regression.
Improvements
validatereports advisories separately in its summary line and listsadvisories on|offbeside the warning classes.- The JavaScript
ErrorInfo.kindunion inindex.d.tsnow lists the kinds by the names riddlc actually renders (Severe,Error,Warning,Deprecation,Completeness,Usage,Missing,Style,Advisory,Tip,Info); the old spellings remain for compatibility. - The
Messagesscaladoc records the admission test between Missing and Completeness: Missing = the author owes something unwritten; Completeness = the written things do not connect.
Internal
- BAST
FORMAT_REVISION24 → 25 (statement sub-kinds 22append, 23remove; the earlier bump to 24 in this cycle carriedon quiescenceandsend … at). A revision-24 reader refuses a 25 file cleanly. adaptor-implied-outlet-ambiguous(five days old, from the implied-ports design) is retired; its code will not be reused. Two new rule ids for the collection statements, four for the event-sourcing counts, two for the incompleteness checks.- The Computational Model (
RIDDL-Computational-Model.md) records every ruling above, with the reversed 2026-09-06 paragraphs kept as history. CLAUDE.mdwas split: per-construct reference now lives indocs/claude/language-constructs.mdand build/API notes indocs/claude/build-and-api.md.