Skip to content

Reindex-time diagnostic: surface cross-note b2id collisions (and identity restamps) instead of silently shadowing #81

Description

@samkeen

Extracted from #79's pathology list ("b2id collision across notes") so #79 can close on its PR's merge. This is the one candidate self-healing behavior from that list that #79's implementation deliberately did not cover, because it can't be diagnosed from a single file's parse — it needs index-wide knowledge, i.e. the reindex/projection pass.

The gap

The in-app path can no longer create a collision — Vault::write_frontmatter (#79) refuses any edit that changes, removes, or duplicates the open note's b2id. But the external paths #79 is really hardening against still can:

  • Duplicate-file collision. Copy a note in Finder (note.mdnote copy.md): two files now carry one b2id. Today's behavior (db::upsert_note): ON CONFLICT(b2id) DO UPDATE SET path = excluded.paththe last-projected file silently wins the identity, and the other file is shadowed out of the index entirely (no row, no tree entry, no search hits). Which file wins depends on walk order. Nothing tells the human.
  • Blanked-id identity churn. An external edit that blanks the value (b2id: with nothing after it) reads as absent (scan_b2id skips empty values — the Invalid YAML frontmatter triggers a b2id re-stamp loop (file grows every reindex; desktop can loop on it) #75 discipline), so the next projection stamps a fresh id: the note's identity changes, every inbound edge keyed to the old id dangles (G5 surfaces them as broken links, correctly), but nothing says why — "this note's identity was restamped" is invisible.

Both are tolerate-don't-corrupt today (nothing crashes, bytes are preserved, W4 is honored) — the missing piece is surfacing, per #79's own framing: "Surface as a fixable notice, not a crash and not a silent rewrite."

Proposed shape

Follow the ProjectReport.skipped precedent (the unreadable-file surfacing): the projection pass already visits every note, so it can cheaply report

  • collisions: paths that presented an already-claimed b2id this pass ("notes/a.md and notes/a copy.md both claim 01ABC… — the index kept <winner>; give one of them a fresh identity by removing its b2id: line"), and
  • (possibly) restamped: notes that received a fresh b2id while inbound edges still reference an id no note carries — the churn signature.

Adapters render these as notices (CLI reindex summary; desktop toast/flash, maybe a per-note marker), never auto-fix (W4 — the human decides which file keeps the identity).

Non-goals

Sources

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions