from __future__ import annotations import uuid from typing import Any from sqlmodel import Session, select from ..db.models import AuditEvent from ..core.time import utc_now from ..core.redaction import redact_message, redact_value def _redact_mapping(value: dict[str, Any] | None) -> dict[str, Any] | None: return redact_value(value) if value else None def log_event( session: Session, *, source: str, identity: str, action_id: str, target: str, risk: str, policy_decision: str, reason: str, result: str, target_type: str = 'app', execution_plan: dict[str, Any] | None = None, before_state: dict[str, Any] | None = None, after_state: dict[str, Any] | None = None, post_checks: dict[str, Any] | None = None, redacted_metadata: dict[str, Any] | None = None, ) -> AuditEvent: from .user_access import actor_identity user_identity = actor_identity.get() if user_identity and identity in {"manual", "voice", "automation", "system"}: # Keep the request channel separately from the authenticated actor. redacted_metadata = {**(redacted_metadata or {}), "requestSource": identity} identity = user_identity or identity event = AuditEvent( id=f'audit-{uuid.uuid4().hex[:12]}', timestamp=utc_now(), source=source, identity=identity, action_id=redact_message(action_id), target=redact_message(target), target_type=redact_message(target_type), risk=risk, policy_decision=policy_decision, reason=redact_message(reason), execution_plan=_redact_mapping(execution_plan) or {}, result=result, before_state=_redact_mapping(before_state), after_state=_redact_mapping(after_state), post_checks=_redact_mapping(post_checks), redacted_metadata=_redact_mapping(redacted_metadata), ) session.add(event) session.commit() session.refresh(event) return event def list_events(session: Session, *, limit: int = 200, offset: int = 0): statement = ( select(AuditEvent) .order_by(AuditEvent.timestamp.desc(), AuditEvent.id.desc()) .offset(max(0, offset)) .limit(max(1, min(limit, 500))) ) return session.exec(statement).all()