auditlake

package
v0.1.2 Latest Latest
Warning

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

Go to latest
Published: Aug 3, 2026 License: Apache-2.0 Imports: 26 Imported by: 0

Documentation

Overview

Package auditlake exports the kernel's two evidence streams — the journal (full event coverage, payload-classified) and the pkg/security HMAC audit chain — as hive-partitioned, signed, gap-explainable JSONL.gz partitions on a pluggable object store (a local directory for dev, GCS for cloud/BYOC). The layout is readable natively by BigQuery external tables, Spark, and DuckDB; analysis tooling lives lake-side, never in the kernel.

Architecture (binding decisions D1–D14 of the audit-lake spec):

  • The JOURNAL table is exported by exactly ONE elected exporter per cluster (the journal offset space is cluster-global — pkg/etcdstore keys carry no node component), elected through the kernel's existing ServiceLease single-activation primitive. The exporter runs a second siemexport.Consumer group ("audit-lake-export") and inherits the full commit-after-export / stall / gap backpressure contract.
  • The AUDIT CHAIN table is exported by a synchronous write-through sink hooked at security.AuditLog.Append time on EVERY node (chain entries have no durable replay source — only the head tuple persists — so a consumer cannot export them). Entries are spooled + fsynced BEFORE the append becomes visible, and confirmed after the durable head advances.
  • Both tables write through a sequential per-table write-ahead spool with a durable watermark recovered from the newest signed manifest, so crash replay never silently loses a row and residual at-least-once duplicates are absorbed by the lake-side dedup contract (D13).
  • Journal payloads pass a typed classification registry (decision / structural / free_text / sensitive) so full event COVERAGE never becomes full content exfiltration (D11). Default mode is "structural".

Everything here is off by default: a daemon with no lake store configured pays nothing. Stdlib-only — no new go.mod dependencies.

Index

Constants

View Source
const (
	// ConsumerGroup is the journal subscription group the lake's journal
	// exporter owns. It is SEPARATE from siemexport.ConsumerGroup so a lake /
	// object-store outage can never withhold the SIEM cursor (D3).
	ConsumerGroup = "audit-lake-export"

	// JournalExporterService is the ServiceLease name electing the single
	// journal exporter per cluster (D10).
	JournalExporterService = "audit-lake-journal-exporter"

	// TableJournal and TableAuditChain are the two lake table directories.
	TableJournal    = "journal"
	TableAuditChain = "audit_chain"

	// SchemaVersion is recorded in every manifest. Schema evolution within a
	// table is additive-only; a breaking change becomes a new table dir.
	SchemaVersion = 1
)
View Source
const (
	// GapCauseRetentionForceDrop explains a seq jump observed in-band: the
	// journal force-dropped this group's cursor to the retention floor (the
	// H3 backpressure path) and recorded an EventDeadLetter marker.
	GapCauseRetentionForceDrop = "retention_force_drop"
	// GapCauseFailoverSpoolLost explains a takeover gap: the previous elected
	// exporter committed rows that only ever reached its local spool, and the
	// journal's retention horizon has already passed them.
	GapCauseFailoverSpoolLost = "exporter_failover_spool_lost"
	// GapCauseKeyRotation explains a forced segment rotation at a key-epoch
	// boundary (D12). No rows are lost; the annotation marks the boundary.
	GapCauseKeyRotation = "key_rotation"
)

The closed gap-cause taxonomy (D5). A manifest gap annotation carries exactly one of these; the offline verifier treats every cause as "explained" and anything else as tamper-suspect.

View Source
const EventJournalGap kernel.EventType = "AuditLakeJournalGap"

