Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

ADR-027: Bitemporal facts: valid-time + supersession

Date: 2026-06-12 Status: Accepted (owner-delegated review, 2026-06-12) Author: Rômulo Febasi


Context

Synapse facts are atemporal. The system already has transaction-time for free: git commit time plus the x-ai-history chain and audit.ndjson form an append-only “known-to-system” log, and syn at <ref> already time-travels along it. What is missing is valid-time (when a fact was true in the world, which is independent of when Synapse learned it) and an explicit way to record that one fact superseded another.

This is the half of bitemporality that the frontier (Zep/Graphiti) owns and that mem0/basic-memory lack; combined with Synapse’s signed provenance it becomes something even Graphiti cannot do: prove what was believed true, and when. The constraint: Markdown frontmatter is the source of truth, the SQLite index is disposable, and the design must stay deterministic and small. Extends ADR-012.

Decision

Store valid-time only; derive transaction-time from git. Add to a fact’s frontmatter, closed-open [from, until) per the SQL:2011 convention:

x-valid-from: 2024-03-01         # world-time the fact became true (required)
x-valid-until: 2025-06-01        # exclusive; omitted/null = open ("still true")
x-supersedes: <fact-id-or-hash>  # optional: this fact replaces that one

Transaction-time is not stored in frontmatter; it is recovered from git (syn at <ref>) and x-ai-*/audit.ndjson. Storing it twice would duplicate the source of truth and risk drift (XTDB’s invariant: transaction-time is system-assigned and immutable; git already provides exactly that).

Supersession = invalidation, not deletion (Graphiti’s rule). When a new fact F2 contradicts F1: write x-supersedes: F1 on F2 and set F1’s x-valid-until to F2’s x-valid-from. F1 is never deleted or edited destructively: closing its interval is itself a new append. A pure retraction (the fact was never true) is an empty interval (x-valid-until == x-valid-from) plus an x-ai-history note. x-supersedes is the world-time analogue of what x-ai-history already does in system-time.

Two orthogonal time axes, each reusing an existing mechanism:

AxisMechanismSurface
Transaction-time (“what did we know at ref R”)git ref (existing)syn at <ref> -- … (unchanged)
Valid-time (“what was true in the world at date D”)x-valid-* filter in the indexnew --as-of <date>

They compose: syn at <ref> -- search --as-of <date> = “using only what we knew at R, what was true at D”: the full bitemporal query, nearly free because each axis is a separate, already-existing filter. The index gains valid_from, valid_until, supersedes columns (a pure reindex: no stored-data migration). The default query predicate becomes the SQL:2011 “current row” filter, hand-coded: valid_from <= now AND (valid_until IS NULL OR valid_until > now) at the supersession-chain tip.

audit replay stays correct because valid-time lives in the frontmatter payload it already replays: provided two code invariants hold: replay reconstructs frontmatter fields generically, and supersession edits (closing F1’s interval) are appended as ordinary mutations, never written in place.

Migration is deterministic and lazy: existing facts get x-valid-from = their first git-commit date, open-ended (the only honest default: world-validity isn’t knowable retroactively); x-supersedes absent; a one-shot syn reindex populates the new columns; frontmatter is stamped lazily on next mutation (no bulk rewrite of Markdown). Implemented in SYN-223 (split: schema+migration / write+supersession / time-aware replay+at).

Consequences

  • Improves: matches Graphiti’s strongest feature locally and with per-fact signed provenance it lacks; enables a “belief-lineage” view (walk x-supersedes); the minimal shape is 2 interval fields + 1 pointer (AeonG reports ~10% overhead for full bitemporal); the index stays 100% rebuildable.
  • Worsens: audit replay and at must become time-aware (the hard part, gated by the append-only-supersession invariant); the migration default (valid_from = first-commit-date) is an approximation of world-validity.
  • Open: free-text contradiction detection is deferred to SYN-242 (structured fields first, NLI later); decay (SYN-224/ADR-029) must operate on the valid-time axis as a derived salience score and must never delete superseded facts or touch transaction-time; the belief-lineage edge surfaces in the live graph (SYN-244). Verify before building: that audit replay reconstructs arbitrary frontmatter fields generically today.

Alternatives considered

  • Full TSQL2 / a temporal query algebra. TSQL2 was never adopted; SQL:2011 won by stripping to two columns + PERIOD FOR. Sequenced/non-sequenced semantics and coalescing are months of work the single-user case never exercises. Copy the column convention, not the algebra.
  • An Allen interval-algebra reasoning engine. Full-algebra satisfiability is NP-complete (Vilain & Kautz; only ORD-Horn-style subclasses are polynomial). Synapse needs the 13 relations as vocabulary for point-in-time filters (two comparisons), not constraint propagation. Borrow the words, refuse the engine.
  • Store transaction-time in frontmatter. Duplicates git, invites drift, violates “one place per fact.” Derive from <ref>/audit instead.
  • JUXT “bitemporality must live in the DB engine.” That targets multi-writer RDBMS whose planners can’t see temporal columns. Our index is single-writer and hand-queried: we can hand-roll correctly. Take JUXT’s failure-mode list as a checklist, reject its conclusion.
  • Closed-closed intervals / a custom Period datatype. Exactly what SQL:2011 removed from TSQL2. Use closed-open ISO strings.
  • Datomic-style retraction datoms. Redundant with x-ai-history; encode retraction as an empty valid interval instead.

Research grounding

Snodgrass/Jensen TSQL2; SQL:2011 application-time + system-versioned tables; Allen’s interval algebra + Vilain & Kautz / Nebel & Bürckert (NP-completeness); Fowler, Bitemporal History; Datomic (transaction-time only); XTDB/Crux (immutable transaction-time); Zep/Graphiti t_valid/t_invalid invalidation (arXiv 2501.13956); AeonG bitemporal extension (ADBIS 2025, ~10% overhead); temporal/bitemporal RDF; AGM belief revision (Gärdenfors & Makinson).