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.