Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 6 additions & 3 deletions src/basic_memory/services/context_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -245,9 +245,12 @@ async def build_context(
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,
Expand Down
41 changes: 41 additions & 0 deletions tests/services/test_context_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -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."""
Expand Down
Loading