Release 2.2.0
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 form
remove 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, and
set 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 and
show-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 beevent-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 itsask— 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|off
beside 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.md
and build/API notes indocs/claude/build-and-api.md.