feat(audit): live-append event for the dashboard audit view

Follow-up to the audit-log backend + surface. Emits a DashboardEvent on
each agent-initiated privileged action so the audit view live-appends off
/dashboard/stream instead of polling.

- new DashboardEvent::AuditEntryAdded { seq, <flattened AuditEntry> } —
  serde tag `audit_entry_added`; the AuditEntry fields flatten to the top
  level so the wire shape matches one /api/audit-log `entries` row exactly.
- Coordinator::emit_audit_entry helper (stamps seq like the others).
- audit_log::record now returns the canonical inserted AuditEntry (id + ts
  assigned) so the streamed event is the same row that was stored — no
  drift. Best-effort unchanged (None on a sqlite blip).
- handle_restart_infra records + emits for every attempt (ok/err/denied),
  threading the coordinator through.

Tests: kind_tag round-trip now covers the new variant; added a flatten
test pinning the top-level wire shape (kind/seq/id/…/detail, no nesting).

Pairs with iris's audit view (the /dashboard/stream listener half).
This commit is contained in:
atlas 2026-06-13 16:03:04 +02:00 committed by mara
commit 629f08a113
4 changed files with 122 additions and 19 deletions

View file

@ -692,6 +692,18 @@ impl Coordinator {
self.meta_updates_active.load(Ordering::SeqCst) > 0
}
/// Emit `AuditEntryAdded` immediately after a privileged-action row
/// is recorded, so the dashboard audit view live-appends it off
/// `/dashboard/stream`. Pass the [`AuditEntry`](crate::audit_log::AuditEntry)
/// returned by `audit_log::record` so the streamed event is the same
/// canonical row that was stored.
pub fn emit_audit_entry(&self, entry: crate::audit_log::AuditEntry) {
self.emit_dashboard_event(DashboardEvent::AuditEntryAdded {
seq: self.next_seq(),
entry,
});
}
/// Emit `ApprovalAdded` immediately after the row is inserted in
/// sqlite. Caller passes the diff text it already computed (or
/// `None` for spawn approvals which carry no diff).