Documentation
¶
Overview ¶
Package state owns Harbor's persistence floor: the single mandatory `StateStore` interface that every persistence-shaped subsystem (sessions, tasks, governance accumulators, planner checkpoints, memory snapshots, steering events) saves through.
The surface is generic by design (D-027): identity-scoped CRUD keyed on `(identity.Quadruple, Kind string, Bytes []byte)` with idempotency on a caller-supplied `EventID` (ULID), plus ONE explicitly-elevated maintenance scan (`ListKind` — RFC §6.11, D-207). Consuming subsystems land their typed wrappers at their own layer atop this interface — a `SessionRegistry.Save(s Session)` reduces to `StateStore.Save(StateRecord{Identity: s.Identity, Kind: "session.lifecycle", Bytes: marshal(s)})`.
Three V1 drivers ship to the §9 persistence triad (in-memory, SQLite, Postgres). Phase 07 ships only the in-memory reference; SQLite (Phase 15) and Postgres (Phase 16) inherit the conformancetest suite verbatim.
Identity is mandatory at the API boundary. Any `Quadruple` whose tenant / user / session is empty is rejected with `ErrIdentityRequired`. Empty `RunID` is acceptable for state that is session-scoped rather than run-scoped.
Audit redaction is upstream of `Save`. The store stores opaque `Bytes`; mixing redaction into the persistence layer would couple a leaf package to the audit subsystem and split responsibility (D-020).
Index ¶
- Constants
- Variables
- func Register(name string, factory Factory)
- func RegisteredDrivers() []string
- func ValidateIdentity(q identity.Quadruple) error
- func ValidateListKind(scope ListScope, kindPrefix string) error
- func ValidateRecord(r StateRecord) error
- func WithStore(ctx context.Context, store StateStore) context.Context
- type EventID
- type Factory
- type ListScope
- type StateRecord
- type StateStore
Constants ¶
const DefaultDriver = "inmem"
DefaultDriver is the Phase 07 production driver name. Phase 15 (SQLite) and Phase 16 (Postgres) register additional names; Open switches on cfg.Driver.
Variables ¶
var ( // ErrNotFound — Load / LoadByEventID was called for a key that // has no record. Wraps drivers' own not-found shapes. ErrNotFound = errors.New("state: record not found") // ErrIdempotencyConflict — Save with a previously-seen EventID // but different Bytes (or routed to a different key). Tells the // caller a retry policy bug exists upstream. ErrIdempotencyConflict = errors.New("state: idempotency conflict") // ErrIdentityRequired — Save / Load / Delete called with a // Quadruple missing one of (tenant, user, session). Empty RunID // is allowed for session-scoped state. ErrIdentityRequired = errors.New("state: identity triple incomplete") // ErrStoreClosed — Save / Load / Delete called after Close. ErrStoreClosed = errors.New("state: store is closed") // ErrInvalidRecord — record fails structural validation // (empty Kind, empty EventID). ErrInvalidRecord = errors.New("state: invalid record") // ErrUnknownDriver — Open was asked for a driver name no // registered factory handles. ErrUnknownDriver = errors.New("state: unknown driver") // ErrMaintenanceScopeRequired — ListKind was called without the // explicit ListScope.MaintenanceScoped claim. The cross-identity // scan fails closed (CLAUDE.md §6). ErrMaintenanceScopeRequired = errors.New("state: ListKind requires an explicit maintenance scope claim") )
Sentinel errors. Callers compare via errors.Is.
Functions ¶
func Register ¶
Register installs a driver factory under name. Drivers self-register from their package init(); cmd/harbor blank-imports the production driver to trigger registration. Per AGENTS.md §4.4.
Re-registering the same name panics — the registration model is write-once-at-init and a duplicate signals a build mis-configuration.
func RegisteredDrivers ¶
func RegisteredDrivers() []string
RegisteredDrivers returns a sorted list of driver names. Useful for boot-log output and for surfacing in error messages.
func ValidateIdentity ¶
ValidateIdentity checks that the triple is fully specified. Empty RunID is accepted (session-scoped state). Returns wrapped ErrIdentityRequired when any of tenant/user/session is empty.
func ValidateListKind ¶ added in v1.3.0
ValidateListKind checks ListKind's fail-closed preconditions shared by every driver: the explicit maintenance scope claim must be set (ErrMaintenanceScopeRequired) and the kind prefix must be non-empty (ErrInvalidRecord — a whole-store dump is never a valid maintenance scan).
func ValidateRecord ¶
func ValidateRecord(r StateRecord) error
ValidateRecord checks structural invariants Save needs before touching driver storage: identity triple present, EventID non-empty, Kind non-empty.
Types ¶
type EventID ¶
type EventID string
EventID is a caller-supplied ULID used as the canonical idempotency key for `Save`. ULID gives us monotonic, lexicographically sortable IDs that work as both primary keys and secondary indices.
Callers are free to construct the value externally; `NewEventID` is provided as a convenience that uses crypto-strong entropy.
func NewEventID ¶
func NewEventID() EventID
NewEventID generates a fresh ULID-shaped EventID using crypto/rand. Implementations may use any ULID source; this helper exists so callers don't need a separate dependency just for generating idempotency keys.
type Factory ¶
type Factory func(config.StateConfig) (StateStore, error)
Factory builds a StateStore from a StateConfig. Drivers expose one Factory each via init() → Register.
type ListScope ¶ added in v1.3.0
type ListScope struct {
// MaintenanceScoped asserts the caller is a runtime maintenance
// loop acting on each returned record under that record's own
// identity. False (the zero value) is rejected with
// ErrMaintenanceScopeRequired.
MaintenanceScoped bool
}
ListScope is the explicit scope claim ListKind requires. The zero value fails closed: a caller must set MaintenanceScoped to assert it understands the call crosses identity boundaries (CLAUDE.md §6 rule 5; the §13 elevated-scope-claim rule applied to the persistence floor).
type StateRecord ¶
type StateRecord struct {
ID EventID
Identity identity.Quadruple
Kind string
Version int
Bytes []byte
UpdatedAt time.Time
}
StateRecord is the unit of persistence.
`Bytes` is opaque to the store — callers serialize their domain types and run them through audit redaction upstream of `Save`. The store does not interpret payloads or re-redact.
`Kind` is a free-form caller-namespaced key (e.g. "session.lifecycle", "task.checkpoint", "governance.cost"). Two records with the same (Quadruple, Kind) are treated as a single keyed slot — `Save` overwrites; `Load` returns the latest.
`Version` is a hint for optimistic-concurrency at the typed-wrapper layer (e.g. `SessionRegistry` MAY refuse to apply an update whose Version is stale). The StateStore itself does NOT enforce CAS — it stores and returns the int.
`UpdatedAt` is set by the store at `Save` time when zero; callers MAY override (useful for tests with controllable clocks).
type StateStore ¶
type StateStore interface {
// Save persists a record. Idempotent on `EventID`:
//
// - Same EventID + byte-equal Bytes: no-op (no error, no
// duplicate write).
// - Same EventID + different Bytes: ErrIdempotencyConflict.
//
// If a record already exists at (Identity, Kind) but with a
// different EventID, Save overwrites it (the new EventID becomes
// the active one for that slot; the previous EventID is no
// longer LoadByEventID-resolvable).
Save(ctx context.Context, r StateRecord) error
// Load returns the record at (id, kind). Returns ErrNotFound
// (wrapped) when no record exists for that key.
Load(ctx context.Context, id identity.Quadruple, kind string) (StateRecord, error)
// LoadByEventID returns the record whose ID matches eventID.
// Useful for replaying a specific event by its idempotency key.
// Returns ErrNotFound (wrapped) when not present.
LoadByEventID(ctx context.Context, eventID EventID) (StateRecord, error)
// Delete removes the record at (id, kind). Returns nil when the
// record is absent (idempotent), wrapped error on store failure.
Delete(ctx context.Context, id identity.Quadruple, kind string) error
// ListKind enumerates every record whose Kind starts with
// kindPrefix — the store's ONE maintenance-scan surface
// (RFC §6.11, D-207). Unlike every other method, the scan crosses
// identity boundaries: it exists so runtime maintenance loops (the
// pause sweeper's crash-orphan rescan is the first consumer) can
// find records whose identities the process has never seen. That
// elevation is explicit and fail-closed:
//
// - scope.MaintenanceScoped MUST be true, or the call fails with
// ErrMaintenanceScopeRequired (CLAUDE.md §6 rule 5 / §13 "no
// cross-session queries without an explicit elevated scope
// claim"). There is no identity-scoped mode — identity-scoped
// reads stay on Load / LoadByEventID.
// - kindPrefix MUST be non-empty (ErrInvalidRecord) — a
// whole-store dump is never a valid maintenance scan.
// - Callers MUST act on each returned record under that record's
// OWN identity (every record carries its Quadruple); ListKind
// grants visibility for the scan, never a widened mutation
// scope.
//
// kindPrefix matches literally (no wildcard interpretation — SQL
// drivers escape LIKE metacharacters). Result order is
// unspecified; an empty result is ([]StateRecord{} or nil, nil),
// never an error.
ListKind(ctx context.Context, scope ListScope, kindPrefix string) ([]StateRecord, error)
// Close releases driver resources. Subsequent calls return
// ErrStoreClosed (wrapped). Implementations MUST honour ctx
// during long teardowns.
Close(ctx context.Context) error
}
StateStore is Harbor's persistence interface — single mandatory surface, no `Supports*` capability ceremony (AGENTS.md §4.4 + §9).
Implementations MUST be safe for concurrent use by N goroutines against a single shared instance (D-025). Mutable state must be guarded; per-call state lives in `ctx`, never on the driver.
func From ¶
func From(ctx context.Context) (StateStore, bool)
From returns the StateStore in ctx and a presence bool. Use when absence is recoverable.
func MustFrom ¶
func MustFrom(ctx context.Context) StateStore
MustFrom returns the StateStore in ctx; panics with ErrStoreClosed (used as the sentinel for "no store configured") when none is present. Use in handler/runtime paths where a store is mandatory.
func Open ¶
func Open(_ context.Context, cfg config.StateConfig) (StateStore, error)
Open returns the StateStore built by the factory whose name matches cfg.Driver (defaults to DefaultDriver when cfg.Driver is empty).
func OpenDriver ¶
func OpenDriver(name string, cfg config.StateConfig) (StateStore, error)
OpenDriver opens a specific driver by name; useful for tests that want to exercise the registry against a non-default driver.
Directories
¶
| Path | Synopsis |
|---|---|
|
Package conformancetest exposes the canonical correctness suite every state.StateStore driver must pass.
|
Package conformancetest exposes the canonical correctness suite every state.StateStore driver must pass. |
|
drivers
|
|
|
inmem
Package inmem is Harbor's V1 in-memory StateStore driver.
|
Package inmem is Harbor's V1 in-memory StateStore driver. |
|
postgres
Package postgres is Harbor's V1 Postgres-backed StateStore driver.
|
Package postgres is Harbor's V1 Postgres-backed StateStore driver. |
|
sqlite
Package sqlite is Harbor's SQLite-backed `state.StateStore` driver (Phase 15).
|
Package sqlite is Harbor's SQLite-backed `state.StateStore` driver (Phase 15). |