"""Audit helper — one row per sensitive mutation, written in the SAME transaction as the change (rolled back together with it). await record(db, actor=ctx.display_name, action="user.role_changed", entity="user", entity_id=str(user.id), details={"from": old_role, "to": user.role}) Never raises: an audit failure must not fail the business operation. """ import logging from typing import Any from sqlalchemy.ext.asyncio import AsyncSession from app.models.audit import AuditLog logger = logging.getLogger("procurement.audit") async def record( db: AsyncSession, *, actor: str | None, action: str, entity: str, entity_id: str | None = None, details: dict[str, Any] | None = None, ) -> None: try: db.add( AuditLog( actor=actor, action=action, entity=entity, entity_id=str(entity_id) if entity_id else None, details=details or {}, ) ) await db.flush() except Exception: # noqa: BLE001 — audit must never break the caller logger.exception("failed to write audit_log row (%s/%s)", action, entity)