pycontextdb 0.4.3__tar.gz → 0.4.4__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (151) hide show
  1. {pycontextdb-0.4.3 → pycontextdb-0.4.4}/CHANGELOG.md +6 -0
  2. {pycontextdb-0.4.3 → pycontextdb-0.4.4}/PKG-INFO +1 -1
  3. {pycontextdb-0.4.3 → pycontextdb-0.4.4}/contextdb/__init__.py +1 -1
  4. {pycontextdb-0.4.3 → pycontextdb-0.4.4}/contextdb/store/postgres_store.py +29 -1
  5. {pycontextdb-0.4.3 → pycontextdb-0.4.4}/pyproject.toml +1 -1
  6. {pycontextdb-0.4.3 → pycontextdb-0.4.4}/tests/unit/test_pg_sql.py +33 -1
  7. {pycontextdb-0.4.3 → pycontextdb-0.4.4}/.github/CODEOWNERS +0 -0
  8. {pycontextdb-0.4.3 → pycontextdb-0.4.4}/.github/copilot-instructions.md +0 -0
  9. {pycontextdb-0.4.3 → pycontextdb-0.4.4}/.github/pull_request_template.md +0 -0
  10. {pycontextdb-0.4.3 → pycontextdb-0.4.4}/.github/workflows/ci.yml +0 -0
  11. {pycontextdb-0.4.3 → pycontextdb-0.4.4}/.github/workflows/publish.yml +0 -0
  12. {pycontextdb-0.4.3 → pycontextdb-0.4.4}/.gitignore +0 -0
  13. {pycontextdb-0.4.3 → pycontextdb-0.4.4}/.pre-commit-config.yaml +0 -0
  14. {pycontextdb-0.4.3 → pycontextdb-0.4.4}/COMMERCIAL.md +0 -0
  15. {pycontextdb-0.4.3 → pycontextdb-0.4.4}/CONTRIBUTING.md +0 -0
  16. {pycontextdb-0.4.3 → pycontextdb-0.4.4}/LICENSE +0 -0
  17. {pycontextdb-0.4.3 → pycontextdb-0.4.4}/NOTICE +0 -0
  18. {pycontextdb-0.4.3 → pycontextdb-0.4.4}/OPEN_CORE.md +0 -0
  19. {pycontextdb-0.4.3 → pycontextdb-0.4.4}/README.md +0 -0
  20. {pycontextdb-0.4.3 → pycontextdb-0.4.4}/ai-tools/.cursorrules +0 -0
  21. {pycontextdb-0.4.3 → pycontextdb-0.4.4}/ai-tools/CLAUDE.md +0 -0
  22. {pycontextdb-0.4.3 → pycontextdb-0.4.4}/ai-tools/README.md +0 -0
  23. {pycontextdb-0.4.3 → pycontextdb-0.4.4}/ai-tools/llms-full.txt +0 -0
  24. {pycontextdb-0.4.3 → pycontextdb-0.4.4}/ai-tools/llms.txt +0 -0
  25. {pycontextdb-0.4.3 → pycontextdb-0.4.4}/assets/README.md +0 -0
  26. {pycontextdb-0.4.3 → pycontextdb-0.4.4}/assets/architecture.png +0 -0
  27. {pycontextdb-0.4.3 → pycontextdb-0.4.4}/assets/benchmarks.png +0 -0
  28. {pycontextdb-0.4.3 → pycontextdb-0.4.4}/assets/hero.png +0 -0
  29. {pycontextdb-0.4.3 → pycontextdb-0.4.4}/assets/performance.png +0 -0
  30. {pycontextdb-0.4.3 → pycontextdb-0.4.4}/assets/problem.png +0 -0
  31. {pycontextdb-0.4.3 → pycontextdb-0.4.4}/assets/quickstart.png +0 -0
  32. {pycontextdb-0.4.3 → pycontextdb-0.4.4}/benchmarks/__init__.py +0 -0
  33. {pycontextdb-0.4.3 → pycontextdb-0.4.4}/benchmarks/run_benchmarks.py +0 -0
  34. {pycontextdb-0.4.3 → pycontextdb-0.4.4}/benchmarks/trust_bakeoff.py +0 -0
  35. {pycontextdb-0.4.3 → pycontextdb-0.4.4}/contextdb/agents/__init__.py +0 -0
  36. {pycontextdb-0.4.3 → pycontextdb-0.4.4}/contextdb/agents/memory_bus.py +0 -0
  37. {pycontextdb-0.4.3 → pycontextdb-0.4.4}/contextdb/agents/rl_manager.py +0 -0
  38. {pycontextdb-0.4.3 → pycontextdb-0.4.4}/contextdb/cli.py +0 -0
  39. {pycontextdb-0.4.3 → pycontextdb-0.4.4}/contextdb/client.py +0 -0
  40. {pycontextdb-0.4.3 → pycontextdb-0.4.4}/contextdb/connectors/__init__.py +0 -0
  41. {pycontextdb-0.4.3 → pycontextdb-0.4.4}/contextdb/connectors/base.py +0 -0
  42. {pycontextdb-0.4.3 → pycontextdb-0.4.4}/contextdb/connectors/postgres.py +0 -0
  43. {pycontextdb-0.4.3 → pycontextdb-0.4.4}/contextdb/core/__init__.py +0 -0
  44. {pycontextdb-0.4.3 → pycontextdb-0.4.4}/contextdb/core/clock.py +0 -0
  45. {pycontextdb-0.4.3 → pycontextdb-0.4.4}/contextdb/core/config.py +0 -0
  46. {pycontextdb-0.4.3 → pycontextdb-0.4.4}/contextdb/core/exceptions.py +0 -0
  47. {pycontextdb-0.4.3 → pycontextdb-0.4.4}/contextdb/core/models.py +0 -0
  48. {pycontextdb-0.4.3 → pycontextdb-0.4.4}/contextdb/core/policy.py +0 -0
  49. {pycontextdb-0.4.3 → pycontextdb-0.4.4}/contextdb/core/slots.py +0 -0
  50. {pycontextdb-0.4.3 → pycontextdb-0.4.4}/contextdb/dynamics/__init__.py +0 -0
  51. {pycontextdb-0.4.3 → pycontextdb-0.4.4}/contextdb/dynamics/evolution.py +0 -0
  52. {pycontextdb-0.4.3 → pycontextdb-0.4.4}/contextdb/dynamics/formation.py +0 -0
  53. {pycontextdb-0.4.3 → pycontextdb-0.4.4}/contextdb/dynamics/retrieval.py +0 -0
  54. {pycontextdb-0.4.3 → pycontextdb-0.4.4}/contextdb/dynamics/salience.py +0 -0
  55. {pycontextdb-0.4.3 → pycontextdb-0.4.4}/contextdb/dynamics/trust.py +0 -0
  56. {pycontextdb-0.4.3 → pycontextdb-0.4.4}/contextdb/graphs/__init__.py +0 -0
  57. {pycontextdb-0.4.3 → pycontextdb-0.4.4}/contextdb/graphs/base.py +0 -0
  58. {pycontextdb-0.4.3 → pycontextdb-0.4.4}/contextdb/graphs/causal.py +0 -0
  59. {pycontextdb-0.4.3 → pycontextdb-0.4.4}/contextdb/graphs/entity.py +0 -0
  60. {pycontextdb-0.4.3 → pycontextdb-0.4.4}/contextdb/graphs/semantic.py +0 -0
  61. {pycontextdb-0.4.3 → pycontextdb-0.4.4}/contextdb/graphs/temporal.py +0 -0
  62. {pycontextdb-0.4.3 → pycontextdb-0.4.4}/contextdb/integrations/__init__.py +0 -0
  63. {pycontextdb-0.4.3 → pycontextdb-0.4.4}/contextdb/integrations/act.py +0 -0
  64. {pycontextdb-0.4.3 → pycontextdb-0.4.4}/contextdb/integrations/autogen.py +0 -0
  65. {pycontextdb-0.4.3 → pycontextdb-0.4.4}/contextdb/integrations/crewai.py +0 -0
  66. {pycontextdb-0.4.3 → pycontextdb-0.4.4}/contextdb/integrations/langchain.py +0 -0
  67. {pycontextdb-0.4.3 → pycontextdb-0.4.4}/contextdb/integrations/livekit.py +0 -0
  68. {pycontextdb-0.4.3 → pycontextdb-0.4.4}/contextdb/integrations/openai_tools.py +0 -0
  69. {pycontextdb-0.4.3 → pycontextdb-0.4.4}/contextdb/integrations/pipecat.py +0 -0
  70. {pycontextdb-0.4.3 → pycontextdb-0.4.4}/contextdb/integrations/prompting.py +0 -0
  71. {pycontextdb-0.4.3 → pycontextdb-0.4.4}/contextdb/mcp.py +0 -0
  72. {pycontextdb-0.4.3 → pycontextdb-0.4.4}/contextdb/memory/__init__.py +0 -0
  73. {pycontextdb-0.4.3 → pycontextdb-0.4.4}/contextdb/memory/experiential.py +0 -0
  74. {pycontextdb-0.4.3 → pycontextdb-0.4.4}/contextdb/memory/factual.py +0 -0
  75. {pycontextdb-0.4.3 → pycontextdb-0.4.4}/contextdb/memory/working.py +0 -0
  76. {pycontextdb-0.4.3 → pycontextdb-0.4.4}/contextdb/pool.py +0 -0
  77. {pycontextdb-0.4.3 → pycontextdb-0.4.4}/contextdb/privacy/__init__.py +0 -0
  78. {pycontextdb-0.4.3 → pycontextdb-0.4.4}/contextdb/privacy/audit.py +0 -0
  79. {pycontextdb-0.4.3 → pycontextdb-0.4.4}/contextdb/privacy/injection.py +0 -0
  80. {pycontextdb-0.4.3 → pycontextdb-0.4.4}/contextdb/privacy/pii_detector.py +0 -0
  81. {pycontextdb-0.4.3 → pycontextdb-0.4.4}/contextdb/privacy/retention.py +0 -0
  82. {pycontextdb-0.4.3 → pycontextdb-0.4.4}/contextdb/py.typed +0 -0
  83. {pycontextdb-0.4.3 → pycontextdb-0.4.4}/contextdb/serve.py +0 -0
  84. {pycontextdb-0.4.3 → pycontextdb-0.4.4}/contextdb/store/__init__.py +0 -0
  85. {pycontextdb-0.4.3 → pycontextdb-0.4.4}/contextdb/store/base.py +0 -0
  86. {pycontextdb-0.4.3 → pycontextdb-0.4.4}/contextdb/store/factory.py +0 -0
  87. {pycontextdb-0.4.3 → pycontextdb-0.4.4}/contextdb/store/pg_sql.py +0 -0
  88. {pycontextdb-0.4.3 → pycontextdb-0.4.4}/contextdb/store/sqlite_store.py +0 -0
  89. {pycontextdb-0.4.3 → pycontextdb-0.4.4}/contextdb/store/vector_index.py +0 -0
  90. {pycontextdb-0.4.3 → pycontextdb-0.4.4}/contextdb/utils/__init__.py +0 -0
  91. {pycontextdb-0.4.3 → pycontextdb-0.4.4}/contextdb/utils/embeddings.py +0 -0
  92. {pycontextdb-0.4.3 → pycontextdb-0.4.4}/contextdb/utils/llm.py +0 -0
  93. {pycontextdb-0.4.3 → pycontextdb-0.4.4}/contextdb/utils/migrations.py +0 -0
  94. {pycontextdb-0.4.3 → pycontextdb-0.4.4}/docs/api.md +0 -0
  95. {pycontextdb-0.4.3 → pycontextdb-0.4.4}/docs/architecture.md +0 -0
  96. {pycontextdb-0.4.3 → pycontextdb-0.4.4}/docs/connectors.md +0 -0
  97. {pycontextdb-0.4.3 → pycontextdb-0.4.4}/docs/index.md +0 -0
  98. {pycontextdb-0.4.3 → pycontextdb-0.4.4}/docs/memory_evolution.md +0 -0
  99. {pycontextdb-0.4.3 → pycontextdb-0.4.4}/docs/migrations.md +0 -0
  100. {pycontextdb-0.4.3 → pycontextdb-0.4.4}/docs/multi_tenant.md +0 -0
  101. {pycontextdb-0.4.3 → pycontextdb-0.4.4}/docs/postgres.md +0 -0
  102. {pycontextdb-0.4.3 → pycontextdb-0.4.4}/docs/prompts.md +0 -0
  103. {pycontextdb-0.4.3 → pycontextdb-0.4.4}/docs/quickstart.md +0 -0
  104. {pycontextdb-0.4.3 → pycontextdb-0.4.4}/docs/serve.md +0 -0
  105. {pycontextdb-0.4.3 → pycontextdb-0.4.4}/docs/trust_policy.md +0 -0
  106. {pycontextdb-0.4.3 → pycontextdb-0.4.4}/examples/customer_support.py +0 -0
  107. {pycontextdb-0.4.3 → pycontextdb-0.4.4}/examples/multi_agent_team.py +0 -0
  108. {pycontextdb-0.4.3 → pycontextdb-0.4.4}/examples/phone_agent.py +0 -0
  109. {pycontextdb-0.4.3 → pycontextdb-0.4.4}/examples/research_assistant.py +0 -0
  110. {pycontextdb-0.4.3 → pycontextdb-0.4.4}/legal/CLA-entity.md +0 -0
  111. {pycontextdb-0.4.3 → pycontextdb-0.4.4}/legal/CLA-individual.md +0 -0
  112. {pycontextdb-0.4.3 → pycontextdb-0.4.4}/legal/signed/README.md +0 -0
  113. {pycontextdb-0.4.3 → pycontextdb-0.4.4}/mkdocs.yml +0 -0
  114. {pycontextdb-0.4.3 → pycontextdb-0.4.4}/scripts/check_open_core_boundary.py +0 -0
  115. {pycontextdb-0.4.3 → pycontextdb-0.4.4}/scripts/check_release.py +0 -0
  116. {pycontextdb-0.4.3 → pycontextdb-0.4.4}/tests/__init__.py +0 -0
  117. {pycontextdb-0.4.3 → pycontextdb-0.4.4}/tests/conftest.py +0 -0
  118. {pycontextdb-0.4.3 → pycontextdb-0.4.4}/tests/evals/__init__.py +0 -0
  119. {pycontextdb-0.4.3 → pycontextdb-0.4.4}/tests/evals/test_trust_model.py +0 -0
  120. {pycontextdb-0.4.3 → pycontextdb-0.4.4}/tests/integration/__init__.py +0 -0
  121. {pycontextdb-0.4.3 → pycontextdb-0.4.4}/tests/integration/test_client.py +0 -0
  122. {pycontextdb-0.4.3 → pycontextdb-0.4.4}/tests/integration/test_langchain_integration.py +0 -0
  123. {pycontextdb-0.4.3 → pycontextdb-0.4.4}/tests/pg_util.py +0 -0
  124. {pycontextdb-0.4.3 → pycontextdb-0.4.4}/tests/test_version.py +0 -0
  125. {pycontextdb-0.4.3 → pycontextdb-0.4.4}/tests/unit/__init__.py +0 -0
  126. {pycontextdb-0.4.3 → pycontextdb-0.4.4}/tests/unit/test_audit.py +0 -0
  127. {pycontextdb-0.4.3 → pycontextdb-0.4.4}/tests/unit/test_config.py +0 -0
  128. {pycontextdb-0.4.3 → pycontextdb-0.4.4}/tests/unit/test_connectors.py +0 -0
  129. {pycontextdb-0.4.3 → pycontextdb-0.4.4}/tests/unit/test_decision_scope.py +0 -0
  130. {pycontextdb-0.4.3 → pycontextdb-0.4.4}/tests/unit/test_embeddings.py +0 -0
  131. {pycontextdb-0.4.3 → pycontextdb-0.4.4}/tests/unit/test_evolution.py +0 -0
  132. {pycontextdb-0.4.3 → pycontextdb-0.4.4}/tests/unit/test_formation.py +0 -0
  133. {pycontextdb-0.4.3 → pycontextdb-0.4.4}/tests/unit/test_graphs.py +0 -0
  134. {pycontextdb-0.4.3 → pycontextdb-0.4.4}/tests/unit/test_host_api.py +0 -0
  135. {pycontextdb-0.4.3 → pycontextdb-0.4.4}/tests/unit/test_init.py +0 -0
  136. {pycontextdb-0.4.3 → pycontextdb-0.4.4}/tests/unit/test_memory_bus.py +0 -0
  137. {pycontextdb-0.4.3 → pycontextdb-0.4.4}/tests/unit/test_memory_evolution.py +0 -0
  138. {pycontextdb-0.4.3 → pycontextdb-0.4.4}/tests/unit/test_models.py +0 -0
  139. {pycontextdb-0.4.3 → pycontextdb-0.4.4}/tests/unit/test_open_core_boundary.py +0 -0
  140. {pycontextdb-0.4.3 → pycontextdb-0.4.4}/tests/unit/test_pii_detector.py +0 -0
  141. {pycontextdb-0.4.3 → pycontextdb-0.4.4}/tests/unit/test_postgres_concurrency.py +0 -0
  142. {pycontextdb-0.4.3 → pycontextdb-0.4.4}/tests/unit/test_postgres_connector.py +0 -0
  143. {pycontextdb-0.4.3 → pycontextdb-0.4.4}/tests/unit/test_postgres_memory_evolution.py +0 -0
  144. {pycontextdb-0.4.3 → pycontextdb-0.4.4}/tests/unit/test_retention.py +0 -0
  145. {pycontextdb-0.4.3 → pycontextdb-0.4.4}/tests/unit/test_retrieval.py +0 -0
  146. {pycontextdb-0.4.3 → pycontextdb-0.4.4}/tests/unit/test_scoped_id_access.py +0 -0
  147. {pycontextdb-0.4.3 → pycontextdb-0.4.4}/tests/unit/test_serve.py +0 -0
  148. {pycontextdb-0.4.3 → pycontextdb-0.4.4}/tests/unit/test_store.py +0 -0
  149. {pycontextdb-0.4.3 → pycontextdb-0.4.4}/tests/unit/test_trust_policy.py +0 -0
  150. {pycontextdb-0.4.3 → pycontextdb-0.4.4}/tests/unit/test_vector_index.py +0 -0
  151. {pycontextdb-0.4.3 → pycontextdb-0.4.4}/tests/unit/test_vector_scope.py +0 -0
