record

package
v0.3.0 Latest Latest
Warning

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

Go to latest
Published: Oct 31, 2026 License: MIT Imports: 24 Imported by: 0

Documentation

Overview

Package record supplies typed audit snapshots and generated registration contracts below audit storage. Capturing a record performs no database I/O.

Index

Constants

View Source
const MaxCapturedValueBytes = jsonwire.MaxBytes / 4

MaxCapturedValueBytes bounds one captured value before the record-level payload bound applies. Larger values are stored as an Oversized digest; the recorder's configured value limit can replace smaller values the same way.

View Source
const MaxFields = 1024

Variables

This section is empty.

Functions

func Capture

func Capture[M, K, V any](builder *Builder[M, K], name string, c codec.Codec[V], change lifecycle.FieldChange[V], disclosure Disclosure) error

Capture adds one generated field to builder. Updates, soft deletes and restorations record only assigned or changed fields plus the subject key, so unchanged columns never invoke their codec or enlarge the stored row. Create and delete operations retain complete snapshots.

func FoundryCapture

func FoundryCapture[M, K any](builder *Builder[M, K], field FoundryStoredField, disclosure Disclosure, seal sqlowner.MetadataSeal) error

FoundryCapture reuses capture policy with already computed lifecycle flags. A valid metadata capability is required before any callback. Example: FoundryCapture(builder, field, Automatic, sqlowner.Metadata()).

func FoundryReadField

func FoundryReadField[M, K, V any](view View[M, K], name string, kind codec.ParameterType, decode func(any) (V, error), seal sqlowner.MetadataSeal) (value.Optional[FieldChange[V]], error)

FoundryReadField retains a declaration's existing decoder without rebuilding a codec. The metadata capability is checked before the decoder is retained. Example: FoundryReadField(view, "name", codec.TypeText, decode, sqlowner.Metadata()).

func ObserverName

func ObserverName[M any]() (string, error)

ObserverName derives one stable identifier per concrete model for the existing database-wide observer namespace. It is not a durable model/subject identity. Hashing avoids invalid package-path characters and identifier truncation.

func ReadField

func ReadField[M, K, V any](view View[M, K], name string, c codec.Codec[V]) (value.Optional[FieldChange[V]], error)

ReadField is a generated declaration boundary. Model adapters supply the column and existing codec; ordinary application code uses their named fields.

func SensitiveName

func SensitiveName(name string) bool

SensitiveName is the current shared rule for stored columns and nested JSON keys. Explicit exclusions are still required for secrets whose names do not identify their meaning.

Types

type Builder

type Builder[M, K any] struct {
	// contains filtered or unexported fields
}

Builder is a generated, operation-local capture boundary. It is not safe for concurrent use; the completed Model is immutable. Add errors remain sticky so ignoring one cannot publish a partial record. No database work occurs here. Updates, soft deletes and restorations keep only assigned or changed fields and the subject key; creation and deletion keep complete snapshots.

func NewBuilder

func NewBuilder[M, K any](template model.Reference[M, K], operation lifecycle.Operation, primary string, disclosure Disclosure) (*Builder[M, K], error)

NewBuilder is emitted from the model's existing primary metadata. A subject key is necessarily present in audit identity; excluding/redacting that field would misleadingly retain its value elsewhere. Such policies, including a conventionally sensitive primary column, fail before binding the subject key.

func (*Builder[M, K]) Add

func (b *Builder[M, K]) Add(field Field[M]) error

func (*Builder[M, K]) Build

func (b *Builder[M, K]) Build() (Model[M, K], error)

func (Builder[M, K]) Format

func (Builder[M, K]) Format(state fmt.State, _ rune)

type Declaration

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

Declaration wraps a generated model's existing observer registration. It is opaque outside this explicit declaration boundary and owns no global state.

func Declare

Declare is called by generated model adapters. Application providers pass the resulting declaration to audit.Register with their typed database/writer keys.

