Bitemporal in one sentence
Every fact has a valid time (when it was true) and a recorded time (when we wrote it down). They're independent. Most databases collapse them; we don't.
Why it matters
Every CMS sooner or later has to handle: late corrections.
Someone writes an article. It publishes. Three weeks later, legal flags a typo as of the publish date. You correct it. Now what does history() show?
In a single-time-axis system: the original is gone. Replaced by the corrected version. history() shows you "edited at week 3", but if an auditor asks "what did the public site say between weeks 1 and 3?" — you have nothing.
In a bitemporal system: the original is preserved. The correction is a new fact that says "this was true from publish date, but we observed it three weeks later". history() shows both. Auditors get the whole picture.
The technical shape
MATCH (n:Article {slug: 'launching-staticowl'})
WHERE n._valid_from <= '2026-04-22T15:00:00Z'
AND (n._valid_to IS NULL OR n._valid_to > '2026-04-22T15:00:00Z')
AND n._recorded_from <= '2026-04-25T00:00:00Z'
AND (n._recorded_to IS NULL OR n._recorded_to > '2026-04-25T00:00:00Z')
RETURN n
Read: "what did this article look like as of April 22 at 3pm, observed as of April 25". Long version of AT syntax — same thing under the hood.
What this enables
- Compliance corrections that don't rewrite history
- Auditor questions like "what did your homepage say on the morning of the SEC filing?" — answerable
- Multi-environment time travel —
Site(env, time)is a query - A change feed that's actually useful for forensic work
What it costs
Storage: ~2x. Each property change appends a new version row. Compaction recycles slack but doesn't delete history.
Mental load: editorial users don't see any of this. Engineers querying the graph have to know whether they want AT (specific point in time) or just current (_valid_to IS NULL). The CMS API hides this; raw Cypher exposes it.
We think it's worth it. So do our design partners. Read the architecture doc →