From f2ade9d92ec7f51e4eac9509922dff47cd272c50 Mon Sep 17 00:00:00 2001 From: Joe Danziger Date: Mon, 5 Oct 2026 15:17:15 -0400 Subject: [PATCH] doctor: resolve duplicate stems in the indexer's order Two doctor checks built their stem map from component-sorted paths while the indexer (and relink) use plain string order, so a bare [[Dup]] could resolve to a different note in cross-refs and near-duplicate checks than in search. Claude-Session: https://claude.ai/code/session_012XGaE1cGxpYryus7cxfAFD --- src/brain/doctor.py | 4 ++-- tests/test_doctor.py | 17 +++++++++++++++++ 2 files changed, 19 insertions(+), 2 deletions(-) diff --git a/src/brain/doctor.py b/src/brain/doctor.py index 054c22c..d90af49 100644 --- a/src/brain/doctor.py +++ b/src/brain/doctor.py @@ -436,7 +436,7 @@ def _check_duplicates(master: Path, org: Org, rules: tuple[SpaceRule, ...], # siblings under one parent still are. all_paths = set(rels) by_stem_all: dict[str, str] = {} - for r in rels: + for r in sorted(rels): # string order, as the indexer resolves duplicate stems by_stem_all.setdefault(_stem(r), r) up_of: dict[str, set[str]] = {} for r in rels: @@ -831,7 +831,7 @@ def _check_cross_space_refs(master: Path, org: Org, rules: tuple[SpaceRule, ...] rels = run.content_files() paths = set(rels) by_stem: dict[str, str] = {} - for rel in rels: + for rel in sorted(rels): # string order, as the indexer resolves duplicate stems by_stem.setdefault(_stem(rel), rel) readers_of = run.readers_of(org, rules) diff --git a/tests/test_doctor.py b/tests/test_doctor.py index e7f423b..4ec3004 100644 --- a/tests/test_doctor.py +++ b/tests/test_doctor.py @@ -123,6 +123,23 @@ def test_cross_space_reference_warns_and_same_space_is_silent(master): assert "bob" in home[0].message # the reader who cannot see it +def test_cross_refs_resolve_duplicate_stems_like_the_indexer(master): + seed_meta(master) + # "Clients/a-b/Dup.md" sorts before "Clients/a/Dup.md" as a string ('-' < '/'), + # which is the order the indexer resolves a bare [[Dup]] in. Doctor must pick + # the same note: the public one, so the link leaks nothing. + (master / "_meta/spaces.yaml").write_text( + SPACES_YAML + + ' - {path: "Clients/a", read: ["person:alice"], write: ["person:alice"]}\n' + + ' - {path: "Clients/a-b", read: [everyone], write: ["person:alice"]}\n') + for rel in ("Clients/a/Dup.md", "Clients/a-b/Dup.md"): + (master / rel).parent.mkdir(parents=True, exist_ok=True) + (master / rel).write_text("# Dup\nbody.\n") + (master / "Company/Pointer.md").write_text("# Pointer\nSee [[Dup]].\n") + findings = [f for f in run_doctor(master) if f.check == "cross-refs"] + assert not [f for f in findings if f.message.startswith("Company/Pointer.md")] + + def _restrict_vandenberg(master): """Add a Vandenberg client space readable only by alice.""" (master / "_meta/spaces.yaml").write_text(