Ten of what?

Every mainstream format ships magnitudes and strands the units — in a column name, a comment, a wiki page someone will not read. On units as part of the type: curated and canonicalized, byte-compared and never silently converted, with bits and bytes as first-class citizens and one famously ambiguous spelling refused at the door.


In 1999 the Mars Climate Orbiter disintegrated in the Martian atmosphere because one system emitted thruster impulse in pound-force seconds and another read it as newton seconds. The numbers were flawless. The units lived out-of-band — in a specification document, not in the data — and the data said nothing to contradict either reading.

Every config file plays a small round of the same game. timeout=30 — thirty of what? max_upload=10 — ten of what? The convention rides in a suffix (timeout_ms), a comment, or institutional memory, and every consumer re-derives it, independently, forever. The format transports the magnitude and abandons the meaning.

The unit is part of the type

In kaiv, a unit is not a naming convention — it is a component of the type annotation, between the type and its constraints’ end, introduced by :

$ cat quota.saiv
.!saiv 1 acme/quota
!int[1,100]:GiB
max_upload=

and it is enforced. A document that agrees:

$ printf '!int:GiB\nmax_upload=10\n' | kaiv build | kaiv validate - quota.saiv
pass

A document that shows up with the same number in the wrong unit:

$ printf '!int:MB\nmax_upload=10\n' | kaiv build | kaiv validate - quota.saiv
kaiv: TypeMismatchError: ::max_upload=10 (type !int:MB) does not satisfy !int:GiB /^-?[0-9]+$/ ..num [1,100] (line 2)

Note what the validator did not do: convert. Ten megabytes is expressible in gibibytes, and a helpful system would quietly do the arithmetic. kaiv’s position is that converting is arithmetic on your data, and a format that mutates data in transit is not a format — it is a participant. Units are byte-compared, both directions; conversion belongs to application code that chose to do it, visibly. The validator’s whole job is to make sure the disagreement is loud — which is exactly what the Mars probe never got.

One spelling per unit

Units compound — multiplication, division, integer exponents — and compounds canonicalize to a single spelling: factors ASCII-sorted, each denominator factor introduced by its own /, shared factors cancelled:

$ kaiv unit 's^-2*m*kg'
kg*m/s^2
$ kaiv unit 'W*s/W'
s

Newton’s second law arrives in whatever order you typed it and leaves as kg*m/s^2, every time, in every document — which is what makes grep ':km/h' a meaningful query and unit equality a byte comparison in the first place.

Bits and bytes, first-class

The most common units in configuration files are not meters or seconds. They are data sizes and data rates — and they are a base dimension of their own, underivable from SI. kaiv ships them built in: the bit b, the byte B (eight bits), the SI decimal multiples (kB MB GB TB PB, ×1000), and the IEC binary multiples (KiB MiB GiB TiB PiB, ×1024). Rates need no dedicated names — they are ordinary compounds — and the telecom spellings are accepted as input and canonicalized to what they actually mean:

$ printf '!int:GiB\nmax_upload=10\n!int:Mbps\nlink_rate=100\n!float:MiB/s\nthroughput=93.7\n' | kaiv build
.!daiv
!int:GiB'::max_upload=10
!int:Mb/s'::link_rate=100
!float:MiB/s'::throughput=93.7

Mbps is a million bits per second — decimal, as on every datasheet ever printed — so it canonicalizes to Mb/s, never to mebibits. Two prefix families, cleanly separated: decimal means 1000, binary means 1024, and each multiple says which family it belongs to.

Which leaves the one spelling that refuses to say. KB is not SI — there is no prefix K — and JEDEC tradition reads it as 1024 while half its readers assume 1000. It is the Norway problem of storage sizes, and kaiv gives it the Norway treatment:

$ kaiv unit KB
kaiv: KB is ambiguous: write kB (1000 B) or KiB (1024 B)

Refused — not as an unknown name, but at the unit grammar level, so no custom package can ever claim the spelling and quietly pick a side. The rejection teaches both resolutions. A format that will not guess about NO being Norway will not guess about your disk quota either.

Currencies: honest about what they don’t know

Currencies are units too — tilde-prefixed, sharing the money dimension:

$ printf '!float:~EUR\ntotal=99.50\n' | kaiv build
.!daiv
!float:~EUR'::total=99.50

and they are the clearest case of the no-conversion rule. ~EUR and ~USD share a dimension, but kaiv deliberately carries no conversion factors between them: exchange rates are external, time-varying facts, not structural ones. A format that embedded Tuesday’s rate would be wrong by Wednesday — forever, on an eternalink registry. What the type system knows is that the value is euros; what euros were worth is provenance-stamped application data, where it belongs.

Your units, published

The curated set ends where your domain begins, and the ecosystem picks up: custom units are defined in .faiv unit libraries and published to the unit registry (kfaiv.com — this local directory again plays the registry):

$ cat astro.faiv
.!faiv 1 astro/units

// Astronomical unit
m 1.495978707e11
&au=
$ mkdir -p registry/astro
$ cp astro.faiv registry/astro/units.faiv
$ printf '/registries::astro=./registry\n' > kaiv.kaiv

A definition is a dimension, a factor, and a name — au is 1.495978707×10¹¹ meters — and a document opts in with .!units, exactly parallel to .!types:

$ printf '.!units astro/units\n\n!float:au\n/probe::distance=1.37\n' | kaiv build
.!daiv
.!units astro/units
!float:au'/probe::distance=1.37

The import survives into the canonical output — resolution metadata, like .!schema — so the .daiv still names where au comes from. Published unit libraries are eternalinks like everything else on the registries: astro/units today is astro/units in 2040, factor and all, which is what lets a frozen document keep meaning the same distance.

What the line knows

Assemble the pieces and reread the densest line kaiv can produce:

!float:km/h?gps@20260717T120000Z'/car::speed=120.5

Address: which value. Type: what shape. Provenance: who says so, and when. And the unit: of what. Four questions every consumer of every measurement eventually asks, answered in the line, checked by the schema, surviving to the certified reader. The Mars Climate Orbiter’s two teams agreed on the number to the last decimal place. The only thing they never wrote down is the only thing kaiv insists on writing down.