fix(graph): tombstones are terminal — re-derivation cannot resurrect facts - #69
Merged
Merged
Conversation
…facts (#66) The ingest write path (addFact and the open-loops block) only deduped against ACTIVE lineage facts, so a resolved/superseded row never matched its own tombstone and the deterministic upsert id flipped status back to active whenever a later compaction pass re-derived the same content from the transcript. Both paths now skip active writes when the latest lineage fact for the same kind+factKey is non-active. Writes that themselves carry a non- active status (extraction resolving a loop, fact overrides) are unaffected, and sibling-branch isolation is preserved.
alpertarhan
added a commit
that referenced
this pull request
Sep 17, 2026
…) (#70) - forgetProjectGraph(projectId): drains queued index writes first (so a scheduled compaction cannot resurrect the memory), then deletes FTS copies, edges, and nodes in one transaction - /smart-compact forget: cwd-resolves the project, shows node/session counts, requires a TUI confirmation dialog; compaction state (restore data) and backups are deliberately kept - tombstone-terminal semantics from #69 guarantee deleted facts cannot be re-derived back into existence by later compactions
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #66.
Root cause
markFactStatuswrites the resolved/superseded tombstone onto the same deterministic row id (stableId(projectId, sessionId, kind, factKey, branchHeadId)) that the ingest path computes for the same fact. But the write path's dedup check,sameActiveFact, requiresrow.status === "active"— a tombstoned row never matches, so the next compaction that re-derived the same content from the transcript hit theON CONFLICT(id) DO UPDATE SET status = excluded.statusupsert and flipped the fact back toactive.Net effect:
/smart-compact loops resolve(and extraction-driven resolves) silently undid themselves on the next pass.Fix
Both ingest write paths —
addFactand the open-loops block — now fetch the latest lineage fact once and, in addition to the existing active-dedup check, skip the write when the latest lineage fact is non-active and the incoming node is active:factOverrides) are unaffectedlatestLineageFact, which already ranks by branch lineageSemantics note
Content-addressed
factKeycannot distinguish a genuine recurrence from a transcript echo. This PR deliberately makes resolve terminal per lineage; re-opening requires an explicit override (a futureunresolve), not passive re-ingest. This is also the foundation#63'sforgetneeds — deletion must not be resurrectable through the same path.Verification