Skip to content

fix(graph): migrate inline anchors atomically with grounds_to moves - #187

Open
abhinav-phi wants to merge 1 commit into
mex-memory:mainfrom
abhinav-phi:fix/persist-moved-groundings-anchors
Open

fix(graph): migrate inline anchors atomically with grounds_to moves#187
abhinav-phi wants to merge 1 commit into
mex-memory:mainfrom
abhinav-phi:fix/persist-moved-groundings-anchors

Conversation

@abhinav-phi

Copy link
Copy Markdown
Contributor

Resolves #128. All credit to @tcconnally for the deterministic reproduction and the exact root-cause read — the fix follows their suggested atomic ordering.

Root cause (confirmed against the issue)

persistMovedGroundings processed grounds_to entries first; that branch called deleteGroundedSource(scaffoldFile, oldId) before the inline-anchor pass ran. When a node is grounded both in frontmatter grounds_to and as an inline mex:// anchor, the anchor pass then found:

  • getGroundedSource(scaffoldFile, anchor.nodeId) → null (row just deleted), and
  • anchorFingerprints.get(oldId) → undefined (snapshot taken from the post-move store),

so if (!baseline) continue; skipped the anchor. The next mex check reported GROUNDING_GONE — permanent until hand-edited, exactly the reporter's step 9.

Fix

Per-node migration is atomic, as the issue suggests: the grounds_to pass records its resolution (oldId → newId) in a per-file map and defers the baseline-row delete; the anchor pass consults that map first, so the anchor rewrites from the same resolution without needing any store state the migration itself just destroyed. The stale baseline rows are deleted only after every rewrite for the file is applied.

One nuance worth review: on current main, a fresh rebuild leaves the old node reachable through node_aliases, which sometimes let the anchor resolve anyway — the bug surfaced deterministically in the reporter's 0.7.1 store where the alias route was also gone. The fix makes the anchor's migration not depend on that coincidence.

Tests

Two new tests in test/graph-integration.test.ts, both following the issue's repro (ground both forms → move with identical body → rebuild → sync):

  1. The natural path: both forms migrate (moved = 2), anchor rewritten, no GROUNDING_GONE on the next check.
  2. The reporter's store state: old node's grounded-source, fingerprint, and alias rows already gone. Mutation-verified: without the fix the test fails with expected 1 to be 2 (anchor skipped); with the fix both forms migrate.

The 3 pre-existing Windows-env failures in this file (symlink/WAL/atomic-replace) reproduce identically on clean main and are unrelated.

@tcconnally

Copy link
Copy Markdown

Great stuff! Sorry, I kind of gave up on my memory system and the whole Perseus platform in general. Still stoked to see what you continue to build as I think there's definitely room for innovative solutions, just not derivative ones like mine.

@abhinav-phi
abhinav-phi force-pushed the fix/persist-moved-groundings-anchors branch from 8f485a6 to 6e7b29d Compare September 9, 2026 21:37
When a node is grounded BOTH in frontmatter grounds_to and as an
inline mex:// anchor, the anchor pass must follow the grounds_to
resolution instead of re-resolving from store state the migration may
have already destroyed (deleted baseline row, missing fingerprint and
alias). Otherwise the anchor is silently skipped and the next mex
check reports a permanent GROUNDING_GONE.

The grounds_to pass records each oldId -> newId resolution in a
per-file map that the anchor pass consults first, alongside the
pendingMoves baseline deferral. Anchor-only references keep their
existing behavior.

Resolves mex-memory#128
@abhinav-phi
abhinav-phi force-pushed the fix/persist-moved-groundings-anchors branch from 6e7b29d to c72d9f4 Compare September 9, 2026 21:52
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

persistMovedGroundings leaves inline mex:// anchors stale when a node is grounded both in grounds_to and as an inline anchor

2 participants