Skip to content

fix(folder): stable root ids in source_ref; migrate legacy rows in place - #38

Merged
skishchampi merged 2 commits into
mainfrom
fix/folder-root-stable-ids
Jul 8, 2026
Merged

fix(folder): stable root ids in source_ref; migrate legacy rows in place#38
skishchampi merged 2 commits into
mainfrom
fix/folder-root-stable-ids

Conversation

@skishchampi

Copy link
Copy Markdown
Contributor

Problem

FolderAdapter writes source_ref as {root_index}:{relative_path} — the root's position in the config list. Reordering, adding, or removing folder roots silently orphans every existing chunk (identity includes source_ref) and re-embeds the corpus at full provider cost. Measured on a real DB: 12,319 of 64,466 folder chunk identities exist under more than one ref variant, 6,426 of them orphaned pre-v0.2.4 absolute-path rows — and search_fulltext surfaces those rows as duplicate/stale hits.

Fix

  1. Stable root ids: source_ref is now r<10-hex-of-root-path-hash>:{rel} — immune to config order. Resolution accepts every historical format (stable / positional / absolute / bare relative).
  2. In-place migration: new optional adapter hook migrate_source_refs(store), called by run_indexing before walking (duck-typed — NOT added to the CorpusAdapter protocol, so existing external adapters keep passing isinstance checks). Positional and in-root absolute refs are rewritten in place; rows whose rewrite target already exists (past drift duplicates) are merged, vectors preserved — the legacy row's vectors move to the survivor wherever the run slot is free. Unmappable refs (absolute paths outside every configured root) are left untouched and counted as skipped. Idempotent.

Tests

  • Refs identical under reordered roots; all four formats resolve.
  • Migration: rewrites positional rows, merges drift pairs with both vectors surviving, skips out-of-root rows, no-ops on rerun.
  • End-to-end regression: index roots [A,B], extend with [B,A] → 0 chunks re-created, 0 vectors re-embedded, all skipped.

folder source_ref embedded the root's POSITION in the config list
("{idx}:{rel}"), so reordering roots orphaned every chunk and
re-embedded the corpus. The live cost is real: reordering already
produced thousands of duplicate chunk identities, and the orphaned
rows surface as duplicate/stale FTS hits.

source_ref now uses a stable per-root id ("r" + 10 hex of the resolved
root path's SHA-256), immune to config order. Resolution accepts every
format ever written (stable, positional, absolute, bare relative).

A new optional adapter hook — migrate_source_refs(store), discovered by
duck-typing so external adapters keep validating — runs at the start of
every indexing pass and heals legacy rows in place: positional and
in-root absolute refs are rewritten; rows whose rewrite target already
exists (past drift duplicates) are merged with their vectors preserved
on the survivor; unmappable refs are left alone and counted.

End-to-end regression: index roots [A,B], extend with [B,A] — zero
chunks re-created, zero vectors re-embedded.
@skishchampi

Copy link
Copy Markdown
Contributor Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 1aa7588ec7

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/partial_recall/corpus/adapters/folder.py Outdated
Codex review: mapping a legacy positional ref through the CURRENT root
order mis-assigns it when the user reordered roots before upgrading —
"0:alpha.md" written under old root A would take B's stable id, and
the walk would then re-create (and re-embed) A's chunk anyway.

The item's corpus_ref — the absolute file path item_key is hashed
from — is the order-independent authority; the migration now consults
it first. The positional fallback (corpus_ref missing) only rewrites
when the file actually exists at the mapped location.

Regression test covers the exact scenario, with a same-named decoy
file in the wrong root so naive order-mapping would succeed wrongly.
@skishchampi
skishchampi merged commit b4aa42d into main Jul 8, 2026
8 checks passed
@skishchampi
skishchampi deleted the fix/folder-root-stable-ids branch July 8, 2026 00:30
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.

1 participant