Documentation
¶
Overview ¶
Package audit implements the AI Interaction Log (Black Box Recorder) for BubbleFish Nexus. Every HTTP interaction generates a structured, CRC32-protected interaction record appended to a dedicated append-only log file with the same durability guarantees as the WAL.
The interaction log is separate from the WAL. The WAL records payload data for crash recovery. The interaction log records operational metadata for audit, compliance, and forensics.
Reference: Tech Spec Addendum Sections A2.1–A2.7.
Index ¶
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func NewRecordID ¶
func NewRecordID() string
NewRecordID generates a cryptographically random UUID for use as record_id. Uses crypto/rand; panics on failure (catastrophic — OS entropy exhausted).
Types ¶
type AuditFilter ¶
type AuditFilter struct {
Source string // Filter by source name
ActorType string // Filter: user, agent, system
ActorID string // Filter by specific actor
Operation string // Filter: write, query, admin
PolicyDecision string // Filter: allowed, denied, filtered
Subject string // Filter by subject namespace
Destination string // Filter by destination
After time.Time // Records after this timestamp
Before time.Time // Records before this timestamp
Limit int // Max records (1–1000), default 100
Offset int // Pagination offset
}
AuditFilter defines the query parameters for filtering interaction records. All fields are optional; zero-value means no filter on that field.
Reference: Tech Spec Addendum Section A2.5.
type AuditLogger ¶
type AuditLogger struct {
// contains filtered or unexported fields
}
AuditLogger is an append-only, CRC32-protected, optionally HMAC'd and encrypted audit trail that records every HTTP interaction with Nexus. It supports dual-file write (primary + shadow) for flight-recorder-grade durability.
Thread-safe: all methods are safe for concurrent use.
Reference: Tech Spec Addendum Section A2.3, Update U1.1–U1.5.
func NewAuditLogger ¶
func NewAuditLogger(logFile string, opts ...LoggerOption) (*AuditLogger, error)
NewAuditLogger creates an AuditLogger that writes to logFile. The directory is created with 0700 if it doesn't exist. The file is opened with O_APPEND|O_WRONLY|O_CREATE, permissions 0600.
Reference: Tech Spec Addendum Section A2.3, Update U1.1–U1.3.
func (*AuditLogger) Close ¶
func (l *AuditLogger) Close() error
Close flushes and closes all audit log files.
func (*AuditLogger) Log ¶
func (l *AuditLogger) Log(record InteractionRecord) error
Log appends an interaction record to the audit log.
When dual_write is enabled, the record is written to both primary and shadow files. Both are fsync'd. If one write fails but the other succeeds, a WARN is logged and the request still succeeds.
Log failure MUST NOT cause request failure — the caller logs WARN and increments bubblefish_audit_log_errors_total.
Reference: Tech Spec Addendum Sections A2.3, A2.4, Update U1.1–U1.3.
type AuditReader ¶
type AuditReader struct {
// contains filtered or unexported fields
}
AuditReader reads and queries the interaction log files. It discovers all rotated log files via glob, parses JSONL, validates CRC32, and applies filters. Supports shadow fallback and cross-segment dedup.
Reference: Tech Spec Addendum Section A2.5, Update U1.3–U1.5.
func NewAuditReader ¶
func NewAuditReader(logFile string, opts ...ReaderOption) *AuditReader
NewAuditReader creates an AuditReader for the given log file path. The reader discovers rotated files in the same directory via glob.
func (*AuditReader) CRCFailures ¶
func (r *AuditReader) CRCFailures() int64
CRCFailures returns the count of records where both primary and shadow had CRC32 mismatches. Reference: Update U1.7.
func (*AuditReader) Count ¶
func (r *AuditReader) Count(filter AuditFilter) (int, error)
Count returns the total number of records matching the filter (no pagination).
func (*AuditReader) Query ¶
func (r *AuditReader) Query(filter AuditFilter) (QueryResult, error)
Query performs a full scan of all audit log files and returns matching records. Time complexity is O(total records on disk). This is acceptable because:
- The endpoint is admin-only (requires admin token)
- The endpoint is rate-limited (admin_rate_limit_per_minute)
- Results are hard-capped at 1000 records per call
- Audit log scale is bounded by retention policy
If audit log scale grows beyond ~1M records, consider adding a SQLite index file alongside the JSON-Lines log for time-range pruning.
Files are read oldest-first (rotated files sorted by filename, then the current file). Rotation marker records are skipped. Cross-segment dedup by record_id.
Reference: Tech Spec Addendum Section A2.5, Update U1.3–U1.5.
func (*AuditReader) ShadowRecoveries ¶
func (r *AuditReader) ShadowRecoveries() int64
ShadowRecoveries returns the count of records recovered from shadow after primary corruption. Reference: Update U1.7.
type InteractionRecord ¶
type InteractionRecord struct {
// Identity
RecordID string `json:"record_id"` // UUID via crypto/rand, unique per interaction
RequestID string `json:"request_id"` // Correlation ID from HTTP ingress
Timestamp time.Time `json:"timestamp"` // RFC3339Nano, when the interaction started
// Actor
Source string `json:"source"` // Source name from config
ActorType string `json:"actor_type"` // user, agent, or system
ActorID string `json:"actor_id"` // Identity of the actor
EffectiveIP string `json:"effective_ip"` // Client IP (GDPR-sensitive — see A2.3)
// Operation
OperationType string `json:"operation_type"` // write, query, admin
Endpoint string `json:"endpoint"` // e.g. /inbound/claude, /query/sqlite
HTTPMethod string `json:"http_method"` // GET, POST
HTTPStatusCode int `json:"http_status_code"` // Response status
// Write-specific (empty for reads)
PayloadID string `json:"payload_id,omitempty"`
Destination string `json:"destination,omitempty"`
Subject string `json:"subject,omitempty"`
IdempotencyKey string `json:"idempotency_key,omitempty"`
IsDuplicate bool `json:"is_duplicate,omitempty"`
SensitivityLabelsSet []string `json:"sensitivity_labels_set,omitempty"` // Labels assigned on write
// Read-specific (empty for writes)
RetrievalProfile string `json:"retrieval_profile,omitempty"`
StagesHit []string `json:"stages_hit,omitempty"`
ResultCount int `json:"result_count,omitempty"`
CacheHit bool `json:"cache_hit,omitempty"`
// Policy
PolicyDecision string `json:"policy_decision"` // allowed, denied, filtered
PolicyReason string `json:"policy_reason,omitempty"` // Reason for denial/filtering
SensitivityLabelsFiltered []string `json:"sensitivity_labels_filtered,omitempty"` // Labels that caused filtering
TierFiltered bool `json:"tier_filtered,omitempty"` // True if tier caused filtering
// Performance
LatencyMs float64 `json:"latency_ms"`
WALAppendMs float64 `json:"wal_append_ms,omitempty"`
// Integrity — CRC32 computed over JSON with this field set to empty string.
CRC32 string `json:"crc32"`
}
InteractionRecord is the schema for one audit entry in the interaction log. Every HTTP request reaching the auth layer generates exactly one record.
Reference: Tech Spec Addendum Section A2.2.
type LoggerOption ¶
type LoggerOption func(*AuditLogger)
LoggerOption configures an AuditLogger. Pass to NewAuditLogger.
func WithDualWrite ¶
func WithDualWrite(enabled bool) LoggerOption
WithDualWrite enables or disables dual-file write (primary + shadow). Default: true.
Reference: Update U1.3.
func WithEncryption ¶
func WithEncryption(key []byte) LoggerOption
WithEncryption enables AES-256-GCM encryption with the given 32-byte key. This key is SEPARATE from the WAL encryption key.
Reference: Update U1.2.
func WithIntegrityMode ¶
func WithIntegrityMode(mode string, key []byte) LoggerOption
WithIntegrityMode sets the integrity mode and HMAC key. mode must be "crc32" or "mac". If "mac", key must be non-empty. This key is SEPARATE from the WAL HMAC key.
Reference: Update U1.1.
func WithLogger ¶
func WithLogger(logger *slog.Logger) LoggerOption
WithLogger sets the structured logger for warnings and errors.
func WithMaxFileSize ¶
func WithMaxFileSize(bytes int64) LoggerOption
WithMaxFileSize sets the rotation threshold in bytes. Default: 100 MB (100 * 1024 * 1024).
type QueryResult ¶
type QueryResult struct {
Records []InteractionRecord `json:"records"`
TotalMatching int `json:"total_matching"`
Limit int `json:"limit"`
Offset int `json:"offset"`
HasMore bool `json:"has_more"`
}
QueryResult is the response from AuditReader.Query.
Reference: Tech Spec Addendum Section A2.5.
type ReaderOption ¶
type ReaderOption func(*AuditReader)
ReaderOption configures an AuditReader.
func WithReaderDualWrite ¶
func WithReaderDualWrite(enabled bool) ReaderOption
WithReaderDualWrite enables shadow fallback on CRC failure.
Reference: Update U1.3.
func WithReaderEncryption ¶
func WithReaderEncryption(key []byte) ReaderOption
WithReaderEncryption enables decryption using the given 32-byte AES-256 key. This key is SEPARATE from the WAL encryption key.
Reference: Update U1.2.
func WithReaderIntegrity ¶
func WithReaderIntegrity(mode string, key []byte) ReaderOption
WithReaderIntegrity sets the integrity mode and HMAC key for validation. This key is SEPARATE from the WAL HMAC key.
func WithReaderLogger ¶
func WithReaderLogger(logger *slog.Logger) ReaderOption
WithReaderLogger sets the structured logger.