audit

package
v1.1.0 Latest Latest
Warning

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

Go to latest
Published: Jul 10, 2026 License: Apache-2.0 Imports: 17 Imported by: 0

Documentation

Overview

Package audit is the durable, append-only, field-level audit trail (roadmap E1): a standardized record of who changed what — entity, field, before/after, actor, capacity, impersonator, request id — written INSIDE the business transaction so an audit row commits iff the change does. Append-only is enforced by the grants (app_rt has no UPDATE/DELETE on audit_logs); this package never offers a mutate path. Cryptographic tamper-evidence (hash-chaining, S6) layers on top of this table later.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func ExportAnchors

func ExportAnchors(ctx context.Context, pool *pgxpool.Pool) (int, error)

ExportAnchors snapshots every tenant's current audit-chain head into the append-only audit_anchors table as immutable tamper-evidence (roadmap CA-11), returning the number of anchors written. Drive it from the leader-safe scheduler (a single replica claims each interval) so anchors are written once, not once per replica. It runs cross-tenant as app_platform (one INSERT..SELECT for all tenants, mirroring the idempotency sweep) on the platform pool.

A tenant is anchored only when its chain head advanced past its last anchored seq, so re-running within an interval (or on a quiescent chain) writes nothing and the table stays bounded — one anchor per burst of audit activity.

Types

type Entry

type Entry struct {
	Action         string
	EntityType     string
	EntityID       uuid.UUID // uuid.Nil → NULL
	Field          string
	OldValue       string
	NewValue       string
	Reason         string
	ActorKind      string    // user | system | webhook (optional)
	ImpersonatorID uuid.UUID // support impersonation (optional)
	Metadata       map[string]any
}

Entry is a change to record. Action is required (e.g. "document.download", "receipt.void"); the rest are optional. For a field-level change set Field + OldValue + NewValue; for a whole-entity action leave Field empty. Values are passed through the Writer's redactor before persistence.

type Filter

type Filter struct {
	EntityType string
	EntityID   uuid.UUID
	ActorID    uuid.UUID
	Action     string
	Limit      int
}

Filter narrows a Query. Zero-valued fields are ignored; Limit defaults to 100.

type Log

type Log struct {
	ID             uuid.UUID
	OccurredAt     time.Time
	ActorID        *uuid.UUID
	ActorKind      string
	ImpersonatorID *uuid.UUID
	RequestID      string
	Action         string
	EntityType     string
	EntityID       *uuid.UUID
	Field          string
	OldValue       string
	NewValue       string
	Reason         string
	TxID           string // database transaction id (forensic correlation; roadmap CA-11)
}

Log is a persisted audit row returned by Query.

type Redactor

type Redactor func(*Entry)

Redactor may mutate an Entry before it is written — e.g. mask the values of known-sensitive fields so they never land in the audit table. It is the module's per-record redaction hook (blueprint 07 §1 "per-module redaction").

type VerifyResult

type VerifyResult struct {
	OK        bool
	Count     int64  // rows checked
	HeadSeq   int64  // last seq seen
	BrokenSeq int64  // first seq where the chain broke (0 when OK)
	Reason    string // why it broke
}

VerifyResult reports a chain verification. OK is true when every row's hash recomputes and links to its predecessor with no seq gap.

type Writer

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

Writer appends and queries audit rows. It is stateless beyond its id generator and optional redactor.

func New

func New(idgen model.IDGen, redact Redactor) *Writer

New builds a Writer. redact may be nil (no redaction).

func (*Writer) Anchor

func (w *Writer) Anchor(ctx context.Context, db database.TenantDB) (seq int64, headHash string, err error)

Anchor returns the tenant's current chain head — the last seq and its hash. Exporting/publishing an anchor lets a later Verify prove no tampering occurred up to that seq. Returns (0, "") when the tenant has no audit rows yet.

func (*Writer) CheckAnchor

func (w *Writer) CheckAnchor(ctx context.Context, db database.TenantDB, seq int64, headHash string) (present bool, err error)

CheckAnchor reports whether the tenant's LIVE chain still contains the row at anchored seq with anchored hash — the offline verifier's primitive against a previously exported anchor (roadmap CA-11). It closes Verify's blind spot: a tail truncation (drop the last k rows and rewind audit_chain.head_hash) leaves the remaining chain internally consistent, so Verify still returns OK; but the anchored (seq, hash) is gone, so CheckAnchor returns false. present=false means the tail was truncated or the anchored row rewritten after the anchor was taken. Read-only; safe in a read-only transaction.

func (*Writer) Query

func (w *Writer) Query(ctx context.Context, db database.TenantDB, f Filter) ([]Log, error)

Query returns audit rows matching the filter, newest first, in the caller's tenant transaction (RLS-scoped). All filter values are bound as parameters.

func (*Writer) Record

func (w *Writer) Record(ctx context.Context, db database.TenantDB, e Entry) error

Record appends one audit row in db's transaction (so it commits with the business write) and extends the tenant's hash chain (S6). The acting actor id and request id are read from ctx; the caller supplies the semantic fields via e. Action is required. Concurrent audit writes for a tenant serialize on the audit_chain head row, so seq is gap-free and the chain is well-ordered.

func (*Writer) Verify

func (w *Writer) Verify(ctx context.Context, db database.TenantDB) (VerifyResult, error)

Verify walks the current tenant's audit chain in seq order, recomputing each row's hash and checking the prev-links and sequence continuity. It detects any mutation of a past row (its hash no longer matches) and any deletion (a seq gap). Read-only; safe in a read-only transaction.

Jump to

Keyboard shortcuts

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