@@ -2,6 +2,12 @@
2
2
 
3
3
  ## Unreleased
4
4
 
5
+ ## 0.4.4 — 2026-09-20
6
+
7
+ - PostgreSQL sibling composition now uses an ordered user-plus-entity cache
8
+ index, so a scoped recall does not scan other users that share the same
9
+ entity key.
10
+
5
11
  ## 0.4.3 — 2026-09-20
6
12
 
7
13
  - High-cardinality Postgres recall now indexes cached IDs by user and entity,
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: pycontextdb
3
- Version: 0.4.3
3
+ Version: 0.4.4
4
4
  Summary: The unified context layer for AI agents — memory you can act on without treating wishes as facts
5
5
  Project-URL: Homepage, https://github.com/atomsai/contextdb
6
6
  Project-URL: Documentation, https://contextdb.dev
@@ -41,7 +41,7 @@ from contextdb.core.policy import TrustPolicy
41
41
  from contextdb.pool import ContextDBPool
42
42
  from contextdb.utils.embeddings import EmbeddingProvider
43
43
 
44
- __version__ = "0.4.3"
44
+ __version__ = "0.4.4"
45
45
 
46
46
  __all__ = [
47
47
  "Clock",
@@ -282,6 +282,10 @@ class PostgresStore(BaseStore):
282
282
  self._index_items: dict[str, MemoryItem] = {}
283
283
  self._index_ids_by_user: dict[str | None, set[str]] = {}
284
284
  self._index_ids_by_entity: dict[str, dict[str, None]] = {}
285
+ self._index_ids_by_user_entity: dict[
286
+ tuple[str | None, str],
287
+ dict[str, None],
288
+ ] = {}
285
289
  self._embedding_dim = embedding_dim
286
290
  self._embedding_model_id = embedding_model_id
287
291
  self._index_loaded = False
@@ -389,6 +393,10 @@ class PostgresStore(BaseStore):
389
393
  self._index_ids_by_user.setdefault(item.user_id, set()).add(item.id)
390
394
  if item.entity_key is not None:
391
395
  self._index_ids_by_entity.setdefault(item.entity_key, {})[item.id] = None
396
+ self._index_ids_by_user_entity.setdefault(
397
+ (item.user_id, item.entity_key),
398
+ {},
399
+ )[item.id] = None
392
400
 
393
401
  def _discard_index_item(self, memory_id: str) -> None:
394
402
  item = self._index_items.pop(memory_id, None)
@@ -405,11 +413,22 @@ class PostgresStore(BaseStore):
405
413
  entity_ids.pop(memory_id, None)
406
414
  if not entity_ids:
407
415
  self._index_ids_by_entity.pop(item.entity_key, None)
416
+ scoped_entity_ids = self._index_ids_by_user_entity.get(
417
+ (item.user_id, item.entity_key)
418
+ )
419
+ if scoped_entity_ids is not None:
420
+ scoped_entity_ids.pop(memory_id, None)
421
+ if not scoped_entity_ids:
422
+ self._index_ids_by_user_entity.pop(
423
+ (item.user_id, item.entity_key),
424
+ None,
425
+ )
408
426
 
409
427
  def _clear_index_items(self) -> None:
410
428
  self._index_items.clear()
411
429
  self._index_ids_by_user.clear()
412
430
  self._index_ids_by_entity.clear()
431
+ self._index_ids_by_user_entity.clear()
413
432
 
414
433
  def _scope_sql(self, user_id: str | None = None) -> tuple[str, list[Any]]:
415
434
  clauses: list[str] = []
@@ -1101,8 +1120,17 @@ class PostgresStore(BaseStore):
1101
1120
  await self._ensure_index()
1102
1121
  excluded = exclude_ids or set()
1103
1122
  items: list[MemoryItem] = []
1123
+ scope = self._resolve_scope(user_id)
1104
1124
  for entity_key in dict.fromkeys(entity_keys):
1105
- for memory_id in self._index_ids_by_entity.get(entity_key, {}):
1125
+ entity_ids = (
1126
+ self._index_ids_by_entity.get(entity_key, {})
1127
+ if scope is None
1128
+ else self._index_ids_by_user_entity.get(
1129
+ (scope, entity_key),
1130
+ {},
1131
+ )
1132
+ )
1133
+ for memory_id in entity_ids:
1106
1134
  item = self._index_items.get(memory_id)
1107
1135
  if item is None:
1108
1136
  continue
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "pycontextdb"
7
- version = "0.4.3"
7
+ version = "0.4.4"
8
8
  description = "The unified context layer for AI agents — memory you can act on without treating wishes as facts"
9
9
  readme = "README.md"
10
10
  license = "Apache-2.0"
@@ -98,6 +98,16 @@ async def test_list_by_entities_filters_and_copies_only_the_requested_limit(
98
98
  )
99
99
  for index in range(20)
100
100
  ]
