2026 Revision Series
The specification is a 2026 working draft. Each revision is published under its own path and kept there permanently, so a hash-pinned reference into a revision never breaks when the next one opens. Revision 37 is the working draft; earlier revisions are retained as-published.
When the draft is finalised it will be published as TSON version 1 and frozen; until then the series is subject to change without compatibility guarantees.
- Revision 37Current working draft
Adjudicates the 21-entry spec-feedback register against revision 36.
identifieris a text family, and an enum states which text type its members are — name checks now follow a value's type rather than where it sits, so identifier-typed data and map keys get look-alike protection toovalueandvoideach get a constructor, andunitis gone —unitwas the one type a processor had to recognise by its name- Template parameters carry a type, which a declaration may narrow:
<T: text, N: int8>— an argument can be checked where the template is applied, not only once it has been substituted - A field group's option may hold several fields, and
( … )+means at least one — shapes like "host and port, or a socket" and "email or phone, or both" had no spelling optional(the key may be missing) andvoidable(the value may be_) replace the state vocabulary, and_is the void sentinel — one word, "absent", covered both kinds of nothingorderedreplacesunorderedand maps state it too; a set may be empty — every container now says whether order is part of its value, and a set's bounds work like an array'surirequires a scheme and is US-ASCII, beside newuri_reference,iriandiri_referencetypes — RFC 3986 gives every URI a scheme, yeturiadmitted relative references, and identifiers beyond ASCII (RFC 3987) had no typenormalizationis a text facet: a value is its text put into the type's form — whether two spellings of "café" are one value is for the type to say, not the processor- Core drops seven entries, such as the sign-bound integers; meta's annotations are trimmed, and
@docis CommonMark — core holds only what a schema cannot do without, and a bound like "non-negative" is a one-line declaration of a schema's own - A record family's member must be declared, not created by a template application at a use site — a member with no name cannot be selected or referred to
- New
policy.tndeclares the vocabulary of a processor's identifier, token and limits policy — a deployment can write its policy down as data, and a processor can report the policy it judged under in one shape
- Revision 36Retained revision
Adjudicates the 21-entry spec-feedback register against revision 35.
- A record field is three slots, replacing six field states:
?on the name (key omittable),?on the type (_admitted), and its modifier — the six states tangled three separate questions, leaving useful combinations such asa: T?unspellable - Records can be declared
abstractorfinal— a record had no way to say whether it may be instantiated directly or extended - A field written
=?is the selector a sealed family's members each pin, replacing@discriminator— it changes which values conform, and an annotation never may @restis gone; a record is always closed — open-ended data is a map, written the same way in every encoding- A declaration naming an application is that application's entry — consumers see the name you declared, not a generated name behind a reference hop
- Enums gain a TEXT profile, and
textgains a member set — vocabularies like"lightly active"are not identifiers, and text had no way to list its allowed values disjointmeans the same in every encoding — a choice safe to leave untagged in text, like( float64 | text ), could be ambiguous in JSON- An unobtainable schema is reported as not judged, rather than invalid — failing to fetch a schema says nothing about whether the document is valid
- New Part 3, the JSON encoding, with the
TSON-Schemaout-of-band schema channel — JSON needs a defined mapping onto TSON types, and an encoding with no directives needs another way to name its schema
- A record field is three slots, replacing six field states:
- Revision 35Retained revision
Adjudicates the 36-entry implementation feedback register against revision 34.
- TSON is no longer a JSON superset; a JSON document is read through a JSON reader — the claim imposed rules on the notation that existed only for JSON's sake
nullleaves the notation;voidadmits_alone — it was a second spelling of absence that only the text format could see- A field name is an identifier at every layer — names were left loose only for JSON compatibility; a key that is not a name belongs in a map
- A comma may follow a value:
[1, 2, ]— TSON has no elision, so a trailing comma cannot be mistaken for a missing element - The
~constructor marker goes; applicability becomes IS-Atop— the marker decided nothing the type system did not already know scopedreplacesexternandunknown—unknownhad no way to be read, andexterncould name only one schema- One
bytestype replaces the spelled alphabets (!base64,!hex, …) — they were four types over the same octets; an alphabet is an encoding, not a type durationsplits into elapsed time (duration) and calendar period (period) — mixing seconds with months left durations without a total order (isP1Mlonger thanP30D?)- Equality is stated over value spaces — two spellings of one value, such as
Zand+00:00, are now the same value everywhere - Name hygiene and resource limits become reportable properties of a processor, with defaults — a policy refusal is not a verdict on validity, and unstated limits were not portable
- Revision 34Retained revision
Adjudicates the 17-entry implementation feedback register against revision 33.
- Type names, field names and enum members must be identifiers — lookalike-name checks need a defined name grammar to attach to
- Open template bodies are held rather than quoted — quoting needed special machinery and could not place a parameter inside a collection, as in
<T> ( T | error ) - New optional-value map form
{K => V?}— a map had no way to admit_as a value, as an array already could - Fixed and default values are checked against the field's type — a default that does not fit its own field is an authoring error, caught at load
require_timezoneis gone;precisionis at most N fractional digits — RFC 3339 already requires the offset, andprecisionhad no defined meaning- Name hygiene is on by default — lookalike names (Latin
pass, Cyrillicраѕѕ) are a spoofing risk, so the check runs unless code relaxes it
- Revision 33Retained revision
Adopts CR-structure-templates in full, and adjudicates the 59-entry implementation feedback register from revision 32.
- Cross-namespace template linkage removed; the container constructors de-parameterised — a generic name now resolves in one namespace, with no fallback order to reason about
- A sugar form for the map type,
{K => V}— a schema reads like the data it describes - Duplicate record fields and map keys are rejected (were last-value-wins) — last-value-wins silently discards data
- One severity: every former warning is an error or is deleted — a warning leaves "valid" ambiguous; a rule is either enforced or not there
- Imports are transitive — matching
!!meta, so every schema in a chain sees the same names - Annotations are resolved and validated — they had no conformance force, so a misspelt annotation went unnoticed
- The file extension is
.tn;.tn1is reserved for version 1 —.tn1would claim version 1 conformance before version 1 exists
- Revision 32Retained revision
Editorial refactor.
- Non-normative rationale and design history moved to the developer guide — the specification keeps to the rules; the guide carries the reasoning
- The series framing restated around the type system — the schema system is the product; the text format is its notation