EventJournalGap is the typed journal event the journal exporter appends when a takeover discovers an unexportable range (the dead holder's spool rows fell past the journal retention horizon, D10). Never a silent jump.

Variables

View Source
var (
	// ErrObjectExists is returned by ObjectStore.Put when the create-only
	// object already exists. Callers treat it as the benign at-least-once
	// replay/collision case (D4/D10).
	ErrObjectExists = errors.New("auditlake: object already exists")
	// ErrSpoolFull is returned by Export when the local spool directory has
	// reached its disk cap. The cursor is withheld and H3 backpressure
	// engages upstream — never a silent drop.
	ErrSpoolFull = errors.New("auditlake: spool disk cap reached")
	// ErrNotContiguous is returned when a spool append would break the
	// strictly sequential per-table seq order without an explaining gap.
	ErrNotContiguous = errors.New("auditlake: spool append not seq-contiguous")
	// ErrLeaseLost is returned by the upload fence when the elected journal
	// exporter no longer holds its ServiceLease; the rotator aborts without
	// uploading (D10 fencing).
	ErrLeaseLost = errors.New("auditlake: journal exporter lease lost")
)

Lake-layer error sentinels. Test with errors.Is.

Functions

func FileTokenSource

func FileTokenSource(path string) siemexport.TokenSource

FileTokenSource reads a bearer token from a file on every call — the D8 "file:<path>" token mode for daemons without a metadata server whose token is refreshed by an external helper (the kernel never execs gcloud). An unreadable or empty file is an explicit error.

func LakeRecordFromEvent

func LakeRecordFromEvent(ev kernel.Event, mode PayloadMode, keyEpoch int64) siemexport.Record

LakeRecordFromEvent maps ONE journal event to a lake row (D2). Unlike siemexport.RecordFromEvent it never thins an offset away — every offset becomes a row with its true event_type, so seq density is verifiable — but payload CONTENT passes the classification registry: decision and structural payloads export in full, free_text payloads pass the allowlist redactor (unless mode is ModeFull), and sensitive/unregistered payloads are digests only. The record carries the current key epoch; the caller's Exporter signs it AFTER this redaction (sign-what-you-export).

func RegisteredEventTypes

func RegisteredEventTypes() []kernel.EventType

RegisteredEventTypes returns the sorted registry keys (for tests and the /proc-style operator surface).

Types

type ChainContinuity

type ChainContinuity struct {
	NodeID             string     `json:"node_id"`
	KeyScope           string     `json:"key_scope,omitempty"`
	FirstEntryPrevHash string     `json:"first_entry_prev_hash"`
	LastEntry          Checkpoint `json:"last_entry"`
	// EnabledFromCheckpoint is set when this partition does NOT stitch to the
	// previous one: entries sealed before lake enablement (or while it was
	// down) exist only as the durable AuditChainHead tuple, so the partition
	// declares the checkpoint it verifiably stitches FROM instead.
	EnabledFromCheckpoint *Checkpoint `json:"enabled_from_checkpoint,omitempty"`
	// RotatedFrom is set on the first partition after a key-epoch rotation.
	RotatedFrom *RotationBoundary `json:"rotated_from,omitempty"`
}

ChainContinuity is the audit_chain manifest stitching block: partition N+1 stitches to N when FirstEntryPrevHash == prev.LastEntry.Hash. The two declared boundaries keep restarts/enablement distinguishable from tamper.

type ChainSink

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

ChainSink is the audit_chain table's write-through export (D14): chain entries have NO durable replay source — only the head tuple persists — so they are spooled + fsynced synchronously inside AuditLog.Append BEFORE the entry becomes visible (Stage), and confirmed once the durable head has advanced (Confirm). It runs on EVERY node; chains are per-node partitions.

Crash semantics: a staged-but-unconfirmed row either (a) gets superseded by the retry that re-mints the same seq (finalize keeps the LAST row per seq), or (b) is dropped at recovery because the durable head never reached it — the entry never existed in the visible chain. Confirmed rows always export.

func NewChainSink

func NewChainSink(ctx context.Context, cfg ChainSinkConfig) (*ChainSink, error)

NewChainSink recovers the chain spool: drops rows beyond the durable head (unconfirmed tail — never chain-visible), drops rows at or below the newest manifest watermark (already uploaded), re-finalizes the survivors, and seeds the continuity chain. A recovered segment whose key epoch differs from the current one finalizes under ITS epoch and stamps the rotation boundary on the next manifest (D12).

func (*ChainSink) Close

func (s *ChainSink) Close(ctx context.Context) error

Close force-finalizes the active segment (clean shutdown).

func (*ChainSink) Confirm

func (s *ChainSink) Confirm(seq int64)

Confirm implements security.AuditEntrySink: the durable head advanced past seq, so the staged row may finalize.

func (*ChainSink) RotateIfDue

func (s *ChainSink) RotateIfDue(ctx context.Context, force bool) error

RotateIfDue seals + finalizes the active segment when due (or force=true). Only rows up to the confirmed watermark finalize; an unconfirmed tail is re-staged into the fresh open segment (fsync'd) BEFORE the sealed file can be deleted, so no durability window exists. A crash between the re-stage and the delete leaves the tail in BOTH segments — a benign byte-identical overlap the D13 dedup contract absorbs. Driven by the daemon's chain flusher goroutine on EVERY node.

func (*ChainSink) RunFlusher

func (s *ChainSink) RunFlusher(ctx context.Context)

RunFlusher drives RotateIfDue on a ticker until ctx ends — the per-node chain flusher goroutine.

func (*ChainSink) Stage

func (s *ChainSink) Stage(e security.AuditEntry) error

Stage implements security.AuditEntrySink: spool + fsync the sealed entry BEFORE the append becomes visible. An error here fails the Append — the chain never advances past an entry the lake could lose (D14). Same-seq re-stages (a failed Append retried) supersede via finalize keep-last.

type ChainSinkConfig

type ChainSinkConfig struct {
	Store    ObjectStore
	SpoolDir string
	Keys     *KeyEpochs
	Mode     PayloadMode
	// NodeID partitions the chain (node=<id>/ — per-node chains, D6) and
	// labels continuity blocks.
	NodeID string
	// KeyScope is the non-secret chain key scope (security.AuditKeyScope) so
	// the verifier can match partitions to the durable AuditChainHead.
	KeyScope string
	// DurableHead is the AuditChainHead checkpoint the local AuditLog resumed
	// from at wiring time: recovered spool rows BEYOND it were staged by an
	// Append that never became chain-visible and are dropped (D14).
	DurableHead    security.AuditChainCheckpoint
	HasDurableHead bool

	MaxSegmentBytes int64
	FlushInterval   time.Duration
	SpoolDiskCap    int64
	Logger          *slog.Logger
	Now             func() time.Time
}

ChainSinkConfig assembles a ChainSink.

type Checkpoint

type Checkpoint struct {
	Seq  int64  `json:"seq"`
	Hash string `json:"hash"`
}

Checkpoint is a {seq, hash} chain boundary (mirrors security.AuditChainCheckpoint without importing it into the wire format).

type DirStore

type DirStore struct {
	Root string
}

DirStore is the local-directory object store (AGENTOS_LAKE_DIR). Object keys map to file paths under Root; Put is create-only via an O_EXCL hard-link publish, so a concurrent or replayed writer observes exactly the GCS ifGenerationMatch=0 semantics.

func NewDirStore

func NewDirStore(root string) (*DirStore, error)

NewDirStore validates the root and returns a DirStore.

func (*DirStore) Get

func (s *DirStore) Get(_ context.Context, key string) ([]byte, error)

Get implements ObjectStore.

func (*DirStore) List

func (s *DirStore) List(_ context.Context, prefix string) ([]string, error)

List implements ObjectStore.

func (*DirStore) Name

func (s *DirStore) Name() string

Name implements ObjectStore.

func (*DirStore) Put

func (s *DirStore) Put(_ context.Context, key string, data []byte) error

Put implements ObjectStore: write to a temp file, fsync, then publish with link(2) — which fails with EEXIST if the object already exists (create-only).

type GCSStore

type GCSStore struct {
	Bucket   string
	Prefix   string                 // optional key prefix inside the bucket
	Endpoint string                 // default https://storage.googleapis.com (injectable for tests)
	Client   *http.Client           // nil => 30s-timeout default
	Token    siemexport.TokenSource // nil => unauthenticated (test endpoints only)
}

GCSStore is the cloud/BYOC object store, speaking the raw GCS JSON API (no SDK — the kernel stays dependency-light, mirroring siemexport's GCSEvidenceSink). Auth is a pluggable siemexport.TokenSource: the metadata server on GCE/Cloud Run, or FileTokenSource for an externally-refreshed bearer token. Token acquisition failure is an explicit error, never an unauthenticated attempt (D8).

func NewGCSStore

func NewGCSStore(bucket, prefix string, token siemexport.TokenSource) (*GCSStore, error)

NewGCSStore validates the bucket and returns a GCSStore.

func (*GCSStore) Get

func (s *GCSStore) Get(ctx context.Context, key string) ([]byte, error)

Get implements ObjectStore via alt=media.

func (*GCSStore) List

func (s *GCSStore) List(ctx context.Context, prefix string) ([]string, error)

List implements ObjectStore via the paginated objects list API.

func (*GCSStore) Name

func (s *GCSStore) Name() string

Name implements ObjectStore.

func (*GCSStore) Put

func (s *GCSStore) Put(ctx context.Context, key string, data []byte) error

Put implements ObjectStore via uploadType=media with ifGenerationMatch=0 (create-only); a 412 maps to ErrObjectExists.

type GapAnnotation

type GapAnnotation struct {
	FromSeq int64  `json:"gap_from_seq"`
	ToSeq   int64  `json:"gap_to_seq"`
	Cause   string `json:"cause"`
}

GapAnnotation explains one legitimate hole in the journal seq density chain with a typed cause from the closed taxonomy (D5). Anything unexplained is tamper-suspect to the verifier.

type JournalExporter

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

JournalExporter owns the cluster-singleton journal lake export: it wins or renews the audit-lake ServiceLease, runs the second consumer group while leader, drives spool rotation, and executes the D10 takeover rule on a fresh acquisition. Non-holders run NOTHING for the journal table.

func NewJournalExporter

func NewJournalExporter(cfg JournalExporterConfig) (*JournalExporter, error)

NewJournalExporter wires the exporter. The sink's fence and lease-epoch hooks must already point at this exporter via the construction order in the daemon (see ExporterFence / LeaseEpoch).

func (*JournalExporter) Fence

func (e *JournalExporter) Fence() error

Fence is the D10 upload fence the sink's finalizer calls before every Put: it refuses when this exporter does not believe it holds a live lease.

func (*JournalExporter) LeaseEpoch

func (e *JournalExporter) LeaseEpoch() int64

LeaseEpoch exposes the current lease epoch for manifest provenance.

func (*JournalExporter) Run

func (e *JournalExporter) Run(ctx context.Context)

Run drives RunOnce on a ticker until ctx ends. Errors are logged and retried next tick (the uncommitted batch replays — at-least-once). On shutdown while holding the lease, the active segment is force-finalized (the D6 clean-shutdown rotation trigger).

func (*JournalExporter) RunOnce

func (e *JournalExporter) RunOnce(ctx context.Context) (bool, int, error)

RunOnce executes one exporter tick: elect/renew, takeover on a fresh acquisition, poll+export one bounded batch, then rotate if due. Returns (held, records exported, error). Callable directly by scenario tests.

type JournalExporterConfig

type JournalExporterConfig struct {
	Journal kernel.Journal
	Leases  *kernel.LeaseManager
	// NodeID is the lease holder identity and manifest writer_node.
	NodeID string
	Sink   *JournalSink
	// Exporter signs lake records (current-epoch key) and fans out to Sink.
	Exporter *siemexport.Exporter
	// Mode + KeyEpoch parameterize the record builder (D11/D12): every event
	// becomes a payload-classified row carrying the current signing epoch.
	Mode     PayloadMode
	KeyEpoch int64
	// LeaseTTL bounds how long a dead holder blocks takeover (default 30s).
	LeaseTTL time.Duration
	// Interval is the poll/rotate tick (default 2s).
	Interval time.Duration
	// BatchSize bounds one poll/export batch (default 256).
	BatchSize int
	// StallLagThreshold feeds the consumer's H3 typed stall condition.
	StallLagThreshold int64
	Logger            *slog.Logger
	Now               func() time.Time
}

JournalExporterConfig assembles the elected journal exporter (D3/D10).

type JournalSink

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

JournalSink is the journal table's lake sink (D2/D4): a siemexport.Sink whose Export appends the batch to the sequential write-ahead spool and returns after ONE batch-atomic fsync. A background-driven RotateIfDue finalizes sealed segments into hive-partitioned parts. It runs ONLY on the node holding the elected-exporter lease (D10).

func NewJournalSink

func NewJournalSink(cfg JournalSinkConfig) (*JournalSink, error)

NewJournalSink validates the configuration. NO recovery or upload happens here: the journal table is owned by the elected exporter, so spool recovery and watermark seeding run in Recover — called by the exporter at takeover, once the ServiceLease (and therefore the upload fence) is held (D10).

func (*JournalSink) Close

func (s *JournalSink) Close(ctx context.Context) error

Close force-finalizes the active segment (clean shutdown trigger, D6).

func (*JournalSink) ExpectFailoverGap

func (s *JournalSink) ExpectFailoverGap()

ExpectFailoverGap latches the next observed seq jump as an exporter-failover gap rather than a retention force-drop.

func (*JournalSink) Export

func (s *JournalSink) Export(_ context.Context, recs []siemexport.Record) error

Export implements siemexport.Sink: skip rows at or below the watermark (at-least-once replay), demand ascending seq order, latch explained gaps, then append the batch to the spool with one fsync. The consumer commits its cursor only after Export returns nil, so a spool-full / fsync error engages H3 backpressure upstream — never a drop.

func (*JournalSink) Name

func (s *JournalSink) Name() string

Name implements siemexport.Sink.

func (*JournalSink) Recover

func (s *JournalSink) Recover(ctx context.Context) error

Recover runs the D4/D10 recover-then-watermark sequence: seal any live spool (a re-election), re-finalize surviving segments (their uploads advance manifests — a recovered segment from an older key epoch finalizes under ITS epoch, the D12 forced rotation), seed the durable watermark from the newest manifest, then open a fresh spool. Idempotent; the caller must hold the exporter lease (the fence gates every upload).

func (*JournalSink) RotateIfDue

func (s *JournalSink) RotateIfDue(ctx context.Context, force bool) error

RotateIfDue seals + finalizes the active segment when a size/age trigger has fired (or force=true: lease handoff / clean shutdown). Driven from the exporter loop goroutine.

func (*JournalSink) SetFailoverGapHook

func (s *JournalSink) SetFailoverGapHook(fn func(from, to int64))

SetFailoverGapHook wires the takeover gap callbacks (D10).

func (*JournalSink) Watermark

func (s *JournalSink) Watermark() (int64, bool)

Watermark exposes the durable dedup boundary (for takeover + tests).

type JournalSinkConfig

type JournalSinkConfig struct {
	Store      ObjectStore
	SpoolDir   string
	Keys       *KeyEpochs
	Mode       PayloadMode
	WriterNode string
	// Fence is checked before every object-store Put (D10 lease fencing).
	Fence func() error
	// LeaseEpoch supplies the elected exporter's current lease epoch for
	// manifest provenance.
	LeaseEpoch func() int64
	// MaxSegmentBytes / FlushInterval / SpoolDiskCap override the rotation
	// triggers (zero selects the defaults: 64 MiB / 5m / 1 GiB).
	MaxSegmentBytes int64
	FlushInterval   time.Duration
	SpoolDiskCap    int64
	Now             func() time.Time
}

JournalSinkConfig assembles a JournalSink.

type KeyEpochs

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

KeyEpochs is the D12 key-rotation map: every signing epoch the deployment has ever used, plus the current epoch new partitions sign under. Manifests and records carry their epoch, the offline verifier resolves each epoch through this map, a missing epoch is "unverifiable", never "tampered".

Spec format (env/flag value): "1=<key>,2=<key>" — comma-separated epoch=key pairs; keys are raw strings and must not contain commas. The current epoch must be present in the map.

func ParseKeyEpochs

func ParseKeyEpochs(spec string, current int64) (*KeyEpochs, error)

ParseKeyEpochs parses the epoch map spec and pins the current epoch.

func SingleKeyEpochs

func SingleKeyEpochs(key []byte) (*KeyEpochs, error)

SingleKeyEpochs wraps one key as epoch 1 — the no-rotation default when only the audit root key is configured.

func (*KeyEpochs) CurrentEpoch

func (k *KeyEpochs) CurrentEpoch() int64

CurrentEpoch returns the epoch new segments sign under.

func (*KeyEpochs) CurrentKey

func (k *KeyEpochs) CurrentKey() []byte

CurrentKey returns the current epoch's signing key.

func (*KeyEpochs) Key

func (k *KeyEpochs) Key(epoch int64) ([]byte, bool)

Key resolves one epoch's key. false means the epoch is unknown to this deployment's map — the verifier's "unverifiable epoch" outcome.

type Manifest

type Manifest struct {
	SchemaVersion int    `json:"schema_version"`
	Table         string `json:"table"`
	PartPath      string `json:"part_path"`
	PartSHA256    string `json:"part_sha256"`
	PayloadMode   string `json:"payload_mode"`
	KeyEpoch      int64  `json:"key_epoch"`
	Rows          int    `json:"rows"`
	FirstSeq      int64  `json:"first_seq"`
	LastSeq       int64  `json:"last_seq"`
	// PrevPartLastSeq chains journal parts across the WHOLE table in seq
	// order regardless of dt partition (the seq space is cluster-global).
	// nil on the table's first part.
	PrevPartLastSeq *int64 `json:"prev_part_last_seq,omitempty"`
	// LeaseEpoch + WriterNode make journal exporter handoffs auditable (D10).
	LeaseEpoch int64  `json:"lease_epoch,omitempty"`
	WriterNode string `json:"writer_node,omitempty"`
	// Gaps are the explained journal density holes covered by this part's
	// boundary (typed causes only).
	Gaps []GapAnnotation `json:"gaps,omitempty"`
	// Chain is the audit_chain stitching block (nil for journal manifests).
	Chain     *ChainContinuity `json:"chain,omitempty"`
	CreatedAt time.Time        `json:"created_at"`
	// Sig is the HMAC-SHA256 over the canonical manifest under the KeyEpoch
	// key (sig field cleared for signing).
	Sig string `json:"sig,omitempty"`
}

Manifest is the signed sidecar index for ONE part file (D5). It is an accelerator, not the cryptographic root: per-record signatures (journal) and the entry hash chain (audit_chain) survive manifest loss.

func NewestManifest

func NewestManifest(ctx context.Context, store ObjectStore, table, nodeFilter string) (*Manifest, bool, error)

NewestManifest finds the table's durable watermark manifest: the manifest with the highest last_seq under _manifests/<table>/, optionally filtered to one node partition ("node=<id>/" for audit_chain — per-node chains). Returns (nil, false, nil) when the table has no manifests yet.

func (*Manifest) Sign

func (m *Manifest) Sign(key []byte)

Sign stamps the manifest under key. An empty key leaves it unsigned (the degraded-development posture, mirrored from the audit chain).

func (Manifest) VerifySig

func (m Manifest) VerifySig(key []byte) bool

VerifySig recomputes the manifest seal under key.

type ObjectStore

type ObjectStore interface {
	Name() string
	// Put writes a NEW object. An existing object returns ErrObjectExists —
	// the caller decides whether the collision is benign (replay) or fatal.
	Put(ctx context.Context, key string, data []byte) error
	// Get reads one object. A missing object returns fs.ErrNotExist.
	Get(ctx context.Context, key string) ([]byte, error)
	// List returns every object key under prefix, sorted ascending.
	List(ctx context.Context, prefix string) ([]string, error)
}

ObjectStore is the minimal pluggable object-store seam the lake writes through: create-only Put (the at-least-once idempotence primitive), Get and List (only ever used for manifests — the durable watermark recovery, D4). Implementations: DirStore (local dev / M5 laptop), GCSStore (cloud/BYOC).

type PayloadClass

type PayloadClass string

PayloadClass types what an event's payload may carry across the export boundary (D11). Full event COVERAGE (every journal offset becomes a row) is independent of payload CONTENT: the class decides the content.

const (
	// ClassDecision — full payload. Terminal acceptance decisions; these are
	// the records the SIEM exporter already ships today.
	ClassDecision PayloadClass = "decision"
	// ClassStructural — full payload. Lifecycle/scheduler/provider/cost/
	// capacity events whose payloads are structured metadata. This is what
	// makes lane-lifecycle, provider-timeline and cost analyses possible.
	ClassStructural PayloadClass = "structural"
	// ClassFreeText — payload passes a per-event-type allowlist redactor:
	// fields not on the keep-list are dropped and replaced by a "_redacted"
	// list with per-field {len, sha256} digests (forensic correlation without
	// content).
	ClassFreeText PayloadClass = "free_text"
	// ClassSensitive — every payload field is redacted to digests. Also the
	// fail-safe class for any event type the registry does not know.
	ClassSensitive PayloadClass = "sensitive"
)

The four payload classes.

func Classify

func Classify(t kernel.EventType) (PayloadClass, bool)

Classify returns the payload class for an event type and whether the type is registered. Unregistered types classify as ClassSensitive (fail-safe).

type PayloadMode

type PayloadMode string

PayloadMode selects the export posture. The mode is recorded in every manifest so an auditor knows what a partition can and cannot contain.

const (
	// ModeStructural is the default: free_text payloads are redacted through
	// the per-type allowlist, sensitive/unknown payloads fully redacted.
	ModeStructural PayloadMode = "structural"
	// ModeFull is an explicit opt-in that exports free_text payloads raw.
	// ClassSensitive (and unregistered event types) stay redacted even in
	// full mode — that is what distinguishes the class from free_text.
	ModeFull PayloadMode = "full"
)

The two payload modes.

func ParsePayloadMode

func ParsePayloadMode(s string) (PayloadMode, bool)

ParsePayloadMode validates a configured mode string. Empty selects the default (structural); anything else unknown is a hard config error upstream.

type RotationBoundary

type RotationBoundary struct {
	KeyEpoch int64  `json:"key_epoch"`
	Seq      int64  `json:"seq"`
	Hash     string `json:"hash"`
}

RotationBoundary marks a D12 chain key-epoch rotation checkpoint.

Jump to

Keyboard shortcuts

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