TSON 2026 Revision 34 — Change Log
This document records the changes accepted into Revision 34 of the TSON specification series, adjudicated from the implementation’s spec-feedback register (17 entries, renumbered from #1 against Revision 33). SPEC-FEEDBACK.md remains a record against Revision 33 and is not modified by this revision; each of its entries receives a disposition here.
Provenance markers used in the disposition table:
- [settled] — the resolution was settled by the spec author inside the feedback register itself, is already implemented in the revised companion artifacts shipped with this revision, or was decided by the revision editor at adjudication.
- [open] — a genuine design decision deliberately left open. Collected in §5.
1. Baseline: the revised artifacts
Four of the register’s proposals arrived already implemented in the companion artifacts, and Revision 34 adopts them as its baseline. Summarising the normative effect:
- #3 step 1 — the
identifierprimitive. The kernel’stokenprimitive is replaced byidentifier, an instance ofunitcarrying a stated contract:XID_Start-initial,XID_Continue ∪ { - }thereafter, NFC, applied to a name’s decoded text however it was spelled.type_name,field_nameandparam_namealias it;token_setbecomesenum_set => !set { element_type: identifier min_items: 1 }, so enum members are identifiers and an empty enum is a schema-load error.tokenandtoken_setare gone. (Part 1 §7.1, new §7.7; Part 2 §4.2, §7.4, §9, §12.1; meta-kernel.) - #5 — held open bodies. An open entry’s body is the constructor application as
written, held unread until materialisation substitutes its parameters away. Substitution
is one token walk at any depth; a parameter in a collection-valued slot needs no
representation of its own, so
result => <T> ( T | error )resolves.instance_template,template_argumentandrecord_field.value_paramare deleted; theinstance-template/template-def/template-bindproductions go with them, andinstancetakes the optional parameter list. (Part 2 §1.3, §4.2, §5.2, §5.3, §5.7, §5.10, §8.1, §8.2, §9, §12.1; meta-kernel.) - #7 —
reference.target: type_ref. An alias to an application states the arguments it binds, so a partial application is an ordinary open entry —<B> !reference { target: { name: pair arguments: [ { name: text } { name: B } ] } }, the application intype_ref’s record form — andsourceno longer does double duty. Use-site flattening walks through a REFERENCE entry but stops at an argument-bearing target. (Part 2 §5.10, §8.1, §8.3, §9; meta-kernel.) - #12 —
map.state. Themapconstructor gainsstate: element_state ~ REQUIRED; the sugar spells it as{K => V?};_at a map entry value is permitted only where the map’s value state is OPTIONAL, exactly as for array elements. (Part 2 §5.3, §7.6, §9, §12.1; meta-kernel.)
2. Disposition summary
| # | Entry (abridged) | Disposition |
|---|---|---|
| 1 | !duration and ISO 8601 PnW | Open (carried) — deliberately left open a second time; the §5.4 table is unchanged. |
| 2 | Hash pin in the URI query | Open (carried) — the query form is kept; whether the pin moves to a fragment or a structured directive value stays recorded here. |
| 3 | §9.4 has nowhere to attach; token carries no contract | Accept (two layers) — Layer 1, the identifier profile, is normative validity: a new Part 1 identifier grammar (§7.7) matched against a name’s decoded text as §7.6 matches a number; type-ref = "!" identifier, annotation = "@" identifier; field-name = unquoted-token / (multi-line dropped; Class 1 field names stay lexical, so JSON compatibility is untouched and data conforms under a schema by construction); Part 2 type-name = identifier, subsuming the “numbers are not declarable names” rule; enum members are identifiers. Layer 2 — skeleton distinctness within each named scope, Identifier_Status=Allowed, and UTS #39 restriction levels with a per-segment unit — is MUST implement, enforced by default, never validity: a new Part 1 §8.2 defines the mechanisms, scopes, defaults, the distinguishable-refusal rule, the code-only relaxation rule, and the data-version-in-diagnostic rule; Part 2 §11.4 adds the schema scopes (declared names, the !!import merge) and the enum scope. Settled sub-questions: profile extension characters (-) do not participate in Layer 2; the namespace scope spans !!import; choice variants are not a scope; no Unicode version is pinned. §9.4 shrinks to a pointer. [settled] |
| 4 | Type-argument literal identity | Accept (option 3) — the argument is recorded as written; identity applies [TSON-DATA] §4.3’s equivalence before comparing (radix, digit separators, redundant sign, float scale, .inf/.infinity), with base type as the boundary (1 and 1.0 remain two arguments). Stated in §8.2, with §8.1 agreeing. The D6 merge pass is stated as required, not incidental. [settled] |
| 5 | Collection-slot boundary; held open bodies | Accept — the baseline above. Additionally stated: the one-spelling rule for held bodies; the declaration-time /e<c>) needs no special spelling. Supersedes Revision 33’s #53 decision. [settled — implemented] |
| 6 | Kernel-declared open container templates | Declined — the lift targets are grammar-supplied and must not depend on the import set; the fixed-arity subset covers too little of the family to justify a new category of always-available name. No edit. |
| 7 | reference.target cannot state arguments | Accept — the baseline above; §5.10 names reference as a dispatched head (not a ~ constructor; kind REFERENCE from the alias form, not a base kind). [settled — implemented] |
| 8 | Which declared types may carry a fixed/ | Accept (restrictive) — a ~/= value is admitted only on a field whose declared type resolves (after flattening) to an atom-family instance or an enum; on any other declared type it is a resolver error at the declaration. void, unknown and extern fall out of the rule. (P2 §5.2.) [settled] |
| 9 | precision /require_timezone undefined | Accept — precision: N means at most N fractional-second digits, judged on the written token, a validation constraint (so it orders and refines like every other bound; precision: 0 admits no fractional part); require_timezone is deleted — RFC 3339 full-time/date-time already mandate the offset, so the facet was vacuous or widening. (P2 §5.5 new prose; meta.tn; meta-resolved.tn.) [settled — artifacts edited] |
| 10 | ”and their kin” | Accept — replaced by the general rule: every family coherence rule of §5.3 and §5.5 applies again at materialisation over the operands that were parameters. (P2 §8.2.) [settled] |
| 11 | Empty brace at array/ | Accept (narrow) — {} resolves to the empty record or the empty map according to the expected type; at any other typed position it is a validation error. Part 1 §2.8’s sentence is narrowed to match. (P1 §2.8; P2 §7.7.) [settled] |
| 12 | {K => V?} and map.state | Accept — the baseline above; §5.3’s table gains the row, its “neither side admits ?” sentence keeps only the key half, §7.6’s map row becomes conditional. A language change: data writing _ at a map value validates only where the schema wrote ?. [settled — implemented] |
| 13 | atom-refinement takes record-def | Accept — atom-refinement = "!" type-name ws "^" ws ( record /: the braced subset of the core-value payload instance takes, read by the same data grammar, so the braced-only restriction is grammar rather than prose; §12.1’s import list and count corrected. An error, not a design question. [settled] |
| 14 | ZWNJ/ | Accept (keep the algebra) — Continue = XID_Continue ∪ { - + . } stands; the blanket exclusion and its “MUST be quoted” remedy are deleted; UTS #39 §3.1.1.1’s contextual rule (A1/Cf or control character is in XID_Continue and names the bidi controls. (P1 §7.1, §7.7.) [settled] |
| 15 | §5.11’s datetime example does not parse | Accept — the instance is quoted; time, datetime, and durations carrying a time part join §7.1’s always-quote list. (P2 §5.11; P1 §7.1.) [settled] |
| 16 | LRM/ | Accept — §7.2 rule 1 is split as UAX31-R3a-1 splits the property: line terminators, the two ignorable format controls (admitted only where a token boundary already exists, contributing nothing; interior occurrence is a lexer error), horizontal space. §9.5’s SHOULD is retired; the section becomes a note pointing at the rule, citing UTS #55 §3.2. (P1 §7.2, §7.3, §9.5.) [settled] |
| 17 | Duplicates have no §8.1 category | Accept (layered) — “is a resolver error” in §2.5 and §2.6 for the textual and decoded-value layers of identity, whichever of the two detects a pair; the declared-type layer remains the Class 2 validation error P2 §7.7 already states (its 0xFF/255 example, a decoded-value duplicate, is dropped). §8.1’s resolver bullet gains duplicates. (P1 §2.5, §2.6, §8.1; P2 §7.7.) [settled] |
Counts: 13 accepted (3, 4, 5, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17 — fourteen rows, of which 5 and 7 are one design), 1 declined (6), and 2 open questions carried in this change log only (1, 2). Open questions are recorded here and are NOT reflected as open text in the specification.
3. Accepted changes by target document
3.1 Part 1 — Text Data Format
- §1.5 — Class 1 obligations gain the identifier grammar at
!/@positions and the record-scope name-hygiene check (#3). - §2.4 — quoting-by-kind cross-reference to the temporal kinds (#15).
- §2.5 / §2.6 — duplicates “are a resolver error” (#17).
- §2.8 — empty brace resolves to the empty record or map only (#11).
- §3.1 / §3.2 — annotation and type-annotation names are identifiers (#3).
- §7.1 — the identifier profile stated beside the token profile; §7.1’s “declared
profile of Unicode identifiers” sentence reworded (the profile constrains identifiers,
of which an unquoted token is one spelling); ZWNJ/ZWJ paragraph replaced by the
contextual rule’s pointer (#14); the no-
Cfsentence beside the BOM paragraph (#14); times and datetimes in the always-quote list (#15). - §7.2 — rule 1 split per R3a-1; the format-control clause (#16).
- §7.3 —
wssplit intoline-term/horizontal-space/ignorable-format; the two ignorable characters removed from the horizontal set (#16). - §7.4 —
type-ref = "!" identifier,annotation = "@" identifier,field-name = unquoted-token / single-line-token(#3). - §7.7 (new) — the identifier grammar: the profile as ABNF, the decoded-text matching rule, case sensitivity, NFC, the joining-control contexts (#3, #14).
- §8.1 — resolver category gains duplicates; the Layer 2 refusal is distinguished from the four categories (#3, #17).
- §8.2 (new) — Name hygiene: the three mechanisms, the scopes, the defaults, the reporting and relaxation rules, the data-version rule (#3).
- §9.4 / §9.5 — §9.4 reduced to a pointer at §8.2; §9.5 reduced to a note at §7.2 (#3, #16).
- §10 — UTS #39 and UTS #55 become normative references; series links to
/2026/34/.
3.2 Part 2 — Type System and Schema
- §1 / §1.3 — grammar-import sentence corrected (
core-valueat two points, #13); resolved-output consumer tier restated over held bodies (#5). - §4.2 —
identifierreplacestokenamong the unit instances (#3); labelled-only parameter rule for~declarations restated over held bodies (#5). - §5.1 / §5.7 — stale
~array<T> ^ { … }examples corrected to the parameterless form (housekeeping). - §5.2 — value-conformance rule and eligible-type rule for
~/=values (#5, #8); open-modifier prose retargeted fromvalue_paramto the heldvalueslot (#5). - §5.3 —
{K => V?}row; map paragraph rewritten (#12); lift rule unchanged but restated over held open synthetics (#5). - §5.5 — atom-refinement body is the constructor payload (#13); temporal facets
defined (
precision),require_timezoneremoved (#9). - §5.6 — end state restated: closed bodies are binding records, open bodies are held applications (#5).
- §5.7 — open modifiers, fixation at materialisation, with no
value_param(#5). - §5.10 — rewritten around holding: the one-spelling rule, checks split by phase,
diagnostics located by name, argument-kind rule dropped, enum-member arguments,
referenceas a dispatched head, the required D6 merge (#5, #7). - §5.11 — example quoted (#15).
- §7.4 — enum member semantics over
enum_set; theidentifierprimitive replaces thetokenparagraph (#3). - §7.6 — map entry value row conditional (#12).
- §7.7 — empty-brace rule narrowed and the exclusion stated (#11).
- §8.1 —
type_definitionprose withoutinstance_template; held-body serialization contract;reference.targetastype_ref; ingest checks restated (#5, #7). - §8.2 — identity: value normalisation before comparison (#4); coherence rule general (#10); the required merge pass (#5).
- §8.3 — the walk stops at an argument-bearing target (#7).
- §9 — meta-layer table updated (
identifier,enum_set, noinstance_template/template_argument,map.state, norequire_timezone); “every sum” / “every choice” wording fordisjointreconciled (housekeeping). - §11.4 (new) — schema-layer name hygiene: scopes and the
!!importmerge (#3). - §12.1 / §12.2 / §12.3 —
instance = [type-params] "!" type-name ws core-value;atom-refinementpayload;type-name = identifier;instance-templateand its productions deleted; dispatch summary updated (#3, #5, #13). - §13 — references:
identifier-related UTS entries; “hash pin to be published” replaced by the shipped pins; identities to/2026/34/.
3.3 Companion artifacts
The revised artifacts shipped with this revision already carry #3 step 1, #5, #7 and #12.
This revision additionally: bumps every artifact to Revision 34 with /2026/34/
identities; deletes require_timezone from meta.tn and meta-resolved.tn and documents
precision (#9); removes the stray empty groups: [ ] from the kernel fixture’s
record_field (default omission); aligns the kernel’s record_field and type_ref doc
strings with §5.10’s held-body wording; and recomputes the hash chain bottom-up.
4. Notable normative changes (reader’s digest)
Changes a Revision 33 implementer must act on:
- Names are identifiers. Type names, parameter names, annotation names,
!targets and declared field names must match the identifier grammar;!42xand@x.yare parse errors; a declared field named42is impossible. Class 1 field names stay lexical. - Enum members are identifiers, and an enum has at least one.
!enum [1 2 3]and!enum []are schema-load errors. - Open bodies are held, not quoted.
instance_template,template_argumentandvalue_paramno longer exist; a parameter may stand in any slot, collections included. {K => V?}exists, and_at a map value is valid only under it (language change: Revision 33 data writing_at a map value may now fail validation).reference.targetis atype_ref.- Argument identity normalises numeric spellings before comparing;
<255>and<0xFF>are one application. - Fixed and default values are checked against the field’s type, and only atom- or enum-typed fields may carry one.
require_timezoneis gone;precisionis at-most-N fractional digits.{}at an array or tuple position is a validation error.- LRM/RLM are ignorable format controls, legal only at token boundaries.
- ZWNJ/ZWJ are admitted by context, not excluded; the “quote it” remedy is gone.
- Name hygiene is on by default: skeleton distinctness,
Identifier_Status, and a restriction level, all relaxable only through code configuration and never validity. - Duplicates are resolver errors;
atom-refinementtakes the constructor payload.
5. Open questions carried by this change log
Adjudicated 2026-08-29. The following remain deliberately open. They live in this change log only — the specification text carries no open questions.
| Ref | Question | Status |
|---|---|---|
| #1 | Does !duration accept ISO 8601’s PnW week form? | Open — carried a second time; the §5.4 table is unchanged. |
| #2 | Should the content-hash pin move out of the URI query (fragment, or a structured directive value)? | Open — the query form is kept; §2.2.1 is unchanged. |
Decisions taken 2026-08-29: #3 two-layer split (Layer 1 validity, Layer 2 default-on policy);
#4 option 3; #8 restrictive; #9 at-most-N and delete require_timezone; #11 narrow; #6
declined.
6. Artifact work executed
- Revision markers and identities — all six artifacts carry “2026 Revision 34” and
/2026/34/identities. meta.tn—require_timezoneremoved fromtime_typeanddatetime_type; both constructors’@docstrings defineprecision(#9).meta-kernel.tn— doc strings onrecord_fieldandtype_refalign with §5.10’s held-body and #4’s identity wording; the header note no longer claims the published drafts spell pinsxxhash.- Fixtures —
meta-kernel-resolved.tndropsrecord_field’s emptygroups;meta-resolved.tndrops the tworequire_timezonefields; conventions notes restated. - Hash chain — recomputed bottom-up over the shipped bytes (kernel body → kernel
!!iddigest → meta pins → meta digest → core pin → core digest). Hash values remain non-normative; only the pin’s shape is.
7. Developer Guide
tson-guide.md ships with Revision 34, realigned on the same two principles as before (it
describes the design as it stands; history lives in the change logs). §2.7 now states the
held-body model and the one-spelling requirement in place of the typed-quotation account;
§3.2’s always-quote list gains times and datetimes and its ZWNJ/ZWJ footnote is rewritten
around the contextual rule; a new §3.5 carries the standing rationale for the identifier
layer and for name hygiene being default-on policy rather than validity (stability of the
underlying Unicode data, and the non-composition of skeleton distinctness across !!import);
§4.3 and §6.1 read identifier for token; §7’s worked example serializes the open
flagged template as its declaration with the parameter in the plain value slot; §8.1
adds value-normalised argument identity and the required merge pass; §8.3 adds the
locate-by-name and refusal-vs-error conventions; identities are bumped to /2026/34/. The
Revision 33 guide candidates (#4, #7, #13 of that register) remain outstanding.