amfs-adapter-postgres 0.2.30__tar.gz → 0.2.32__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 (26) hide show
  1. {amfs_adapter_postgres-0.2.30 → amfs_adapter_postgres-0.2.32}/PKG-INFO +2 -2
  2. {amfs_adapter_postgres-0.2.30 → amfs_adapter_postgres-0.2.32}/pyproject.toml +2 -2
  3. {amfs_adapter_postgres-0.2.30 → amfs_adapter_postgres-0.2.32}/src/amfs_postgres/adapter.py +37 -4
  4. {amfs_adapter_postgres-0.2.30 → amfs_adapter_postgres-0.2.32}/src/amfs_postgres/async_adapter.py +27 -1
  5. {amfs_adapter_postgres-0.2.30 → amfs_adapter_postgres-0.2.32}/.gitignore +0 -0
  6. {amfs_adapter_postgres-0.2.30 → amfs_adapter_postgres-0.2.32}/src/amfs_postgres/__init__.py +0 -0
  7. {amfs_adapter_postgres-0.2.30 → amfs_adapter_postgres-0.2.32}/src/amfs_postgres/_fts.py +0 -0
  8. {amfs_adapter_postgres-0.2.30 → amfs_adapter_postgres-0.2.32}/src/amfs_postgres/knn.py +0 -0
  9. {amfs_adapter_postgres-0.2.30 → amfs_adapter_postgres-0.2.32}/src/amfs_postgres/migrations/001_initial.sql +0 -0
  10. {amfs_adapter_postgres-0.2.30 → amfs_adapter_postgres-0.2.32}/src/amfs_postgres/migrations/002_artifact_refs.sql +0 -0
  11. {amfs_adapter_postgres-0.2.30 → amfs_adapter_postgres-0.2.32}/src/amfs_postgres/migrations/002_search_indexes.sql +0 -0
  12. {amfs_adapter_postgres-0.2.30 → amfs_adapter_postgres-0.2.32}/src/amfs_postgres/migrations/003_knowledge_graph.sql +0 -0
  13. {amfs_adapter_postgres-0.2.30 → amfs_adapter_postgres-0.2.32}/src/amfs_postgres/migrations/004_agent_groups.sql +0 -0
  14. {amfs_adapter_postgres-0.2.30 → amfs_adapter_postgres-0.2.32}/src/amfs_postgres/migrations/005_configurable_embedding_dim.sql +0 -0
  15. {amfs_adapter_postgres-0.2.30 → amfs_adapter_postgres-0.2.32}/src/amfs_postgres/migrations/006_is_artifact.sql +0 -0
  16. {amfs_adapter_postgres-0.2.30 → amfs_adapter_postgres-0.2.32}/src/amfs_postgres/migrations/006_outcome_confidence_semantics.sql +0 -0
  17. {amfs_adapter_postgres-0.2.30 → amfs_adapter_postgres-0.2.32}/src/amfs_postgres/migrations/007_outcome_propagation_row_copy.sql +0 -0
  18. {amfs_adapter_postgres-0.2.30 → amfs_adapter_postgres-0.2.32}/src/amfs_postgres/migrations/008_outcome_evidence.sql +0 -0
  19. {amfs_adapter_postgres-0.2.30 → amfs_adapter_postgres-0.2.32}/src/amfs_postgres/migrations/009_action_priors.sql +0 -0
  20. {amfs_adapter_postgres-0.2.30 → amfs_adapter_postgres-0.2.32}/src/amfs_postgres/migrations/010_local_evidence.sql +0 -0
  21. {amfs_adapter_postgres-0.2.30 → amfs_adapter_postgres-0.2.32}/src/amfs_postgres/migrations/011_outcome_task_text.sql +0 -0
  22. {amfs_adapter_postgres-0.2.30 → amfs_adapter_postgres-0.2.32}/src/amfs_postgres/retention.py +0 -0
  23. {amfs_adapter_postgres-0.2.30 → amfs_adapter_postgres-0.2.32}/src/amfs_postgres/schema.sql +0 -0
  24. {amfs_adapter_postgres-0.2.30 → amfs_adapter_postgres-0.2.32}/src/amfs_postgres/tenant_context.py +0 -0
  25. {amfs_adapter_postgres-0.2.30 → amfs_adapter_postgres-0.2.32}/src/amfs_postgres/tenant_gucs.py +0 -0
  26. {amfs_adapter_postgres-0.2.30 → amfs_adapter_postgres-0.2.32}/src/amfs_postgres/trace_partitions.py +0 -0
