audit

package module
v1.0.0 Latest Latest
Warning

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

Go to latest
Published: Aug 26, 2026 License: MIT Imports: 16 Imported by: 8

README

audit

CI CodeQL Coverage Mutation Documentation Go Reference Release Go License

audit is an infrastructure-neutral Go library for immutable-by-contract, security-relevant and business-relevant records. It records who did what, to which stable resource, when, from where, and with what outcome.

Audit records are deliberately separate from logs, traces, metrics, domain events, and event-sourcing history. Using an event store does not make that store a compliant audit trail, and using this library does not by itself establish legal or regulatory compliance.

Packages

  • github.com/faustbrian/go-audit: records, validation, delivery policy, privacy, querying, export, integrity, retention, and safe observation hooks.
  • github.com/faustbrian/go-audit/memory: bounded process-local adapter for tests; it is not durable.
  • github.com/faustbrian/go-audit/postgres: separately releasable PostgreSQL durable adapter and caller-owned transaction writer.

Quick start

builder, err := audit.NewBuilder(audit.BuilderConfig{})
if err != nil {
    return err
}
record, err := builder.Build(audit.RecordInput{
    OccurredAt: time.Now(),
    Action:     "invoice.approved",
    Outcome:    audit.OutcomeSucceeded,
    Actor:      audit.ActorInput{Kind: audit.ActorHuman, ID: userID},
    Subject:    audit.SubjectInput{Type: "invoice", ID: invoiceID},
    Context:    audit.ContextInput{TenantID: tenantID},
    Changes:    audit.ChangeSetInput{NoChange: true},
})
if err != nil {
    return err
}
redactor, err := audit.NewRedactor(audit.RedactionRules{})
if err != nil {
    return err
}
recorder, err := audit.NewRecorder(audit.RecorderConfig{
    Sink: durableSink, Redactor: redactor, Mode: audit.DeliveryFailClosed,
})
if err != nil {
    return err
}
_, err = recorder.Submit(ctx, record)
return err

The caller must select fail-closed, fail-open-with-alert, or durable-buffer delivery. The library never silently discards a record and performs no hidden retry. Repeating the same record ID and canonical bytes is idempotent.

See the documentation index, threat model, delivery semantics, privacy policy boundary, and PostgreSQL operations.

License

MIT. See LICENSE.

Ecosystem

Use the Golib documentation portal to choose companion packages, supported stacks, recipes, and operations guidance.

Documentation

Overview

Package audit provides immutable-by-contract records for security-relevant and business-relevant actions. It is not a logging, tracing, metrics, authorization, event-sourcing, or domain-event framework.

Index

Examples

Constants

View Source
const (
	// AuthenticationMethodAPIKey identifies API-key authentication without
	// retaining the key value.
	AuthenticationMethodAPIKey = "api_key"
	// AuthenticationMethodCertificate identifies certificate authentication.
	AuthenticationMethodCertificate = "certificate"
	// AuthenticationMethodEmailOTP identifies a one-time code delivered by email.
	AuthenticationMethodEmailOTP = "email_otp"
	// AuthenticationMethodHOTP identifies HMAC-based one-time-password authentication.
	AuthenticationMethodHOTP = "hotp"
	// AuthenticationMethodMutualTLS identifies mutual-TLS authentication.
	AuthenticationMethodMutualTLS = "mTLS"
	// AuthenticationMethodMagicLink identifies magic-link authentication.
	AuthenticationMethodMagicLink = "magic_link"
	// AuthenticationMethodOAuth2 identifies OAuth 2 authentication.
	AuthenticationMethodOAuth2 = "oauth2"
	// AuthenticationMethodOIDC identifies OpenID Connect authentication.
	AuthenticationMethodOIDC = "oidc"
	// AuthenticationMethodPasskey identifies passkey authentication.
	AuthenticationMethodPasskey = "passkey"
	// AuthenticationMethodPassword identifies password authentication without
	// retaining the password value.
	AuthenticationMethodPassword = "password"
	// AuthenticationMethodRecoveryCode identifies recovery-code authentication.
	AuthenticationMethodRecoveryCode = "recovery_code"
	// AuthenticationMethodSAML identifies SAML authentication.
	AuthenticationMethodSAML = "saml"
	// AuthenticationMethodSession identifies an already-established session.
	AuthenticationMethodSession = "session"
	// AuthenticationMethodSMSOTP identifies a one-time code delivered by SMS.
	AuthenticationMethodSMSOTP = "sms_otp"
	// AuthenticationMethodTOTP identifies time-based one-time-password authentication.
	AuthenticationMethodTOTP = "totp"
	// AuthenticationMethodWebAuthn identifies WebAuthn authentication.
	AuthenticationMethodWebAuthn = "webauthn"
	// AuthenticationMethodWorkloadIdentity identifies workload-identity authentication.
	AuthenticationMethodWorkloadIdentity = "workload_identity"
)
View Source
const (
	// MaxAppendBatchRecords is the absolute core ceiling for one append call.
	MaxAppendBatchRecords = 1_000
)
View Source
const (
	// MaxQueryRecords is the absolute number of records one query or export may
	// request.
	MaxQueryRecords uint32 = 1_000
)
View Source
const MaxRecoveryTimeout = time.Minute

MaxRecoveryTimeout is the absolute bound for alerting or durable buffering after the caller's primary-operation context has ended.

Variables

View Source
var (
	ErrDeliveryAlertFailed = errors.New("audit: delivery alert failed")
	ErrDurableBufferFailed = errors.New("audit: durable buffer failed")
)
View Source
var (
	// ErrInvalidArgument classifies a caller-supplied value that violates the
	// bounded audit record contract.
	ErrInvalidArgument = errors.New("audit: invalid argument")
	// ErrRecordIDUnavailable reports an opaque ID-generator failure or panic.
	ErrRecordIDUnavailable = errors.New("audit: record ID is unavailable")
	// ErrClockUnavailable reports an opaque builder-clock panic.
	ErrClockUnavailable = errors.New("audit: clock is unavailable")
	// ErrSensitiveData classifies data rejected before persistence because it
	// belongs to a prohibited secret-bearing namespace.
	ErrSensitiveData = errors.New("audit: sensitive data is prohibited")
	// ErrIntegrityInvalid reports a digest, sequence, partition, or key mismatch.
	ErrIntegrityInvalid = errors.New("audit: integrity verification failed")
)
View Source
var (
	// ErrDuplicateConflict reports reuse of a record ID with different bytes.
	ErrDuplicateConflict = errors.New("audit: record ID already has different content")
	// ErrBackpressure reports an explicit bounded-capacity rejection.
	ErrBackpressure = errors.New("audit: sink capacity exceeded")
	// ErrBatchTooLarge reports a batch above the configured or absolute limit.
	ErrBatchTooLarge = errors.New("audit: append batch exceeds limit")
	// ErrSinkProtocol reports an acknowledgement that cannot prove which
	// submitted records were accepted or deduplicated.
	ErrSinkProtocol = errors.New("audit: sink returned an invalid acknowledgement")
	// ErrRedactionRequired reports persistence attempted without an explicit
	// caller-owned privacy policy having processed the record.
	ErrRedactionRequired = errors.New("audit: redaction is required before persistence")
)
View Source
var ErrExportConsumerFailed = errors.New("audit: export consumer failed")

ErrExportConsumerFailed reports that an export consumer returned an error or panicked. Dependency diagnostics are intentionally not retained.

View Source
var ErrExportFailed = errors.New("audit: export failed")

ErrExportFailed reports an opaque exporter dependency failure.

View Source
var ErrKeyUnavailable = errors.New("audit: integrity key is unavailable")

Functions

func CanonicalJSON

func CanonicalJSON(record Record) ([]byte, error)

CanonicalJSON returns the versioned deterministic bytes used by integrity, persistence, export, and cross-adapter comparison. Struct field order is fixed and encoding/json sorts every map key lexicographically.

func MerkleRoot

func MerkleRoot(records []Record) ([]byte, error)

MerkleRoot returns an order-sensitive deterministic SHA-256 Merkle root over canonical record bytes. Odd levels duplicate their final node.

func NewAppendError

func NewAppendError(outcome AppendOutcome, cause error) error

NewAppendError creates a classified append error. Invalid inputs are conservatively represented as an unknown outcome.

Types

type Actor

type Actor struct {
	// contains filtered or unexported fields
}

Actor is an immutable actor identity. A zero Actor represents no delegated actor; top-level records always contain a validated non-zero actor kind.

func (Actor) AuthenticationMethod

func (actor Actor) AuthenticationMethod() string

AuthenticationMethod returns the safe optional authentication-method label.

func (Actor) DelegatedBy

func (actor Actor) DelegatedBy() Actor

DelegatedBy returns the immediate delegating actor, or the zero Actor when delegation was absent. Nested delegation is rejected during construction.

func (Actor) ID

func (actor Actor) ID() string

ID returns the stable actor ID, or empty for anonymous and unknown actors.

func (Actor) Kind

func (actor Actor) Kind() ActorKind

Kind returns the actor identity semantics.

type ActorInput

type ActorInput struct {
	Kind ActorKind
	ID   string
	// AuthenticationMethod is empty or one of the exported
	// AuthenticationMethod constants. It records a mechanism, never a credential.
	AuthenticationMethod string
	DelegatedBy          *ActorInput
}

ActorInput supplies actor identity to a new record.

type ActorKind

type ActorKind uint8

ActorKind distinguishes known identities from explicit system, anonymous, and unknown actors. Its zero value is invalid.

const (
	// ActorHuman identifies a person by a required stable ID.
	ActorHuman ActorKind = iota + 1
	// ActorService identifies a workload by a required stable ID.
	ActorService
	// ActorSystem identifies an explicit system or subsystem actor.
	ActorSystem
	// ActorAnonymous identifies a deliberately unauthenticated actor without ID.
	ActorAnonymous
	// ActorUnknown identifies an unavailable actor identity without guessing.
	ActorUnknown
)

type AlertFunc

type AlertFunc func(context.Context, DeliveryAlert) error

AlertFunc adapts a function to Alerter.

func (AlertFunc) Alert

func (alert AlertFunc) Alert(ctx context.Context, value DeliveryAlert) error

Alert invokes the adapted function with identifier-free failure metadata.

type Alerter

type Alerter interface {
	Alert(context.Context, DeliveryAlert) error
}

Alerter must durably or operationally surface fail-open delivery loss.

type AppendError

type AppendError struct {
	// contains filtered or unexported fields
}

AppendError preserves an inspectable cause while its own safe text discloses only the persistence outcome.

func (*AppendError) AppendOutcome

func (failure *AppendError) AppendOutcome() AppendOutcome

AppendOutcome returns the classified durability state.

func (*AppendError) Error

func (failure *AppendError) Error() string

Error returns bounded text containing only the durability classification.

func (*AppendError) Is

func (failure *AppendError) Is(target error) (matches bool)

Is preserves programmatic classification without exposing the underlying diagnostic through formatting or unwrapping.

type AppendOutcome

type AppendOutcome uint8

AppendOutcome classifies whether a failed append reached durable storage. Unknown is the conservative zero value and requires reconciliation by ID before retrying anything other than the same idempotent record.

const (
	// AppendUnknown means durability cannot be determined without reconciliation.
	AppendUnknown AppendOutcome = iota
	// AppendRejected means no member was committed.
	AppendRejected
	// AppendCommitted means durability is confirmed despite a later error.
	AppendCommitted
)

func AppendOutcomeOf

func AppendOutcomeOf(err error) (outcome AppendOutcome)

AppendOutcomeOf extracts a failure's durability classification. Unclassified failures are conservatively unknown.

type AppendResult

type AppendResult struct {
	RecordID string
	Status   AppendStatus
}

AppendResult is the result for one submitted record.

type AppendStatus

type AppendStatus uint8

AppendStatus describes a successful idempotent submission.

const (
	// AppendAccepted reports a newly persisted record.
	AppendAccepted AppendStatus = iota + 1
	// AppendDuplicate reports an identical previously persisted record ID.
	AppendDuplicate
)

type BatchResult

type BatchResult struct {
	Results []AppendResult
}

BatchResult preserves deterministic input-order results. Implementations must document whether a returned error can accompany committed members.

type BufferLimits

type BufferLimits struct {
	MaxRecords      int
	MaxBytes        int
	MaxBatchRecords int
}

BufferLimits declares the finite capacity of a caller-owned durable buffer. MaxBytes includes the complete persisted representation and adapter overhead.

type Builder

type Builder struct {
	// contains filtered or unexported fields
}

Builder constructs immutable validated records.

Example
package main

import (
	"fmt"
	"time"

	"github.com/faustbrian/go-audit"
)

func main() {
	now := time.Date(2026, time.August, 9, 12, 0, 0, 0, time.UTC)
	builder, _ := audit.NewBuilder(audit.BuilderConfig{
		Clock:       func() time.Time { return now },
		IDGenerator: func() (string, error) { return "record-1", nil },
	})
	record, err := builder.Build(audit.RecordInput{
		OccurredAt: now,
		Action:     "invoice.approved",
		Outcome:    audit.OutcomeSucceeded,
		Actor:      audit.ActorInput{Kind: audit.ActorHuman, ID: "user-1"},
		Subject:    audit.SubjectInput{Type: "invoice", ID: "invoice-1"},
		Changes:    audit.ChangeSetInput{NoChange: true},
	})
	fmt.Println(record.ID(), record.Action(), err)
}
Output:
record-1 invoice.approved <nil>

func NewBuilder

func NewBuilder(config BuilderConfig) (*Builder, error)

NewBuilder constructs a record builder. Nil functions use secure defaults.

func (*Builder) Build

func (builder *Builder) Build(input RecordInput) (Record, error)

Build validates input, generates a globally random record identity, and defensively owns every mutable value.

type BuilderConfig

type BuilderConfig struct {
	Clock       func() time.Time
	IDGenerator func() (string, error)
	Limits      Limits
}

BuilderConfig supplies bounded construction dependencies.

type Chain

type Chain struct {
	// contains filtered or unexported fields
}

Chain seals and verifies deterministic records. It owns no sequence or key persistence and does not claim non-repudiation.

Example
package main

import (
	"context"
	"fmt"
	"time"

	"github.com/faustbrian/go-audit"
)

func main() {
	now := time.Date(2026, time.August, 9, 12, 0, 0, 0, time.UTC)
	builder, _ := audit.NewBuilder(audit.BuilderConfig{
		Clock:       func() time.Time { return now },
		IDGenerator: func() (string, error) { return "record-1", nil },
	})
	record, _ := builder.Build(audit.RecordInput{
		OccurredAt: now, Action: "invoice.approved", Outcome: audit.OutcomeSucceeded,
		Actor:   audit.ActorInput{Kind: audit.ActorService, ID: "billing"},
		Subject: audit.SubjectInput{Type: "invoice", ID: "invoice-1"},
		Changes: audit.ChangeSetInput{NoChange: true},
	})
	chain, _ := audit.NewChain(audit.ChainConfig{Algorithm: audit.IntegritySHA256})
	sealed, err := chain.Seal(context.Background(), record, audit.ChainLink{Partition: "tenant-1", Sequence: 1})
	verifyErr := chain.Verify(context.Background(), []audit.Record{sealed})
	fmt.Println(sealed.Integrity().Enabled(), len(sealed.Integrity().Digest()), err, verifyErr)
}
Output:
true 32 <nil> <nil>

func NewChain

func NewChain(config ChainConfig) (*Chain, error)

NewChain validates an integrity policy. HMAC requires a KeyProvider; plain SHA-256 does not imply authenticity or non-repudiation.

func (*Chain) Seal

func (chain *Chain) Seal(ctx context.Context, record Record, link ChainLink) (Record, error)

Seal returns a copied record containing the supplied partition and sequence plus a deterministic digest. The caller remains responsible for serialized durable sequence allocation and checkpoint persistence.

func (*Chain) Verify

func (chain *Chain) Verify(ctx context.Context, records []Record) error

Verify checks a complete single-partition chain beginning at sequence one.

func (*Chain) VerifyFromCheckpoint

func (chain *Chain) VerifyFromCheckpoint(ctx context.Context, previous Checkpoint, records []Record, expectedFinal Checkpoint) error

VerifyFromCheckpoint verifies an archived suffix and requires it to end at expectedFinal, detecting missing prefixes, links, and truncated exports.

type ChainConfig

type ChainConfig struct {
	Algorithm IntegrityAlgorithm
	Keys      KeyProvider
	Observer  Observer
}

ChainConfig selects the digest algorithm, external HMAC key source, and optional identifier-free observer.

type ChainLink struct {
	Partition      string
	Sequence       uint64
	PreviousDigest []byte
}

ChainLink supplies caller-persisted ordering state for the next record. PreviousDigest remains caller-owned and is copied by Seal.

type ChangeSet

type ChangeSet struct {
	// contains filtered or unexported fields
}

ChangeSet is an immutable structured change description.

func (ChangeSet) After

func (changes ChangeSet) After() map[string]string

After returns a defensive copy of the safe post-action fields.

func (ChangeSet) Before

func (changes ChangeSet) Before() map[string]string

Before returns a defensive copy of the safe pre-action fields.

func (ChangeSet) NoChange

func (changes ChangeSet) NoChange() bool

NoChange reports an explicit assertion that no structured state changed.

func (ChangeSet) Redacted

func (changes ChangeSet) Redacted() bool

Redacted reports that structured changes existed but policy removed every field. It never means that no state changed.

type ChangeSetInput

type ChangeSetInput struct {
	NoChange bool
	Redacted bool
	Before   map[string]string
	After    map[string]string
}

ChangeSetInput supplies either structured before/after values or an explicit NoChange state. Values are safe bounded strings, not arbitrary request or response bodies.

type Checkpoint

type Checkpoint struct {
	// contains filtered or unexported fields
}

Checkpoint binds a partition's durable sequence to its verified digest.

func NewCheckpoint

func NewCheckpoint(partition string, sequence uint64, digest []byte) (Checkpoint, error)

NewCheckpoint validates and defensively copies one durable chain boundary.

func (Checkpoint) Digest

func (checkpoint Checkpoint) Digest() []byte

Digest returns a defensive copy of the final verified digest.

func (Checkpoint) Partition

func (checkpoint Checkpoint) Partition() string

Partition returns the stable chain partition.

func (Checkpoint) Sequence

func (checkpoint Checkpoint) Sequence() uint64

Sequence returns the final verified sequence represented by the checkpoint.

type ContextInput

type ContextInput struct {
	TenantID, CorrelationID, CausationID, RequestID, TraceID, IdempotencyID string
	SourceService, SourceVersion, Environment, NetworkOrigin, UserAgent     string
}

ContextInput supplies optional request and deployment context. Empty values mean the value was absent; callers use explicit "unknown" values only when policy requires distinguishing unknown from absent.

type Cursor

type Cursor struct {
	// contains filtered or unexported fields
}

Cursor is a stable exclusive position ordered by recording time and record ID. Its fields are private so malformed cursor state cannot be constructed.

func NewCursor

func NewCursor(recordedAt time.Time, recordID string) (Cursor, error)

NewCursor validates and constructs an export-safe cursor.

func NewSnapshotCursor

func NewSnapshotCursor(recordedAt time.Time, recordID string, snapshot uint64) (Cursor, error)

NewSnapshotCursor validates a stable pagination position and the adapter's inclusive acceptance watermark. Snapshot must come from the adapter that produced the page.

func ParseCursor

func ParseCursor(value string) (Cursor, error)

ParseCursor decodes the versioned URL-safe cursor representation.

func (Cursor) IsZero

func (cursor Cursor) IsZero() bool

IsZero reports whether the cursor denotes no pagination position.

func (Cursor) RecordID

func (cursor Cursor) RecordID() string

RecordID returns the stable record-ID tie breaker.

func (Cursor) RecordedAt

func (cursor Cursor) RecordedAt() time.Time

RecordedAt returns the canonical recording-time component.

func (Cursor) Snapshot

func (cursor Cursor) Snapshot() uint64

Snapshot returns the inclusive adapter-owned acceptance watermark. Zero identifies a legacy live cursor without snapshot isolation.

func (Cursor) String

func (cursor Cursor) String() string

String returns the versioned URL-safe cursor, or an empty string for zero.

type DeliveryAlert

type DeliveryAlert struct{ Outcome AppendOutcome }

DeliveryAlert contains bounded, identifier-free failure metadata.

type DeliveryBatchResult

type DeliveryBatchResult struct {
	Disposition DeliveryDisposition
	Append      BatchResult
}

DeliveryBatchResult is the policy result for one bounded atomic batch.

type DeliveryDisposition

type DeliveryDisposition uint8

DeliveryDisposition states what happened after redaction.

const (
	// DeliveryPersisted reports primary sink success.
	DeliveryPersisted DeliveryDisposition = iota + 1
	// DeliveryProceededAfterAlert reports explicit fail-open handling.
	DeliveryProceededAfterAlert
	// DeliveryBuffered reports successful fallback persistence.
	DeliveryBuffered
)

type DeliveryMode

type DeliveryMode uint8

DeliveryMode is the caller's explicit response to primary sink failure.

const (
	// DeliveryFailClosed returns the primary sink error to the caller.
	DeliveryFailClosed DeliveryMode = iota + 1
	// DeliveryFailOpenWithAlert permits progress only after a successful alert.
	DeliveryFailOpenWithAlert
	// DeliveryDurableBuffer permits progress only after a successful buffer write.
	DeliveryDurableBuffer
)

type DeliveryResult

type DeliveryResult struct {
	Disposition DeliveryDisposition
	Append      AppendResult
}

DeliveryResult reports whether the redacted record persisted, was buffered, or was explicitly allowed to proceed after an alert.

type DurableBuffer

type DurableBuffer interface {
	Sink
	BufferLimits() BufferLimits
}

DurableBuffer is a sink whose implementation contract survives process failure and whose finite capacity is observable before recording starts. The caller remains responsible for selecting and operating the adapter.

type Exporter

type Exporter interface {
	Export(context.Context, Query, func(Record) error) error
}

Exporter streams stable ordered records without defining read authorization.

Example
package main

import (
	"context"
	"fmt"
	"time"

	"github.com/faustbrian/go-audit"
	"github.com/faustbrian/go-audit/memory"
)

func main() {
	now := time.Date(2026, time.August, 9, 12, 0, 0, 0, time.UTC)
	builder, _ := audit.NewBuilder(audit.BuilderConfig{
		Clock:       func() time.Time { return now },
		IDGenerator: func() (string, error) { return "record-1", nil },
	})
	record, _ := builder.Build(audit.RecordInput{
		OccurredAt: now, Action: "invoice.viewed", Outcome: audit.OutcomeSucceeded,
		Actor:   audit.ActorInput{Kind: audit.ActorHuman, ID: "user-1"},
		Subject: audit.SubjectInput{Type: "invoice", ID: "invoice-1"},
		Context: audit.ContextInput{TenantID: "tenant-1"},
		Changes: audit.ChangeSetInput{NoChange: true},
	})
	store, _ := memory.New(memory.Config{MaxRecords: 1, MaxBytes: 1 << 20, MaxBatchRecords: 1})
	redactor, _ := audit.NewRedactor(audit.RedactionRules{})
	record, _ = redactor.Redact(context.Background(), record)
	_, _ = store.Append(context.Background(), record)
	tenant, _ := audit.Tenant("tenant-1")
	query, _ := audit.NewQuery(audit.QueryInput{Tenant: tenant, Limit: 1})
	err := store.Export(context.Background(), query, func(record audit.Record) error {
		fmt.Println(record.ID(), record.Action())
		return nil
	})
	fmt.Println(err)
}
Output:
record-1 invoice.viewed
<nil>

