fix(tolaria): C5F Phase B — search-source indexability path-independent (valid C3 object_id suffices; LEGACY kept; out-of-scope dirs no longer auto-exclude indexable knowledge)
This commit is contained in:
parent
5e4191578a
commit
12635e8672
2 changed files with 86 additions and 12 deletions
|
|
@ -81,6 +81,7 @@ from rq_c5b import (
|
|||
content_hash,
|
||||
detect_secret,
|
||||
KnowledgeScope,
|
||||
LEGACY_SPECIAL_PATHS,
|
||||
SCOPE_IN_SCOPE,
|
||||
SCOPE_LEGACY_SPECIAL,
|
||||
)
|
||||
|
|
@ -281,6 +282,17 @@ class SearchSourceBuilder:
|
|||
Liest den Vault (read-only) und klassifiziert die indexierbaren Objekte.
|
||||
Rueckgabe: {"indexable": [{path, id, content, fm, body}],
|
||||
"excluded": [...], "vault_object_count": int}
|
||||
|
||||
INDEXIERBARKEIT ist PFAD-INDEPENDENT (C5F Phase B):
|
||||
Ein Vault-Objekt ist indexierbar, wenn es
|
||||
* eine gueltige C3 object/<FULL_UUID>-ID im Frontmatter traegt
|
||||
(extract_object_id) ODER
|
||||
* ein expliziter LEGACY_SPECIAL-Sonderfall ist (README.md x2, vps.md;
|
||||
ohne C3-ID, KEEP_DISTINCT / DEFERRED).
|
||||
Der Pfad (z.B. notes/trading/second-brain/, notes/ai-agents/) ist dabei
|
||||
NICHT mehr ausschlaggebend — jedes Markdown mit gueltiger object_id ist
|
||||
ein Knowledge-Objekt und wird indexiert. Objekte OHNE gueltige object_id
|
||||
(ausser Legacy) sind nicht indexierbar -> excluded (HUMAN_REVIEW/Orphan).
|
||||
"""
|
||||
entries = self.tolaria.list(VAULT_PREFIX) or []
|
||||
indexable: List[Dict[str, Any]] = []
|
||||
|
|
@ -294,18 +306,15 @@ class SearchSourceBuilder:
|
|||
if content is None:
|
||||
excluded.append({"path": rel, "reason": "unreadable"})
|
||||
continue
|
||||
cls = self.scope.classify_with_content(rel, content)
|
||||
sc = cls.get("scope")
|
||||
if sc == SCOPE_IN_SCOPE:
|
||||
oid = cls.get("object_id")
|
||||
# Pfad-unabhaengige Indexierbarkeit
|
||||
if rel in LEGACY_SPECIAL_PATHS:
|
||||
indexable.append({"path": rel, "id": None, "content": content})
|
||||
continue
|
||||
oid = extract_object_id(content)
|
||||
if oid:
|
||||
indexable.append({"path": rel, "id": oid, "content": content})
|
||||
continue
|
||||
excluded.append({"path": rel, "reason": "no_object_id"})
|
||||
elif sc == SCOPE_LEGACY_SPECIAL:
|
||||
indexable.append({"path": rel, "id": None, "content": content})
|
||||
else:
|
||||
excluded.append({"path": rel, "reason": sc})
|
||||
return {"indexable": indexable, "excluded": excluded,
|
||||
"vault_object_count": len(entries)}
|
||||
|
||||
|
|
|
|||
|
|
@ -678,5 +678,70 @@ class TestRealisticIntegration(unittest.TestCase):
|
|||
self.assertGreater(base["total"], 0)
|
||||
|
||||
|
||||
class TestPhaseB_PathIndependentScope(unittest.TestCase):
|
||||
"""C5F Phase B: Indexierbarkeit ist pfad-unabhaengig (gültige C3-ID reicht)."""
|
||||
|
||||
def _collect(self, fake, src=None):
|
||||
import tempfile as _tf
|
||||
if src is None:
|
||||
src = os.path.join(_tf.mkdtemp(prefix="c5fb_"), "source.json")
|
||||
builder = _make_builder(fake, src)
|
||||
return builder._collect_indexable()
|
||||
|
||||
def test_object_under_second_brain_with_valid_id_indexed(self):
|
||||
# Objekt unter notes/trading/second-brain/ (vorher OUT_OF_SCOPE via Pfad)
|
||||
# mit gültiger C3-ID muss jetzt indexiert werden.
|
||||
fake = FakeTolaria()
|
||||
fake.vault[_vault_path("notes/trading/second-brain/regelwerk.md")] = (
|
||||
"---\nkid: 1\nid: " + UUID_A + "\ntitle: Regelwerk\n---\nbody")
|
||||
r = self._collect(fake)
|
||||
paths = [o["path"] for o in r["indexable"]]
|
||||
ids = [o["id"] for o in r["indexable"]]
|
||||
self.assertIn("notes/trading/second-brain/regelwerk.md", paths)
|
||||
self.assertIn(UUID_A, ids)
|
||||
|
||||
def test_object_under_ai_agents_with_valid_id_indexed(self):
|
||||
fake = FakeTolaria()
|
||||
fake.vault[_vault_path("notes/ai-agents/reference.md")] = (
|
||||
"---\nkid: 1\nid: " + UUID_B + "\ntitle: Ref\n---\nbody")
|
||||
r = self._collect(fake)
|
||||
paths = [o["path"] for o in r["indexable"]]
|
||||
self.assertIn("notes/ai-agents/reference.md", paths)
|
||||
|
||||
def test_unknown_file_without_id_excluded(self):
|
||||
# Unbekannte Nicht-Knowledge-Datei OHNE gültige C3-ID -> ausgeschlossen,
|
||||
# auch wenn sie .md ist.
|
||||
fake = FakeTolaria()
|
||||
fake.vault[_vault_path("notes/inbox/notizen.md")] = "kein frontmatter id"
|
||||
r = self._collect(fake)
|
||||
paths = [o["path"] for o in r["indexable"]]
|
||||
excl = [e["path"] for e in r["excluded"]]
|
||||
self.assertNotIn("notes/inbox/notizen.md", paths)
|
||||
self.assertIn("notes/inbox/notizen.md", excl)
|
||||
|
||||
def test_legacy_special_indexed_without_id(self):
|
||||
# LEGACY_SPECIAL (z.B. vps.md) bleibt ohne C3-ID indexierbar.
|
||||
fake = FakeTolaria()
|
||||
fake.vault[_vault_path("vps.md")] = "---\ntitle: VPS\n---\nbody"
|
||||
r = self._collect(fake)
|
||||
paths = [o["path"] for o in r["indexable"]]
|
||||
legacy = [o for o in r["indexable"] if o["id"] is None]
|
||||
self.assertIn("vps.md", paths)
|
||||
self.assertTrue(any(o["path"] == "vps.md" for o in legacy))
|
||||
|
||||
def test_mixed_scope_total_count(self):
|
||||
# Mixed: Root mit ID + second-brain mit ID + Notiz ohne ID + Legacy.
|
||||
fake = FakeTolaria()
|
||||
fake.vault[_vault_path("modul-09.md")] = (
|
||||
"---\nkid: 1\nid: " + UUID_A + "\ntitle: M\n---\nbody")
|
||||
fake.vault[_vault_path("notes/trading/second-brain/x.md")] = (
|
||||
"---\nkid: 1\nid: " + UUID_B + "\ntitle: X\n---\nbody")
|
||||
fake.vault[_vault_path("notes/inbox/notiz.md")] = "kein fm id"
|
||||
fake.vault[_vault_path("README.md")] = "---\ntitle: R\n---\nbody"
|
||||
r = self._collect(fake)
|
||||
self.assertEqual(len(r["indexable"]), 3) # 2 mit ID + 1 Legacy
|
||||
self.assertEqual(len(r["excluded"]), 1) # Notiz ohne ID
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
Loading…
Reference in a new issue