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.