Three artifacts that ship and version together: a taxonomy, a record format, and a distance metric.
A record is a JSON object with three parts. A claim — a label, when it happened, who was involved. A classification. A provenance object saying who recorded it.
The claim is canonicalised and hashed. Provenance is hashed separately. Two systems that record the same claim produce the same content hash, so the records line up without a shared database or a join table. Changing who recorded something does not change its identity.
A classification puts mass on nodes in a versioned taxonomy. At most three nodes per lens, and abstaining is allowed. Node ids never change meaning, so a classification made today can be compared with one made next year, or by another party, using the metric.
The taxonomy is two separate trees, called lenses. Lens B covers human action. Lens A covers recorded public events. No path runs between them; a cross-lens distance returns no answer.
A bridge maps an action in Lens B to the event it becomes in Lens A when the record keeps it. Bridges are directed and there are 26 of them, across four relations: recorded-as, scales-up-to, constitutes, and unrecorded. Finding an action's bridge is deterministic — walk up its parents to the first one.
Three bridges use the relation unrecorded and map to no event: migration, travel, and
courtship. These are the kernel. They record that the public archive keeps almost nothing about things
that happen constantly.
An action with no bridge at all means something different: nobody has mapped it yet.
Ten vectors. Each is a JSON file with an input and the expected canonical form and hash. Reproduce all ten byte-for-byte and your implementation conforms.
Two implementations run them on every push: the Rust crate and a Python port written independently against the vectors. The port found a real bug on its own side during development.
Source-available under the Business Source License 1.1. Read it, run it, build against it, fork it. You cannot offer it as a hosted service competing with ours. Each version converts to Apache-2.0 four years after that version is published.
Releases follow a published schedule. An id never changes meaning; a structural change means a version bump. Retired ids stay in the bundle so stored readings still resolve.