knowledge: fact supersession + photo-date valid_from
Two Phase-2 followups in one commit since they're coupled at the
write path:
* Agent populates valid_from from the source photo's date_taken
when calling store_fact. Loose semantics — date_taken is *evidence
at that date*, not strictly when the fact started being true — but
gives the curator a calendar anchor and pairs with supersession to
close intervals cleanly. valid_until stays NULL (a single photo
can't tell us when something stopped). Honours the existing
upsert_fact dedup (corroborated facts keep their first-recorded
valid_from).
* Supersession: new column entity_facts.superseded_by INTEGER
(migration 2026-05-10-000200), new status value 'superseded',
new DAO method supersede_fact, new HTTP endpoint
POST /knowledge/facts/{id}/supersede.
Marking an old fact as replaced by a new one atomically: flips
status to 'superseded', sets superseded_by, and stamps
valid_until from the new fact's valid_from (when not already
set). delete_fact clears dangling supersession pointers in the
same transaction so the column never points at a missing row —
no FK because SQLite can't ALTER ADD with REFERENCES, but the
DAO maintains the invariant.
Pairs with conflict detection from the previous slice: once the
old fact's valid_until is closed, its interval no longer overlaps
the new fact's, so they stop flagging — the supersede action
resolves the conflict.
Two tests pin the contract: supersede stamps valid_until from
new.valid_from while respecting an existing valid_until, and
deleting the supersedeR clears the dangling pointer while leaving
the old fact's 'superseded' status in place for history.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -2672,6 +2672,19 @@ Return ONLY the summary, nothing else."#,
|
||||
file_path
|
||||
);
|
||||
|
||||
// Anchor the fact in valid-time using the source photo's
|
||||
// `date_taken` (Apollo's naive-as-UTC convention is fine
|
||||
// here — we only care about calendar ordering, not absolute
|
||||
// UTC). The semantic stretch: a photo *evidences* the fact at
|
||||
// that date — the fact may have started earlier — so this is
|
||||
// best read as "no later than this it started being true",
|
||||
// not a strict lower bound. Still useful: gives the curator a
|
||||
// calendar anchor and lets supersession (next slice) close
|
||||
// intervals cleanly when a newer fact arrives. valid_until
|
||||
// stays NULL — a single photo can't tell us when something
|
||||
// *stopped* being true.
|
||||
let valid_from = self.fetch_exif(file_path).and_then(|e| e.date_taken);
|
||||
|
||||
let fact = InsertEntityFact {
|
||||
subject_entity_id,
|
||||
predicate,
|
||||
@@ -2684,11 +2697,9 @@ Return ONLY the summary, nothing else."#,
|
||||
created_at: chrono::Utc::now().timestamp(),
|
||||
persona_id: persona_id.to_string(),
|
||||
user_id,
|
||||
// The agentic loop doesn't yet derive valid-time from the
|
||||
// photo's date_taken. Left NULL for now; Phase 2's
|
||||
// supersession + a future agent tool will populate these.
|
||||
valid_from: None,
|
||||
valid_from,
|
||||
valid_until: None,
|
||||
superseded_by: None,
|
||||
};
|
||||
|
||||
let mut kdao = self
|
||||
|
||||
Reference in New Issue
Block a user