Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

A reason may grow

Closed September 2026. The wire promised that a report stays additive within a major, and a closed enumeration broke that promise the moment it gained a value. A report carrying a reason that did not exist in 0.29.1 was read by the released 0.29.1 binary as amiss render: the input is not a scanner report envelope, exit 2, and the same report without that value read fine. Three such values were already waiting in branches, and more will arrive as documentation generators get modelled, so minting a major for each one was never going to work. The report stays the live chapter; this page records what 3 changed and where the line fell.

The line runs between a reason and a verdict. A reason says why an answer could not be given, and the answer sits beside it: a document row’s unsupported_reason next to its status, an unsupported-semantics row’s reason next to its kind. A consumer that meets an unfamiliar reason still knows the document went unscanned, or the reference went unevaluated, so it loses nothing by keeping a spelling it cannot name. A kind, a disposition, a status, a schema name and compatibility itself are the opposite. A consumer that meets an unfamiliar one of those cannot judge the verdict at all, so they stay closed and a reader still refuses them.

Two enumerations crossed. The document row’s unsupported reason and the unsupported-semantics reason are open strings under 3. Both stay derive-only: strum carries the closed spellings, and #[strum(default, transparent)] on a variant holding a String gives the tolerant FromStr and the Display that writes it back, with nothing written by hand. An unfamiliar reason therefore round-trips byte for byte, which is not a nicety. The payload digest covers the reason, so a report the reader could not re-spell exactly would fail its own seal. A contract test reads a report carrying an invented reason, writes it back, and compares the bytes.

Making the semantics reason grow cost one shape. Under 2 each reason selected its own body, so query and code-fragment carried a target, fragment carried a blob target, and the four route reasons carried none. Under 3 the row is one shape for every reason, the reason and the target it located when it located one, and a new reason needs no new row. The engine still writes a target for query, code-fragment and fragment and for nothing else, but the reader no longer refuses a target on a reason that would not carry one. The missing reasons did not move, since each of those selects which evidence its row carries and each feeds the finding key.

The mint itself followed 2. The writers emit 3 from one wire constant, the schema pins the same value by a contract test, and the example that opens the major is retained permanently at spec/examples/scanner-report.frozen-3.json, byte-pinned like its two predecessors, with every later schema in the major required to keep validating it. frozen-2 stays in the tree as the record of major 2, on the terms frozen-1 already had. The example the last release shipped keeps carrying 2 until the release workflow refreshes it, and the contract test allows exactly that window. Every reader of 3 refuses a 2 report: the render and refs verbs, the external plan, and the controller. 3 is additive within its major as 2 was, and a reason can now grow without minting 4.

Last change: , commit: 6e8029dc