func (Declaration) Register

func (d Declaration) Register(r *foundation.Registrar, pool foundation.Key[*database.DB], resolve ResolveWriter) error

Register invokes the generated registration using foundation/database's existing duplicate, dependency, construction and frozen-state handling.

type Digest

type Digest struct {
	Size   int64
	SHA256 string
}

Digest describes an oversized value without retaining it. Size is the number of captured bytes: UTF-8 text, raw bytes or the already-redacted canonical JSON document. SHA256 is the lowercase hexadecimal digest of those bytes; it is empty for JSON that was too large to inspect for sensitive keys, so a digest never allows confirming a guessed secret.

type Disclosure

type Disclosure uint8

Disclosure controls one generated model field's audit representation. Automatic includes ordinary fields and redacts sensitive codecs or conventional sensitive names. Exclude omits the entire field; Redact retains change flags without its value. There is deliberately no option to override sensitive-value/name protection.

const (
	Automatic Disclosure = iota
	Exclude
	Redact
)

func (Disclosure) Validate

func (d Disclosure) Validate() error

type Document

type Document[P any] struct {
	// contains filtered or unexported fields
}

Document captures a typed domain audit DTO. Sensitive JSON keys use the same redaction rules as model fields. Redacted documents remain audit representations; they cannot be decoded into partially populated application DTOs.

func CaptureDocument

func CaptureDocument[P any](input P) (Document[P], error)

CaptureDocument freezes an independent representation and isolates custom JSON callback faults. Domain DTOs should omit private fields altogether when even a redacted marker is inappropriate.

func ParseDocument

func ParseDocument[P any](text string, redacted bool) (Document[P], error)

ParseDocument checks restored redaction metadata under CurrentRedaction. Use ParseDocumentWith for stored history, which records the policy that wrote it.

func ParseDocumentWith

func ParseDocumentWith[P any](policy Redaction, text string, redacted bool) (Document[P], error)

ParseDocumentWith checks restored redaction metadata under the policy that captured the document and applies the concrete DTO schema only to complete, unredacted data. It never trusts a persisted flag to hide an unsanitized secret value, and extending the current policy never invalidates old rows. Keys that CurrentRedaction treats as sensitive in an older document are masked on read: the result is redacted and reports CurrentRedaction.

func (Document[P]) Decode

func (d Document[P]) Decode() (P, error)

Decode returns a fresh DTO or a Missing error for redacted history. Payload remains available for an explicit, already-redacted audit export.

func (Document[P]) Format

func (Document[P]) Format(state fmt.State, _ rune)

func (Document[P]) Payload

func (d Document[P]) Payload() (string, error)

func (Document[P]) Redacted

func (d Document[P]) Redacted() bool

func (Document[P]) Redaction

func (d Document[P]) Redaction() Redaction

Redaction reports the sensitive-name policy the payload satisfies: the policy that captured it, or CurrentRedaction when an older document contained keys the current policy treats as sensitive and was masked when parsed.

type Entry

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

Entry is the immutable heterogeneous storage boundary of a model audit. Normal application code receives the model-owned Model wrapper and generated fields. Entry is attribution/history, never authentication or authorization evidence. Constructors validate the complete document once; copies share that result.

func ParseEntry

func ParseEntry(identity model.Identity, operation lifecycle.Operation, payload string) (Entry, error)

ParseEntry is an explicit persistence/transport boundary. It validates stored metadata, snapshot states and redaction before allowing export or typed restore. Each stored document is checked against the redaction policy that wrote it.

func (Entry) Compact

func (e Entry) Compact(limit int) (Entry, error)

Compact returns an entry whose disclosed values above limit bytes are stored as Oversized digests. It never fails because values are large; it is used by recorders to apply their configured per-value threshold before storage. Entries captured under an older policy are returned unchanged.

func (Entry) Format

func (Entry) Format(state fmt.State, _ rune)

