Requests for comment · RFC 0011
Freezing the grammar: this is not YAML
The corpus now has three normative surfaces, nine worked contracts and a working parser, and they disagree in small ways that all point one direction: the written grammar lags the fixtures, so the fixtures have quietly become the specification. This RFC proposes the freeze - state plainly that the language is not YAML, lock one spelling per construct, freeze the document, block and expression layers while naming the two holes that stay open, and make the lock executable.
- STATUS
- Open for comment
- TOPICS
- Surface syntax, why it only looks like YAML, one spelling per construct, a layered freeze, canonical signing form
This is a request for comment, not part of the specification. The syntax is a strawman and much of it is pseudo-code; it exists to be argued with, not adopted by default.
The situation
| Surface | Says |
|---|---|
| The standard’s worked example | bare positional declarations, a forbidden marking verb, the reject-trigger form of the guard |
| The registry (newest) | the assertion form of the guard is canonical, the marking verb is forbidden, and the record verb has two forms |
| The surface-grammar draft (oldest) | the reject-trigger form as the only gate, a positive-duty sugar the registry forbids, two verbs the registry rejects, and “no user functions” - which the registry has since adopted |
| The nine worked contracts (most recent) | colon-form declarations, module imports, split tables, conditional ownership transfer |
| The parser (tests green) | follows the registry |
Nobody is wrong on purpose. But three of the grammar draft’s own open questions were answered by the fixtures and the parser without ever being recorded - which is the definition of a de-facto specification.
This is not YAML - say it out loud
The declaration layer deliberately borrows YAML’s look: name: value, two-space indentation, block structure. That was a good decision for readability and a dangerous one to leave unstated, because a reader who assumes YAML assumes things that are false here: anchors and aliases, merge keys, implicit typing, multi-document streams, flow style, tags.
YAML’s own history is the argument. Implicit typing is why a bare no becomes false, why version-like strings become numbers, and why a strict subset had to be invented as a reaction. A signed legal document is the last place on earth for a value whose type depends on a parser’s guess.
Proposal, stated in the standard and enforced by the parser:
- The language is a line-oriented block grammar with a closed keyword set. It is not YAML, not a YAML subset, and no YAML parser may be used to read it.
- Numbers are typed by suffix; a bare number is a syntax error.
895 SEK,3000 h,4%,30 days,2027-01-01. - Strings carry human meaning only. No rule may branch on string content, enforced by the type system rather than by convention.
- Colons after names, nothing else - the form the fixtures and the parser already settled on.
- No anchors, no aliases, no implicit typing, no flow style, no tags, no multi-document streams. Absence is prohibition, applied to syntax.
One spelling per construct
The vocabulary sweep is RFC 0007; the grammar carries the shapes. Locked here:
| Symbol or shape | Means, and only means |
|---|---|
:= |
binds a name, once, immutably |
= |
records a fact into declared state |
== |
compares |
-> |
in a table row or transition: becomes; in a rule: the effect of a gate |
| pipe-delimited rows | a tabular term, with a declared kind (unique or ladder) |
| two-space indent, max depth three | block, entry, branch. Tabs illegal. The linter refuses rather than guessing |
# |
comment to end of line - binding nobody, hashed with the document |
Plus the two inflection cleanups the fixtures forced: third-person flow verbs collapse to imperative forms, and the deadline primitive gets one spelling instead of two.
What freezes now, and what stays open
Freezing everything would be a lie, because two data-model questions are genuinely undecided. So the freeze is layered, and the open holes are named in the grammar rather than left as silence:
| Layer | Freeze now? | Why |
|---|---|---|
| Document layer (front matter, attributed headings, prose clauses, fenced blocks) | yes | stable across all nine worked contracts |
| Block layer (closed keyword set, declaration lines, table entries, annotation lines) | yes | the annotation classes have held since the first draft |
| Expression layer (comparisons, boolean operators, arithmetic with unit physics, the positional prelude, temporal operators per RFC 0005) | yes, with the aggregate hole named | the combinator grammar is the one real gap |
| State declarators | partially | keying and status cells will move (RFC 0004); freeze the rest |
| Rule layer (guard, match, bounded map, binding, the effect set) | yes, after the vocabulary sweep | the effect set is the sweep’s output |
| Module layer (imports, parameters, requirements, flatten rules) | yes | worked in nine contracts and already normative |
How the lock is enforced
An EBNF nobody checks is a wish. The freeze is only real if it is executable:
- One normative pair. The surface grammar folds into the registry: registry = vocabulary, laws and types; grammar = surface, EBNF and validation contract. Neither may contradict the other, and where prose and parser disagree, the parser’s tests are the tiebreak - because they are the only artifact that cannot lie.
- Every worked contract must parse. The nine contracts and nine modules become the conformance corpus, parsed on every change. A fixture that stops parsing is either a grammar regression or a fixture bug, and either way someone finds out the same day.
- Round-trip property. Parsing a rendered document reproduces the same syntax tree, property-tested - which is also what makes a formatter safe to run on a document destined to be signed.
- Canonical signing target. The signature binds the pair of surface bytes as read and a canonical encoding of the tree, with a round-trip proof. The canonical form is deterministic binary encoding, for the same reason XML needed canonicalisation: two byte sequences that mean the same thing must hash the same, or signatures become a formatting argument.
- One grammar, one toolchain. Generate the parser and the language server from the same grammar, so an editor cannot drift from the standard. An editor that accepts what the linter rejects is a trap for the person signing.
Honest creaks
- Freezing before the keying and status-cell questions land means the state block will move. That is why the freeze is layered - but a layered freeze is a promise about which parts are stable, and if keying turns out to touch expression syntax too, that promise gets embarrassing.
- A literate document layer means depending on a markdown parser. The subset must be pinned - which constructs, which parser, which version - or “valid” varies by toolchain, which is exactly the fragmentation the freeze exists to prevent.
- A grammar-driven toolchain is a dependency in the trust path. Generating the parser from the grammar is right; it also means a third-party implementer either uses our generator or hand-writes against the EBNF. The EBNF must therefore be complete enough to implement from, not merely to generate from.
- A formatter on a signed document is a loaded gun. Formatting changes surface bytes, and the signature binds surface bytes. Format before signing, never after - and the tool should refuse when it sees a signature block.
- The freeze will be broken once, deliberately - by the vocabulary sweep. Sequencing matters: sweep first, then freeze, or the freeze is stale on arrival.
Prior art
YAML’s implicit-typing history and the strict subset invented in response - the case for looking like YAML without being it. CommonMark - a specification with an executable conformance suite, the model for the corpus rule. XML canonicalisation and deterministic binary encoding - canonicalisation as a signing prerequisite. Grammar-driven language tooling - one grammar generating both parser and language server. Canonical formatters in modern languages - one layout, no debate, and the reason review is about substance. Decision-table hit policies - already borrowed for tables, and the completeness and non-overlap checks come with them.
This RFC is exploratory. It is published as a basis for discussion, not as normative text; where it touches the standard, the standard governs until a decision is recorded.