@@ -1,9 +1,9 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: amfs-adapter-postgres
3
- Version: 0.2.30
3
+ Version: 0.2.32
4
4
  Summary: AMFS Postgres adapter with back-propagation triggers
5
5
  License-Expression: Apache-2.0
6
6
  Requires-Python: >=3.11
7
- Requires-Dist: amfs-core>=0.3.42
7
+ Requires-Dist: amfs-core>=0.3.43
8
8
  Requires-Dist: psycopg-pool>=3.1
9
9
  Requires-Dist: psycopg[binary]<4,>=3.1
@@ -1,13 +1,13 @@
1
1
  [project]
2
2
  name = "amfs-adapter-postgres"
3
- version = "0.2.30"
3
+ version = "0.2.32"
4
4
  description = "AMFS Postgres adapter with back-propagation triggers"
5
5
  requires-python = ">=3.11"
6
6
  license = "Apache-2.0"
7
7
  dependencies = [
8
8
  # 0.3.14 adds the capture fields this adapter now persists and selects back,
9
9
  # tool_calls among them.
10
- "amfs-core>=0.3.42",
10
+ "amfs-core>=0.3.43",
11
11
  "psycopg[binary]>=3.1,<4",
12
12
  "psycopg-pool>=3.1",
13
13
  ]
@@ -1454,9 +1454,11 @@ class PostgresAdapter(AdapterABC):
1454
1454
  # tenants can have entries/agents/branches/tags with the same names.
1455
1455
  #
1456
1456
  # ``uq_entry_version`` also includes ``branch``: versions are numbered
1457
- # per branch — ``write`` reads the newest row on *its* branch and adds
1458
- # one, ``merge_branch`` renumbers onto the parent — so the same
1459
- # (entity, key, version) legitimately exists on main and on every
1457
+ # per branch — ``write`` reads the newest row on *its* branch, or for a
1458
+ # key the branch has not touched the parent's live row (the same row
1459
+ # ``read`` returns, so the engine and the adapter agree on the next
1460
+ # number), and adds one; ``merge_branch`` renumbers onto the parent —
1461
+ # so the same (entity, key, version) legitimately exists on every
1460
1462
  # branch that has written the key. Without ``branch`` in the constraint
1461
1463
  # the first write of an existing key on a fresh branch raised
1462
1464
  # ``UniqueViolation`` (seen 2026-09-22: the repair loop could not open a
