Module store

Module store 

Source
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§

AuditRow
One audit log row (SCALE-05, #184). before_state and after_state are optional JSON strings (None for create/delete).
ConsumerRecord
One consumer row.
CredentialRecord
One credential row (already hashed; never contains a plaintext secret).
EventWalRow
One event WAL row (REL-14, #249). Plain data — the events module owns the domain types; the store stays backend-neutral. The payload is the JSON-serialized EventPayload.
LeaderRow
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.
PrincipalRow
One principal row (SCALE-05, #184). roles_json is a JSON array of role-name strings.
RoleRow
One RBAC role row (SCALE-05, #184). permissions_json is a JSON array of {"action":"read|write|admin","workspace":"<name or *>"}.
StateStore
SQLite state store with an in-memory hot cache.
WorkspaceRow
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§

CredentialKind
Kind of credential record.
StoreError
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).