func NewObservedExporter

func NewObservedExporter(exporter Exporter, observer Observer) (Exporter, error)

NewObservedExporter decorates an exporter with identifier-free counts.

type Integrity

type Integrity struct {
	// contains filtered or unexported fields
}

Integrity is immutable optional chain metadata.

func (Integrity) Algorithm

func (value Integrity) Algorithm() IntegrityAlgorithm

Algorithm returns the selected integrity algorithm, or zero when disabled.

func (Integrity) Digest

func (value Integrity) Digest() []byte

Digest returns a defensive copy of this link's digest.

func (Integrity) Enabled

func (value Integrity) Enabled() bool

Enabled reports whether any integrity metadata is present.

func (Integrity) KeyID

func (value Integrity) KeyID() string

KeyID returns external key-rotation metadata for HMAC records.

func (Integrity) Partition

func (value Integrity) Partition() string

Partition returns the stable chain partition.

func (Integrity) PreviousDigest

func (value Integrity) PreviousDigest() []byte

PreviousDigest returns a defensive copy of the prior link digest.

func (Integrity) Sequence

func (value Integrity) Sequence() uint64

Sequence returns the caller-allocated partition sequence.

type IntegrityAlgorithm

type IntegrityAlgorithm uint8

IntegrityAlgorithm identifies a standard-library digest construction.

const (
	// IntegritySHA256 selects an unkeyed SHA-256 chain for corruption detection.
	IntegritySHA256 IntegrityAlgorithm = iota + 1
	// IntegrityHMACSHA256 selects HMAC-SHA-256 with caller-provided keys.
	IntegrityHMACSHA256
)

type IntegrityInput

type IntegrityInput struct {
	Algorithm              IntegrityAlgorithm
	Partition, KeyID       string
	Sequence               uint64
	PreviousDigest, Digest []byte
}

IntegrityInput carries optional chain metadata. Digest bytes are defensively copied; a zero Sequence with empty digests means integrity is disabled.

type IntegrityKey

type IntegrityKey struct {
	ID    string
	Bytes []byte
}

IntegrityKey is external key-rotation metadata. Bytes are copied and never included in records, errors, or diagnostics.

type KeyProvider

type KeyProvider interface {
	Key(context.Context, KeyRequest) (IntegrityKey, error)
}

KeyProvider selects an HMAC key for a bounded request.

type KeyProviderFunc

type KeyProviderFunc func(context.Context, KeyRequest) (IntegrityKey, error)

KeyProviderFunc adapts a function to KeyProvider.

func (KeyProviderFunc) Key

func (provider KeyProviderFunc) Key(ctx context.Context, request KeyRequest) (IntegrityKey, error)

Key invokes the adapted key lookup. Implementations must not expose key bytes through errors or diagnostics.

type KeyRequest

type KeyRequest struct {
	Partition  string
	KeyID      string
	RecordedAt time.Time
}

KeyRequest selects the current key for sealing when KeyID is empty, or the exact persisted historical key for verification when KeyID is present.

type Limits

type Limits struct {
	MaxRecordBytes      int
	MaxFieldBytes       int
	MaxDescriptionBytes int
	MaxAttributeEntries int
	MaxAttributeBytes   int
	MaxChangeEntries    int
	MaxChangeBytes      int
	MaxIntegrityBytes   int
}

Limits bounds every caller-controlled collection and variable-length field.

func DefaultLimits

func DefaultLimits() Limits

DefaultLimits returns conservative limits suitable for online recording.

func (Limits) Validate

func (limits Limits) Validate() error

Validate reports whether every configured limit is positive and coherent.

type Observation

type Observation struct {
	Kind     ObservationKind
	Count    int
	Duration time.Duration
	Outcome  AppendOutcome
}

Observation deliberately contains no actor, subject, tenant, record, or other caller-controlled label.

type ObservationKind

type ObservationKind uint8

ObservationKind is a fixed low-cardinality operation outcome.

const (
	// ObservationAccepted counts newly accepted records.
	ObservationAccepted ObservationKind = iota + 1
	// ObservationRejected counts confirmed pre-commit rejections.
	ObservationRejected
	// ObservationBuffered counts successful fallback buffer writes.
	ObservationBuffered
	// ObservationDuplicated counts idempotent duplicate submissions.
	ObservationDuplicated
	// ObservationFailed counts operations returning errors.
	ObservationFailed
	// ObservationDelayed counts records older than the configured threshold.
	ObservationDelayed
	// ObservationExported counts successfully streamed records.
	ObservationExported
	// ObservationIntegrityInvalid counts failed integrity verification.
	ObservationIntegrityInvalid
)

type Observer

type Observer interface {
	Observe(context.Context, Observation)
}

Observer is a dependency-neutral metrics and tracing hook.

type ObserverFunc

type ObserverFunc func(context.Context, Observation)

ObserverFunc adapts a function to Observer.

func (ObserverFunc) Observe

func (observer ObserverFunc) Observe(ctx context.Context, value Observation)

Observe invokes the adapted identifier-free observation hook.

type Outcome

type Outcome uint8

Outcome is the explicit result of an audited action. Its zero value is invalid so callers cannot accidentally omit the outcome.

const (
	// OutcomeSucceeded reports a completed successful action.
	OutcomeSucceeded Outcome = iota + 1
	// OutcomeFailed reports an attempted action that failed.
	OutcomeFailed
	// OutcomeDenied reports an action rejected by caller-owned policy.
	OutcomeDenied
	// OutcomeUnknown reports an outcome the caller cannot determine.
	OutcomeUnknown
)

