Expand description
SQLite-backed state store with an in-memory hot cache (DW-018).
§Purpose
Durable state for the gateway’s control identity layer: consumers, credential records (pre-hashed secrets), and quota counters. The store is the OSS state layer (single-process SQLite); multi-instance deployments would swap the backend behind the same API surface — the schema and method semantics here are deliberately backend-neutral.
§Threading model
rusqlite is synchronous. StateStore owns ONE writer connection
behind a std::sync::Mutex; every method is a short, non-blocking
critical section (SQLite ops here are all primary-key/index lookups or
small transactions). Callers on an async runtime should wrap
cold-path operations (seeding, quota writes, revocation) in
tokio::task::spawn_blocking; the HOT path —
StateStore::lookup_credential — takes only an RwLock read on the
in-memory cache and touches no SQLite after warmup, so it is safe to
call inline on any thread.
§Cache coherence
The in-memory cache is coherent for writes made THROUGH the same
StateStore handle (writes invalidate the affected cache entries).
A separate process, or a second handle to the same file, will NOT
invalidate this cache: single-process coherence only. Multi-instance
deployments need an external shared cache/invalidation layer — that is
an edition boundary concern, not a schema concern.
§Schema and migrations
The schema is created and evolved by the versioned, transactional
migration set in crate::state::migrations (DW-115): opening a store
applies all pending migrations automatically, taking a file backup
first (see StateStore::open). A database whose user_version is
NEWER than this build supports is refused. All writes are
transactional; WAL journal mode and foreign-key enforcement are on.
The store NEVER sees plaintext secrets: credential values arrive
pre-hashed with a lookup selector (a hash for API keys/Basic since
DW-019, a JWT kid/issuer, or an mTLS subject/fingerprint match value
for bindings). Hashing is the authenticator’s job (DW-019); the
stored API-key/Basic hash format is owned by
config::credentials — peppered
hmac-sha256:<hex> when the deployment configures a pepper (#124,
resolved above this layer), legacy sha256:<hex> otherwise, and
legacy rows upgrade in place on successful verification (see
StateStore::rehash_credential). Config-seeded keys are hashed at
seed time (see sync_consumers_from_config).
Structs§
- Audit
Row - One audit log row (SCALE-05, #184).
before_stateandafter_stateare optional JSON strings (None for create/delete). - Consumer
Record - One consumer row.
- Credential
Record - One credential row (already hashed; never contains a plaintext secret).
- Event
WalRow - One event WAL row (REL-14, #249). Plain data — the events module
owns the domain types; the store stays backend-neutral. The
payloadis the JSON-serializedEventPayload. - Leader
Row - One leader row (SCALE-06, #185). The current leader’s instance ID, epoch, and lease expiry. Plain data — the leader election module owns the domain types; the store stays backend-neutral.
- Principal
Row - One principal row (SCALE-05, #184).
roles_jsonis a JSON array of role-name strings. - RoleRow
- One RBAC role row (SCALE-05, #184).
permissions_jsonis a JSON array of{"action":"read|write|admin","workspace":"<name or *>"}. - State
Store - SQLite state store with an in-memory hot cache.
- Workspace
Row - One workspace row (SCALE-05, #184). Plain data — the workspace
domain types live in [
crate::workspace]; the store serializes at the SQL boundary so it never imports upward.
Enums§
- Credential
Kind - Kind of credential record.
- Store
Error - Typed store error.
Functions§
- sync_
consumers_ from_ config - Bootstrap the store from the gateway config (DW-018 seeding, hashing per DW-019, peppered per #124).