// Package audit provides AuditSink implementations for the capture // capability (I5). This file ships the minimal slog-backed sink used in // #53: it emits the request-level audit record to structured logs, which // the alloy/loki substrate already scrapes. The classification-aware // degradation/refusal sink (confidential fails closed, internal buffers + // reconciles) lands in #54 and replaces this behind the same interface. package audit import ( "context" "log/slog" "github.com/mathiasbq/hyperguild/ingestion/internal/capture" "github.com/mathiasbq/hyperguild/ingestion/internal/classification" ) // SlogSink records audit entries to an slog.Logger. It never fails and is // always centrally available, so its Reserve always grants AuditCentral — // it does not exercise the I5 degradation/floor. That is DegradingSink's // job (loki + durable buffer). SlogSink is the default for deployments // without a loki endpoint configured. A nil logger ⇒ slog.Default(). type SlogSink struct { logger *slog.Logger } // NewSlogSink constructs a SlogSink. nil logger ⇒ slog.Default(). func NewSlogSink(logger *slog.Logger) *SlogSink { if logger == nil { logger = slog.Default() } return &SlogSink{logger: logger} } // Reserve always grants central recording — slog is always available. func (s *SlogSink) Reserve(_ context.Context, _ classification.Level) (capture.AuditOutcome, error) { return capture.AuditCentral, nil } // Record emits the audit entry at info level. Security events, when // present, are logged at warn level so they surface independently of the // routine audit stream. func (s *SlogSink) Record(_ context.Context, e capture.AuditEntry, _ capture.AuditOutcome) error { s.logger.Info("capture audit", "principal", e.Principal, "actor", e.Actor, "harness", e.Harness, "session_ref", e.SessionRef, "classification", e.EffectiveClassification, "items", e.Items, "ts", e.Timestamp, ) for _, ev := range e.SecurityEvents { s.logger.Warn("capture security event", "principal", e.Principal, "event", ev) } return nil }