CX · Reference guideTime: valid time and transaction time
PlaygroundDownloadsAboutv0.17.0

Time: valid time and transaction time

Two different questions about when

\"When\" is two questions wearing one word. When was this fact true in the world?valid time. When did we learn it?transaction time. Systems that fuse them cannot answer the auditor's question — \"what did we know when we sent that invoice?\" — because the moment a correction lands, the past silently repaints. CX keeps the axes separate, and each lives exactly where it belongs.

Axis Lives Coordinate Who owns it
Transaction time the journal envelope the entry sequence number — a position, not a clock the journal
Valid time the payload — your data valid-from= / valid-to= attributes you

Transaction time is position, not wall-clock: each stream's entries are dense, gap-free, and hash-chained, so \"as of entry 27\" is exact, replayable, and machine-independent, while timestamps stay non-authoritative annotations. Valid time is payload data under a reserved vocabulary: valid-from= and valid-to=, half-open [from, to) so adjacent intervals never double-count, with an open end spelled as an absent attribute — never a null. Two records differing only in effective date are different values with different addresses, which is exactly why validity is payload and not metadata (see the lane discipline in value-model).

Corrections — three relations, not one

The journal is append-only, so a correction is a new entry — but \"a later entry\" can mean three different things, and a substrate that cannot tell them apart makes restatement unauditable. A correcting entry names what it corrects by content address ([supersedes hash=…]) and declares which relation it is:

Relation Meaning How it folds
:assertion a late-arriving fact — nothing was wrong joins the timeline; supersedes nothing
:correction the prior fact was WRONG supersedes it across its whole valid extent
:amendment the prior fact was right then, wrong now closes its valid interval; opens a new one

Both entries remain forever — the superseded fact is never deleted, the chain stays verifiable, and the correction itself is attributed (who corrected it, under what authority, at which position). A dangling supersedes reference is a finding from the coherence verb, never a chain break: chain verification stays syntactic.

A fourth relation exists and deliberately stays out of this taxonomy: migration — the vocabulary changed, not the fact (see event-evolution). A migration entry never alters valid time and never folds as a correction; conflating \"we restated the number\" with \"we renamed the field\" is how audit trails rot.

The bitemporal read

Reads take two coordinates, independently optional: {at-seq: N} — the transaction position — and {valid-at: T} — the instant in the world. Four quadrants, one of which is THE bitemporal query:

Query Answers
now / now what do we currently believe is true now
at-seq / now what did we believe at position N about now
now / valid-at what do we currently believe was true at T
at-seq / valid-at what did we know when — the audit query

The worked example the corpus pins: on Aug 5 we learn the Aug 1 price was wrong. Entry 12 asserted 19.99 valid from Aug 1; an order priced from it at entry 13; entry 27 corrects to 17.99, superseding entry 12, valid from the same Aug 1. Then {at-seq: 13, valid-at: Aug 3} returns 19.99 — the defensible invoice, what we knew when we sent it — and {at-seq: 27, valid-at: Aug 3} returns 17.99 — the restatement basis. The pair is the restatement delta: the number the auditor actually asks for, produced from one immutable chain, no rewrites anywhere.

Mechanically, the bitemporal read is a pure projection over entries, composed before the fold — the fold contract itself never changes (see store for folds). The projection selects and collapses per the correction taxonomy; interval predicates over the validity vocabulary need no new query machinery. The same seam hosts version upcasting (event-evolution) — upcast first, then the valid-time projection, so an upcaster may synthesize validity vocabulary for entries that predate it.

What this design refuses

  • No store temporal tables. The journal is the only history; anything table-shaped is a fold over it.
  • No in-place temporal rewrites. Append-only is permanent; a rewritten past is not a past.
  • No wall-clock ordering authority. Positions order entries; timestamps annotate them. A tenant-wide coordinate is a set of per-stream positions (a head-set), carried by signed snapshots — there is no global sequence to pretend to.
  • Validity windows elsewhere are instances, not inventions. Authorization [until], credential issued/expires, session not-before/expiry — each is a valid-time window named by its own module; the vocabulary here is the common language under them.

One composition rule reaches into authorization: a policy decision evaluated against historical context records both coordinates — the position its context was folded at and the instant it was asked about — so \"what would we have decided at position P about instant T\" is a first-class, auditable query. See operations for the operational side of journals, retention, and snapshots.