From e8a4b43049df867826943ff762c683cea842ee89 Mon Sep 17 00:00:00 2001 From: Johann Schopplich Date: Thu, 1 Oct 2026 10:32:00 +0200 Subject: [PATCH 1/3] docs: record out-of-scope decisions --- .out-of-scope/binary-encoding.md | 16 +++++++++++ .out-of-scope/custom-delimiters.md | 17 ++++++++++++ .out-of-scope/encoder-data-rewrites.md | 37 +++++++++++++++++++++++++ .out-of-scope/lenient-quoted-strings.md | 17 ++++++++++++ .out-of-scope/mixed-columnar-arrays.md | 25 +++++++++++++++++ .out-of-scope/multi-document-streams.md | 29 +++++++++++++++++++ .out-of-scope/non-json-data-models.md | 20 +++++++++++++ .out-of-scope/optional-array-length.md | 24 ++++++++++++++++ .out-of-scope/query-language.md | 18 ++++++++++++ .out-of-scope/references-and-aliases.md | 35 +++++++++++++++++++++++ .out-of-scope/schema-language.md | 23 +++++++++++++++ .out-of-scope/trailing-newline.md | 18 ++++++++++++ .out-of-scope/type-annotations.md | 23 +++++++++++++++ 13 files changed, 302 insertions(+) create mode 100644 .out-of-scope/binary-encoding.md create mode 100644 .out-of-scope/custom-delimiters.md create mode 100644 .out-of-scope/encoder-data-rewrites.md create mode 100644 .out-of-scope/lenient-quoted-strings.md create mode 100644 .out-of-scope/mixed-columnar-arrays.md create mode 100644 .out-of-scope/multi-document-streams.md create mode 100644 .out-of-scope/non-json-data-models.md create mode 100644 .out-of-scope/optional-array-length.md create mode 100644 .out-of-scope/query-language.md create mode 100644 .out-of-scope/references-and-aliases.md create mode 100644 .out-of-scope/schema-language.md create mode 100644 .out-of-scope/trailing-newline.md create mode 100644 .out-of-scope/type-annotations.md diff --git a/.out-of-scope/binary-encoding.md b/.out-of-scope/binary-encoding.md new file mode 100644 index 0000000..88b85d2 --- /dev/null +++ b/.out-of-scope/binary-encoding.md @@ -0,0 +1,16 @@ +# Binary Encoding + +TOON is a text format. A binary TOON wire format is out of scope, in the spec and in the reference library and CLI. + +## Why this is out of scope + +TOON exists to put structured data into LLM prompts with fewer tokens (Introduction, "Purpose and Scope"). Models read text, so a binary encoding would always be decoded back to text before use – it saves bytes on the wire, which is not the cost TOON targets. It would also need its own spec, its own conformance suite, and implementations in every language: + +> Adding a second "Binary TOON" wire format would require a separate spec, cross‑language implementations, and long‑term maintenance, while not really helping the primary use case (LLMs still need text, so this would always be decoded before use). For general binary serialization there are already well‑established options like CBOR/MessagePack. +> – [toon#201](https://github.com/toon-format/toon/pull/201#issuecomment-3566643061) + +Compression is the same story: gzip reduces bytes, but a model tokenizes the decompressed text, so token counts stay the same ([toon#125](https://github.com/toon-format/toon/issues/125#issuecomment-3528565342)). Binary serialization belongs to CBOR or MessagePack; a binary TOON variant can live as its own package. + +## Prior requests + +- toon#201 – Binary TOON diff --git a/.out-of-scope/custom-delimiters.md b/.out-of-scope/custom-delimiters.md new file mode 100644 index 0000000..abe6a4d --- /dev/null +++ b/.out-of-scope/custom-delimiters.md @@ -0,0 +1,17 @@ +# Custom Delimiters + +The delimiter set is comma, tab, and pipe (§11). Other delimiters – non-ASCII symbols like `✦`, semicolons, or user-defined separators – are out of scope. + +## Why this is out of scope + +The header declares the active delimiter in the bracket segment, and §6's grammar admits exactly two symbols there (`delimsym = HTAB / "|"`; absent means comma). Every decoder splits rows on that closed set, and encoders quote strings that contain the active delimiter (§7.2, §11.1). Opening the set means new grammar, new quoting rules, and a header symbol for each new delimiter: + +> I want to keep the spec to comma/tab/pipe to stay ASCII-only for maximum interop, editor/terminal safety, and predictable tokenization. Non-ASCII delimiters like ✦ would add complexity and may be token-inefficient on some models. +> – [toon#133](https://github.com/toon-format/toon/issues/133#issuecomment-3532885149) + +The existing set already covers readability: tab for tables (often token-cheaper too), pipe for a visible ASCII separator. Decoders trim spaces around cells (§12), so `1, Alice, admin` decodes fine when written by hand. + +## Prior requests + +- toon#133 – `✦` as delimiter +- toon discussion #139 – one-line rows with `|` and `;` diff --git a/.out-of-scope/encoder-data-rewrites.md b/.out-of-scope/encoder-data-rewrites.md new file mode 100644 index 0000000..4c3030c --- /dev/null +++ b/.out-of-scope/encoder-data-rewrites.md @@ -0,0 +1,37 @@ +# Encoder-Side Data Rewrites + +Encoders don't rewrite the data to make it fit a more compact form. That rules out filling missing fields with `null` so semi-uniform arrays qualify for tabular form (`fillna`), dropping `null`, empty-string, or empty-array fields (`ignoreNullOrEmpty`, `excludeEmptyArrays`), splitting semi-uniform arrays into base and extras tables (`normalizeForToon()`), and abbreviating, renaming, or reordering keys to save tokens. + +## Why this is out of scope + +Each of these changes the value being encoded. Under §2's JSON-model equality, objects compare by their key sequence, so a missing key and a key set to `null` are different values: + +```ts +// in: { sku: 'B2', qty: 2 } +// out: { sku: 'B2', price: null, qty: 2 } +``` + +Keys are part of the value too: "Object key order MUST be preserved as encountered by the encoder" (§2), and an encoder that turns `customer_name` into `cn` emits a document that no other decoder can map back. + +A decoder can't tell a filled-in `null` from a real one, can't restore a field that was dropped, and can't undo a renamed key. `decode(encode(x))` stops returning `x`, which is the guarantee the whole format rests on. TOON saves tokens through its syntax – lengths and field lists declared once (§9.3, §9.5), minimal quoting (§7.2) – not by changing the data. Whether `null` and "absent" mean the same thing, or which short key names a prompt can live with, is a property of your data, not of the format: + +> TOON is a **transport format**: it encodes the JSON data model as text, and that's where its responsibility ends. Splitting semi-uniform arrays into base + extras tables is a **data transformation step** that belongs in the application layer, not inside the encoder. +> – [toon#292](https://github.com/toon-format/toon/pull/292#issuecomment-4162837784) + +The same ruling covered key abbreviation in the Python port: "These are great ideas but belong in separate packages or higher-level wrappers" ([toon-python#35](https://github.com/toon-format/toon-python/pull/35#issuecomment-4073056071)). + +Normalize before encoding when your data allows it. Filling in `null` takes one line: + +```ts +const fields = [...new Set(items.flatMap(Object.keys))] +encode({ items: items.map(item => Object.fromEntries(fields.map(f => [f, item[f] ?? null]))) }) +``` + +Shorter keys work the same way, and the mapping stays in your code, where it can also go into the prompt. Dropping fields works through the `replacer` option of the TypeScript reference, which returns `undefined` to omit a value. + +## Prior requests + +- toon#344 – `fillna` option without an explicit replacer +- toon#292 – pre-encoding normalization for semi-uniform arrays +- spec#48 – `ignoreNullOrEmpty` and `excludeEmptyArrays` (the mixed columnar half is in `mixed-columnar-arrays.md`) +- toon-python#25, toon-python#35 – "semantic optimization": field abbreviation and semantic key ordering diff --git a/.out-of-scope/lenient-quoted-strings.md b/.out-of-scope/lenient-quoted-strings.md new file mode 100644 index 0000000..025dabd --- /dev/null +++ b/.out-of-scope/lenient-quoted-strings.md @@ -0,0 +1,17 @@ +# Lenient Quoted Strings + +A token that starts with `"` must end at its closing quote (§7.4, "Quoted-token boundary"). Relaxing that rule so that LLM output like `text: "hello" said Alice` decodes as a string is out of scope. + +## Why this is out of scope + +v4.1 made the boundary normative in strict and non-strict mode alike: any character after the closing quote MUST error (§7.4, §14.2). Without it, a decoder has to guess whether a leading `"` opens a quoted string or is a literal character – `"hello" said Alice` could decode to `hello said Alice` or to `"hello" said Alice`, and two decoders could pick differently. A decoder that silently repairs input also can't tell a model's mistake from data. + +Model output that breaks the quoting rules is the application's problem to handle before or after decoding, not the format's. What works in practice: + +- **Show the escape in the prompt.** An example like `text: "\"hello\" said Alice"` gets copied far more reliably than a description of the rule. +- **Retry on a strict decode error.** The decode fails at the offending line, so a re-prompt can point at it. +- **Repair before decoding** if your pipeline needs best-effort parsing – as an application step, not a decoder mode. + +## Prior requests + +- spec discussion #41 – quoted string specification diff --git a/.out-of-scope/mixed-columnar-arrays.md b/.out-of-scope/mixed-columnar-arrays.md new file mode 100644 index 0000000..85b76f0 --- /dev/null +++ b/.out-of-scope/mixed-columnar-arrays.md @@ -0,0 +1,25 @@ +# Mixed Columnar Arrays + +Tabular rows carry primitive cells only. Proposals that let a row continue with nested content – spill lines under a row, nested tabular blocks per row, or an encoder switch like `objectArrayLayout: "columnar"` that opts into such a layout – are out of scope. + +## Why this is out of scope + +The tabular form is useful because it is constrained: one line per element, one primitive cell per leaf field (§9.3). Row boundaries are trivial to find, and a strict decoder checks both row count and row width (§14.1). Hanging nested lines off a row turns rows into trees: + +> This proposal removes that constraint and turns rows into tree-structured entities – effectively creating a second tree-encoding path alongside the existing list/object syntax. +> – [spec#21](https://github.com/toon-format/spec/issues/21#issuecomment-4163086929) + +In 4.x the syntax is taken, too: a line deeper than a row is an over-indented line, which strict decoders MUST reject (§14.2). And an encoder option that picks the layout conflicts with §1.4 and §13.1 – the form follows from the value's shape and position, not from encoder preference – so one JSON value has one TOON rendering. + +v4 covers the uniform part of this shape without a second tree syntax: + +- **Nested field groups (§9.3)** put uniform nested objects into the header: `orders[2]{id,customer{name,country},total}:`. +- **Keyed tabular form (§9.5)** handles objects of uniform objects. + +An array whose elements differ, or carry arrays or empty objects, stays in list form (§9.4). That is more verbose, but simple and unambiguous. + +## Prior requests + +- spec#21 – hybrid tabular arrays with nested row content +- spec#48 – mixed columnar arrays and `objectArrayLayout` (the `ignoreNullOrEmpty` and `excludeEmptyArrays` half is in `encoder-data-rewrites.md`) +- spec#47 – draft spec text for spec#48 diff --git a/.out-of-scope/multi-document-streams.md b/.out-of-scope/multi-document-streams.md new file mode 100644 index 0000000..1dfbafa --- /dev/null +++ b/.out-of-scope/multi-document-streams.md @@ -0,0 +1,29 @@ +# Multi-Document Streams + +A TOON document holds one value. There is no multi-document syntax – no `---` separators, no "TOON Lines" counterpart to JSON Lines – and the reference library doesn't ship framing utilities for concatenated documents. + +## Why this is out of scope + +JSON Lines is a framing format around independent JSON values. TOON's root form spans the whole document (§5): once a root array or keyed tabular root is complete, strict decoders MUST reject any further content (§14.2). A separator line would need a new line class, new root-form rules, and new strict-mode errors in every implementation, for structure TOON can already express: + +> JSON Lines is a *framing* format (a sequence of independent JSON values), while TOON is deliberately defined as "one JSON value per document", with no multi‑document syntax or directives. +> – [toon#120](https://github.com/toon-format/toon/issues/120#issuecomment-3558763808) + +The only structure a JSONL stream carries is an ordered sequence. Encode it as one document with an array root, and uniform records get tabular form: + +```toon +[3]{id,event}: + 1,signup + 2,login + 3,logout +``` + +On the library side, `@toon-format/toon` stays single-document. Streaming one large document is covered by `encodeLines` and `decodeStream`. Document separators, concatenated-JSON parsing, and batch encode/decode are framing utilities that fit a companion package built on top of `encode` and `decode` ([toon#163](https://github.com/toon-format/toon/issues/163#issuecomment-3559409605)). + +## Prior requests + +- toon#120 – JSON Lines support +- toon#121 – JSON Lines support (implementation PR) +- toon discussion #119 – what about JSON Lines? +- toon#163 – streaming API with document separators and batch processing +- toon#176 – streaming API for large datasets (implementation PR) diff --git a/.out-of-scope/non-json-data-models.md b/.out-of-scope/non-json-data-models.md new file mode 100644 index 0000000..7c59e27 --- /dev/null +++ b/.out-of-scope/non-json-data-models.md @@ -0,0 +1,20 @@ +# Non-JSON Data Models + +TOON encodes the JSON data model and nothing else. Modes or extensions for XML (attributes, namespaces), HTML/CSS/JS markup, or GraphQL are out of scope. + +## Why this is out of scope + +§2 defines the whole data model: strings, numbers, booleans, null, objects, and arrays. A second model inside the same syntax means the same document decodes differently depending on a flag: + +> **Ambiguity.** The same TOON document would decode differently depending on a `mode` flag. A key like `id: 123` is a plain key-value pair in JSON mode but an XML attribute in XML mode. That breaks TOON's promise of deterministic, unambiguous encoding. +> – [spec#29](https://github.com/toon-format/spec/pull/29#issuecomment-4163086242) + +It would also change the grammar under existing parsers – XML namespace prefixes need colons in unquoted keys, which §5.2 and §7.4 treat as the key-value separator – and add normative sections every implementation must carry even if it never sees XML. + +Convert first, then encode: XML to a JSON representation with `xml2js` or `xmltodict`, and encode that. Markup languages already have dedicated tools – Pug, HAML, JSX ([toon discussion #220](https://github.com/toon-format/toon/discussions/220#discussioncomment-15070508)). A companion format for another data model would be a separate spec, not a TOON mode. + +## Prior requests + +- spec#29 – XML support +- toon discussion #220 – HTML, CSS, and JS as TOON +- toon#3 – GraphQL diff --git a/.out-of-scope/optional-array-length.md b/.out-of-scope/optional-array-length.md new file mode 100644 index 0000000..e446dc9 --- /dev/null +++ b/.out-of-scope/optional-array-length.md @@ -0,0 +1,24 @@ +# Optional Array Length + +Every array header declares its length: `key[N]:`. Requests to drop `[N]`, make it optional for tabular arrays (`users{id,name}:`), allow an unknown length (`[-]`), or write `items[]{…}:` for one element are out of scope. + +## Why this is out of scope + +The length is load-bearing. §6 makes `length` mandatory in the bracket segment, encoders MUST emit lengths that match the actual count (§13.1), and strict decoders MUST error when rows, items, or entries don't add up (§14.1). That count check is how a decoder notices truncated or injected data (§15) – and the Introduction lists it among the reasons to use TOON at all: + +> The `[N]` header is a fundamental invariant in TOON: every array declares its length explicitly. That property underpins strict‑mode validation (count checks, truncation detection), the "structure awareness / structural validation" behavior in the benchmarks, and a big part of TOON's differentiation from "CSV + indent." Making `[N]` optional for tabular arrays would weaken those guarantees and introduce a second, non‑canonical syntax for the same structure. +> – [spec#11](https://github.com/toon-format/spec/pull/11#issuecomment-3569473088) + +An optional length is also not backward compatible: `key[]:` is not a header under §6, so every existing strict decoder rejects it. + +For producers that can't know `N` up front: TOON is a translation layer over a value whose lengths are known. Count first and write the header after, or chunk the data into several arrays ([spec#15](https://github.com/toon-format/spec/issues/15#issuecomment-3569512191)). Streaming decode already works with declared lengths. For hand-edited files, edit JSON and convert, so `[N]` is generated rather than maintained ([toon#145](https://github.com/toon-format/toon/issues/145#issuecomment-3536756237)). + +Empty arrays are the one place without a count: `key: []` (§9.1). + +## Prior requests + +- spec#15 – arrays of unknown size +- spec#11 – optional implicit length for tabular arrays +- spec#19 – `items[]{…}` for length-1 arrays (the empty-array half was accepted as `key: []`) +- toon#135 – UTOON, an unbounded TOON variant +- toon#145 – why arrays need an item count diff --git a/.out-of-scope/query-language.md b/.out-of-scope/query-language.md new file mode 100644 index 0000000..0c96b63 --- /dev/null +++ b/.out-of-scope/query-language.md @@ -0,0 +1,18 @@ +# Query and Patch Language + +TOON defines no query or patch language – no "TOON-path" and no TOON-native patch operations. + +## Why this is out of scope + +TOON is a concrete syntax for the JSON data model (§2). Once decoded, the value is plain JSON, and every standard that operates on JSON values applies unchanged: + +> TOON does not define its own query or patch language. It is intentionally just a compact, line oriented concrete syntax for the JSON data model. +> – [spec#16](https://github.com/toon-format/spec/issues/16#issuecomment-3589490562) + +A TOON-specific path or patch syntax would duplicate those standards and give every implementation a second language to maintain. + +Decode, then use JSONPath, JMESPath, or jq for queries and JSON Pointer or JSON Patch (RFC 6901, RFC 6902) for updates. + +## Prior requests + +- spec#16 – TOON-path diff --git a/.out-of-scope/references-and-aliases.md b/.out-of-scope/references-and-aliases.md new file mode 100644 index 0000000..cc9a508 --- /dev/null +++ b/.out-of-scope/references-and-aliases.md @@ -0,0 +1,35 @@ +# References, Aliases, and Dictionary Encoding + +TOON has no references between values: no `$ID` aliases defined once and reused, no `@tables` foreign-key resolution, no per-column enumerations (`role(admin,user)` with cells `0`/`1`), and no key maps (`_map`) that shorten field names. + +## Why this is out of scope + +Every one of these turns a cell into a pointer, so the document stops being the JSON value it encodes. JSON has no references, and TOON encodes the JSON data model (§2): + +> JSON has no concept of references, foreign keys, or inter-document linking. Introducing `@tables` would make TOON a different kind of format: a relational data language with its own resolution semantics, hydration modes, and cycle-detection requirements. +> – [spec#27](https://github.com/toon-format/spec/issues/27#issuecomment-3941522699) + +The concrete costs: + +- **Two-pass decoding.** A reference can only be resolved once its target is known, so decoders buffer the whole document – which breaks streaming decode. +- **New escaping.** Literal values that look like references (`$100`) would suddenly need quoting, which changes the meaning of documents that are valid today ([spec#36](https://github.com/toon-format/spec/issues/36#issuecomment-4163086597)). +- **No canonical form.** The same JSON could be encoded with different alias names, thresholds, and orderings, while §13.1 requires encoders to pick the form from the value's shape, not by preference. +- **Little gain for LLMs.** Short values like `admin` are already single tokens, and a model has to map `0` back to `admin` in its head ([toon discussion #216](https://github.com/toon-format/toon/discussions/216#discussioncomment-15053977)). + +Repetition is a data-modeling problem: normalize before encoding. Encode the foreign key instead of the embedded object, or put the dictionary into the data itself, and TOON's tabular form compresses the result: + +```toon +roles[2]: admin,user + +users[3]{id,name,roleIndex}: + 1,Alice,0 + 2,Bob,1 + 3,Carol,1 +``` + +## Prior requests + +- spec#27 – relational references (`@tables`) +- spec#36 – value aliasing with `$ID` references +- spec discussion #14 – `_map` key compression and schema IDs +- toon discussion #216 – per-column enumerations and base-62 indices diff --git a/.out-of-scope/schema-language.md b/.out-of-scope/schema-language.md new file mode 100644 index 0000000..d056172 --- /dev/null +++ b/.out-of-scope/schema-language.md @@ -0,0 +1,23 @@ +# Schema Language + +TOON does not define a schema language, embed JSON Schema, or require decoders to validate against a schema. `SPEC.md` describes how JSON values are written as text, nothing more. + +## Why this is out of scope + +TOON is already JSON Schema compatible at the data-model level: any schema that validates a JSON value also validates the decoded output of the same TOON document. A schema authoring syntax inside TOON, with normative `$ref` or `allOf` resolution in decoders, would couple the spec to every future JSON Schema draft: + +> What the RFC is really asking for is a *schema authoring syntax* embedded in TOON, plus normative requirements on decoders to resolve `$ref`, evaluate `allOf`, etc. That's a much larger surface area, and it would couple the core spec to every future JSON Schema draft revision. I'd rather keep SPEC.md scoped to one job: *how JSON values are serialized as text*. +> – [spec#7](https://github.com/toon-format/spec/issues/7#issuecomment-3941530685) + +The Introduction draws the same line: TOON is "not an extended type system or schema language". Header syntax like `@SchemaName` would also break every conforming parser (§6 has no such production). + +What works today: decode, then validate with the tooling you already use. A JSON Schema document itself encodes as TOON like any other JSON value. A companion document or package that sits one layer above the format – never normative in `SPEC.md` – is welcome ([toon#126](https://github.com/toon-format/toon/pull/126#issuecomment-3571906444)). + +## Prior requests + +- spec#7 – make TOON JSON Schema compatible +- spec#17 – JSON Schema in TOON format +- toon#103, toon#109 – describe object schemas in TOON (closed as duplicates of spec#17) +- toon#126 – optional schema validation system with `@SchemaName` headers +- toon discussion #127 – schema validation for type-safe data handling +- toon discussion #140 – TOON Schema proof of concept diff --git a/.out-of-scope/trailing-newline.md b/.out-of-scope/trailing-newline.md new file mode 100644 index 0000000..32c9c7c --- /dev/null +++ b/.out-of-scope/trailing-newline.md @@ -0,0 +1,18 @@ +# Trailing Newline + +Encoders don't end a document with a newline (§12, §13.1). Requests to make a trailing newline the encoder output are out of scope. + +## Why this is out of scope + +An encoder returns the serialized value, not a file. `JSON.stringify()` doesn't append a newline either: + +> TOON follows the same convention as `JSON.stringify()`: the output is the serialized data structure itself, not a file format with specific storage requirements. +> – [toon#23](https://github.com/toon-format/toon/issues/23#issuecomment-3464114715) + +Concatenating two documents doesn't produce a valid document with or without the newline, for TOON as for JSON. + +Decoders SHOULD accept a trailing newline at end of file (§12), so files written by editors or by `echo` decode fine. Add the newline when you write to disk. + +## Prior requests + +- toon#23 – trailing newline at end of output diff --git a/.out-of-scope/type-annotations.md b/.out-of-scope/type-annotations.md new file mode 100644 index 0000000..c02fd00 --- /dev/null +++ b/.out-of-scope/type-annotations.md @@ -0,0 +1,23 @@ +# Type Annotations + +TOON has no type annotations – not on header fields (`{id:int,name:str}`), not on keys (`id: 123`), and no reserved key that declares a document's type (`$type: HikePlan`). + +## Why this is out of scope + +A value's type already follows from its token shape (§4): `true`/`false`/`null` decode to booleans and null, a token matching the number grammar decodes to a number, everything else – including every quoted token – decodes to a string. There is no ambiguity for an annotation to resolve. The Introduction says so directly: TOON "carries the JSON data model; it is a transport/authoring format with explicit structure, not an extended type system or schema language." + +What these requests usually want is different: making an LLM emit `42` instead of `"42"` for a field that should be numeric. That's a constraint on expected values, and the spec only defines how tokens map to values. As the ruling on spec#31 put it: + +> What you're really describing (constraining an LLM to emit `42` instead of `"42"` for a known-numeric field) is a **schema and grammar-constraint concern**, not a wire format concern. That belongs in tooling built on top of TOON (e.g. JSON Schema validation, constrained decoding grammars for LLM samplers), not in the core spec. +> – [spec#31](https://github.com/toon-format/spec/issues/31#issuecomment-3941517754) + +Annotations would also cost tokens on every header and add a second syntax inside field lists (§6) that every decoder has to parse. + +For typed output, validate after decoding – JSON Schema, Zod, Pydantic – or constrain generation with a sampler grammar. A document-level tag like `type: HikePlan` already works as an ordinary key the application reads ([spec#28](https://github.com/toon-format/spec/issues/28#issuecomment-3941514580)). + +## Prior requests + +- spec#31 – type annotations on header fields +- spec#28 – explicitly declare the output type +- spec discussion #10 – column types in the header +- toon#6 – optional types on keys From e2e1a440ebcfedd901dbf5502d86cc6912ff051e Mon Sep 17 00:00:00 2001 From: Johann Schopplich Date: Thu, 1 Oct 2026 14:52:57 +0200 Subject: [PATCH 2/3] docs: tighten out-of-scope records --- .out-of-scope/binary-encoding.md | 4 ++-- .out-of-scope/custom-delimiters.md | 4 ++-- .out-of-scope/encoder-data-rewrites.md | 24 +++--------------------- .out-of-scope/lenient-quoted-strings.md | 8 ++------ .out-of-scope/mixed-columnar-arrays.md | 13 +++---------- .out-of-scope/multi-document-streams.md | 15 +++------------ .out-of-scope/non-json-data-models.md | 8 +++----- .out-of-scope/optional-array-length.md | 10 +++------- .out-of-scope/query-language.md | 6 ++---- .out-of-scope/references-and-aliases.md | 22 ++++------------------ .out-of-scope/schema-language.md | 10 ++++------ .out-of-scope/trailing-newline.md | 8 +++----- .out-of-scope/type-annotations.md | 8 ++------ 13 files changed, 36 insertions(+), 104 deletions(-) diff --git a/.out-of-scope/binary-encoding.md b/.out-of-scope/binary-encoding.md index 88b85d2..c0ccf3a 100644 --- a/.out-of-scope/binary-encoding.md +++ b/.out-of-scope/binary-encoding.md @@ -4,12 +4,12 @@ TOON is a text format. A binary TOON wire format is out of scope, in the spec an ## Why this is out of scope -TOON exists to put structured data into LLM prompts with fewer tokens (Introduction, "Purpose and Scope"). Models read text, so a binary encoding would always be decoded back to text before use – it saves bytes on the wire, which is not the cost TOON targets. It would also need its own spec, its own conformance suite, and implementations in every language: +TOON saves tokens, not bytes, and a model reads text, so a binary encoding would always be decoded back to text first. Compression fails for the same reason: the model tokenizes the decompressed text ([toon#125](https://github.com/toon-format/toon/issues/125#issuecomment-3528565342)). > Adding a second "Binary TOON" wire format would require a separate spec, cross‑language implementations, and long‑term maintenance, while not really helping the primary use case (LLMs still need text, so this would always be decoded before use). For general binary serialization there are already well‑established options like CBOR/MessagePack. > – [toon#201](https://github.com/toon-format/toon/pull/201#issuecomment-3566643061) -Compression is the same story: gzip reduces bytes, but a model tokenizes the decompressed text, so token counts stay the same ([toon#125](https://github.com/toon-format/toon/issues/125#issuecomment-3528565342)). Binary serialization belongs to CBOR or MessagePack; a binary TOON variant can live as its own package. +A binary variant can live as its own package. ## Prior requests diff --git a/.out-of-scope/custom-delimiters.md b/.out-of-scope/custom-delimiters.md index abe6a4d..47cd709 100644 --- a/.out-of-scope/custom-delimiters.md +++ b/.out-of-scope/custom-delimiters.md @@ -4,12 +4,12 @@ The delimiter set is comma, tab, and pipe (§11). Other delimiters – non-ASCII ## Why this is out of scope -The header declares the active delimiter in the bracket segment, and §6's grammar admits exactly two symbols there (`delimsym = HTAB / "|"`; absent means comma). Every decoder splits rows on that closed set, and encoders quote strings that contain the active delimiter (§7.2, §11.1). Opening the set means new grammar, new quoting rules, and a header symbol for each new delimiter: +The header declares the delimiter from a closed set (§6: `delimsym = HTAB / "|"`, absent means comma), and every decoder splits and every encoder quotes against that set (§7.2, §11). Each new delimiter means new grammar and new quoting rules in every implementation: > I want to keep the spec to comma/tab/pipe to stay ASCII-only for maximum interop, editor/terminal safety, and predictable tokenization. Non-ASCII delimiters like ✦ would add complexity and may be token-inefficient on some models. > – [toon#133](https://github.com/toon-format/toon/issues/133#issuecomment-3532885149) -The existing set already covers readability: tab for tables (often token-cheaper too), pipe for a visible ASCII separator. Decoders trim spaces around cells (§12), so `1, Alice, admin` decodes fine when written by hand. +Tab and pipe already cover readability. ## Prior requests diff --git a/.out-of-scope/encoder-data-rewrites.md b/.out-of-scope/encoder-data-rewrites.md index 4c3030c..b974a91 100644 --- a/.out-of-scope/encoder-data-rewrites.md +++ b/.out-of-scope/encoder-data-rewrites.md @@ -1,33 +1,15 @@ # Encoder-Side Data Rewrites -Encoders don't rewrite the data to make it fit a more compact form. That rules out filling missing fields with `null` so semi-uniform arrays qualify for tabular form (`fillna`), dropping `null`, empty-string, or empty-array fields (`ignoreNullOrEmpty`, `excludeEmptyArrays`), splitting semi-uniform arrays into base and extras tables (`normalizeForToon()`), and abbreviating, renaming, or reordering keys to save tokens. +Encoders don't rewrite the data to fit a more compact form. That rules out filling missing fields with `null` so semi-uniform arrays qualify for tabular form, dropping `null`, empty-string, or empty-array fields, splitting semi-uniform arrays into base and extras tables, and abbreviating, renaming, or reordering keys. ## Why this is out of scope -Each of these changes the value being encoded. Under §2's JSON-model equality, objects compare by their key sequence, so a missing key and a key set to `null` are different values: - -```ts -// in: { sku: 'B2', qty: 2 } -// out: { sku: 'B2', price: null, qty: 2 } -``` - -Keys are part of the value too: "Object key order MUST be preserved as encountered by the encoder" (§2), and an encoder that turns `customer_name` into `cn` emits a document that no other decoder can map back. - -A decoder can't tell a filled-in `null` from a real one, can't restore a field that was dropped, and can't undo a renamed key. `decode(encode(x))` stops returning `x`, which is the guarantee the whole format rests on. TOON saves tokens through its syntax – lengths and field lists declared once (§9.3, §9.5), minimal quoting (§7.2) – not by changing the data. Whether `null` and "absent" mean the same thing, or which short key names a prompt can live with, is a property of your data, not of the format: +Each of these changes the value, so `decode(encode(x))` stops returning `x` under §2's JSON-model equality: objects compare by their ordered key sequence, a missing key and a key set to `null` are different values, and no decoder can restore a dropped field or undo a renamed key. Whether `null` and "absent" mean the same thing, or which short names a prompt can live with, is a property of your data, not of the format: > TOON is a **transport format**: it encodes the JSON data model as text, and that's where its responsibility ends. Splitting semi-uniform arrays into base + extras tables is a **data transformation step** that belongs in the application layer, not inside the encoder. > – [toon#292](https://github.com/toon-format/toon/pull/292#issuecomment-4162837784) -The same ruling covered key abbreviation in the Python port: "These are great ideas but belong in separate packages or higher-level wrappers" ([toon-python#35](https://github.com/toon-format/toon-python/pull/35#issuecomment-4073056071)). - -Normalize before encoding when your data allows it. Filling in `null` takes one line: - -```ts -const fields = [...new Set(items.flatMap(Object.keys))] -encode({ items: items.map(item => Object.fromEntries(fields.map(f => [f, item[f] ?? null]))) }) -``` - -Shorter keys work the same way, and the mapping stays in your code, where it can also go into the prompt. Dropping fields works through the `replacer` option of the TypeScript reference, which returns `undefined` to omit a value. +[toon-python#35](https://github.com/toon-format/toon-python/pull/35#issuecomment-4073056071) sent key abbreviation to higher-level wrappers as well. Normalize before encoding where your data allows it. ## Prior requests diff --git a/.out-of-scope/lenient-quoted-strings.md b/.out-of-scope/lenient-quoted-strings.md index 025dabd..3e3603b 100644 --- a/.out-of-scope/lenient-quoted-strings.md +++ b/.out-of-scope/lenient-quoted-strings.md @@ -4,13 +4,9 @@ A token that starts with `"` must end at its closing quote (§7.4, "Quoted-token ## Why this is out of scope -v4.1 made the boundary normative in strict and non-strict mode alike: any character after the closing quote MUST error (§7.4, §14.2). Without it, a decoder has to guess whether a leading `"` opens a quoted string or is a literal character – `"hello" said Alice` could decode to `hello said Alice` or to `"hello" said Alice`, and two decoders could pick differently. A decoder that silently repairs input also can't tell a model's mistake from data. +v4.1 made characters after the closing quote an error in strict and non-strict mode alike (§7.4, §14.2). Without the rule, a decoder has to guess whether a leading `"` opens a quoted string or is literal data, and two decoders can guess differently. A decoder that repairs input silently also can't tell a model's mistake from data. -Model output that breaks the quoting rules is the application's problem to handle before or after decoding, not the format's. What works in practice: - -- **Show the escape in the prompt.** An example like `text: "\"hello\" said Alice"` gets copied far more reliably than a description of the rule. -- **Retry on a strict decode error.** The decode fails at the offending line, so a re-prompt can point at it. -- **Repair before decoding** if your pipeline needs best-effort parsing – as an application step, not a decoder mode. +Malformed model output is the application's to handle: show the escaped form in the prompt, re-prompt on the strict decode error, or repair before decoding. ## Prior requests diff --git a/.out-of-scope/mixed-columnar-arrays.md b/.out-of-scope/mixed-columnar-arrays.md index 85b76f0..017b956 100644 --- a/.out-of-scope/mixed-columnar-arrays.md +++ b/.out-of-scope/mixed-columnar-arrays.md @@ -1,22 +1,15 @@ # Mixed Columnar Arrays -Tabular rows carry primitive cells only. Proposals that let a row continue with nested content – spill lines under a row, nested tabular blocks per row, or an encoder switch like `objectArrayLayout: "columnar"` that opts into such a layout – are out of scope. +Tabular rows carry primitive cells only. Proposals that let a row continue with nested content – spill lines under a row, nested tabular blocks per row, or an encoder option like `objectArrayLayout: "columnar"` that opts into such a layout – are out of scope. ## Why this is out of scope -The tabular form is useful because it is constrained: one line per element, one primitive cell per leaf field (§9.3). Row boundaries are trivial to find, and a strict decoder checks both row count and row width (§14.1). Hanging nested lines off a row turns rows into trees: +The tabular form is useful because it is constrained: one line per element and one primitive cell per leaf field, so row boundaries are trivial and a strict decoder checks row count and width (§9.3, §14.1). Nested lines under a row turn rows into trees: > This proposal removes that constraint and turns rows into tree-structured entities – effectively creating a second tree-encoding path alongside the existing list/object syntax. > – [spec#21](https://github.com/toon-format/spec/issues/21#issuecomment-4163086929) -In 4.x the syntax is taken, too: a line deeper than a row is an over-indented line, which strict decoders MUST reject (§14.2). And an encoder option that picks the layout conflicts with §1.4 and §13.1 – the form follows from the value's shape and position, not from encoder preference – so one JSON value has one TOON rendering. - -v4 covers the uniform part of this shape without a second tree syntax: - -- **Nested field groups (§9.3)** put uniform nested objects into the header: `orders[2]{id,customer{name,country},total}:`. -- **Keyed tabular form (§9.5)** handles objects of uniform objects. - -An array whose elements differ, or carry arrays or empty objects, stays in list form (§9.4). That is more verbose, but simple and unambiguous. +A layout option also conflicts with §1.4: the form follows from the value's shape and position, not from encoder preference. Uniform nested objects already fit the header as nested field groups (§9.3); arrays whose elements differ stay in list form (§9.4). ## Prior requests diff --git a/.out-of-scope/multi-document-streams.md b/.out-of-scope/multi-document-streams.md index 1dfbafa..9276385 100644 --- a/.out-of-scope/multi-document-streams.md +++ b/.out-of-scope/multi-document-streams.md @@ -1,24 +1,15 @@ # Multi-Document Streams -A TOON document holds one value. There is no multi-document syntax – no `---` separators, no "TOON Lines" counterpart to JSON Lines – and the reference library doesn't ship framing utilities for concatenated documents. +A TOON document holds one value. There is no multi-document syntax – no `---` separators, no "TOON Lines" counterpart to JSON Lines – and the reference library stays single-document. ## Why this is out of scope -JSON Lines is a framing format around independent JSON values. TOON's root form spans the whole document (§5): once a root array or keyed tabular root is complete, strict decoders MUST reject any further content (§14.2). A separator line would need a new line class, new root-form rules, and new strict-mode errors in every implementation, for structure TOON can already express: +TOON's root form spans the whole document: once a root array or keyed tabular root is complete, strict decoders MUST reject further content (§5, §14.2). A separator would need a new line class and new root-form rules in every implementation, for structure TOON already expresses: > JSON Lines is a *framing* format (a sequence of independent JSON values), while TOON is deliberately defined as "one JSON value per document", with no multi‑document syntax or directives. > – [toon#120](https://github.com/toon-format/toon/issues/120#issuecomment-3558763808) -The only structure a JSONL stream carries is an ordered sequence. Encode it as one document with an array root, and uniform records get tabular form: - -```toon -[3]{id,event}: - 1,signup - 2,login - 3,logout -``` - -On the library side, `@toon-format/toon` stays single-document. Streaming one large document is covered by `encodeLines` and `decodeStream`. Document separators, concatenated-JSON parsing, and batch encode/decode are framing utilities that fit a companion package built on top of `encode` and `decode` ([toon#163](https://github.com/toon-format/toon/issues/163#issuecomment-3559409605)). +The only structure a JSONL stream carries is an ordered sequence, so encode it as one document with an array root. Separators and batch encode/decode are framing utilities for a companion package built on `encode` and `decode` ([toon#163](https://github.com/toon-format/toon/issues/163#issuecomment-3559409605)). ## Prior requests diff --git a/.out-of-scope/non-json-data-models.md b/.out-of-scope/non-json-data-models.md index 7c59e27..2e565a6 100644 --- a/.out-of-scope/non-json-data-models.md +++ b/.out-of-scope/non-json-data-models.md @@ -1,17 +1,15 @@ # Non-JSON Data Models -TOON encodes the JSON data model and nothing else. Modes or extensions for XML (attributes, namespaces), HTML/CSS/JS markup, or GraphQL are out of scope. +TOON encodes the JSON data model and nothing else (§2). Modes or extensions for XML (attributes, namespaces), HTML/CSS/JS markup, or GraphQL are out of scope. ## Why this is out of scope -§2 defines the whole data model: strings, numbers, booleans, null, objects, and arrays. A second model inside the same syntax means the same document decodes differently depending on a flag: +A second data model inside the same syntax makes one document decode differently depending on a flag: > **Ambiguity.** The same TOON document would decode differently depending on a `mode` flag. A key like `id: 123` is a plain key-value pair in JSON mode but an XML attribute in XML mode. That breaks TOON's promise of deterministic, unambiguous encoding. > – [spec#29](https://github.com/toon-format/spec/pull/29#issuecomment-4163086242) -It would also change the grammar under existing parsers – XML namespace prefixes need colons in unquoted keys, which §5.2 and §7.4 treat as the key-value separator – and add normative sections every implementation must carry even if it never sees XML. - -Convert first, then encode: XML to a JSON representation with `xml2js` or `xmltodict`, and encode that. Markup languages already have dedicated tools – Pug, HAML, JSX ([toon discussion #220](https://github.com/toon-format/toon/discussions/220#discussioncomment-15070508)). A companion format for another data model would be a separate spec, not a TOON mode. +It would also change the grammar under existing parsers – namespace prefixes put a colon into the key, where §5.2 ends it – and add normative sections every implementation carries even if it never sees XML. Convert to JSON first and encode that; markup already has dedicated tools ([toon discussion #220](https://github.com/toon-format/toon/discussions/220#discussioncomment-15070508)). A format for another data model is a separate spec, not a TOON mode. ## Prior requests diff --git a/.out-of-scope/optional-array-length.md b/.out-of-scope/optional-array-length.md index e446dc9..0fefcff 100644 --- a/.out-of-scope/optional-array-length.md +++ b/.out-of-scope/optional-array-length.md @@ -1,19 +1,15 @@ # Optional Array Length -Every array header declares its length: `key[N]:`. Requests to drop `[N]`, make it optional for tabular arrays (`users{id,name}:`), allow an unknown length (`[-]`), or write `items[]{…}:` for one element are out of scope. +Every array header declares its length: `key[N]:`. Dropping `[N]`, making it optional for tabular arrays, allowing an unknown length (`[-]`), or writing `items[]{…}:` for one element is out of scope. ## Why this is out of scope -The length is load-bearing. §6 makes `length` mandatory in the bracket segment, encoders MUST emit lengths that match the actual count (§13.1), and strict decoders MUST error when rows, items, or entries don't add up (§14.1). That count check is how a decoder notices truncated or injected data (§15) – and the Introduction lists it among the reasons to use TOON at all: +The length is load-bearing: §6 makes it mandatory, encoders MUST emit the actual count (§13.1), and strict decoders MUST error when rows, items, or entries don't add up (§14.1) – that check is how truncated or injected data gets noticed (§15). > The `[N]` header is a fundamental invariant in TOON: every array declares its length explicitly. That property underpins strict‑mode validation (count checks, truncation detection), the "structure awareness / structural validation" behavior in the benchmarks, and a big part of TOON's differentiation from "CSV + indent." Making `[N]` optional for tabular arrays would weaken those guarantees and introduce a second, non‑canonical syntax for the same structure. > – [spec#11](https://github.com/toon-format/spec/pull/11#issuecomment-3569473088) -An optional length is also not backward compatible: `key[]:` is not a header under §6, so every existing strict decoder rejects it. - -For producers that can't know `N` up front: TOON is a translation layer over a value whose lengths are known. Count first and write the header after, or chunk the data into several arrays ([spec#15](https://github.com/toon-format/spec/issues/15#issuecomment-3569512191)). Streaming decode already works with declared lengths. For hand-edited files, edit JSON and convert, so `[N]` is generated rather than maintained ([toon#145](https://github.com/toon-format/toon/issues/145#issuecomment-3536756237)). - -Empty arrays are the one place without a count: `key: []` (§9.1). +It isn't backward compatible either: strict decoders reject `key[]:` (§6). A producer that can't know `N` up front counts first or chunks the data into several arrays ([spec#15](https://github.com/toon-format/spec/issues/15#issuecomment-3569512191)); hand-edited data is better edited as JSON and converted, so `[N]` is generated ([toon#145](https://github.com/toon-format/toon/issues/145#issuecomment-3536756237)). ## Prior requests diff --git a/.out-of-scope/query-language.md b/.out-of-scope/query-language.md index 0c96b63..7df3980 100644 --- a/.out-of-scope/query-language.md +++ b/.out-of-scope/query-language.md @@ -4,14 +4,12 @@ TOON defines no query or patch language – no "TOON-path" and no TOON-native pa ## Why this is out of scope -TOON is a concrete syntax for the JSON data model (§2). Once decoded, the value is plain JSON, and every standard that operates on JSON values applies unchanged: +A decoded TOON document is a plain JSON value (§2), so every standard that operates on JSON applies unchanged, and a TOON-specific language would duplicate them: > TOON does not define its own query or patch language. It is intentionally just a compact, line oriented concrete syntax for the JSON data model. > – [spec#16](https://github.com/toon-format/spec/issues/16#issuecomment-3589490562) -A TOON-specific path or patch syntax would duplicate those standards and give every implementation a second language to maintain. - -Decode, then use JSONPath, JMESPath, or jq for queries and JSON Pointer or JSON Patch (RFC 6901, RFC 6902) for updates. +Decode, then query with JSONPath, JMESPath, or jq and update with JSON Pointer or JSON Patch. ## Prior requests diff --git a/.out-of-scope/references-and-aliases.md b/.out-of-scope/references-and-aliases.md index cc9a508..2ca279b 100644 --- a/.out-of-scope/references-and-aliases.md +++ b/.out-of-scope/references-and-aliases.md @@ -4,32 +4,18 @@ TOON has no references between values: no `$ID` aliases defined once and reused, ## Why this is out of scope -Every one of these turns a cell into a pointer, so the document stops being the JSON value it encodes. JSON has no references, and TOON encodes the JSON data model (§2): +Each of these turns a cell into a pointer, and the JSON data model TOON encodes (§2) has none: > JSON has no concept of references, foreign keys, or inter-document linking. Introducing `@tables` would make TOON a different kind of format: a relational data language with its own resolution semantics, hydration modes, and cycle-detection requirements. > – [spec#27](https://github.com/toon-format/spec/issues/27#issuecomment-3941522699) -The concrete costs: +Resolving references means buffering the whole document, which breaks streaming decode; literal values that look like references (`$100`) would suddenly need quoting ([spec#36](https://github.com/toon-format/spec/issues/36#issuecomment-4163086597)); and one JSON value could encode with different aliases, where §1.4 lets only its shape and position pick the rendering. The gain is small, too: values like `admin` are already single tokens, and a model has to map `0` back to `admin` ([toon discussion #216](https://github.com/toon-format/toon/discussions/216#discussioncomment-15053977)). -- **Two-pass decoding.** A reference can only be resolved once its target is known, so decoders buffer the whole document – which breaks streaming decode. -- **New escaping.** Literal values that look like references (`$100`) would suddenly need quoting, which changes the meaning of documents that are valid today ([spec#36](https://github.com/toon-format/spec/issues/36#issuecomment-4163086597)). -- **No canonical form.** The same JSON could be encoded with different alias names, thresholds, and orderings, while §13.1 requires encoders to pick the form from the value's shape, not by preference. -- **Little gain for LLMs.** Short values like `admin` are already single tokens, and a model has to map `0` back to `admin` in its head ([toon discussion #216](https://github.com/toon-format/toon/discussions/216#discussioncomment-15053977)). - -Repetition is a data-modeling problem: normalize before encoding. Encode the foreign key instead of the embedded object, or put the dictionary into the data itself, and TOON's tabular form compresses the result: - -```toon -roles[2]: admin,user - -users[3]{id,name,roleIndex}: - 1,Alice,0 - 2,Bob,1 - 3,Carol,1 -``` +Repetition is a data-modeling problem: normalize before encoding – encode the foreign key instead of the embedded object, or put the dictionary into the data as an ordinary array. ## Prior requests - spec#27 – relational references (`@tables`) - spec#36 – value aliasing with `$ID` references -- spec discussion #14 – `_map` key compression and schema IDs +- spec discussion #14 – `_map` key compression - toon discussion #216 – per-column enumerations and base-62 indices diff --git a/.out-of-scope/schema-language.md b/.out-of-scope/schema-language.md index d056172..4a36c4c 100644 --- a/.out-of-scope/schema-language.md +++ b/.out-of-scope/schema-language.md @@ -1,23 +1,21 @@ # Schema Language -TOON does not define a schema language, embed JSON Schema, or require decoders to validate against a schema. `SPEC.md` describes how JSON values are written as text, nothing more. +TOON does not define a schema language, embed JSON Schema, or require decoders to validate against a schema. ## Why this is out of scope -TOON is already JSON Schema compatible at the data-model level: any schema that validates a JSON value also validates the decoded output of the same TOON document. A schema authoring syntax inside TOON, with normative `$ref` or `allOf` resolution in decoders, would couple the spec to every future JSON Schema draft: +TOON is already JSON Schema compatible at the data-model level: a schema that validates a JSON value validates the decoded TOON document too. What these requests add is schema syntax inside TOON, with decoders required to resolve it: > What the RFC is really asking for is a *schema authoring syntax* embedded in TOON, plus normative requirements on decoders to resolve `$ref`, evaluate `allOf`, etc. That's a much larger surface area, and it would couple the core spec to every future JSON Schema draft revision. I'd rather keep SPEC.md scoped to one job: *how JSON values are serialized as text*. > – [spec#7](https://github.com/toon-format/spec/issues/7#issuecomment-3941530685) -The Introduction draws the same line: TOON is "not an extended type system or schema language". Header syntax like `@SchemaName` would also break every conforming parser (§6 has no such production). - -What works today: decode, then validate with the tooling you already use. A JSON Schema document itself encodes as TOON like any other JSON value. A companion document or package that sits one layer above the format – never normative in `SPEC.md` – is welcome ([toon#126](https://github.com/toon-format/toon/pull/126#issuecomment-3571906444)). +Header syntax like `@SchemaName` would also break every conforming parser (§6). Decode, then validate with the tooling you already use; a JSON Schema document itself encodes as TOON like any other value. A companion document or package one layer above the format – never normative in `SPEC.md` – is welcome ([toon#126](https://github.com/toon-format/toon/pull/126#issuecomment-3571906444)). ## Prior requests - spec#7 – make TOON JSON Schema compatible - spec#17 – JSON Schema in TOON format -- toon#103, toon#109 – describe object schemas in TOON (closed as duplicates of spec#17) +- toon#103, toon#109 – describe object schemas in TOON - toon#126 – optional schema validation system with `@SchemaName` headers - toon discussion #127 – schema validation for type-safe data handling - toon discussion #140 – TOON Schema proof of concept diff --git a/.out-of-scope/trailing-newline.md b/.out-of-scope/trailing-newline.md index 32c9c7c..a43682e 100644 --- a/.out-of-scope/trailing-newline.md +++ b/.out-of-scope/trailing-newline.md @@ -1,17 +1,15 @@ # Trailing Newline -Encoders don't end a document with a newline (§12, §13.1). Requests to make a trailing newline the encoder output are out of scope. +Encoders don't end a document with a newline (§12, §13.1). Requests to make a trailing newline part of the encoder output are out of scope. ## Why this is out of scope -An encoder returns the serialized value, not a file. `JSON.stringify()` doesn't append a newline either: +An encoder returns the serialized value, not a file: > TOON follows the same convention as `JSON.stringify()`: the output is the serialized data structure itself, not a file format with specific storage requirements. > – [toon#23](https://github.com/toon-format/toon/issues/23#issuecomment-3464114715) -Concatenating two documents doesn't produce a valid document with or without the newline, for TOON as for JSON. - -Decoders SHOULD accept a trailing newline at end of file (§12), so files written by editors or by `echo` decode fine. Add the newline when you write to disk. +A newline wouldn't make concatenated documents valid either. Decoders SHOULD accept a trailing newline (§12), so add one when you write to disk. ## Prior requests diff --git a/.out-of-scope/type-annotations.md b/.out-of-scope/type-annotations.md index c02fd00..a0ef9bd 100644 --- a/.out-of-scope/type-annotations.md +++ b/.out-of-scope/type-annotations.md @@ -4,16 +4,12 @@ TOON has no type annotations – not on header fields (`{id:int,name:str}`), not ## Why this is out of scope -A value's type already follows from its token shape (§4): `true`/`false`/`null` decode to booleans and null, a token matching the number grammar decodes to a number, everything else – including every quoted token – decodes to a string. There is no ambiguity for an annotation to resolve. The Introduction says so directly: TOON "carries the JSON data model; it is a transport/authoring format with explicit structure, not an extended type system or schema language." - -What these requests usually want is different: making an LLM emit `42` instead of `"42"` for a field that should be numeric. That's a constraint on expected values, and the spec only defines how tokens map to values. As the ruling on spec#31 put it: +A value's type already follows from its token shape (§4): `true`, `false`, and `null` are literals, a token matching the number grammar is a number, and everything else – including every quoted token – is a string. There is no ambiguity for an annotation to resolve, and TOON is "not an extended type system or schema language" (Introduction). What these requests want is an LLM that emits `42` instead of `"42"` for a numeric field: > What you're really describing (constraining an LLM to emit `42` instead of `"42"` for a known-numeric field) is a **schema and grammar-constraint concern**, not a wire format concern. That belongs in tooling built on top of TOON (e.g. JSON Schema validation, constrained decoding grammars for LLM samplers), not in the core spec. > – [spec#31](https://github.com/toon-format/spec/issues/31#issuecomment-3941517754) -Annotations would also cost tokens on every header and add a second syntax inside field lists (§6) that every decoder has to parse. - -For typed output, validate after decoding – JSON Schema, Zod, Pydantic – or constrain generation with a sampler grammar. A document-level tag like `type: HikePlan` already works as an ordinary key the application reads ([spec#28](https://github.com/toon-format/spec/issues/28#issuecomment-3941514580)). +Annotations would also cost tokens on every header. Validate after decoding or constrain generation with a sampler grammar; a document-level `type: HikePlan` already works as an ordinary key ([spec#28](https://github.com/toon-format/spec/issues/28#issuecomment-3941514580)). ## Prior requests From 4bcb6bc7c0eaccfd267d027ad706b9bfcc7add81 Mon Sep 17 00:00:00 2001 From: Johann Schopplich Date: Thu, 1 Oct 2026 15:01:41 +0200 Subject: [PATCH 3/3] docs: correct comment-line and key-folding details in out-of-scope records --- .out-of-scope/inline-annotations.md | 2 +- .out-of-scope/key-folding.md | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.out-of-scope/inline-annotations.md b/.out-of-scope/inline-annotations.md index b196c3a..f0ff07c 100644 --- a/.out-of-scope/inline-annotations.md +++ b/.out-of-scope/inline-annotations.md @@ -8,7 +8,7 @@ Inline comments intersect every quoting rule in the spec at once: §7.2 (string The underlying need is real but is served without grammar changes: -- **Hand-authored documents** (config files, prompt schemas): v4 adopts *full-line* `#` comments – a line whose first non-whitespace character is `#` is stripped before parsing. In a hand-authored prompt the model reads those lines, so field-adjacent guidance works by placing a comment line above the field. +- **Hand-authored documents** (config files, prompt schemas): v4 adopts *full-line* `#` comments – a line whose first character after leading spaces is `#` is stripped before parsing. In a hand-authored prompt the model reads those lines, so field-adjacent guidance works by placing a comment line above the field. - **Programmatic per-field annotations that must survive encode**: use a `_note:` key convention – annotations as data – or the `rawString` replacer primitive (toon#308/toon#321) for controlled raw output. The distinction matters: decode-side comments are never *emitted* by encoders (JSON has no comments to encode), so anything that must flow through `encode()` has to be data, not comment syntax. diff --git a/.out-of-scope/key-folding.md b/.out-of-scope/key-folding.md index bbae330..e60c7ec 100644 --- a/.out-of-scope/key-folding.md +++ b/.out-of-scope/key-folding.md @@ -8,11 +8,11 @@ Measured and verified against the reference implementation before removal: - **Zero benefit on real data.** 0.00% token savings on all six of the project's own benchmark datasets – folding only fires on single-key chains, which realistic tabular/nested data lacks. The published benchmark numbers never enabled it. - **Wire ambiguity by construction.** `encode({'a.b.c': 1})` and `encode({a:{b:{c:1}}}, {keyFolding:'safe'})` produce the byte-identical document, so `expandPaths` cannot distinguish a folded chain from a genuine dotted literal key – it silently corrupts the latter. Two distinct JSON values collapsing to one wire document breaks TOON's determinism and round-trip guarantees. -- **Streaming-incompatible by design.** Expansion requires the fully materialized tree (§13.4 applied it after all parsing); the reference streaming decoder throws on it. It was the only feature in the spec that prevented streaming decode. +- **Streaming-incompatible by design.** Expansion requires the fully materialized tree (§13.4 applied it after all parsing); the reference streaming decoder threw on it. It was the only feature in the spec that prevented streaming decode. - **Security surface.** The `IdentifierSegment` pattern admitted `__proto__`, enabling prototype pollution through path expansion (fixed in the v2.x line; the v4 spec makes prototype-key handling normative in §15 independent of folding). - **Round-trip asymmetry.** Both options defaulted to `off`, so correctness depended on out-of-band option agreement between producer and consumer. -Folded documents remain valid TOON forever: dotted keys decode as literal keys (a MUST since v3.x). Users who need dotted-path notation can expand in userland after decode – with the caveat that userland expansion must guard `__proto__`/`constructor`/`prototype`. +Folded documents remain valid TOON forever: dotted keys decode as literal keys (a MUST since v4.0). Users who need dotted-path notation can expand in userland after decode – with the caveat that userland expansion must guard `__proto__`/`constructor`/`prototype`. ## Prior requests