fix(core): use Observation.permalink in build_context to match the search index (#946)

Closes #929

Signed-off-by: phernandez <paul@basicmemory.com>
This commit is contained in:
Drew Cain
2026-06-10 14:05:02 -05:00
committed by GitHub
parent db578ccfdb
commit 7bb7664fae
2 changed files with 47 additions and 3 deletions
+6 -3
View File
@@ -245,9 +245,12 @@ class ContextService:
type="observation",
id=obs.id,
title=f"{obs.category}: {obs.content[:50]}...",
permalink=generate_permalink(
f"{primary_item.permalink}/observations/{obs.category}/{obs.content}"
),
# Observation.permalink is the single definition of the
# synthetic permalink format (200-char truncation plus
# content digest); rebuilding it inline diverged from the
# search index for long observations (#929). The parent
# entity is eager-loaded by ObservationRepository.
permalink=obs.permalink,
file_path=primary_item.file_path,
content=obs.content,
category=obs.category,
+41
View File
@@ -212,6 +212,47 @@ async def test_build_context_with_observations(context_service, test_graph):
assert "Root note" in note_observation.content
@pytest.mark.asyncio
async def test_build_context_observation_permalinks_match_search_index(
context_service, search_service, entity_service
):
"""Regression test for #929: observation permalinks must match the search index.
build_context used to rebuild the synthetic observation permalink inline,
without the 200-char truncation (#446) or the content digest (#931) that
Observation.permalink applies, so for long observations it returned
permalinks the search index doesn't contain.
"""
from basic_memory.schemas.base import Entity as EntitySchema
from basic_memory.schemas.search import SearchQuery
long_observation = "x" * 210 + " LONG_OBS_MARKER"
entity, _ = await entity_service.create_or_update_entity(
EntitySchema(
title="Long Obs Entity",
note_type="test",
directory="test",
content=f"# Long Obs Entity\n- [note] {long_observation}\n",
)
)
await search_service.index_entity(entity)
url = memory_url.validate_strings(f"memory://{entity.permalink}")
context_result = await context_service.build_context(url, include_observations=True)
assert len(context_result.results) == 1
context_item = context_result.results[0]
assert len(context_item.observations) == 1
obs_row = context_item.observations[0]
# The model property is the single definition of the permalink format
assert obs_row.permalink == entity.observations[0].permalink
# The search index row for this observation carries the same permalink
index_rows = await search_service.search(SearchQuery(text="LONG_OBS_MARKER"))
obs_permalinks = [r.permalink for r in index_rows if r.type == SearchItemType.OBSERVATION.value]
assert obs_permalinks == [obs_row.permalink]
@pytest.mark.asyncio
async def test_build_context_not_found(context_service):
"""Test handling non-existent permalinks."""