func (Entry) Identity

func (e Entry) Identity() model.Identity

func (Entry) Operation

func (e Entry) Operation() lifecycle.Operation

func (Entry) Payload

func (e Entry) Payload() (string, error)

Payload exposes the versioned snapshot document only at a deliberate storage or report boundary. It contains audit values, not serialized model DTOs.

func (Entry) Redaction

func (e Entry) Redaction() Redaction

Redaction reports the sensitive-name policy that captured this entry. Values of an entry captured under an older policy are additionally masked under CurrentRedaction when it is parsed, and the result stays valid under the capturing policy.

func (Entry) Validate

func (e Entry) Validate() error

Validate reports whether the entry came from Build or ParseEntry, which check metadata, snapshot states and redaction under the entry's own policy.

type Field

type Field[M any] struct {
	// contains filtered or unexported fields
}

Field is an opaque, model-owned captured field for generated audit adapters. Its constructor is a declaration boundary; normal callers use model Changes.

func CaptureField

func CaptureField[M, V any](name string, c codec.Codec[V], change lifecycle.FieldChange[V], disclosure Disclosure) (Field[M], error)

CaptureField consumes the already-computed stored change. It never compares values again or invokes model presentation accessors. Excluded/redacted fields do not invoke their persistence codec at all. Codecs must be pure and return owned values, as for ordinary persistence; custom callback faults are isolated. Values above MaxCapturedValueBytes become Oversized digests rather than failing the business write.

func (Field[M]) Format

func (Field[M]) Format(state fmt.State, _ rune)

type FieldChange

type FieldChange[V any] struct {
	// contains filtered or unexported fields
}

FieldChange contains a stored audit field with concrete before/after types. The surrounding Optional returned by generated readers represents a field omitted by policy or absent from an older record's schema.

func (FieldChange[V]) After

func (c FieldChange[V]) After() FieldValue[V]

func (FieldChange[V]) Assigned

func (c FieldChange[V]) Assigned() bool

func (FieldChange[V]) Before

func (c FieldChange[V]) Before() FieldValue[V]

func (FieldChange[V]) Changed

func (c FieldChange[V]) Changed() bool

func (FieldChange[V]) Format

func (FieldChange[V]) Format(state fmt.State, _ rune)

type FieldValue

type FieldValue[V any] struct {
	// contains filtered or unexported fields
}

FieldValue retains the declared storage type while exposing disclosure state. A redacted value cannot hydrate T; JSON export remains the explicitly redacted audit representation rather than a partial application model.

func (FieldValue[V]) Format

func (FieldValue[V]) Format(state fmt.State, _ rune)

func (FieldValue[V]) Get

func (v FieldValue[V]) Get() (value.Optional[V], error)

Get returns absence only when the model snapshot was absent. A nullable field decodes to its concrete Nullable type. Redaction is an explicit error, never a fabricated zero field value. Each decode gives the caller its own value.

func (FieldValue[V]) Snapshot

func (v FieldValue[V]) Snapshot() Snapshot

func (FieldValue[V]) State

func (v FieldValue[V]) State() State

type FoundryStoredField

type FoundryStoredField struct {
	Name                             string
	Type                             codec.ParameterType
	Sensitive                        bool
	Before, After, Assigned, Changed bool
	Bind                             func(after bool) (driver.Value, error)
}

FoundryStoredField is the framework's deferred stored-change boundary. Bind runs only for selected, disclosed snapshots. Example: FoundryStoredField{Name: "name", Type: codec.TypeText, After: true, Changed: true, Bind: bindStored}.

type Model

type Model[M, K any] struct {
	// contains filtered or unexported fields
}

Model preserves the audited subject and stored key types. Its snapshots remain audit representations: exclusions/redaction never produce incomplete models.

func RestoreModel

func RestoreModel[M, K any](template model.Reference[M, K], entry Entry) (Model[M, K], error)

