ADR-046: Fact lifecycle is an auditable relation between assertions¶
Status¶
Accepted. Builds on ADR-024, ADR-025 and ADR-032.
Context¶
Validity dates and drawer supersession preserve when knowledge was claimed, but cannot say why two independent sources disagree, or why a person corrected one assertion. Similar drawer text does not establish either truth or contradiction. The original drawers must remain evidence even when a graph assertion is no longer believed.
Decision¶
flowchart LR
D1[Evidence drawer A] --> F1[Graph assertion A]
D2[Evidence drawer B] --> F2[Graph assertion B]
F2 -->|fact_link: confirms / contradicts / supersedes / refines| F1
F1 -->|valid_from, valid_to| Time[Validity interval]
F2 -->|valid_from, valid_to| Time
The relates_to edge is the assertion; the fact_link table holds immutable decisions connecting assertion IDs.
Each link records its kind, origin (inferred or explicit), reason, recorded instant and an evidence-drawer ID when
available.
Direct checkpoint assertions point to the checkpoint drawer through assertion; extracted edges retain their existing
drawer/job/extractor provenance.
Lifecycle state is computed from these records on read rather than stored as a mutable status column.
Schema is applied by SurrealKit and no legacy claims are guessed or merged during migration.
After resolving entities, extraction confirms claims with equal subject, predicate and object and overlapping validity
from independent evidence.
When a source drawer replaces an earlier version, resolved facts with the same subject and predicate are also linked as
supersedes to the closed assertions from that predecessor drawer.
This uses explicit drawer lineage rather than assuming that two independently mined documents are revisions.
It identifies different objects as contradictory only when the normalized predicate is located_in, interpreted by
this first policy as one primary location; other predicates (works_on, uses, member_of, and so on) are multi-valued.
Free-text assertions are not automatically assigned a predicate's cardinality.
An inferred disagreement leaves both edges open and visible with conflicting state; it never chooses truth from recency.
For presentation only, an unresolved conflict's preferred ID is ranked by direct assertion, then confidence,
valid_from, then ID, and basis describes the tie breaks.
This hint does not remove or invalidate other evidence.
The rule and its version are recorded in each inferred link's reason, not obtained from a provider-specific extractor.
An explicit checkpoint supersession atomically closes the targeted edge, opens its replacement and links the two.
An explicit invalidation closes an edge without replacement and records its reason.
A caller may explicitly link two existing assertions as confirms, contradicts or refines with a nonblank reason;
refinement is not inferred from text similarity.
Deterministic link IDs make retries safe, and fact IDs derived from the checkpoint item or extracted drawer preserve the
same identity across a replay.
Inferred links never override an explicit correction.
An assertion's validity uses the existing half-open [valid_from, valid_to) interval.
An as_of read ignores explicit decisions recorded after its requested instant and judges conflicts only against other
claims valid at that instant; inferred comparisons remain visible when their evidence was discovered later.
Current graph and graph expansion omit closed edges but include both sides of unresolved conflicts.
Drawer search still returns verbatim evidence under its own temporal filter; an assertion link never rewrites a drawer.
GET /api/relationships/{id}/history, memcastle_fact_history and memcastle fact history expose the decision trail.
Alternatives rejected¶
Automatically superseding a disagreeing claim by arrival time would mistake later extraction for a correction and erase
the visible disagreement.
Inferring conflicts for every different object would treat multi-valued facts such as uses as mutually exclusive.
Asking an LLM to arbitrate every claim would make replay dependent on a provider and would hide the policy behind a model.
Storing a mutable current flag on every edge would duplicate validity and link state and could diverge after a crash.
Consequences¶
Corrections preserve their predecessor's content and provenance, and conflicting sources can coexist without silent overwriting. There is no general-purpose truth maintenance: a claim outside the conservative cardinality rule remains open until someone explicitly relates or corrects it.