audit

package
v0.1.2 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Apr 10, 2026 License: AGPL-3.0 Imports: 21 Imported by: 0

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:

  1. The endpoint is admin-only (requires admin token)
  2. The endpoint is rate-limited (admin_rate_limit_per_minute)
  3. Results are hard-capped at 1000 records per call
  4. 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.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL