What bitemporal state invariants must survive retroactive corrections to past events?

**Date of Record:** 2026 October 27 **Scope:** Applies when an agentic session maintains state with both valid-time (when the fact was true in the real world) and transaction-time (when the fact was recorded in the system), and a retroactive correction alters the valid-time interpretation of past events. Does not apply to strictly current-state systems where past interpretations are never revisited.

Core Distinction

The separation between **valid-time** (when the fact was true in the real world) and **transaction-time** (when the system recorded it). Relying on a single timestamp creates the **Temporal Conflation Trap**: when a retroactive correction changes the valid-time of a fact, the system cannot distinguish "what was true at time T" from "what the system believed at time T," so agents reasoning about the past either see the corrected truth (losing the audit trail) or the original belief (missing the correction).

What Was Observed

In temporal agent architectures, failing to preserve bitemporal distinction produces three primary failure modes:

  1. **Retroactive Rewrite:** A "transaction settled" event is retroactively corrected to have occurred a week earlier. The system rewrites the past, and any agent that already made decisions based on the original timeline cannot reconstruct its rationale.
  2. **Audit Collapse:** A regulatory audit asks "what did the agent believe on date X?" The system shows corrected data rather than the agent's original view, so the audit cannot reproduce the original decision.
  3. **Causal Disconnect:** An agent orders events by timestamp, but retroactive corrections reorder them, breaking the causal chains the agent relies on.

Recorded Elements

  1. **`valid_time_range`:** The interval during which the fact was true in the real world, independent of when it was recorded.
  2. **`transaction_time_range`:** The interval during which the system held this version of the fact, capturing the system's evolving belief.
  3. **`correction_lineage_receipt`:** For each correction, the cryptographic proof of what was corrected, why, and by whom, allowing audit of correction chains.
  4. **`query_time_mode_selector`:** The explicit declaration in each query of which time dimension (valid, transaction, or both) to use, preventing implicit conflation.

Condition of Validity

* **Applies when:** The session records facts that can be retroactively corrected, and downstream reasoning depends on either the real-world truth or the system's historical belief. * **Does not apply when:** The system never corrects past facts (append-only, no retroactive corrections), or the distinction between valid-time and transaction-time is irrelevant to the use case.

Decision Boundary

  1. **Does the fact being recorded have both valid-time and transaction-time semantics?** If no, standard timestamping applies. If yes, proceed.
  2. **Is the query explicit about which time dimension it uses?** If no, the query must be rejected or defaulted to a documented mode.
  3. **Has a retroactive correction been applied to this fact?** If yes, the correction must be preserved via `correction_lineage_receipt`, not overwritten.
  4. **Conversely, when can the system conflate valid-time and transaction-time?** Only when the fact is strictly append-only with no corrections, or when downstream reasoning is provably insensitive to the distinction.

Operational Effect

  1. **Temporal Auditability:** Enables audits to reconstruct both real-world truth and historical system belief.
  2. **Correction Discipline:** Corrections are recorded, not overwritten, preserving the lineage.
  3. **Query Clarity:** Forces explicit time-dimension selection, preventing silent conflation.
from dataclasses import dataclass
from enum import Enum, auto
import hashlib
import json

class TimeMode(Enum):
    VALID_TIME = auto()
    TRANSACTION_TIME = auto()
    BOTH = auto()

class BitemporalVerdict(Enum):
    SINGLE_TIMESTAMP = auto()
    BITEMPORAL_QUERY_SAFE = auto()
    IMPLICIT_CONFLATION = auto()
    CORRECTION_OVERWRITE_RISK = auto()

@dataclass(frozen=True)
class BitemporalFact:
    fact_id: str
    valid_start: float
    valid_end: float
    transaction_start: float
    transaction_end: float
    has_correction_lineage: bool

@dataclass(frozen=True)
class BitemporalQuery:
    fact_id: str
    time_mode: TimeMode | None

@dataclass(frozen=True)
class BitemporalPolicy:
    require_explicit_mode: bool
    require_correction_lineage: bool

@dataclass(frozen=True)
class BitemporalManifest:
    verdict: BitemporalVerdict
    directive: str
    manifest_digest: str

class BitemporalGuard:
    @staticmethod
    def _hash(data: str) -> str:
        return hashlib.sha256(data.encode("utf-8")).hexdigest()

    @classmethod
    def evaluate_query(
        cls, fact: BitemporalFact, query: BitemporalQuery, policy: BitemporalPolicy
    ) -> BitemporalManifest:
        
        # Gate 1: Explicit mode required
        if policy.require_explicit_mode and query.time_mode is None:
            return BitemporalManifest(
                verdict=BitemporalVerdict.IMPLICIT_CONFLATION,
                directive=f"HALT: Query for {query.fact_id} lacks explicit time mode.",
                manifest_digest=cls._hash(f"NO_MODE:{query.fact_id}")
            )

        # Gate 2: Correction lineage
        if policy.require_correction_lineage and not fact.has_correction_lineage:
            if fact.valid_start != fact.transaction_start:
                return BitemporalManifest(
                    verdict=BitemporalVerdict.CORRECTION_OVERWRITE_RISK,
                    directive=f"HALT: Fact {fact.fact_id} has valid/transaction time divergence but no correction lineage.",
                    manifest_digest=cls._hash(f"NO_LINEAGE:{fact.fact_id}")
                )

        # Gate 3: Single timestamp fallback
        if fact.valid_start == fact.transaction_start and fact.valid_end == fact.transaction_end:
            digest = cls._hash(json.dumps({
                "fact": fact.fact_id,
                "valid": f"{fact.valid_start:.0f}-{fact.valid_end:.0f}",
                "txn": f"{fact.transaction_start:.0f}-{fact.transaction_end:.0f}"
            }, sort_keys=True))
            return BitemporalManifest(
                verdict=BitemporalVerdict.SINGLE_TIMESTAMP,
                directive=f"PROCEED: Fact {fact.fact_id} has aligned timestamps.",
                manifest_digest=digest
            )

        # Gate 4: Bitemporal safe
        digest = cls._hash(json.dumps({
            "fact": fact.fact_id,
            "valid": f"{fact.valid_start:.0f}-{fact.valid_end:.0f}",
            "txn": f"{fact.transaction_start:.0f}-{fact.transaction_end:.0f}",
            "mode": query.time_mode.name if query.time_mode else "NONE",
            "lineage": str(fact.has_correction_lineage)
        }, sort_keys=True))
        
        return BitemporalManifest(
            verdict=BitemporalVerdict.BITEMPORAL_QUERY_SAFE,
            directive=f"PROCEED: Bitemporal fact {fact.fact_id} with mode {query.time_mode.name if query.time_mode else 'NONE'}.",
            manifest_digest=digest
        )