RestoreModel reapplies the expected model/key owner to an already validated history entry. The template's current key is ignored, as with Reference.Parse.

func (Model[M, K]) Entry

func (m Model[M, K]) Entry() Entry

func (Model[M, K]) Format

func (Model[M, K]) Format(state fmt.State, _ rune)

func (Model[M, K]) Operation

func (m Model[M, K]) Operation() lifecycle.Operation

func (Model[M, K]) Subject

func (m Model[M, K]) Subject() (model.Reference[M, K], error)

Subject restores a fresh concrete key from the captured identity. It does not reuse a caller's later-mutated key or perform model lookup or getter evaluation.

type Redaction

type Redaction uint8

Redaction identifies the sensitive-name policy that captured a stored audit row. Every row retains the policy that wrote it, so extending the recognized names never invalidates older history; reads check each row against its own policy. New captures always use CurrentRedaction.

const (
	// RedactionV1 recognizes password, secret, token, credential, authorization
	// and API/private-key names split at snake, kebab and camel-case boundaries.
	RedactionV1 Redaction = 1
	// RedactionV2 additionally splits letter/digit boundaries, ignores digit
	// suffixes and simple plurals, recognizes joined names such as accesstoken,
	// and adds passphrase, OTP, PIN, CVV/CVC, SSN, card number, cookie, session
	// and recovery-code conventions.
	RedactionV2 Redaction = 2
	// CurrentRedaction is the policy applied to newly captured audit data.
	CurrentRedaction = RedactionV2
)

func (Redaction) Sensitive

func (r Redaction) Sensitive(name string) bool

Sensitive applies this policy version's naming rule. Unknown versions treat every name as sensitive rather than disclosing data under a guessed rule.

func (Redaction) Validate

func (r Redaction) Validate() error

type ResolveWriter

type ResolveWriter func(foundation.Resolver) (Writer, error)

ResolveWriter binds the writer during ordinary application construction, before the database starts. Generated callbacks retain it without a runtime service lookup or second application container.

type Snapshot

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

Snapshot owns one immutable audit value. It is an audit representation, not a model field or response DTO. Format omits content; JSON is an explicit export.

func (Snapshot) Digest

func (s Snapshot) Digest() (Digest, bool)

Digest returns the size marker of an Oversized snapshot.

func (Snapshot) Format

func (Snapshot) Format(state fmt.State, _ rune)

func (Snapshot) MarshalJSON

func (s Snapshot) MarshalJSON() ([]byte, error)

func (Snapshot) State

func (s Snapshot) State() State

func (*Snapshot) UnmarshalJSON

func (s *Snapshot) UnmarshalJSON(data []byte) error

type State

type State uint8

State distinguishes missing model snapshots, disclosed SQL values, completely redacted values and JSON whose sensitive descendants have been redacted. SQL NULL is a disclosed SQL value, distinct from an absent model snapshot. Oversized marks a value that exceeded the audit value bound; its Digest retains the stored size and, when safe, a SHA-256 of the captured bytes.

const (
	Absent State = iota
	Disclosed
	Redacted
	RedactedJSON
	Oversized
)

type View

type View[M, K any] struct {
	// contains filtered or unexported fields
}

View indexes an immutable model audit once for generated typed field readers. Its map is private and owned; reading many fields does not repeatedly decode the entire payload. A zero view is invalid. A field missing from an update record was neither assigned nor changed by that operation.

func Inspect

func Inspect[M, K any](record Model[M, K]) (View[M, K], error)

func (View[M, K]) Format

func (View[M, K]) Format(state fmt.State, _ rune)

type Writer

type Writer interface {
	RecordModel(context.Context, *database.Tx, Entry) error
}

Writer is the narrow storage boundary used by generated audit observers. The supplied transaction is the real business transaction; returning an error fails its model write. Implementations must support concurrent operations.

Jump to

Keyboard shortcuts

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