type Page

type Page struct {
	Records []Record
	Next    Cursor
}

Page is one stable bounded query page. Next is zero on the final page.

type PolicyMetadata

type PolicyMetadata struct{ PolicyID, Version string }

PolicyMetadata identifies the caller-owned audit and redaction policy used to construct a record.

type Query

type Query struct {
	// contains filtered or unexported fields
}

Query is a validated immutable authorization-neutral query.

func NewQuery

func NewQuery(input QueryInput) (Query, error)

NewQuery validates bounded deterministic query options.

func (Query) Action

func (query Query) Action() string

Action returns the optional exact action filter.

func (Query) ActorID

func (query Query) ActorID() string

ActorID returns the optional exact actor filter.

func (Query) After

func (query Query) After() Cursor

After returns the exclusive stable pagination position.

func (Query) CorrelationID

func (query Query) CorrelationID() string

CorrelationID returns the optional exact correlation filter.

func (Query) From

func (query Query) From() time.Time

From returns the inclusive lower recording-time bound, or zero when absent.

func (Query) Limit

func (query Query) Limit() uint32

Limit returns the mandatory bounded result ceiling.

func (Query) Outcome

func (query Query) Outcome() Outcome

Outcome returns the optional outcome filter; zero means no outcome filter.

func (Query) RecordID

func (query Query) RecordID() string

RecordID returns the optional exact record-ID reconciliation filter.

func (Query) SubjectID

func (query Query) SubjectID() string

SubjectID returns the optional exact subject-ID filter.

func (Query) SubjectType

func (query Query) SubjectType() string

SubjectType returns the optional exact subject-type filter.

func (Query) Tenant

func (query Query) Tenant() TenantScope

Tenant returns the explicit authorization-neutral tenant scope.

func (Query) Through

func (query Query) Through() time.Time

Through returns the inclusive upper recording-time bound, or zero when absent.

func (Query) Valid

func (query Query) Valid() bool

Valid reports whether the query is coherent and bounded.

type QueryInput

type QueryInput struct {
	Tenant                                                           TenantScope
	From, Through                                                    time.Time
	RecordID, ActorID, SubjectType, SubjectID, Action, CorrelationID string
	Outcome                                                          Outcome
	Limit                                                            uint32
	After                                                            Cursor
}

QueryInput supplies bounded filters. Time bounds are inclusive. After is an exclusive stable cursor. Tenant scope is always required.

type Reader

type Reader interface {
	Query(context.Context, Query) (Page, error)
}

Reader executes bounded authorization-neutral queries.

type Record

type Record struct {
	// contains filtered or unexported fields
}

Record is immutable by contract. All mutable inputs and returned values are copied, so callers cannot mutate a constructed record through aliases.

func ParseCanonicalJSON

func ParseCanonicalJSON(encoded []byte, limits Limits) (Record, error)

ParseCanonicalJSON strictly decodes one versioned canonical record and reapplies all current size, identity, and privacy validation before taking ownership of its mutable fields.

func (Record) Action

func (record Record) Action() string

Action returns the application-defined action name.

func (Record) Actor

func (record Record) Actor() Actor

Actor returns the immutable actor context.

func (Record) Attributes

func (record Record) Attributes() map[string]string

Attributes returns a defensive copy of namespaced extensible attributes.

func (Record) Changes

func (record Record) Changes() ChangeSet

Changes returns a defensive copy of structured before/after state.

func (Record) Context

func (record Record) Context() RecordContext

Context returns the immutable request and source context.

func (Record) Description

func (record Record) Description() string

Description returns the optional policy-safe human-readable summary.

func (Record) ID

func (record Record) ID() string

ID returns the globally unique stable record identifier.

func (Record) Integrity

func (record Record) Integrity() Integrity

Integrity returns defensive copies of the optional chain metadata.

func (Record) OccurredAt

func (record Record) OccurredAt() time.Time

OccurredAt returns when the audited action occurred in canonical UTC.

func (Record) Outcome

func (record Record) Outcome() Outcome

Outcome returns the explicit action outcome.

func (Record) Policy

func (record Record) Policy() PolicyMetadata

Policy returns the policy identifier and version applied by the caller.

func (Record) ReasonCode

func (record Record) ReasonCode() string

ReasonCode returns the optional stable machine-readable reason.

func (Record) RecordedAt

func (record Record) RecordedAt() time.Time

RecordedAt returns when the record was constructed in canonical UTC.

func (Record) RedactionApplied

func (record Record) RedactionApplied() bool

RedactionApplied reports whether an explicit caller-owned privacy policy was applied in this process before persistence.

func (Record) Subject

func (record Record) Subject() Subject

Subject returns the immutable resource identity.

type RecordContext

type RecordContext struct {
	// contains filtered or unexported fields
}

RecordContext is immutable contextual metadata.

func (RecordContext) CausationID

func (value RecordContext) CausationID() string

CausationID returns the optional causal predecessor identifier.

func (RecordContext) CorrelationID

func (value RecordContext) CorrelationID() string

CorrelationID returns the optional correlation identifier.

func (RecordContext) Environment

func (value RecordContext) Environment() string

Environment returns the optional deployment environment.

func (RecordContext) IdempotencyID

func (value RecordContext) IdempotencyID() string

IdempotencyID returns the optional business-operation idempotency identifier.

func (RecordContext) NetworkOrigin

func (value RecordContext) NetworkOrigin() string

NetworkOrigin returns the optional policy-permitted network origin.

func (RecordContext) RequestID

func (value RecordContext) RequestID() string

RequestID returns the optional request identifier.

func (RecordContext) SourceService

func (value RecordContext) SourceService() string

SourceService returns the optional producing service name.

func (RecordContext) SourceVersion

func (value RecordContext) SourceVersion() string

SourceVersion returns the optional producing service version.

func (RecordContext) TenantID

func (value RecordContext) TenantID() string

TenantID returns the optional caller-supplied stable tenant ID.

