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:
parent
9bd51a7440
commit
629f08a113
4 changed files with 122 additions and 19 deletions
|
|
@ -522,7 +522,7 @@ async fn handle_restart_child(coord: &Arc<Coordinator>, agent: &str, name: &str)
|
|||
// tool. These names are never agent children, so this branch is
|
||||
// disjoint from the child-restart path below.
|
||||
if hive_sh4re::priv_proto::RESTARTABLE_INFRA_CONTAINERS.contains(&name) {
|
||||
return handle_restart_infra(agent, name).await;
|
||||
return handle_restart_infra(coord, agent, name).await;
|
||||
}
|
||||
if let Some(err) = require_child(agent, name, "restart") {
|
||||
return err;
|
||||
|
|
@ -544,13 +544,24 @@ async fn handle_restart_child(coord: &Arc<Coordinator>, agent: &str, name: &str)
|
|||
/// known to be in `RESTARTABLE_INFRA_CONTAINERS`; this gates on the
|
||||
/// capability and routes the systemctl restart through hive-priv (which
|
||||
/// re-validates the name root-side). Direct, not approval-gated.
|
||||
async fn handle_restart_infra(agent: &str, container: &str) -> AgentResponse {
|
||||
// Record the attempt in the operator-visible privileged-action audit trail.
|
||||
// Best-effort: no-op when the global handle isn't installed (early
|
||||
// startup / tests). `action` is stable so the dashboard can group.
|
||||
async fn handle_restart_infra(
|
||||
coord: &Arc<Coordinator>,
|
||||
agent: &str,
|
||||
container: &str,
|
||||
) -> AgentResponse {
|
||||
// Record the attempt in the operator-visible privileged-action audit
|
||||
// trail, then emit a live `AuditEntryAdded` so the dashboard audit view
|
||||
// appends it off `/dashboard/stream`. Best-effort: `record` returns the
|
||||
// canonical row (or `None` on a sqlite blip), and we stream exactly that
|
||||
// row so the stored + streamed views can't drift. `action` is stable so
|
||||
// the dashboard can group/filter.
|
||||
let audit = |outcome: crate::audit_log::AuditOutcome, detail: Option<&str>| {
|
||||
if let Some(log) = crate::audit_log::global() {
|
||||
log.record(agent, "restart_infra", container, outcome, detail);
|
||||
if let Some(entry) =
|
||||
coord
|
||||
.audit_log
|
||||
.record(agent, "restart_infra", container, outcome, detail)
|
||||
{
|
||||
coord.emit_audit_entry(entry);
|
||||
}
|
||||
};
|
||||
if !crate::capabilities::has_cap(agent, hive_sh4re::Capability::InfraAdmin) {
|
||||
|
|
|
|||
Loading…
Reference in a new issue