Skip to content

symfonic.memory.backends.postgres_conversation_erase_log

postgres_conversation_erase_log

A standalone conversation database's serialized, hash-chained erase log (CORE-171).

The conversation counterpart of postgres_memory_erase_log, for a PostgresConversationLog that carries its own erase anchor (postgres_conversation_anchor). Every transcript forget appends one row in its own transaction: a kind-aware erase through its generation advance, which a deferred constraint trigger requires at commit, and a positional erase directly. The append trigger locks the log, so log order is commit order, and assigns seq = head + 1 and digest = sha256(prior digest || row); the row carries its transaction ID and a nonce the trigger draws, so a copy that replays the same forgets cannot reproduce a digest. Row 0 is written by the install. Two marker rows serve the validated restore: SEAL closes the source, OPEN reopens the restored database.

Both trigger functions run as the log's owner (SECURITY DEFINER, search_path pinned to pg_catalog, EXECUTE revoked from PUBLIC). Every check attests their exact definition before it trusts the log. A database owner or superuser can still rewrite both; that is the stated tamper boundary, as for memory.

attest async

attest(conn: Any) -> None

Refuse TAMPERED unless every log trigger runs its shipped definition.

Source code in src/symfonic/memory/backends/postgres_conversation_erase_log.py
async def attest(conn: Any) -> None:
    """Refuse ``TAMPERED`` unless every log trigger runs its shipped definition."""
    for table, trigger, body in _TRIGGERS:
        row = await conn.fetchrow(_ATTEST, table, trigger)
        if (row is None or row["prosrc"] != body or not row["prosecdef"]
                or list(row["proconfig"] or ()) != ["search_path=pg_catalog"]
                or not row["owned"] or row["public_exec"]):
            raise MemoryContractError(TAMPERED)

install async

install(conn: Any) -> None

Owner-run: create the log once; an installed log is attested, never replaced.

Source code in src/symfonic/memory/backends/postgres_conversation_erase_log.py
async def install(conn: Any) -> None:
    """Owner-run: create the log once; an installed log is attested, never replaced."""
    if await present(conn):
        await attest(conn)
        return
    await conn.execute(LOG_DDL)

log_advance async

log_advance(conn: Any, path: str) -> None

Log path's just-advanced generation in the advancing transaction, if installed.

Source code in src/symfonic/memory/backends/postgres_conversation_erase_log.py
async def log_advance(conn: Any, path: str) -> None:
    """Log ``path``'s just-advanced generation in the advancing transaction, if installed."""
    if await present(conn):
        await conn.execute(
            f"INSERT INTO {LOG_TABLE} (scope_path, generation) SELECT scope_path, generation "
            "FROM native_conversation_generations WHERE scope_path = $1", path)

log_forget async

log_forget(conn: Any, path: str, generation: int = 0) -> None

Log a positional erase, or a restore marker, in the caller's transaction.

Source code in src/symfonic/memory/backends/postgres_conversation_erase_log.py
async def log_forget(conn: Any, path: str, generation: int = 0) -> None:
    """Log a positional erase, or a restore marker, in the caller's transaction."""
    if not await present(conn):
        raise MemoryContractError(UNLOGGED)
    await conn.execute(
        f"INSERT INTO {LOG_TABLE} (scope_path, generation) VALUES ($1, $2)", path, generation)

sealed async

sealed(conn: Any) -> bool

The head is a SEAL marker: a validated restore closed this database.

Source code in src/symfonic/memory/backends/postgres_conversation_erase_log.py
async def sealed(conn: Any) -> bool:
    """The head is a ``SEAL`` marker: a validated restore closed this database."""
    return await conn.fetchval(
        f"SELECT generation FROM {LOG_TABLE} ORDER BY seq DESC LIMIT 1") == SEAL