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.