Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

0002. YAML 1.2 strict semantics by default; 1.1 opt-in

  • Status: accepted
  • Date: 2026-05-06
  • Authors: Sebastien Rousseau

Context

YAML 1.1 (2005) and YAML 1.2 (2009) disagree on plain-scalar resolution in three places:

FormYAML 1.1YAML 1.2 (core schema)
yes / no / on / offboolstring
0644 (bare zero prefix)int 420 (octal)int 644 (decimal)
60:00 (colon-separated)int 3600 (sexagesimal)string

The 1.1 lexicon is responsible for the Norway problem: the country code for Norway is NO, so YAML 1.1 silently coerces country: NO to country: false. This has been a real-world data-loss bug in production for over a decade.

YAML 1.2 narrowed the boolean lexicon specifically to fix this. But existing 1.1 documents — Ruby YAML configs, pyyaml output, crontab-style time-of-day notation — exist in the wild and cannot be unilaterally re-parsed under 1.2.

The library has to default to one of these. Defaulting to 1.1 preserves quiet bug-compatibility with old configs but ships a known correctness hazard. Defaulting to 1.2 surfaces the correctness hazard at parse time but breaks documents that genuinely intended 1.1 semantics.

Decision

noyalib defaults to YAML 1.2 strict semantics. Plain scalars follow the core schema:

  • yes / no / on / off → string
  • 0644 → integer 644 (decimal)
  • 60:00 → string

YAML 1.1 semantics are available as an opt-in via ParserConfig::version(YamlVersion::V1_1), which flips three fine-grained flags as a bundle:

#![allow(unused)]
fn main() {
cfg.legacy_booleans      = true;  // yes / no / on / off
cfg.legacy_octal_numbers = true;  // 0644 → octal
cfg.legacy_sexagesimal   = true;  // 60:00 → 3600
}

The fine-grained flags remain available so callers can mix and match — e.g. “1.1 booleans but reject octal 0644”.

%YAML 1.1 directives in the document body are recorded but do not auto-enable 1.1 mode. Honouring them silently would be the exact silent-coercion failure mode 1.2 was created to fix; users who want directive-driven version selection can layer it on top of ParserConfig.

Consequences

Positive:

  • The Norway problem cannot bite a noyalib user by default. country: NO is a string, and stays a string.
  • The 1.2 spec is the active spec — defaulting to it aligns noyalib with the YAML committee’s stated direction.
  • Migration from 1.1-era libraries is a single version() call, not a full rewrite. The opt-in is loud but cheap.

Negative:

  • Documents that genuinely use 1.1 sexagesimal (crontab: 1:30) silently parse as strings under default settings. Users who expect 1.1 must explicitly opt in. This is the trade-off the decision makes — surfacing the discrepancy beats coercing it.
  • Double documentation: the README has to explain both modes, and the per-feature flags exist independently of the version preset, so the surface is slightly larger.

Neutral:

  • noyalib accepts certain 1.1-tolerant forms regardless of version (e.g. tab-relaxed indentation in flow contexts) where doing so does not introduce silent data loss. The boundary is “would this cause a Norway-style coercion?” — if no, accept; if yes, gate behind the version flag.

Alternatives considered

Default to 1.1 for compatibility

Match serde_yaml 0.9’s behaviour and accept the Norway problem as a documented quirk. Rejected — serde_yaml’s archival in 2024 was partly driven by the inability to fix this without breaking its existing user base. noyalib gets the freedom to fix it; declining would be an own goal.

Auto-detect from %YAML 1.1 directive

Read the first directive in the stream and auto-enable 1.1 mode when present. Rejected because documents commonly carry stale directives — a %YAML 1.1 from 2008 in a file that now expects 1.2 behaviour is worse than the manual opt-in. The directive is recorded for inspection; auto-application would be silent coercion in disguise.

Per-call version override only (no default)

Force every caller to specify a version explicitly. Rejected because the 99% case is “I have a YAML file, parse it” — adding mandatory version selection is friction that yields no correctness benefit for the ~95% of users on 1.2-compatible documents.

Strict 1.2 only, no 1.1 mode at all

Drop legacy_* flags entirely; force all users to migrate. Rejected because YAML 1.1 documents exist and noyalib’s selling point is being able to parse them losslessly. Refusing to handle them would push users back to serde_yaml’s archived fork.

References

  • YAML 1.2 spec, §10.2: https://yaml.org/spec/1.2.2/#102-tags
  • Norway problem write-up: https://hitchdev.com/strictyaml/why/implicit-typing-removed/
  • Implementation: crates/noyalib/src/de.rs — YamlVersion, ParserConfig::version
  • Test coverage: crates/noyalib/tests/yaml_version.rs (11 tests)
  • Commit that introduced version(): 1cffa2c