The one place validators could disagree
Ordering text is cultural, and culture doesn’t fit in a grammar. On kaiv’s Level 3: locale collation quarantined behind an opt-in span, pinned to one CLDR version — and a conformance rule where refusing is always legal and silently disagreeing never is.
Same value, same constraint, two locales:
$ cat ol.kaiv
.!kaiv
beer=öl
$ kaiv validate ol.kaiv sv.saiv # ..lex[sv] [a,z]
kaiv: ConstraintViolationError: ::beer=öl (type !str) violates ..lex[sv] [a,z] (line 2)
$ kaiv validate ol.kaiv de.saiv # ..lex[de] [a,z]
pass
In Swedish, ö is a letter of its own that sorts after z
— so öl falls outside [a,z]. In German, ö collates with
o — comfortably inside. Same bytes, same range, opposite
verdicts, and neither is wrong. Welcome to collation: the
one corner of data validation where the correct answer depends
on a human culture rather than on arithmetic.
Most formats handle this by not handling it. Byte order is
deterministic, so sort bytes and shrug — which files Étude
after Zebra and calls it alphabetical. Or reach for a
collation library wherever sorting happens — unpinned, so the
verdict quietly changes with the library version, the platform,
the locale data of the machine that happened to run the check.
Two services disagree about whether a value is in range, and
nobody can even say which one is broken.
kaiv’s answer has three parts: quarantine it, pin it, and make refusal legal.
Quarantine: locale order is opt-in, per span
Plain ..lex is byte order — deterministic, culture-free, and
what the format uses unless you say otherwise. Locale order is
a span you ask for, field by field: ..lex[sv],
..lex[fr], attached to the named types that genuinely hold
human text. Collation is a property of a catalog-title type,
not of str — the culturally-dependent corner is fenced
exactly as large as it needs to be and no larger.
And the fence has a name: Level 3. Levels 0–2 of kaiv —
scalars, trees, tables — are byte-deterministic everywhere. A
validator that implements only those levels doesn’t guess at
locale spans: it rejects them, loudly
(CollationUnsupportedError), and remains fully conforming.
The conformance rule of Level 3 fits in one sentence:
rejecting is always conforming; silently disagreeing is
not.
Pin: one CLDR, one strength, one answer per tag
Two Level 3 validators agree only if they resolve a locale tag
against the same collation data and the same options — so
the spec pins both. Reference data is CLDR 48 (UTS #10);
comparisons run at tertiary strength with non-ignorable
variable handling — the CLDR root defaults — unless the tag
itself carries a BCP 47 -u- extension. A given tag yields
exactly one ordering under the pin; advancing the CLDR version
is a change to the spec, not something an implementation
does on its own. Your verdicts don’t drift because a base
image updated a library.
The pin matters more than it first appears, because collation governs equality as well as order — enum membership and range endpoints, not just sorting.
Equality is deeper than bytes
Here is a validator error that looks like a bug report about itself:
$ kaiv validate nfd.kaiv byenum.saiv # {étude}, byte order
kaiv: ConstraintViolationError: ::word=étude (type !str) violates {étude} (line 2)
étude violates {étude}? The document’s value is é
written as e plus combining accent (NFD); the enum’s is the
precomposed character (NFC). Byte-identity says different;
your eyes say same. Under the locale span, the letters —
not the bytes — are compared:
$ kaiv validate nfd.kaiv frenum.saiv # ..lex[fr] {étude}
pass
The -u- extensions tune the comparison further, per the
pinned semantics. At primary strength, accents stop counting:
$ kaiv validate res.kaiv l1.saiv # ..lex[en-u-ks-level1] {résumé}
pass
resume is a member of {résumé} — by declaration, in the
schema, reproducibly. Even the famous German phonebook
collation is just a tag: under de-u-co-phonebk-ks-level1,
where ö expands to oe,
$ kaiv validate ol.kaiv pb.saiv # {oel}
pass
öl is oel. Every one of these is the same machinery —
strings, an ordering, membership — with the ordering chosen
precisely and pinned.
Two backends, one honesty rule
Since 0.5.0 the reference implementation ships Level 3 twice, as mutually exclusive builds:
collation-icu(the default): full-fidelity ICU4X — every locale and every-u-override above, backed by the full CLDR data (~1.3 MB).collation-colligo: a lightweight, context-free collation backend at a fraction of the weight — derived from the same pinned CLDR 48 line and verified against the ICU oracle, for the locales whose tailorings fit a context-free model. Where a tailoring doesn’t fit, colligo does not approximate. It refuses.
French-Canadian is the instructive case: fr-CA sorts accents
from the end of the word backwards — a context-sensitive
rule no context-free table can reproduce exactly. So the two
backends answer the same document differently:
$ kaiv validate qc.kaiv qc.saiv # ..lex[fr-CA], ICU backend
pass
while the playground at demo.kaiv.io — the same crate compiled to WebAssembly with the colligo backend, 1.9 MB total where ICU’s data would have tripled it — returns, for the very same input:
{"ok":false,"error":"CollationUnsupportedError: ::city=Gatineau
(type !str) needs a collation unavailable in ..lex[fr-CA]
[a,z] (line 2)"}
One accepts; one refuses; both are conforming — because
the one thing neither is allowed to do is return a different
verdict about the ordering. That is the Level 3 rule doing
its job under real engineering pressure: the lightweight build
trades coverage, never correctness. (The two features even
refuse to coexist — enabling both is a compile_error!,
because a dependency graph silently picking a winner would be
exactly the kind of disagreement the level exists to prevent.)
The design lesson
Every other corner of kaiv is arithmetic: bytes, digits, lockstep scans, one right answer. Human alphabetical order is the one ingredient that isn’t — so the format treats it like the hazardous material it is. Contain it behind an opt-in span. Pin its parameters so one tag means one ordering, everywhere, indefinitely. And write the escape hatch into the conformance rules themselves: any validator, on any budget, may always say “I don’t do locales” — loudly, by name — and remain correct. The only forbidden move is to guess.
kaiv is an immutable structural type system for data at rest. The specification § 5 defines Level 3, the reference CLDR version, and the strength options precisely; the User Manual covers collation spans hands-on; the playground runs the colligo backend — including the refusal above — in your browser.