@@ -1791,6 +1793,34 @@ class PostgresAdapter(AdapterABC):
1791
1793
  """
1792
1794
  return branch_scope_sql(branch, self._parent_branch(cur, branch))
1793
1795
 
1796
+ def _parent_live_row(self, cur: Any, branch: str, entity_path: str, key: str) -> Any:
1797
+ """The parent's live row for a key *branch* has not written, as
1798
+ ``read`` resolves it — or ``None``.
1799
+
1800
+ ``write`` numbers a branch's first version of an inherited key from
1801
+ this row, so the version the engine computed from the overlay read
1802
+ (parent's ``n``, next ``n + 1``) is the one the branch stores. Reading
1803
+ the parent's ``n`` and then insisting on ``1`` is how the repair loop
1804
+ lost every branch for a key main already held (2026-09-26: the first
1805
+ regime-shift fixes the repair agent drafted all fell back to "writes
1806
+ to main on Ship" with ``expected 9, found 0``). Not locked: the parent
1807
+ row is a baseline, not the row being superseded."""
1808
+ parent = self._parent_branch(cur, branch)
1809
+ if parent is None:
1810
+ return None
1811
+ cur.execute(
1812
+ f"""
1813
+ SELECT version, value, confidence{_EVIDENCE_SELECT if self._has_evidence_cols else ""}{", validators" if self._has_validators_col else ""}
1814
+ FROM amfs_memory_entries
1815
+ WHERE namespace = %s AND branch = %s
1816
+ AND entity_path = %s AND key = %s
1817
+ AND superseded_at IS NULL
1818
+ ORDER BY version DESC LIMIT 1
1819
+ """,
1820
+ (self._namespace, parent, entity_path, key),
1821
+ )
1822
+ return cur.fetchone()
1823
+
1794
1824
  def read(
1795
1825
  self,
1796
1826
  entity_path: str,
@@ -1886,6 +1916,9 @@ class PostgresAdapter(AdapterABC):
1886
1916
  (self._namespace, branch, entry.entity_path, entry.key),
1887
1917
  )
1888
1918
  row = cur.fetchone()
1919
+ own_row = row is not None
1920
+ if row is None and branch != "main":
1921
+ row = self._parent_live_row(cur, branch, entry.entity_path, entry.key)
1889
1922
  current_version = row["version"] if row else 0
1890
1923
  new_version = current_version + 1
1891
1924
  if row and self._has_evidence_cols:
@@ -1899,7 +1932,7 @@ class PostgresAdapter(AdapterABC):
1899
1932
  current_version,
1900
1933
  )
1901
1934
 
1902
- if row:
1935
+ if own_row:
1903
1936
  cur.execute(
1904
1937
  """
1905
1938
  UPDATE amfs_memory_entries
@@ -283,6 +283,27 @@ class AsyncPostgresAdapter:
283
283
  see exactly what a sync ``list`` or ``search`` on the branch sees."""
284
284
  return branch_scope_sql(branch, await self._parent_branch(cur, branch))
285
285
 
286
+ async def _parent_live_row(self, cur: Any, branch: str, entity_path: str, key: str) -> Any:
287
+ """The parent's live row for a key *branch* has not written, as
288
+ ``read`` resolves it — or ``None``. ``write`` numbers a branch's first
289
+ version of an inherited key from it, matching the version the engine
290
+ computed from the overlay read; see ``PostgresAdapter._parent_live_row``."""
291
+ parent = await self._parent_branch(cur, branch)
292
+ if parent is None:
293
+ return None
294
+ await cur.execute(
295
+ f"""
296
+ SELECT version, value, confidence{_EVIDENCE_SELECT if self._has_evidence_cols else ""}
297
+ FROM amfs_memory_entries
298
+ WHERE namespace = %s AND branch = %s
299
+ AND entity_path = %s AND key = %s
300
+ AND superseded_at IS NULL
301
+ ORDER BY version DESC LIMIT 1
302
+ """,
303
+ (self._namespace, parent, entity_path, key),
304
+ )
305
+ return await cur.fetchone()
306
+
286
307
  # ──────────────────────────────────────────────────────────────
287
308
  # 1. read
288
309
  # ──────────────────────────────────────────────────────────────
@@ -357,6 +378,11 @@ class AsyncPostgresAdapter:
357
378
  (self._namespace, branch, entry.entity_path, entry.key),
358
379
  )
359
380
  row = await cur.fetchone()
381
+ own_row = row is not None
382
+ if row is None and branch != "main":
383
+ row = await self._parent_live_row(
384
+ cur, branch, entry.entity_path, entry.key
385
+ )
360
386
  current_version = row["version"] if row else 0
361
387
  new_version = current_version + 1
362
388
  if row and self._has_evidence_cols:
@@ -370,7 +396,7 @@ class AsyncPostgresAdapter:
370
396
  current_version,
371
397
  )
372
398
 
373
- if row:
399
+ if own_row:
374
400
  await cur.execute(
375
401
  """
376
402
  UPDATE amfs_memory_entries