101
+ foreign = [
102
+ MemoryItem(
103
+ content=f"foreign-{index}",
104
+ entity_key="entity-a",
105
+ user_id="user-2",
106
+ tenant_id="tenant-1",
107
+ agent_id="agent-1",
108
+ )
109
+ for index in range(1_000)
110
+ ]
101
111
  future = MemoryItem(
102
112
  content="future",
103
113
  entity_key="entity-a",
@@ -106,13 +116,15 @@ async def test_list_by_entities_filters_and_copies_only_the_requested_limit(
106
116
  agent_id="agent-1",
107
117
  valid_from=now + timedelta(days=1),
108
118
  )
109
- indexed = [*entity_b, future, *entity_a]
119
+ indexed = [*foreign, *entity_b, future, *entity_a]
110
120
  for item in indexed:
111
121
  store._cache_index_item(item)
112
122
  store._index_loaded = True
113
123
 
114
124
  original_model_copy = MemoryItem.model_copy
125
+ original_scope_allows = store._item_scope_allows
115
126
  copies = 0
127
+ scope_checks = 0
116
128
 
117
129
  def counted_model_copy(
118
130
  self: MemoryItem,
@@ -125,6 +137,13 @@ async def test_list_by_entities_filters_and_copies_only_the_requested_limit(
125
137
  return original_model_copy(self, update=update, deep=deep)
126
138
 
127
139
  monkeypatch.setattr(MemoryItem, "model_copy", counted_model_copy)
140
+
141
+ def counted_scope_allows(item: MemoryItem, user_id: str | None) -> bool:
142
+ nonlocal scope_checks
143
+ scope_checks += 1
144
+ return original_scope_allows(item, user_id)
145
+
146
+ monkeypatch.setattr(store, "_item_scope_allows", counted_scope_allows)
128
147
  results = await store.list_by_entities(
129
148
  ["entity-a", "entity-b"],
130
149
  user_id="user-1",
@@ -135,6 +154,7 @@ async def test_list_by_entities_filters_and_copies_only_the_requested_limit(
135
154
 
136
155
  assert len(results) == 5
137
156
  assert copies == 5
157
+ assert scope_checks == 7
138
158
  assert all(item.entity_key == "entity-a" for item in results)
139
159
  assert entity_a[0].id not in {item.id for item in results}
140
160
  results[0].metadata["nested"].append(99)
@@ -238,6 +258,12 @@ def test_postgres_secondary_indexes_follow_cache_lifecycle() -> None:
238
258
  }
239
259
  assert list(store._index_ids_by_entity["entity-a"]) == [first.id]
240
260
  assert list(store._index_ids_by_entity["entity-b"]) == [second.id]
261
+ assert list(store._index_ids_by_user_entity["user-1", "entity-a"]) == [
262
+ first.id
263
+ ]
264
+ assert list(store._index_ids_by_user_entity["user-2", "entity-b"]) == [
265
+ second.id
266
+ ]
241
267
 
242
268
  replacement = first.model_copy(
243
269
  update={"user_id": "user-3", "entity_key": "entity-c"}
@@ -245,18 +271,24 @@ def test_postgres_secondary_indexes_follow_cache_lifecycle() -> None:
245
271
  store._cache_index_item(replacement)
246
272
  assert "user-1" not in store._index_ids_by_user
247
273
  assert "entity-a" not in store._index_ids_by_entity
274
+ assert ("user-1", "entity-a") not in store._index_ids_by_user_entity
248
275
  assert store._index_ids_by_user["user-3"] == {first.id}
249
276
  assert list(store._index_ids_by_entity["entity-c"]) == [first.id]
277
+ assert list(store._index_ids_by_user_entity["user-3", "entity-c"]) == [
278
+ first.id
279
+ ]
250
280
 
251
281
  store._discard_index_item(first.id)
252
282
  assert "user-3" not in store._index_ids_by_user
253
283
  assert "entity-c" not in store._index_ids_by_entity
284
+ assert ("user-3", "entity-c") not in store._index_ids_by_user_entity
254
285
  assert first.id not in store._index_items
255
286
 
256
287
  store._clear_index_items()
257
288
  assert store._index_items == {}
258
289
  assert store._index_ids_by_user == {}
259
290
  assert store._index_ids_by_entity == {}
291
+ assert store._index_ids_by_user_entity == {}
260
292
 
261
293
 
262
294
  @pytest.mark.asyncio
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes