RIDDL 2.3.0 Released
RIDDL 2.3.0 is now available.
RIDDL 2.3.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.3.0 is a language release. It answers riddl-generator’s eight-feature request in full:
bounded arithmetic, repository storage statements, schema keys, collection predicates, a log
statement, and two new lints — every one of them a sentence that models previously had to write
as prompt("…") prose for an AI to fill in. Thirteen new value and statement kinds, nine new
keywords, fifteen new diagnostics.
Nothing that parsed on 2.2.0 stops parsing: all 191 models of the riddl-models corpus still
validate with zero errors, and every retired spelling still produces the same AST. The BAST
binary format moved 25 → 30, so .bast files written by 2.2.x must be regenerated with
riddlc bastify.
Features
- Arithmetic, bounded on purpose —
+ - * /on numbers,+on strings, timestamp arithmetic (system.now + 30 days,t - opened), duration literals (30 days,1.50 hours), comparisons whose operands are full expressions, and constants that are expressions (constant Bonus: Natural = PointsPerDollar * 2). This reverses the 2026-08-23 “RIDDL does no arithmetic” ruling within stated bounds: power, roots and every math-library function remainprompt("…"), because they are system-dependent. Every expression now has a real type, not a category: a boolean expression isBoolean, and a numeric expression takes the smallest constrained numeric type that contains its operands and its result —Natural + NaturalisNatural,Natural - NaturalisInteger(it may go negative),Natural / NaturalisWhole, andInteger / IntegerisInteger, truncating toward zero. Anything off the table is an Error, never a silent coercion. - Repository storage statements —
store,upsert,update … set … where …anddelete from … where …, legal only in a repository handler, plusquery [one] <table> [where …]as a VALUE that composes withletandreply. Everything is typed against the schema’s stored record, so column names and types are checked rather than invented; a bare name inside awhereor asetis a ROW field and shadows a same-named message field.upsertrequires the schema to declare a key, since without one there is no row identity to update by. - Schema keys and history —
key on field R.iddeclares a UNIQUE natural key (distinct fromindex on, which only speeds retrieval), andof tickets as record R with historyasks the generator to maintain an append-only history of every stored version. Several keys are several independent constraints; there is no composite key. - Collection predicates —
all of xs as e where p,any of …,none of …, the filterxs as e where p,count of xsandxs contains x. The element is bound explicitly and is scoped to the predicate.all ofan empty collection is TRUE;count ofbinds tighter than arithmetic.mapis deliberately absent: a per-element expression is a lambda, and a lambda is general computation. log <value>— record a value for humans: a literal, a field, the bound message underon other as m, or an expression. Deterministic (a generator emits it to its logging facility, never through the AI tier), not state, not a message, legal everywhere a statement is.m.<field>underon other as m— the binding stays the message’s ENVELOPE (A57), and a field the envelope does not have now resolves to a field that EVERY message able to reach the clause carries, with the same type. Partial coverage is an Error naming the members that differ.kind of mneeds no syntax: it ism.type, the envelope’s CloudEvents attribute.- Two new lints — an Advisory on a
prompt(…)used as ayieldargument (430 in the corpus; the hole an AI fills worst), and a Style warning ondoprose that validates a field against a range a TYPE could express (do "validate partySize is between 1 and 20").
Bug Fixes
riddlc dump --jsonthrew aMatchErrorand emitted a zero-byte document for any model using the newlogor storage statements, whileriddlc validateon the same model was clean. The projection’s statement dispatch is total by hand in a module compiled with--no-warnings, so nothing reported the five missing arms. Fixed, and a fixture sweep (DumpProjectionFixturesTest) now runs every**/input/**fixture through the projection so the next missing arm fails the build instead of reaching a consumer. (Note for script authors:riddlcexits non-zero on such a failure, butriddlc … | wc -creports the pipe’s last status — useset -o pipefail.)- A pure sink below an application context could never be reached.
stream-sink-reached-by-no- sourcetreated a chain origin as “bears an outlet and has no inbound edge”, and an application that consumes results always has an inbound edge — so a model whose commands originate in its application could not place a sink anywhere below it without giving the sink an outlet it should not have. Origination is now about what a processor RECEIVES: an outlet, and no inlet admitting a command or query. This also tightens the rule in the other direction — a processor that receives a command with nothing feeding it is no longer an origin. - A
letwith a keyword-qualified type lost the keyword through JSON.let x: record Rround-tripped aslet x: type R, the same defect a schema’sdataentry had in 2.0; both spellings now read and the keyword survives. ConstantRefdegraded to aValueRefwhen rebuilt from JSON — it formatted identically, so nothing caught it. It rebuilds as itself now.Findercould not see a lookup’s indices, anempty’s type ascription, or a constant’s value, so anything walking the AST throughFindersilently returned shorter lists.
Improvements
- Ordering comparisons (
< > <= >=) now accept timestamps and durations as well as numbers.t < system.nowwas silently unchecked before, because a timestamp had no comparison category. - A28’s parse-time refusal of literal comparison operands is reversed:
count > "5"andcount > truenow parse, and validation decides whether the two sides may be compared. - A repository’s
key onsatisfies the “queried repository declares no index” completeness warning — a key is an index. riddlc find -typelearned the new kinds (arithmetic-expression,duration-literal,log-statement,store-statement,upsert-statement,update-statement,delete-statement,query-value,collection-predicate,collection-filter,count-value,membership-value).
Internal
- BAST
FORMAT_REVISION25 → 30 (value tags 14–21, statement sub-kinds 24–28, two schema sequences). A revision-25 reader refuses a 30 file cleanly rather than misreading it. - Fifteen new rule ids, all appended to the published ledger; no code was retired or reused.
- The Computational Semantics document records every ruling in this release, including the two
boundaries that were deliberately NOT crossed: math-library functions and
map. ComparisonExpression.left/rightwidened fromComparandtoValue. Constructing one is unchanged; a consumer that assignsce.leftto aComparand-typed value will need to widen it.Comparandstill types amatchcase’s comparison pattern.