Who says so?
kaiv values can carry their own attribution — source, timestamp, data-point id — as part of the line, checked by the schema, surviving to the certified reader. On provenance as a type constraint instead of a logging afterthought.
A number in production is wrong. Not malformed — wrong:
offset=0.3 where the fleet needs 0.03, and something
downstream is drifting. The first question in the incident
channel is never “what is the value” — everyone can see the
value. It is “who says so?” Which sensor, which import,
which calibration run, which human put this number here, and
when?
Now try to answer it from the data. The value is a bare 0.3
in a config file. git blame gives you a commit — if the file
is in git, at file granularity, telling you who edited it,
not what the number’s authority is. The ETL wiki says the
pipeline was “updated recently.” The actual answer lives in a
Slack thread, or in the memory of whoever left last quarter.
None of this is anyone’s negligence: the formats have no
channel for it. JSON cannot attach “who said this” to a
value — the closest you can get is smuggling _meta keys into
the data’s own shape, corrupting every consumer that didn’t
expect them. So attribution gets bolted on around the data,
in systems that drift away from it.
kaiv gives every value a channel of its own.
The triple
A kaiv line may carry a provenance prefix:
?source@timestamp#dpid — who, when, and which observation —
written before the type’s ' delimiter. Sources are declared
once at the top of the file, mapping a short id to a URI:
$ cat readings.kaiv
.!kaiv
.?probe https://sensors.acme.example/probe-7
.?manual https://wiki.acme.example/calibration
?probe@20260717T140502Z#r-2214
temp_c=21.5
?manual@20260716T090000Z
offset=0.3
scale=1.0
$ kaiv build readings.kaiv
.!daiv
.?probe https://sensors.acme.example/probe-7
.?manual https://wiki.acme.example/calibration
!str?probe@20260717T140502Z#r-2214'::temp_c=21.5
!str?manual@20260716T090000Z'::offset=0.3
!str'::scale=1.0
Read the canonical lines: temp_c was reported by probe-7
at 14:05:02 UTC as observation r-2214; offset came from a
manual calibration the day before; scale says nothing — and
each attribution is scoped to its own line, not the file. The
incident question now has an answer in the data:
offset=0.3 — who says so? — ?manual@20260716T090000Z,
resolving to the calibration wiki. Follow the URI, find the
run.
One deliberate asymmetry in the grammar: the source anchors
every entry — you can write ?probe alone, or qualify it with
@when and #which, but a timestamp cannot appear without a
source. An attributor-less timestamp is not a provenance form,
because provenance answers who says so; the qualifiers only
refine the answer.
Not sugar — data
kaiv’s authored conveniences vanish at build time: variables
resolve, blocks unfold, &type shorthands lower to full
paths. Provenance pointedly does not. The ? prefix
survives canonicalization into the .daiv, and the .?id
declarations survive with it — which means attribution crosses
the certification
boundary and
reaches the certified reader. The runtime that trusts nothing
else about your toolchain can still read who claimed each
value.
And because canonical lines are flat and self-contained, attribution is greppable:
$ kaiv build readings.kaiv > readings.daiv
$ grep "?probe" readings.daiv
.?probe https://sensors.acme.example/probe-7
!str?probe@20260717T140502Z#r-2214'::temp_c=21.5
Everything this sensor ever claimed, plus the declaration that
says what the sensor is — one grep, no parser. The audit
query you’d build a lineage system for is a prefix match on
flat lines.
The schema makes it a contract
Attribution you merely may provide is attribution you’ll eventually stop providing. A kaiv schema can require it:
| Declaration | Meaning |
|---|---|
.!provenance:required |
source and timestamp on every line |
.!provenance:source |
source required; timestamp optional |
.!provenance:none |
provenance prohibited |
And the validator enforces it like any other constraint:
$ cat audit.saiv
.!saiv 1 acme/audit
.!provenance:required
!float
reading=
$ printf '.!kaiv\nreading=21.5\n' | kaiv build > bare.daiv
$ kaiv validate bare.daiv audit.saiv
kaiv: ProvenanceSchemaError: ::reading: schema requires source and timestamp on every line (line 2)
The mirror image holds too — a schema for data where attribution would be noise can prohibit it:
$ kaiv build sourced.kaiv > sourced.daiv
$ kaiv validate sourced.daiv none.saiv
kaiv: ProvenanceSchemaError: ::reading: schema prohibits provenance (line 3)
An unattributed reading simply isn’t valid audit data. Under the registry gate, that sentence has teeth: a document store that validates deposits against this schema cannot contain an unattributed reading, and never will.
The refusal that proves it’s thought through
Here is my favorite corner of this design. Try to write a provenance-required schema with an optional field:
$ cat incoherent.saiv
.!saiv 1 acme/x
.!provenance:required
!float
reading=
!int
retries?=3
$ kaiv schema incoherent.saiv
kaiv: ProvenanceSchemaError
The schema does not compile — and the reason is airtight.
The build materializes absent
optionals: if retries is
absent, the pipeline synthesizes retries=3 into the
artifact. But a synthesized line has no witness — no sensor
observed it, nobody said so — so it could never satisfy
required, and the pipeline could never produce a valid
document. Rather than let you discover that in production,
the schema compiler rejects the combination statically, the
same posture as SchemaOptionalWithoutDefaultError: the
format refuses to compile an incoherence. Materialized
defaults and mandatory attribution cannot coexist, and kaiv
tells you at schema-compile time, not at 3 a.m.
Corroboration, and stable names for observations
A value confirmed by more than one authority carries them all — a comma list, sharing or splitting qualifiers:
$ kaiv build multi.kaiv | tail -1
!str?a,b@20260717T150000Z'::agreed=42
Two attestors, one timestamp: a and b agree on 42. And
the third component — #dpid, the data-point identifier —
gives an observation a stable name (#r-2214, #row-17;
ingestion pipelines assign UUIDs at ingest time), so a
specific reading can be cited, deduplicated, or retracted
upstream without ambiguity about which observation is meant.
The exit tax, stated plainly
Export kaiv to a format without the channel, and the channel closes:
$ kaiv export --json readings.daiv
{"temp_c":"21.5","offset":"0.3","scale":"1.0"}
The values survive; the attribution does not — JSON has
nowhere to put it, which is where this article came in. That
is not an argument against exporting; it is the argument for
what “data at rest” should mean. Keep the resting copy in the
format with the channel, hand consumers the projection they
can hold — and when production asks who says so, the answer
is one grep away, in the artifact itself, checked by the
schema, at every stage of the pipeline down to the certified
reader.
The name knew
One last thing, hiding in plain sight. The extensions of the
kaiv family — .kaiv, .raiv, .daiv — share their tail
for a reason: AIV stands for attributed information
values, and it always has. You could read the A more loosely
as annotated — types, units, and provenance are all
annotations a value carries — but the terminology the format
is defined in chooses the stronger word. Attribution isn’t a
feature bolted onto kaiv; it is the letter A. This article has
simply been that letter, taken seriously.
kaiv is an immutable structural type system for data at rest. The User Manual covers provenance hands-on; the specification § 2.4 defines the triple, the timestamp form, and the schema levels precisely; the playground runs the whole toolchain — including every example above — in your browser.