Documentation
¶
Overview ¶
Package stream defines the hash chain stream entity and store interface.
What is a Stream? ¶
A Stream is the head pointer of an independent SHA-256 hash chain. Every app+tenant combination has exactly one stream. All audit events recorded within that scope are appended to the same chain in monotonically increasing sequence order.
Stream fields:
- ID — TypeID with "stream_" prefix, globally unique
- AppID — the application this stream belongs to
- TenantID — the tenant within the application (empty for single-tenant apps)
- HeadHash — SHA-256 hash of the most recently appended event
- HeadSeq — sequence number of the most recently appended event
Hash Chain Mechanics ¶
When an event is appended, Chronicle:
- Reads HeadHash and HeadSeq from the stream.
- Sets the event's PrevHash = HeadHash and Sequence = HeadSeq + 1.
- Computes the event's Hash over: prevHash|timestamp|action|resource| category|resourceID|outcome|severity|metadata_json (sorted keys).
- Persists the event.
- Updates the stream: HeadHash = event.Hash, HeadSeq = event.Sequence.
For the very first event in a stream, PrevHash is an empty string. Any modification of a past event changes its hash, breaking every subsequent hash in the chain — making tampering cryptographically detectable.
Multi-Tenant Isolation ¶
Each app+tenant pair has its own independent chain. This means:
- Cross-tenant queries are structurally impossible (no shared sequence space).
- Compliance reports, verification, and retention are always scoped.
- The Store.GetStreamByScope method retrieves or creates the stream for a given (appID, tenantID) pair.
Import Cycle Note ¶
The root chronicle package cannot import stream (that would create a cycle). Instead, chronicle defines its own chronicle.StreamInfo type mirroring the fields that the record pipeline needs (ID, AppID, TenantID, HeadHash, HeadSeq). [store.NewAdapter] translates between Stream and chronicle.StreamInfo so both sides remain decoupled.
Store ¶
Store manages stream lifecycle:
- Store.CreateStream — initialise a new stream for an app+tenant scope
- Store.GetStream — retrieve by stream ID
- Store.GetStreamByScope — retrieve by (appID, tenantID)
- Store.ListStreams — paginated listing of all streams
- Store.UpdateStreamHead — atomically advance HeadHash and HeadSeq after append
- Store.UpdateStreamScheme — move the digest pin when a stronger scheme is configured
Package stream defines the hash chain stream entity and store interface.
Index ¶
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
This section is empty.
Types ¶
type Store ¶
type Store interface {
// CreateStream initializes a new hash chain stream.
CreateStream(ctx context.Context, s *Stream) error
// GetStream returns a stream by ID.
GetStream(ctx context.Context, streamID id.ID) (*Stream, error)
// GetStreamByScope returns the stream for a given app+tenant scope.
GetStreamByScope(ctx context.Context, appID, tenantID string) (*Stream, error)
// ListStreams returns all streams.
ListStreams(ctx context.Context, opts ListOpts) ([]*Stream, error)
// UpdateStreamHead updates the stream's head hash and sequence after append.
UpdateStreamHead(ctx context.Context, streamID id.ID, hash string, seq uint64) error
// UpdateStreamScheme moves the stream's digest pin to scheme, applying from
// sequence since.
//
// It exists so turning a stronger digest on actually takes effect on streams
// that already exist. Without it the pin written at creation is the only pin
// the stream ever has, and a chain that is now being written under a keyed
// scheme keeps advertising the weaker one, which is exactly the claim a
// downgrade check needs to be true. Callers must only ever move the pin
// upward; see chronicle.Chronicle's resolveStream.
UpdateStreamScheme(ctx context.Context, streamID id.ID, scheme string, since uint64) error
}
Store manages hash chain streams.
type Stream ¶
type Stream struct {
chronicle.Entity
ID id.ID `json:"id"`
AppID string `json:"app_id"`
TenantID string `json:"tenant_id"`
HeadHash string `json:"head_hash"`
HeadSeq uint64 `json:"head_seq"`
// Scheme is the digest scheme this stream is pinned to, and SchemeSince the
// first sequence it applies from. Events below SchemeSince predate the pin.
Scheme string `json:"scheme"`
SchemeSince uint64 `json:"scheme_since"`
}
Stream represents a hash chain stream, scoped to an app+tenant combination. Each stream maintains its own independent chain.