"""steward/core/audit.py Helpers for writing audit log entries. Each call opens its own DB session so audit events are committed independently of the calling route's transaction (audit is only written after the main action succeeds, by calling this after the main `async with db.begin()` block). """ from __future__ import annotations import json import logging from datetime import datetime, timezone logger = logging.getLogger(__name__) async def log_audit( app, user_id: str | None, username: str, action: str, entity_type: str | None = None, entity_id: str | None = None, detail: dict | None = None, ) -> None: """Write one audit event. Never raises — failures are logged and swallowed.""" from steward.models.audit import AuditEvent try: async with app.db_sessionmaker() as db: async with db.begin(): db.add(AuditEvent( user_id=user_id, username=username or "unknown", action=action, entity_type=entity_type, entity_id=entity_id, detail_json=json.dumps(detail) if detail else None, timestamp=datetime.now(timezone.utc), )) except Exception: logger.exception("Failed to write audit event action=%r", action)