Skip to content

Specification Revisions

The revision log of the Decl specification. Every post-freeze change records: the date, the charter decision it rests on (new or amended D-number), the chapters touched, and a one-line rationale. A change without an entry here did not happen (docs/README.md, freeze rules).

Date Version Decision Chapters Change
2026-08-31 v0.1 — all Initial freeze. P1–P7, D1–D29, chapters 01–13, guide, validation cases; gated by the §0.6 evaluator spike (31/31 green, spike/FINDINGS.md); all open questions resolved (OQ1–OQ7).
2026-08-31 v0.1 — (editorial) 07 Spell out §7.3’s per-site checking mechanism: context obligations collected at declaration, discharged at each embedding site; diagnostics attributed to the embedding line. No semantic change.
2026-08-31 v0.1.1 D30 (new) 03, 07, 11, 12 Context obligations are declared, not inferred: named types using $parent/$root/$key carry explicit context declarations ($parent: P); the variable is typed by its declaration, body checks are modular, and the site check is one subsumption test. Lexically nested type expressions are exempt (parent evident). Supersedes the inferred-obligation mechanism of the entry above. New codes E4094; E4090 reworded.
2026-08-31 v0.1.2 D30 (amended) 07 Context variables are references: $this/$parent/$root type as ref<…> (a value reading would be a self-containing value — the cycle D26 forbids); the declaration states the target bound P, the invariant ref wrapper is never written. $key stays a plain value.
2026-08-31 v0.1.3 D30 (amended) 03, 07 The ref wrapper is written, not implicit: context declarations read $parent: ref<P> — the annotation is the variable’s exact type (D2), the invariant compiler-enforced (bare non-ref bound is an error with the fix supplied). Supersedes v0.1.2’s never-written rule; no ambiguity exists since an owner is never itself a reference.
2026-09-01 v0.1.4 D31 (new) 03, 04 Static assignability is precise, then deferred: int +/-/* over range/literal operands infer interval-arithmetic ranges, and same-base-kind refinement targets (range, pattern, literal set, predicate) whose membership is statically undecidable defer to binding-time validation; kind mismatches stay static errors. Without this the frozen guide/benchmarks are ill-typed under strict S ⊑ T (found implementing the checker).
2026-09-01 v0.1.4 — (clarification) 02, 11 Unit-literal token boundary: the numeric reading is longest-first, so float exponents and radix prefixes never split into number + unit (1e3 is a float, 0o755 an int; 0s, 5eV are unit literals). The published tree-sitter grammar had the overlap and was fixed to §2.7 by construction.
2026-09-01 v0.1.4 — (registry) 12 Populate the E1xxx (lexical) and E2xxx (syntax) bands: E1001/E1002, E2001/E2002. Tools were using a provisional code for parse errors.
2026-09-01 v0.1.5 D32 (new) 04, 07 Roots own places; unbound literals are inert. No implicit re-rooting when a const-built record embeds into a root (E4093, now also static); the normative idiom for reuse is a constructor func bound per root; unbound literals support member access / with / embedding only. Found building the Phase 5 service-graph example.
2026-09-01 v0.1.5 — (clarification) 02, 08 State the declaring-module scope rule for imported types’ member expressions (§8.3, already implemented) and the exact newline continuation points (§2.9 — the grammar was always the source of truth).
2026-09-01 v0.1.6 D16 (consistent) 13 std.string grows split / contains / ends_with (append-only, D20 discipline): validating real edge keys of the form "a->b.c" needed split; the additions stay one-hop, so the OQ3 verdict on std.graph is unchanged.
2026-09-01 v0.1.7 D29 (amended) 10 Interchange leniency: an integer lexeme binds where float is expected iff exactly representable in binary64 (real corpora serialize whole floats as 500); serialization stays canonical (500.0); the reverse direction remains an error.
2026-09-01 v0.1.8 D33 (new) 02, 03, 11 Member positions are their own name space: keywords are ordinary bare member names in declarations, ./?. access, and object keys (real corpora use type, unit, …), with plain sibling references; true/false/null stay literals. Supersedes v0.1’s quoted-keyword rule; grammar + wasm updated.
2026-09-01 v0.2 — (cycle) — v0.2 declared: the revision cycle over the Phase 2–5 findings is complete — v0.1.4 (D31 precise/deferred assignability), v0.1.5 (D32 roots own places), v0.1.6 (std.string growth), v0.1.7 (D29 interchange leniency), v0.1.8 (D33 keyword member names), plus clarifications (lexing boundary, continuation points, declaring-module scope) and the E1xxx/E2xxx registry bands. Adjudications recorded per entry in design/03_v02_revision_candidates.md; implementation, grammar, and corpus updated in step.
2026-08-31 v0.1.3 — (editorial) 07 Add the $key declaration example (map-key bound, array site error) and note the non-existence of $value ($this for entry values; predicate parameters for checked values). No semantic change.
2026-09-03 v0.2.1 — (clarification) 07 A conditional whose branches are navigations is itself a navigation in a ref<T> position: each branch is read as a place, only the taken branch resolves (§7.4). Found porting a real interconnect scratch — the runtimes read if as a value and rejected the maybe-absent branch. Same change: the runtimes now implement $key and $root (specified since v0.1.1), context variables evaluate as references (v0.1.2), pattern interpolation (§3.6), and outputs reading inputs with the on-demand fallback (§5.5–5.6, §9.4); no further spec text changed.
2026-09-03 v0.2.2 — (registry) 03, 12 A pattern body outside the §3.6 core is E4119 with a fixed message set, validated by the implementations themselves: delegating to the host regular-expression engine made the same defect read differently in each implementation (the one parity gap left by v0.2.1).
2026-09-04 v0.2.2 — (editorial) 04, 07 Align the dot-spellable wording of §4.3 and §7.2 with D33 (v0.1.8): reserved keywords are ordinary dotted member names; only the literal keywords true/false/null take the bracket form. No semantic change. Same change: the runtimes now spell map keys in canonical paths as ["…"] (§7.2, unchanged text) and refuse non-canonical reference strings from documents (§7.5); they had spelled identifier-shaped keys with a dot.
2026-09-04 v0.3 D4 (amended), D34 (new), D3/D19/D29 (amended) 01, 02, 03, 04, 05, 07, 09, 10, 11, 12, 13, guide Member kinds by shape; hidden members. ? says input may supply a member, = e says the schema computes it: required x: T, optional x?: T, defaulted x?: T = e, derived x: T = e / x = e — no const in record bodies (the keyword stays the module-level constant). A hidden member x$ = e is computed for the schema’s own use — read by expressions and $referrers, never part of the value (not emitted, compared, copied, or targetable; supplying it is E4006). Breaking syntax: the migration is mechanical (x: T = e → x?: T = e, const x = e → x = e); the tree-sitter grammar, all three implementations, the formatter, the corpus, and the examples moved in step. Found porting a real interconnect scratch whose link members leaked into every document (every peer language marks such members: Pkl hidden, jsonnet ::, Nickel not_exported, CUE _).
2026-09-06 v0.3.1 D35 (new) 05, 10, 12 Rendering is tooling, declared in the module. §5.10 names @render among the known annotations (on an output; metadata, its keys fixed by docs/tooling/05_render.md); §10.6 points to the renderer’s document for the tool-side conversions (documents in YAML, roots as YAML or indented JSON or a template’s text, fan-out) — interchange stays JSON; §12 reserves the E7xxx — rendering band and registers E7001–E7005, and rewords E6004 for a document in YAML. Editorial in the same change: the registry’s superseded E1xxx/E2xxx tables (pre-v0.1.4) removed, leaving the populated ones. The language does not change; the grammar gains the annotation rule §11.3 always had (the parsers lacked it).
2026-09-06 v0.4.1 D36 (new) 03, 13 A record flows into a map. §3.17 gains the clause R ⊑ { [K]: V } (every value member’s name ⊑ K, its type ⊑ V; hidden members are not entries); §3.18 binds a record in a map position as its value entries; §13.8 reads a record argument to std.map as that map. No syntax changes. Found projecting an evaluated tree into a map-shaped document; the implementations also repair, in the same change, object spread (§4.2, never implemented), member reads and with on untyped literals, array equality between literals, the keyword literal types true/false/null in unions (dropped by the parsers), recursive type aliases through arrays, maps, and unions (§3.1, a stack overflow), and reads of an optional member a value’s type does not declare (absent, §4.10).
2026-09-06 v0.4.2 D26 (amended) 07, 09, 12 $referrers answers in rounds. The universe may depend on the answers (a hub deriving its links from the peers its ports found): round 1 answers over the instances that exist without any answer, every later round over the whole universe the previous round produced, recomputing what read an answer, until the queried edges are stable; at most eight rounds, E5009 beyond. A round never sees what it creates, so demand order cannot leak into an answer. Found reimplementing a real generator: the implementations answered from whatever happened to exist, silently, and their answer depended on demand order.
2026-09-07 v0.4.3 D16 (consistent) 13 std.array grows sort / sort_by / unique / reverse (append-only, D20 discipline): a real generator sorts children by name and dedupes tokens everywhere, and the fold-with-spread idiom it had to use builds a lazy chain one level per element — quadratic at best, and deep enough at a few hundred elements to exhaust the host stack. The runtimes also, in the same change, materialize an unmaterialized literal once when it is read as a value (§9.4 memoization; a chain of spreads re-evaluated every level at every read, exponentially).
2026-09-07 v0.4.3 — (clarification) 07 A candidate whose queried member itself waits on an answer of the round is a referrer the next round finds, not a dependency cycle (§7.6, the rule “a round never sees what it creates itself” spelled out for members). The runtimes reported E5007 there — and only when the answer happened to be demanded before the candidate, the demand-order dependence v0.4.2 forbids.
2026-09-07 v0.4.4 D31 (amended) 03, 05 A computed document binds at its site. At a binding site — an output’s value, an input’s fallback, a member’s default or derived expression, the positions inside a literal there — an expression whose static type does not fix an object’s entries (a map, or a union with a map or array arm: Json) defers to binding-time validation against a record, map, or array type it could take, and binds as a document does; a record stays a static judgment, a scalar a kind mismatch, and a func body, a const, or a call argument keeps the static rule. Found reimplementing a real generator’s preprocessor: a module that builds its typed document (ports typed from their peers) could not bind it — the runtimes did, the checker refused — so the pipeline’s second half had to be a second program.
2026-09-08 v0.4.5 D11 (amended) 03 Discrimination is hierarchical. A discriminant member (a literal, or a union of literals, in every arm) partitions a record union’s arms into groups with disjoint values, and each group of more than one arm is discriminated in turn by a further member — a single member need not tell every arm apart (a kind names the family, a second member the variant within it). Unions are associative, so a union arm flattens to its record arms first; the nested `Pointer
2026-09-08 v0.4.6 D26 (amended) 04, 07 A match subject is a value position. A subject of type ref<U> denotes its target’s value U (§7.4’s mirror rule applied to match, §4.7), so a reference to a discriminable union discriminates as that union and the arm binds the target’s value; before, the checker rejected it as “not a discriminable union” (E4103) though the runtime already dereferenced the subject. Found reimplementing a real generator, where a reference to a discriminable union had to be matched.
2026-09-20 v0.4.6 — (clarification) 03 An open record’s undeclared field written in a literal is a value like any other (§3.11): its expression is evaluated when the record is constructed, the value passes through and re-serializes as a document’s field does, and a failing expression is reported at that field. The rule followed from “preserved, compared for equality, and re-serialized faithfully” but no fixture supplied such a field through a literal, and the implementations disagreed: the reference and Python ended emission with an internal error, and Rust emitted null for a nested or failing one. All three now evaluate and materialize it as the record is bound.
2026-09-06 v0.4 — (cycle) — v0.4 declared: the revision cycle over Phase 10 — v0.3.1 (D35: rendering is tooling, declared in the module with @render; documents in YAML; the E7xxx band) and the annotation rule of §11.3 implemented in the grammar and the three parsers (W0001 reported) — with the implementations, the editor extensions, and the corpus moved in step; released as 0.4.0.

© 2026 Luuvish. Decl is open source under the MIT License.

Type: Literata and IBM Plex, under the SIL Open Font License. Built with Astro Starlight.