func (RecordContext) TraceID

func (value RecordContext) TraceID() string

TraceID returns the optional trace identifier retained by caller policy.

func (RecordContext) UserAgent

func (value RecordContext) UserAgent() string

UserAgent returns the optional policy-permitted user agent.

type RecordInput

type RecordInput struct {
	OccurredAt                      time.Time
	Action, ReasonCode, Description string
	Outcome                         Outcome
	Actor                           ActorInput
	Subject                         SubjectInput
	Context                         ContextInput
	Changes                         ChangeSetInput
	Policy                          PolicyMetadata
	Attributes                      map[string]string
	Integrity                       IntegrityInput
}

RecordInput contains the caller-owned values used to construct one record.

type Recorder

type Recorder struct {
	// contains filtered or unexported fields
}

Recorder applies redaction before any sink or delivery-failure handling. It performs no implicit retries and never silently converts a failure to success.

Example
package main

import (
	"context"
	"fmt"
	"time"

	"github.com/faustbrian/go-audit"
	"github.com/faustbrian/go-audit/memory"
)

func main() {
	now := time.Date(2026, time.August, 9, 12, 0, 0, 0, time.UTC)
	builder, _ := audit.NewBuilder(audit.BuilderConfig{
		Clock:       func() time.Time { return now },
		IDGenerator: func() (string, error) { return "record-1", nil },
	})
	record, _ := builder.Build(audit.RecordInput{
		OccurredAt: now, Action: "invoice.viewed", Outcome: audit.OutcomeSucceeded,
		Actor:   audit.ActorInput{Kind: audit.ActorService, ID: "billing-api"},
		Subject: audit.SubjectInput{Type: "invoice", ID: "invoice-1"},
		Changes: audit.ChangeSetInput{NoChange: true},
	})
	sink, _ := memory.New(memory.Config{MaxRecords: 10, MaxBytes: 1 << 20, MaxBatchRecords: 10})
	redactor, _ := audit.NewRedactor(audit.RedactionRules{})
	recorder, _ := audit.NewRecorder(audit.RecorderConfig{Sink: sink, Redactor: redactor, Mode: audit.DeliveryFailClosed})
	result, err := recorder.Submit(context.Background(), record)
	fmt.Println(result.Disposition == audit.DeliveryPersisted, err)
}
Output:
true <nil>

func NewRecorder

func NewRecorder(config RecorderConfig) (*Recorder, error)

NewRecorder validates and constructs an explicit redaction and delivery policy. It starts no goroutines and performs no implicit retries.

func (*Recorder) Submit

func (recorder *Recorder) Submit(ctx context.Context, record Record) (DeliveryResult, error)

Submit redacts one record, invokes the configured sink once, and applies the selected failure policy. Its result never implies an unreported discard.

func (*Recorder) SubmitBatch

func (recorder *Recorder) SubmitBatch(ctx context.Context, records []Record) (DeliveryBatchResult, error)

SubmitBatch redacts every member before invoking the primary sink. Delivery policy is applied to the batch as one unit; compatible sinks therefore preserve their documented atomic batch semantics.

type RecorderConfig

type RecorderConfig struct {
	Sink            Sink
	Redactor        Redactor
	Mode            DeliveryMode
	Alerter         Alerter
	Buffer          DurableBuffer
	Observer        Observer
	Clock           func() time.Time
	DelayThreshold  time.Duration
	RecoveryTimeout time.Duration
}

RecorderConfig wires an explicit delivery policy. Sink, Redactor, and Mode are always required; fail-open requires Alerter and buffering requires Buffer.

type RedactionRules

type RedactionRules struct {
	DropDescription   bool
	DropNetworkOrigin bool
	DropUserAgent     bool
	AllowedAttributes []string
	AllowedChanges    []string
}

RedactionRules are deny-by-default allowlists for extensible data and explicit removals for privacy-sensitive contextual fields.

type Redactor

type Redactor interface {
	Redact(context.Context, Record) (Record, error)
}

Redactor removes or transforms policy-disallowed fields before persistence and before any diagnostic observer receives an operation result.

func NewRedactor

func NewRedactor(rules RedactionRules) (Redactor, error)

NewRedactor compiles deterministic redaction rules. Empty allowlists remove every attribute or structured change key.

type RedactorFunc

type RedactorFunc func(context.Context, Record) (Record, error)

RedactorFunc adapts a function to Redactor.

func (RedactorFunc) Redact

func (redactor RedactorFunc) Redact(ctx context.Context, record Record) (Record, error)

Redact invokes the adapted redaction policy.

type RetentionApplyResult

type RetentionApplyResult struct{ Deleted, Held, Changed int }

RetentionApplyResult classifies planned candidates as deleted, held, or changed since planning.

type RetentionCandidate

type RetentionCandidate struct {
	// contains filtered or unexported fields
}

RetentionCandidate binds an exported record to the exact persisted canonical digest that may later be removed after independent archive verification.

func NewRetentionCandidate

func NewRetentionCandidate(record Record, digest []byte) (RetentionCandidate, error)

NewRetentionCandidate binds a record to a defensively copied persisted SHA-256 digest for later reconciliation.

func (RetentionCandidate) Digest

func (candidate RetentionCandidate) Digest() []byte

Digest returns a defensive copy of the expected persisted digest.

func (RetentionCandidate) Record

func (candidate RetentionCandidate) Record() Record

Record returns the immutable candidate record.

type RetentionEvent

type RetentionEvent struct {
	// contains filtered or unexported fields
}

RetentionEvent appends hold state without rewriting an audit record or prior hold event.

func NewRetentionEvent

func NewRetentionEvent(input RetentionEventInput) (RetentionEvent, error)

NewRetentionEvent validates and canonicalizes one append-only hold or release event. Event and record IDs are stable caller-owned identifiers.

func (RetentionEvent) ID

func (event RetentionEvent) ID() string

ID returns the globally stable retention-event identifier.

func (RetentionEvent) Kind

