Skip to content

ADR-0015: Series are traits with a temporality class; derivations and evidence pinning are declared

ADR-0015: Series are traits with a temporality class; derivations and evidence pinning are declared

Status: accepted (2026-07-03)

Stress-testing the ontology layer against a production city-traffic platform (~10.3K road series at 2-minute cadence, 260M observations, a standing raw→metric→15m/1h→ spatial-rollup→baseline derivation DAG, weather and alerts correlated per road), plus a study of Palantir Foundry’s time-series stack, forced the time-series design. Canon already holds the load-bearing pieces: P4 (Assert / Time-Series Store) keyed by (subject, predicate, validTime, source), and the evidentiary/operational admission split (LLD §26.3). This ADR decides what canon left open.

Temporality is a trait classcurrent (default) or series(cadence, interpolation, retention-class) — at the trait layer (LLD §20 layer 3), alongside freshness, fusion, PII, and admission classes. It is not a tenth kind constructor: temporality (how a slot evolves) is independent of value shape and of admission class — all four admission×temporality combinations occur in practice. The declaration is what lets IR-2 type-check window/rolling/align nodes, lets series traits auto-offer chart widgets, scopes generated chunking/compression/rollup storage, and makes the standing cost of a series a conscious schema decision. Geotemporal composes rather than exists: a moving object is geo(point) × series.

Series identity is (entity, trait, source) — no series-ID keyspace in the model or port API. The port is AssertStorePort (P4’s contract; crate vinxi-port-assert). P4 remains a physically separate, retention-classed, rebuildable store; adapters may intern a surrogate series id (a generated dictionary), which turns sameAs entity conflation into a dictionary repoint instead of a point-row rewrite. Foundry’s series-ID pointer plumbing (time-series properties, qualified series IDs, sync bindings) is the tax of splitting identity from the ontology; we decline it.

Derivations are named, versioned IR-2 fragments plus a materialization policy — no second language. Inputs are (entity, trait) series on a root kind or its linked entities; the output is an ordinary operational series trait, served identically to an asserted one. One language covers ad-hoc reads, standing derivations, and alert conditions, and inherits IR-2’s policy injection. v1 nodes: calendar bucket (timezone from a declared scope entity; calendar-position keys), aggregates {count, sum, avg, min, max, stddev, percentile}, rolling windows, binary ops over aligned series (alignment semantics from the traits’ declared interpolation classes), and rollup-along-link-family (aggregation toward a coarser kind across a §27 link family). The vocabulary must stay incrementally evaluable (bounded-buffer windows, no full-history operators) so a streaming alert evaluator is an evaluator swap, not a language change. Materialization is per-derivation: on_read, or materialize(cadence, hierarchy) projecting to generated rollup storage (never hand-written DDL — §20 keystone); materialized projections serve their staleness, never degrade silently. Derived points carry provenance.source = the rule’s identity with the version in provenance detail, so recomputation is a bitemporal LWW overwrite of the same series (as-of reads preserve prior values), never a parallel series for fusion to referee. Forecasts and causal analyses are not language features; they re-enter as Asserts with model provenance (the ML path).

Decision-grade evidence over series is promoted per window by copy-out, resolving the §8 fork for series traits. High-volume series traits stay operational; when an Action or alert fires, the firing statement carries a bounded window digest in its payload and cites the immutable staged ingest batches in evidence_resource_versions. P4 never grows a pin concept — its compaction lifecycle stays clean, and audit evidence never depends on an evictable store. §26.3’s citation closure gains an exception class: basedOn citing a series trait requires the window-digest copy-out, instead of forcing the whole trait evidentiary.

Rejected: a tenth series constructor with SeriesRef pointer traits (bundles independent axes; second home for “which traits have history”); per-point StatementLog entries (§8/§26.3 already route around the log; 260M rows confirm); pin-flags in P4 (fight chunk/TTL lifecycle machinery, don’t survive adapter rebuilds); a bespoke derivation language (forks typing and policy from IR-2); compute-on-read only (the standing hot DAG needs declared materialization).

Consequences: IR-1 gains temporality declarations, derivation fragments, materialization policies, and rollup-along-family declarations; IR-2 gains the series node set; new crate vinxi-port-assert with memory and TimescaleDB adapters and a storage generator keyed off temporality declarations. Acceptance test: a traffic reality declares its full derivation DAG with zero kernel edits beyond these primitives. Deferred: streaming alert evaluator, trajectory indexes, backfill orchestration, evidentiary×series dual-write, window-digest schema.