async def acknowledge_on(conn, scope, run_id, manifest, *, linked_edges=None):
"""Exact row/generation CAS and ACCEPTED insert share one rollback boundary.
The completion PK fences invocation replay. A durable completion read never
calls this function. A refusal rolls back all access updates and acceptance.
Replacement after handoff refuses the entire completion; no stale row is
silently skipped, and the caller gains no authority to replay model/tools.
"""
from symfonic.memory.backends.postgres_graph import row_to_node
records = decode(manifest, scope, run_id)
# Match the table-before-row order used by native node mutation/forget.
await conn.execute("LOCK TABLE memory_nodes IN ROW EXCLUSIVE MODE")
await _refuse_if_erasing(conn, scope, records)
if any("edge" in item for _, item in records):
from symfonic.memory.backends.postgres_linked_ack import prepare_linked_on
await prepare_linked_on(conn, scope, records, linked_edges)
for publication, item in sorted(records, key=lambda record: record[0].record_id):
row = await conn.fetchrow(
'SELECT * FROM memory_nodes WHERE tenant_id COLLATE "C"=$1 '
'AND id COLLATE "C"=$2 FOR UPDATE',
scope.tenant, publication.record_id,
)
if row is None:
_refuse()
node = row_to_node(row)
actual_owner = node_scope(node)
actual = published_recall_ref(publication.record_id, actual_owner,
node.properties, tenant_id=scope.tenant)
if "edge" in item:
from symfonic.memory.backends.postgres_linked_ack import linked_line_on
if linked_edges is None:
_refuse()
rendered = await linked_line_on(conn, scope, publication, item, linked_edges)
else:
rendered = line(record_from_legacy_node(node_payload(node))).encode("utf-8")
if (actual != publication or actual_owner != publication.owner
or row["scope_path"] != storage_scope_path(publication.owner)
or is_pending(node.properties) or is_retracted(node.properties)
or hashlib.sha256(rendered).hexdigest() != item["line_digest"]
or len(rendered) != item["end"] - item["start"]):
_refuse()
changed = await conn.fetchval(
'UPDATE memory_nodes SET access_count=access_count+1 '
'WHERE tenant_id COLLATE "C"=$1 AND id COLLATE "C"=$2 '
'AND scope_path COLLATE "C"=$3 '
'AND properties->>$4=$5 AND properties->$6=$7::jsonb RETURNING id',
scope.tenant, publication.record_id, storage_scope_path(publication.owner),
GENERATION_KEY, publication.generation, OWNER_KEY,
json.dumps(owner_document(publication.owner)),
)
if changed != publication.record_id:
_refuse()