Files
mjalmoudy94 f1e5df29ce feat: realtime WebSocket layer for live events and streaming logs
- Backend: ConnectionManager, Redis-backed bus with in-process fallback,
  channel authorization, and typed emit helpers wired into workers
  (ai_runtime, sandbox_gateway), workforce/sandbox/billing/audit/error services.
- Frontend: single shared WS client with backoff + heartbeat, RealtimeProvider,
  useLiveChannel/useLiveEvent hooks, live indicators on task detail, manager
  chat, dashboard, traces, sandbox terminal, admin errors, and billing.
- Contracts: WsEvent/WsServerFrame/WsClientFrame types mirroring the port.

All 255 backend tests pass; web typecheck and build pass.
2026-10-13 12:56:36 +03:30

48 lines
1.3 KiB
Python

"""Audit helper used by the HTTP layer.
Every sensitive operation calls this. It is deliberately cheap (one INSERT) so
there is no excuse to skip it.
"""
from __future__ import annotations
from sqlalchemy.ext.asyncio import AsyncSession
from app.application.ports.realtime import emit_audit_recorded
from app.domain.audit.models import AuditEvent
from app.infrastructure.repositories.audit import AuditRepository
async def record_audit(
session: AsyncSession,
*,
action: str,
organization_id: str | None = None,
actor_user_id: str | None = None,
target_type: str = "",
target_id: str = "",
metadata: dict | None = None,
ip: str = "",
user_agent: str = "",
) -> AuditEvent:
event = AuditEvent(
action=action,
organization_id=organization_id,
actor_user_id=actor_user_id,
target_type=target_type,
target_id=target_id,
metadata_json=metadata or {},
ip_address=ip,
user_agent=user_agent,
)
recorded = await AuditRepository(session).record(event=event)
# Live push: the audit timelines (dashboard, traces) update as actions
# happen instead of waiting for the next reload.
await emit_audit_recorded(
organization_id=organization_id,
action=action,
target_type=target_type,
target_id=target_id,
)
return recorded