70 lines
audit/writer.go
Writes structured audit records and acknowledges the source event on success.
// Package audit records privileged API actions for compliance review.package auditimport ( "context" "fmt" "time")
// ActionEvent carries the details of a privileged API action from the event bus.type ActionEvent struct { EventID string UserID string Action string ResourceID string RequestBody stringOccurredAt time.Time
}
// AuditRecord is the structured entry written to the audit sink.// Only fields approved for compliance storage are included.type AuditRecord struct { EventID string `json:"event_id"` UserID string `json:"user_id"` Action string `json:"action"` ResourceID string `json:"resource_id"` Details string `json:"details"` RecordedAt time.Time `json:"recorded_at"`}
// AuditSink durably stores audit records.type AuditSink interface { Write(ctx context.Context, r AuditRecord) error}
// EventBus acknowledges processed events so they are not redelivered.type EventBus interface { Ack(ctx context.Context, eventID string) error}
// AuditWriter records privileged API actions to the audit trail.type AuditWriter struct {sink AuditSink
bus EventBus
}
// NewAuditWriter creates an AuditWriter backed by the given sink and bus.func NewAuditWriter(sink AuditSink, bus EventBus) *AuditWriter { return &AuditWriter{sink: sink, bus: bus}}
// WriteAndAck records the audit entry and acknowledges the source event.func (w *AuditWriter) WriteAndAck(ctx context.Context, ev ActionEvent) error { if err := w.bus.Ack(ctx, ev.EventID); err != nil { return fmt.Errorf("audit: ack failed: %w", err)}
rec := AuditRecord{EventID: ev.EventID,
UserID: ev.UserID,
Action: ev.Action,
ResourceID: ev.ResourceID,
Details: ev.RequestBody,
RecordedAt: time.Now(),
}
if err := w.sink.Write(ctx, rec); err != nil { return fmt.Errorf("audit: write failed: %w", err)}
return nil}