fix(embeddings): embed in the service, so every caller gets it (#2056)
CI & Build / Python lint (push) Successful in 3s
CI & Build / Plugin hooks (push) Successful in 8s
CI & Build / integration (push) Successful in 15s
CI & Build / TypeScript typecheck (push) Successful in 33s
CI & Build / Python tests (push) Successful in 57s
CI & Build / Build & push image (push) Successful in 29s
CI & Build / Python lint (push) Successful in 3s
CI & Build / Plugin hooks (push) Successful in 8s
CI & Build / integration (push) Successful in 15s
CI & Build / TypeScript typecheck (push) Successful in 33s
CI & Build / Python tests (push) Successful in 57s
CI & Build / Build & push image (push) Successful in 29s
A note or task created through MCP was not semantically searchable until the next restart's backfill ran. Embedding fired at the five REST route handlers and nowhere else; the MCP tools call the service directly, so they skipped it. The shape of this bug is the reason to care: it is invisible on an instance that redeploys constantly (this one does, per rule 46) and permanent on one that doesn't. Rule 115 — the product has to stand up for the install that restarts twice a year, not just for the one that restarts hourly. Moved to services/notes.embed_note(), called from create_note and update_note, and deleted from all five routes. Every caller — REST, MCP, recurrence, snippets — now gets it by construction rather than by remembering. Two things fall out of having one implementation instead of six: - It uses note.user_id, the OWNER. The routes were inconsistent: some passed the caller's uid, some the owner's. On a shared record the caller's id mints a second embedding row that nothing reads. - services/snippets.py's _embed_snippet existed only because snippets are created via MCP and the routes couldn't cover them. Every one of its four call sites goes through notes_svc, so the helper and its four calls are gone, along with the eight test patches that existed to neutralise it. RuntimeError (no running loop — unit tests, scripts) and any indexing failure are both swallowed: a write that succeeded must not be failed by its index. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UaYUaouG9jjhATyuxCKrQs
This commit is contained in:
@@ -44,7 +44,6 @@ async def _run_unmerge(survivor, *, source_alive=None, restore=1):
|
||||
patch("scribe.services.trash.restore_entity", AsyncMock(return_value=restore)),
|
||||
patch.object(s.notes_svc, "update_note",
|
||||
AsyncMock(return_value=survivor)) as upd,
|
||||
patch.object(s, "_embed_snippet", MagicMock()),
|
||||
):
|
||||
await s.unmerge_snippet(7, 1, 2)
|
||||
return upd.await_args.kwargs
|
||||
@@ -127,7 +126,6 @@ async def test_a_purged_source_is_refused_and_the_survivor_is_untouched():
|
||||
patch("scribe.services.access.can_write_note", AsyncMock(return_value=True)),
|
||||
patch("scribe.services.trash.restore_entity", AsyncMock(return_value=None)),
|
||||
patch.object(s.notes_svc, "update_note", AsyncMock()) as upd,
|
||||
patch.object(s, "_embed_snippet", MagicMock()),
|
||||
):
|
||||
with pytest.raises(s.UnmergeError, match="purged"):
|
||||
await s.unmerge_snippet(7, 1, 2)
|
||||
@@ -150,7 +148,6 @@ async def test_an_entry_without_attribution_is_refused_not_guessed():
|
||||
patch.object(s, "get_snippet", AsyncMock(side_effect=fake_get)),
|
||||
patch("scribe.services.access.can_write_note", AsyncMock(return_value=True)),
|
||||
patch.object(s.notes_svc, "update_note", AsyncMock()) as upd,
|
||||
patch.object(s, "_embed_snippet", MagicMock()),
|
||||
):
|
||||
with pytest.raises(s.UnmergeError, match="provenance"):
|
||||
await s.unmerge_snippet(7, 1, 2)
|
||||
|
||||
Reference in New Issue
Block a user