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 class — current (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.