func (event RetentionEvent) Kind() RetentionEventKind

Kind returns whether the event holds or releases the record.

func (RetentionEvent) OccurredAt

func (event RetentionEvent) OccurredAt() time.Time

OccurredAt returns the decision time in canonical UTC.

func (RetentionEvent) ReasonCode

func (event RetentionEvent) ReasonCode() string

ReasonCode returns the stable caller-defined hold or release reason.

func (RetentionEvent) RecordID

func (event RetentionEvent) RecordID() string

RecordID returns the affected audit record identifier.

type RetentionEventInput

type RetentionEventInput struct {
	ID, RecordID, ReasonCode string
	Kind                     RetentionEventKind
	OccurredAt               time.Time
}

RetentionEventInput supplies one immutable legal-hold lifecycle event.

type RetentionEventKind

type RetentionEventKind uint8

RetentionEventKind is an immutable legal-hold lifecycle decision.

const (
	// RetentionHold prevents privileged pruning until a later release event.
	RetentionHold RetentionEventKind = iota + 1
	// RetentionRelease permits policy-driven pruning after archive verification.
	RetentionRelease
)

type RetentionPlan

type RetentionPlan struct {
	// contains filtered or unexported fields
}

RetentionPlan is a bounded immutable archive-before-delete handoff.

func NewRetentionPlan

func NewRetentionPlan(candidates []RetentionCandidate) (RetentionPlan, error)

NewRetentionPlan validates, bounds, and defensively owns candidate digests.

func (RetentionPlan) Candidates

func (plan RetentionPlan) Candidates() []RetentionCandidate

Candidates returns a defensive copy of every planned candidate and digest.

type RetentionRequest

type RetentionRequest struct {
	// contains filtered or unexported fields
}

RetentionRequest is a validated immutable archive-planning request.

func NewRetentionRequest

func NewRetentionRequest(input RetentionRequestInput) (RetentionRequest, error)

NewRetentionRequest validates and canonicalizes a bounded retention request.

func (RetentionRequest) Before

func (request RetentionRequest) Before() time.Time

Before returns the exclusive recording-time cutoff.

func (RetentionRequest) Limit

func (request RetentionRequest) Limit() uint32

Limit returns the mandatory candidate ceiling.

func (RetentionRequest) Tenant

func (request RetentionRequest) Tenant() TenantScope

Tenant returns the explicit authorization-neutral tenant scope.

func (RetentionRequest) Valid

func (request RetentionRequest) Valid() bool

Valid reports whether the retention request is coherent and bounded.

type RetentionRequestInput

type RetentionRequestInput struct {
	Tenant TenantScope
	Before time.Time
	Limit  uint32
}

RetentionRequestInput supplies an explicit tenant scope, exclusive cutoff, and mandatory candidate ceiling.

type RetentionStore

type RetentionStore interface {
	AppendRetentionEvent(context.Context, RetentionEvent) (AppendResult, error)
	PlanRetention(context.Context, RetentionRequest) (RetentionPlan, error)
	ApplyRetention(context.Context, RetentionPlan) (RetentionApplyResult, error)
}

RetentionStore separates planning from deletion so archive/export callbacks never execute while database locks are held.

type Sink

type Sink interface {
	Append(context.Context, Record) (AppendResult, error)
	AppendBatch(context.Context, []Record) (BatchResult, error)
}

Sink accepts immutable records. Implementations must honor context cancellation, bound batches, make record IDs idempotent, and classify every error as rejected, committed, or unknown.

type Subject

type Subject struct {
	// contains filtered or unexported fields
}

Subject is an immutable resource identity.

func (Subject) Deleted

func (subject Subject) Deleted() bool

Deleted reports the resource lifecycle state without erasing its identity.

func (Subject) ID

func (subject Subject) ID() string

ID returns the stable resource identifier, including for deleted identities.

func (Subject) Type

func (subject Subject) Type() string

Type returns the stable application-defined resource type.

type SubjectInput

type SubjectInput struct {
	Type    string
	ID      string
	Deleted bool
}

SubjectInput supplies the stable identity of an audited resource. Deleted identities retain their stable ID; Deleted describes lifecycle state only.

type TenantScope

type TenantScope struct {
	// contains filtered or unexported fields
}

TenantScope requires callers to choose an exact tenant, explicitly absent tenancy, or a cross-tenant query. It conveys no authorization decision.

func AllTenants

func AllTenants() TenantScope

AllTenants selects records across every tenant and absent-tenancy records. Authorization remains the caller's responsibility.

func NoTenant

func NoTenant() TenantScope

NoTenant selects only records whose tenant ID was explicitly absent.

func Tenant

func Tenant(id string) (TenantScope, error)

Tenant selects exactly one non-empty stable tenant ID.

func (TenantScope) Includes

func (scope TenantScope) Includes(value string) bool

Includes reports whether a tenant ID is selected by this scope. It conveys no authorization decision.

func (TenantScope) Mode

func (scope TenantScope) Mode() TenantScopeMode

Mode returns the explicit tenant selection mode.

func (TenantScope) TenantID

func (scope TenantScope) TenantID() (string, bool)

TenantID returns the exact tenant ID and true only for TenantScopeExact.

func (TenantScope) Valid

func (scope TenantScope) Valid() bool

Valid reports whether the scope contains one coherent explicit mode.

type TenantScopeMode

type TenantScopeMode uint8

TenantScopeMode describes the explicit tenant selection without conveying authorization.

const (
	// TenantScopeExact selects one stable non-empty tenant ID.
	TenantScopeExact TenantScopeMode = iota + 1
	// TenantScopeAbsent selects records with no tenant ID.
	TenantScopeAbsent
	// TenantScopeAll selects all tenant states without granting authorization.
	TenantScopeAll
)

Directories

Path Synopsis
Package memory provides a bounded process-local audit sink for tests and development.
Package memory provides a bounded process-local audit sink for tests and development.
postgres module

Jump to

Keyboard shortcuts

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