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 ¶
- Constants
- Variables
- func CanonicalJSON(record Record) ([]byte, error)
- func MerkleRoot(records []Record) ([]byte, error)
- func NewAppendError(outcome AppendOutcome, cause error) error
- type Actor
- type ActorInput
- type ActorKind
- type AlertFunc
- type Alerter
- type AppendError
- type AppendOutcome
- type AppendResult
- type AppendStatus
- type BatchResult
- type BufferLimits
- type Builder
- type BuilderConfig
- type Chain
- type ChainConfig
- type ChainLink
- type ChangeSet
- type ChangeSetInput
- type Checkpoint
- type ContextInput
- type Cursor
- type DeliveryAlert
- type DeliveryBatchResult
- type DeliveryDisposition
- type DeliveryMode
- type DeliveryResult
- type DurableBuffer
- type Exporter
- type Integrity
- type IntegrityAlgorithm
- type IntegrityInput
- type IntegrityKey
- type KeyProvider
- type KeyProviderFunc
- type KeyRequest
- type Limits
- type Observation
- type ObservationKind
- type Observer
- type ObserverFunc
- type Outcome
- type Page
- type PolicyMetadata
- type Query
- func (query Query) Action() string
- func (query Query) ActorID() string
- func (query Query) After() Cursor
- func (query Query) CorrelationID() string
- func (query Query) From() time.Time
- func (query Query) Limit() uint32
- func (query Query) Outcome() Outcome
- func (query Query) RecordID() string
- func (query Query) SubjectID() string
- func (query Query) SubjectType() string
- func (query Query) Tenant() TenantScope
- func (query Query) Through() time.Time
- func (query Query) Valid() bool
- type QueryInput
- type Reader
- type Record
- func (record Record) Action() string
- func (record Record) Actor() Actor
- func (record Record) Attributes() map[string]string
- func (record Record) Changes() ChangeSet
- func (record Record) Context() RecordContext
- func (record Record) Description() string
- func (record Record) ID() string
- func (record Record) Integrity() Integrity
- func (record Record) OccurredAt() time.Time
- func (record Record) Outcome() Outcome
- func (record Record) Policy() PolicyMetadata
- func (record Record) ReasonCode() string
- func (record Record) RecordedAt() time.Time
- func (record Record) RedactionApplied() bool
- func (record Record) Subject() Subject
- type RecordContext
- func (value RecordContext) CausationID() string
- func (value RecordContext) CorrelationID() string
- func (value RecordContext) Environment() string
- func (value RecordContext) IdempotencyID() string
- func (value RecordContext) NetworkOrigin() string
- func (value RecordContext) RequestID() string
- func (value RecordContext) SourceService() string
- func (value RecordContext) SourceVersion() string
- func (value RecordContext) TenantID() string
- func (value RecordContext) TraceID() string
- func (value RecordContext) UserAgent() string
- type RecordInput
- type Recorder
- type RecorderConfig
- type RedactionRules
- type Redactor
- type RedactorFunc
- type RetentionApplyResult
- type RetentionCandidate
- type RetentionEvent
- type RetentionEventInput
- type RetentionEventKind
- type RetentionPlan
- type RetentionRequest
- type RetentionRequestInput
- type RetentionStore
- type Sink
- type Subject
- type SubjectInput
- type TenantScope
- type TenantScopeMode
Examples ¶
Constants ¶
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" )
const (
// MaxAppendBatchRecords is the absolute core ceiling for one append call.
MaxAppendBatchRecords = 1_000
)
const ( // MaxQueryRecords is the absolute number of records one query or export may // request. MaxQueryRecords uint32 = 1_000 )
const MaxRecoveryTimeout = time.Minute
MaxRecoveryTimeout is the absolute bound for alerting or durable buffering after the caller's primary-operation context has ended.
Variables ¶
var ( ErrDeliveryAlertFailed = errors.New("audit: delivery alert failed") ErrDurableBufferFailed = errors.New("audit: durable buffer failed") )
var ( // ErrInvalidArgument classifies a caller-supplied value that violates the // bounded audit record contract. ErrInvalidArgument = errors.New("audit: invalid argument") ErrRecordIDUnavailable = errors.New("audit: record ID is unavailable") 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") )
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") )
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.
var ErrExportFailed = errors.New("audit: export failed")
ErrExportFailed reports an opaque exporter dependency failure.
Functions ¶
func CanonicalJSON ¶
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 ¶
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 ¶
AuthenticationMethod returns the safe optional authentication-method label.
func (Actor) DelegatedBy ¶
DelegatedBy returns the immediate delegating actor, or the zero Actor when delegation was absent. Nested delegation is rejected during construction.
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.
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 ¶
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.
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 ¶
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) 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 ¶
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.
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 NewSnapshotCursor ¶
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 ¶
ParseCursor decodes the versioned URL-safe cursor representation.
func (Cursor) RecordedAt ¶
RecordedAt returns the canonical recording-time component.
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 ¶
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>
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) PreviousDigest ¶
PreviousDigest returns a defensive copy of the prior link digest.
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 ¶
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 ¶
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.
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 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) CorrelationID ¶
CorrelationID returns the optional exact correlation filter.
func (Query) SubjectType ¶
SubjectType returns the optional exact subject-type filter.
func (Query) Tenant ¶
func (query Query) Tenant() TenantScope
Tenant returns the explicit authorization-neutral tenant scope.
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 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 ¶
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) Attributes ¶
Attributes returns a defensive copy of namespaced extensible attributes.
func (Record) Context ¶
func (record Record) Context() RecordContext
Context returns the immutable request and source context.
func (Record) Description ¶
Description returns the optional policy-safe human-readable summary.
func (Record) OccurredAt ¶
OccurredAt returns when the audited action occurred in canonical UTC.
func (Record) Policy ¶
func (record Record) Policy() PolicyMetadata
Policy returns the policy identifier and version applied by the caller.
func (Record) ReasonCode ¶
ReasonCode returns the optional stable machine-readable reason.
func (Record) RecordedAt ¶
RecordedAt returns when the record was constructed in canonical UTC.
func (Record) RedactionApplied ¶
RedactionApplied reports whether an explicit caller-owned privacy policy was applied in this process before persistence.
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 ¶
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 ¶
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 ¶
RedactorFunc adapts a function to Redactor.
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.
type SubjectInput ¶
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 )