Skip to documentation
Documentation navigation

Documentation navigation

Documentation / guides

Overview

Append, replay, and validate the durable Session record.

developer

The pkg/journal package defines backend-neutral contracts. It does not open a ledger or know whether the backend is NATS or storage. pkg/sessionstore implements those contracts over storage.Composite.

Writer contract

The exact required writer interface is one method:

type SessionJournal interface {
	Append(context.Context, JournalRecord) (seq uint64, err error)
}

The implementation must serialize records, return strictly increasing ledger sequences, and refuse to write after its lease is lost. The storage implementation also satisfies the optional IdempotentJournal extension described in idempotency.

Reader contracts

Cold replay is split into events and all records:

type EventReplayer interface {
	Open(context.Context, ReplayRequest) (EventCursor, error)
}

type EventCursor interface {
	Next(context.Context) (event.Event, uint64, error)
	Close() error
}

type RecordReplayer interface {
	Open(context.Context, ReplayRequest) (RecordCursor, error)
}

type RecordCursor interface {
	Next(context.Context) (JournalRecord, uint64, error)
	Close() error
}

Next returns io.EOF only when a cold backlog is drained. A malformed frame, missing blob, or read failure is a typed error, never an implicit end of log. Follow: true fails with *journal.FollowUnsupportedError in the storage backend; it does not silently downgrade to a cold read.

Append path

%%{init: {"theme":"dark"}}%%
flowchart TD
    A[JournalRecord] --> V{ready and lease valid?}
    V -- no --> E[typed refusal]
    V -- yes --> F[marshal payload]
    F --> S{over threshold?}
    S -- no --> N[versioned envelope]
    S -- yes --> O[Put full envelope blob]
    O --> P[append blob pointer envelope]
    N --> C[CAS at tracked tip]
    P --> C
    C --> U[advance tip and idempotency index]

The blob is written before its pointer. The pointer carries the key, byte size, and SHA-256; replay verifies all three relationships before decoding.

Persistence errors

Errors carry stable typed context and preserve their leaf through Unwrap:

ErrorMeaning
*journal.JournalNotReadyErroropening fence has not committed
*journal.JournalLeaseLostErrorappend attempted after ownership loss
*journal.AppendErrordefinite CAS conflict or append failure
*journal.AmbiguousAckErrorappend outcome could not be resolved
*journal.MarshalRecordErrorpayload cannot be encoded
*journal.RecordTooLargeErroroffload failed for an over-threshold frame
*journal.IdempotencyCollisionErrorsame ID names different durable bytes
var lost *journal.JournalLeaseLostError
if errors.As(err, &lost) {
	// Stop this writer. A new owner must open a new journal and fence the tip.
}

Source and proof

← back to documentation