Documentation
¶
Overview ¶
Package ledger is the durable half of the session runtime: the record of which runs were admitted, who owns them, and how they ended.
It deliberately holds no liveness. The owner lease lives only in the live backend, so PostgreSQL receives lifecycle writes only — admission, ownership/fencing change, decision, terminal proposal, and terminal transition. Nothing here is a keepalive or a progress sample, which is why write volume is proportional to the number of runs rather than to their duration or token rate. A reader therefore cannot ask this store "is the owner still alive"; only the live backend can answer that, and the reaper is built around that fact.
Index ¶
- Constants
- Variables
- func HasResumeContext(input []byte) bool
- type AdmitParams
- type ClaimParams
- type Cursor
- type FinalizeParams
- type OrphanQuery
- type OrphanResetStore
- type PostgresStore
- func (s *PostgresStore) AcquireReset(ctx context.Context, lease ResetLease, ttl time.Duration) (ResetLease, bool, error)
- func (s *PostgresStore) ActiveRun(ctx context.Context, sessionID string) (Run, error)
- func (s *PostgresStore) ActiveRunsByBot(ctx context.Context, botID string) ([]Run, error)
- func (s *PostgresStore) Admit(ctx context.Context, params AdmitParams) (Run, bool, error)
- func (s *PostgresStore) Claim(ctx context.Context, params ClaimParams) (Run, bool, error)
- func (s *PostgresStore) EffectiveReset(ctx context.Context, botID, sessionID string) (ResetLease, bool, error)
- func (s *PostgresStore) FenceAndFinalizeOrphan(ctx context.Context, reset ResetLease, run Run) (Run, bool, error)
- func (s *PostgresStore) Finalize(ctx context.Context, params FinalizeParams) (Run, bool, error)
- func (s *PostgresStore) Get(ctx context.Context, runID string) (Run, error)
- func (s *PostgresStore) GetByInvocation(ctx context.Context, sessionID, invocationID string) (Run, error)
- func (s *PostgresStore) LatestRun(ctx context.Context, sessionID string) (Run, error)
- func (s *PostgresStore) NextFencingToken(ctx context.Context) (int64, error)
- func (s *PostgresStore) OrphanedRuns(ctx context.Context, query OrphanQuery) ([]Run, error)
- func (s *PostgresStore) PrepareFinish(ctx context.Context, params PrepareFinishParams) (Run, bool, error)
- func (s *PostgresStore) ReleaseReset(ctx context.Context, lease ResetLease) (bool, error)
- func (s *PostgresStore) RenewReset(ctx context.Context, lease ResetLease, ttl time.Duration) (ResetLease, bool, error)
- func (s *PostgresStore) RequestAbort(ctx context.Context, runID string) (Run, bool, error)
- func (s *PostgresStore) Resume(ctx context.Context, runID string, fencingToken int64) (Run, bool, error)
- func (s *PostgresStore) SessionIDsByBot(ctx context.Context, botID string) ([]string, error)
- func (s *PostgresStore) SetWaitingDecision(ctx context.Context, runID string, fencingToken int64) (Run, bool, error)
- func (s *PostgresStore) StaleGenerationRuns(ctx context.Context, query StaleGenerationQuery) ([]Run, error)
- type PrepareFinishParams
- type ResetLease
- type ResetStore
- type Run
- type StaleGenerationQuery
- type State
- type Store
Constants ¶
const ( ResetScopeSession = "session" ResetScopeBot = "bot" )
Variables ¶
var ( // ErrRunNotFound is returned when no row matches the requested identity. ErrRunNotFound = errors.New("ledger: session run not found") // ErrResumeSuperseded means the interrupted intent no longer owns the latest turn. ErrResumeSuperseded = errors.New("ledger: interrupted run has been superseded") // ErrSessionNotFound is returned by Admit when the target session does not // exist or was deleted, which is distinguishable from a duplicate // invocation because a duplicate still resolves to a row. ErrSessionNotFound = errors.New("ledger: session not found") // ErrSessionBusy means the session already has an active run and this is a // different invocation. SR-OWN-001 allows answering busy instead of // queueing, and this design takes that option: the caller owns the retry, // which keeps a queue state, a queue index, queue promotion and queued // aborts out of the runtime entirely. // // It is retryable by construction — the same invocation_id may be submitted // again once the session frees up and will then be admitted normally, // because nothing was persisted for the rejected attempt. ErrSessionBusy = errors.New("ledger: session already has an active run") // ErrHistoryResetInProgress means admission reached the durable reset fence. // Nothing was persisted and the same invocation may be retried after the // lease expires or its owner releases it. ErrHistoryResetInProgress = errors.New("ledger: session history reset is in progress") // ErrResetScopeNotFound means the bot or session named by a reset lease no // longer exists (deleted or soft-deleted). Acquire loops must fail fast on // it instead of retrying: the scope can never become acquirable again. ErrResetScopeNotFound = errors.New("ledger: reset scope no longer exists") )
Functions ¶
func HasResumeContext ¶ added in v0.21.0
HasResumeContext reports whether a run's admission input still carries the resume intent saved at turn start. The recovery worker retires the intent once it can no longer continue, which also removes the run from the pending-resume index.
Types ¶
type AdmitParams ¶
type AdmitParams struct {
// ResumeRunID is checked inside the admission transaction, under the parent lock.
ResumeRunID string
RunID string
BotID string
SessionID string
InvocationID string
TurnID string
Input []byte
InputFingerprint string
}
AdmitParams is one durable admission. RunID and TurnID are minted by the caller so the identity it hands back to a client is the identity that was committed.
type ClaimParams ¶
type ClaimParams struct {
RunID string
// OwnerID is empty for a single-instance memory backend, where there is
// only ever one owner and no cross-process claim to arbitrate.
OwnerID string
FencingToken int64
LiveGeneration string
}
ClaimParams moves ownership of an accepted run to one owner.
type Cursor ¶
Cursor is a keyset position in the stale-generation sweep. The zero Cursor starts a sweep from the beginning.
type FinalizeParams ¶
type FinalizeParams struct {
RunID string
FencingToken int64
State State
ErrorCode string
// ExpectedState is optional. Shutdown uses it to avoid terminalizing a
// decision that parked after its classification read.
ExpectedState State
}
FinalizeParams is the fenced terminal write, used by the owner for completed/aborted/failed and by the reaper for lost.
type OrphanQuery ¶
OrphanQuery finds admissions that committed but were never claimed.
type OrphanResetStore ¶
type OrphanResetStore interface {
FenceAndFinalizeOrphan(ctx context.Context, reset ResetLease, run Run) (Run, bool, error)
}
OrphanResetStore atomically invalidates a disappeared owner's persistence token and terminalizes its active run while the same reset lease is still valid. PostgreSQL implements this as one parent-locked transaction.
type PostgresStore ¶
type PostgresStore struct {
// contains filtered or unexported fields
}
PostgresStore implements Store over the session_runs table.
func (*PostgresStore) AcquireReset ¶
func (s *PostgresStore) AcquireReset(ctx context.Context, lease ResetLease, ttl time.Duration) (ResetLease, bool, error)
func (*PostgresStore) ActiveRunsByBot ¶
func (*PostgresStore) Admit ¶
func (s *PostgresStore) Admit(ctx context.Context, params AdmitParams) (Run, bool, error)
func (*PostgresStore) Claim ¶
func (s *PostgresStore) Claim(ctx context.Context, params ClaimParams) (Run, bool, error)
func (*PostgresStore) EffectiveReset ¶
func (s *PostgresStore) EffectiveReset(ctx context.Context, botID, sessionID string) (ResetLease, bool, error)
func (*PostgresStore) FenceAndFinalizeOrphan ¶
func (s *PostgresStore) FenceAndFinalizeOrphan(ctx context.Context, reset ResetLease, run Run) (Run, bool, error)
func (*PostgresStore) Finalize ¶
func (s *PostgresStore) Finalize(ctx context.Context, params FinalizeParams) (Run, bool, error)
func (*PostgresStore) GetByInvocation ¶
func (*PostgresStore) NextFencingToken ¶
func (s *PostgresStore) NextFencingToken(ctx context.Context) (int64, error)
func (*PostgresStore) OrphanedRuns ¶
func (s *PostgresStore) OrphanedRuns(ctx context.Context, query OrphanQuery) ([]Run, error)
func (*PostgresStore) PrepareFinish ¶ added in v0.20.0
func (s *PostgresStore) PrepareFinish(ctx context.Context, params PrepareFinishParams) (Run, bool, error)
func (*PostgresStore) ReleaseReset ¶
func (s *PostgresStore) ReleaseReset(ctx context.Context, lease ResetLease) (bool, error)
func (*PostgresStore) RenewReset ¶
func (s *PostgresStore) RenewReset(ctx context.Context, lease ResetLease, ttl time.Duration) (ResetLease, bool, error)
func (*PostgresStore) RequestAbort ¶
func (*PostgresStore) SessionIDsByBot ¶ added in v0.22.0
func (*PostgresStore) SetWaitingDecision ¶
func (*PostgresStore) StaleGenerationRuns ¶
func (s *PostgresStore) StaleGenerationRuns(ctx context.Context, query StaleGenerationQuery) ([]Run, error)
type PrepareFinishParams ¶ added in v0.20.0
type PrepareFinishParams struct {
RunID string
FencingToken int64
State State
ErrorCode string
AllowWaitingDecision bool
}
PrepareFinishParams records the fenced, recoverable terminal proposal. A proposal ordinarily starts from running. AllowWaitingDecision is reserved for an explicit abort/failure that must terminalize a parked execution; ordinary terminal stream events must leave waiting_decision parked.
type ResetLease ¶
type ResetLease struct {
Scope string
BotID string
SessionID string
Token string
ExpiresAt time.Time
}
ResetLease is the PostgreSQL half of one crash-recoverable history reset. Token-scoped renewal/release prevents an expired owner from disturbing a successor that acquired the same scope.
func (ResetLease) Valid ¶
func (l ResetLease) Valid() bool
type ResetStore ¶
type ResetStore interface {
AcquireReset(ctx context.Context, lease ResetLease, ttl time.Duration) (ResetLease, bool, error)
RenewReset(ctx context.Context, lease ResetLease, ttl time.Duration) (ResetLease, bool, error)
ReleaseReset(ctx context.Context, lease ResetLease) (bool, error)
EffectiveReset(ctx context.Context, botID, sessionID string) (ResetLease, bool, error)
ActiveRunsByBot(ctx context.Context, botID string) ([]Run, error)
// SessionIDsByBot lists every session a bot-wide reset covers, so the
// live projections of all of them can be invalidated with the history.
SessionIDsByBot(ctx context.Context, botID string) ([]string, error)
}
ResetStore is the durable admission/reset arbiter implemented by PostgreSQL. It stays separate from Store so focused runtime tests and non-admission embedders do not need to fake lifecycle mutation they never exercise.
type Run ¶
type Run struct {
RunID string
BotID string
SessionID string
InvocationID string
// TurnID and TurnPosition are allocated at admission so that a terminal
// write targets a pre-decided turn. Combined with the existing unique index
// on (turn_id, turn_message_seq), that makes terminal replay idempotent
// without any new machinery.
TurnID string
TurnPosition int64
State State
Input []byte
InputFingerprint string
OwnerID string
FencingToken int64
OwnerSince time.Time
// LiveGeneration is the live backend incarnation that claimed this run. It
// is stamped once and never updated, which is what makes it a stable keyset
// cursor for the fail-closed recovery sweep.
LiveGeneration string
AbortRequestedAt time.Time
ProposedState State
ProposedErrorCode string
// ProposedErrorMessage and ErrorMessage are read from rows written before
// the ledger stopped recording error text. Nothing writes them: a run's
// failure is its code, and the cause belongs to the run's result record.
ProposedErrorMessage string
FinishProposedAt time.Time
ErrorCode string
ErrorMessage string
CreatedAt time.Time
UpdatedAt time.Time
}
Run is one row of the durable ledger. Zero values mean "not set" rather than "empty": OwnerID is empty until the run is claimed, and AbortRequestedAt is the zero time until an abort is requested.
type StaleGenerationQuery ¶
StaleGenerationQuery pages through runs whose claiming backend incarnation is no longer current.
type State ¶
type State string
State is a durable run state. The set matches section 3 of docs/design/session-runtime-requirements.md and the session_runs CHECK constraint; do not add a value to one without the other.
const ( // StateAccepted means the input and admission result are persisted, so the // caller can stop retrying, but no owner has claimed the run yet. StateAccepted State = "accepted" // StateRunning means a valid owner is executing. StateRunning State = "running" // StateWaitingDecision means execution is parked on a decision_id. StateWaitingDecision State = "waiting_decision" // StateFinishing means the final output is durable and a fenced terminal // outcome has been proposed, but the terminal row/live projection handshake // has not completed yet. It remains active so admission and lease renewal // cannot pass it. StateFinishing State = "finishing" StateCompleted State = "completed" StateAborted State = "aborted" StateFailed State = "failed" // StateLost means the owner disappeared and the accepted input was not // lost, but execution did not finish. It is not a success terminal. StateLost State = "lost" )
type Store ¶
type Store interface {
// Admit persists one admission. created is false when this invocation was
// already admitted, in which case the original row is returned unchanged
// and no turn position is consumed. Callers compare InputFingerprint to
// decide between "same submit, same answer" and a conflict.
//
// It returns ErrSessionBusy when the session already has a different active
// run. Nothing is persisted in that case, so the same invocation may be
// submitted again later. An implementation is expected to reject busy
// cheaply — contention is ordinary traffic here, not an exceptional event.
Admit(ctx context.Context, params AdmitParams) (run Run, created bool, err error)
Get(ctx context.Context, runID string) (Run, error)
GetByInvocation(ctx context.Context, sessionID, invocationID string) (Run, error)
// ActiveRun returns the session's single active run, or ErrRunNotFound.
ActiveRun(ctx context.Context, sessionID string) (Run, error)
// LatestRun backs the snapshot fallback when live state is gone: it answers
// what PostgreSQL can prove about the most recent run and nothing more.
LatestRun(ctx context.Context, sessionID string) (Run, error)
// NextFencingToken draws from the monotonic sequence shared with the
// PostgreSQL persistence fence, so one token orders both.
NextFencingToken(ctx context.Context) (int64, error)
Claim(ctx context.Context, params ClaimParams) (run Run, applied bool, err error)
SetWaitingDecision(ctx context.Context, runID string, fencingToken int64) (run Run, applied bool, err error)
Resume(ctx context.Context, runID string, fencingToken int64) (run Run, applied bool, err error)
PrepareFinish(ctx context.Context, params PrepareFinishParams) (run Run, applied bool, err error)
Finalize(ctx context.Context, params FinalizeParams) (run Run, applied bool, err error)
// RequestAbort records the intent, which is not fenced: an abort may arrive
// at any instance and the owner applies it.
RequestAbort(ctx context.Context, runID string) (run Run, applied bool, err error)
StaleGenerationRuns(ctx context.Context, query StaleGenerationQuery) ([]Run, error)
OrphanedRuns(ctx context.Context, query OrphanQuery) ([]Run, error)
}
Store is the durable session run ledger.
Every mutation after admission is a fenced idempotent statement: it applies only when the caller's fencing token still matches the row, and it reports applied=false rather than an error when it does not. False means "already applied, or superseded by a newer owner" — both of which are ordinary outcomes for a retrying owner or a reaper that failed over mid-transition.