TemporalTruthMaintenanceAdapter connects explicitly annotated temporal graph
relationships to a truth-maintenance session.
It turns changes in the selected evidence into one batch of source assertions
and retractions. Conclusions survive while they have another active support.
The two query coordinates are independent:
valid_at: the time the claim applies to in the world.known_at: the time at which the evidence was recorded and not yet superseded.
TemporalGraphQuery or its existing
time_axis="both" behavior, which checks both axes at the same timestamp.
Example: a late correction and expiration
This example needs no database, embedding model or LLM:Graph input and history
Pass a dictionary containingentities and relationships; callers with a
ContextGraph can export to_kg_dict() first and pass the result unchanged.
The caller must supply and retain the full managed evidence history, including
superseded relationship versions.
Exporting only an active view cannot reconstruct evidence that it omits.
With ContextGraph, the annotation and the bounds are ordinary keyword
arguments: graph.add_edge(source, target, relation_type, valid_from=..., valid_until=..., recorded_at=..., truth_maintenance={"support_id": ..., "fact": ...}). Extra keyword arguments are stored in edge metadata, so do not
wrap them in an explicit metadata= dictionary.
Each managed relationship has a metadata.truth_maintenance dictionary with
exactly support_id and fact. The fact uses PR1’s canonical atom grammar; no
relation label is automatically translated into a rule predicate. A support ID
identifies one source assertion revision. Use distinct IDs for different
facts from the same document and for later revisions. Unannotated relationships
are ignored; malformed annotations raise ValidationError.
Temporal fields are read from each record itself and, when present, from its
metadata and properties dictionaries. That second location matters because
ContextGraph keeps everything except the valid-time bounds inside metadata
(entities also expose it through properties), so to_kg_dict() output works
without rewriting. A field supplied in more than one place must denote the same
instant; conflicting duplicates raise TemporalValidationError.
Relationship endpoints are read from
source/target or from the canonical
source_id/target_id emitted by to_kg_dict(). Supplying both forms with
different values raises ValidationError instead of silently preferring one.
Null upper bounds, "OPEN", and TemporalBound.OPEN mean unbounded future.
Finite intervals require start < end. Times use the existing temporal parser:
ISO strings, datetimes and numeric Unix timestamps are accepted; naive datetimes
are treated as UTC. Booleans are rejected. There is no day/second truncation.
No default value is taken from the wall clock.
Retained records cannot disappear from a later sync(). Their fact, endpoints,
relation type, valid interval and recorded time cannot be rewritten. An open
superseded_at may be closed once; a closed interval cannot be reopened or
changed. Corrections close an old revision and add a new one. To withdraw a
source from the current known-time view, close its transaction interval and
advance known_at to or past that boundary. Physically removing a row is rejected.
The adapter accepts the source’s explicit transaction history as authoritative;
it does not infer arrival times, resolve conflicting evidence or assign a
replacement relationship automatically.
Entity IDs must be unique strings. If the graph includes entities, every managed
edge needs existing endpoints, and both endpoints must be active at both query
coordinates. Entity scaffolding with no temporal fields is timeless. Its retained
time fields follow the same immutability/transaction-closing rules. Relationship-only
graphs are supported, but cannot later acquire endpoint constraints on the same
adapter: doing so would reinterpret retained history. Create a new adapter when
changing that mode. Predicate arity is checked across all retained evidence,
including records that are inactive at the requested coordinates.
API
TemporalTruthMaintenanceAdapter(rules=...)
Owns a new session with copied, fixed PR1 rules. No externally mutable session is
accepted. All PR1 rule restrictions, including non-recursion and absence of side
effects, apply. Access must be caller-serialized.
sync(graph, *, valid_at, known_at) -> MaintenanceDelta
Validate and ingest the retained evidence projection. Diff its selected support
IDs against the currently active ones and apply one PR1 batch. A source replacement
for the same fact has support changes without intermediate fact churn.
Validation or reasoning failure leaves retained evidence, live facts, clocks and
counters unchanged. Input dictionaries are not mutated or retained by reference.
Non-temporal attributes and unrelated metadata are not part of the managed projection.
advance(*, valid_at, known_at) -> MaintenanceDelta
Select another slice of retained evidence. This is how callers trigger expiration
or activation without modifying the graph. Both forward and backward movement are
allowed. There is no timer: merely waiting does not update the live facts.
query_at(*, valid_at, known_at) -> TemporalFactSnapshot
Build a fresh session for the requested slice and return its facts, active source
supports and direct explanations. The frozen result contains only immutable
values; it exposes explain(fact) and remains unchanged by later updates.
It carries valid_at, known_at and graph_revision. It does not change the
live session or pretend that a historical recomputation has a live commit version.
Live properties and explanation
facts: immutable set of current explicit and inferred facts.explain(fact): current PR1 direct sources and derivations.version: PR1’s committed support-set counter. A clock-only change need not increment it.graph_revision: counter for changes to the normalized retained projection, including inactive evidence. It is local to this adapter, not a global graph ID.valid_at,known_at: current UTC coordinates, initiallyNone.
