docs(arch): ADR-035 — concrete persistence adapter shapes, resolve OQ-36
Commits the concrete adapter shape deferred by ADR-033: read-sync / write-async split with honker NOTIFY/LISTEN for no-restart cache invalidation, against SQLite, in a separate alknet-store-sqlite crate. Two constraints drive the design: (1) the hot-path read trait (IdentityProvider::resolve_from_fingerprint, CredentialStore::get) is sync — called in the accept loop, no .await — so a SQLite-backed adapter must cache in memory and serve sync reads from the cache; (2) auth changes must take effect without a restart (an early issue the project already fixed for ConfigIdentityProvider via ArcSwap config reload). honker's SQLite NOTIFY/LISTEN (single-digit-ms wake, no polling) is the cache-invalidation mechanism that makes both hold: write commits to SQLite + emits NOTIFY, the running process's LISTEN wakes, the in-memory index reloads and atomically swaps, the next read sees the new state. Same ArcSwap-reload pattern as config, generalized from 'config file is source of truth' to 'SQLite is source of truth, honker signals when it changed.' New async IdentityStore write trait (put_peer / update_peer / remove_peer) extends the sync IdentityProvider read trait for peer mutations. ConfigIdentityProvider does NOT implement it (config reload is its write path — a posture enforced by the absence of a backend, not a type-system constraint); SqliteIdentityProvider implements both. CredentialStore::put/delete refined to async (within ADR-031's one-way door — the contract was get/put/delete keyed by provider persisting EncryptedData never decrypting; sync-vs-async was unspecified). CredentialStoreError renamed to shared StoreError covering both traits. alknet-store-sqlite is one crate implementing both IdentityStore and CredentialStore with shared SQLite connection + honker LISTEN infra (splitting later is a two-way door). Schema shape committed (one row per PeerEntry with JSON columns for fingerprints/scopes/resources; one row per EncryptedData blob keyed by provider); exact DDL is an implementation-detail two-way door in the adapter crate. The keypal adapter-factory pattern is intentionally not ported to Rust (runtime column-mapping is a TS affordance; in Rust each adapter is a concrete type, cross-cutting concerns are a shared helper module). Amends ADR-031 (put/delete async refinement, StoreError rename), ADR-033 (concrete adapter shape now specified, two-crate framing collapsed to one), ADR-034 (OQ-36 now resolved), auth.md (IdentityStore section, cache-invalidation summary, OQ-36 reference), config.md (two write paths note), and the OQ-36/OQ-34 entries in open-questions.md. Review fixed 4 criticals (error-type name divergence, duplicate IdentityProvider sketch, upsert/Duplicate ambiguity, 'shape unchanged' contradiction), 7 warnings, 5 suggestions.
This commit is contained in:
@@ -3,7 +3,10 @@
|
||||
## Status
|
||||
|
||||
Accepted (resolves the storage-boundary dimension of OQ-34; establishes the
|
||||
pattern that ADR-030 and ADR-031 follow)
|
||||
pattern that ADR-030 and ADR-031 follow). **The concrete adapter shapes
|
||||
deferred by §"What this does NOT do" are now committed by
|
||||
[ADR-035](035-concrete-persistence-adapter-shapes.md)** (read/write split,
|
||||
honker+SQLite, `alknet-store-sqlite` crate).
|
||||
|
||||
## Context
|
||||
|
||||
@@ -56,10 +59,10 @@ pub trait IdentityProvider: Send + Sync + 'static { // ADR-004
|
||||
}
|
||||
pub struct ConfigIdentityProvider { ... } // in-memory default (ADR-030)
|
||||
|
||||
pub trait CredentialStore: Send + Sync { // ADR-031
|
||||
pub trait CredentialStore: Send + Sync { // ADR-031, refined by ADR-035
|
||||
fn get(&self, provider: &str) -> Option<EncryptedData>;
|
||||
fn put(&self, provider: &str, data: &EncryptedData) -> Result<(), CredentialStoreError>;
|
||||
fn delete(&self, provider: &str) -> Result<(), CredentialStoreError>;
|
||||
async fn put(&self, provider: &str, data: &EncryptedData) -> Result<(), StoreError>;
|
||||
async fn delete(&self, provider: &str) -> Result<(), StoreError>;
|
||||
}
|
||||
pub struct InMemoryCredentialStore { ... } // in-memory default (ADR-031)
|
||||
```
|
||||
@@ -74,6 +77,11 @@ no persistence backend dependency.
|
||||
A persistence adapter (e.g., `alknet-peer-store-sqlite`,
|
||||
`alknet-credential-store-sqlite`) is a **separate crate** that implements a
|
||||
core repo trait against a specific backend. The adapter:
|
||||
(**Update:** [ADR-035](035-concrete-persistence-adapter-shapes.md)
|
||||
collapses these two into a single `alknet-store-sqlite` crate
|
||||
implementing both `IdentityStore` and `CredentialStore` with shared
|
||||
SQLite connection + honker LISTEN infra; splitting later is a two-way
|
||||
door.)
|
||||
|
||||
- Depends on alknet-core (for the trait and the types it implements
|
||||
against).
|
||||
@@ -129,6 +137,13 @@ and passes `Arc<dyn IdentityProvider>` to every handler that needs it.
|
||||
note is that the repo pattern is a tool to reach for when a storage
|
||||
concern is concrete, not a one-size-fits-all mold to apply
|
||||
speculatively. The pattern is committed; the adapters are not.
|
||||
**Update**: [ADR-035](035-concrete-persistence-adapter-shapes.md) now
|
||||
commits the concrete adapter shape for the SQLite+honker backend
|
||||
(read-sync / write-async split, `IdentityStore` write trait, honker
|
||||
NOTIFY cache invalidation, `alknet-store-sqlite` crate). The deferral
|
||||
above applied to *which* backend and *what* shape; ADR-035 resolves
|
||||
both for the SQLite case. Other backends (Redis, Postgres, on-chain)
|
||||
remain "not needed for current scope."
|
||||
- **Does not change the no-DB posture of the core crates.** Core remains
|
||||
DB-free in the sense that it has no backend dependency — only a trait
|
||||
boundary. The in-memory adapter carries no persistence. The persistence
|
||||
@@ -212,11 +227,15 @@ and passes `Arc<dyn IdentityProvider>` to every handler that needs it.
|
||||
`ConfigIdentityProvider` in-memory default)
|
||||
- ADR-031: CredentialStore Repo Trait (the second application —
|
||||
`CredentialStore` trait + `InMemoryCredentialStore` default)
|
||||
- ADR-035: Concrete Persistence Adapter Shapes (commits the concrete
|
||||
adapter shape this ADR's §"What this does NOT do" deferred —
|
||||
read/write split, honker+SQLite, `alknet-store-sqlite` crate)
|
||||
- OQ-34: Persistent Peer Registry (resolved by this ADR — the storage
|
||||
boundary is `core trait + in-memory default`, persistence adapters
|
||||
additive)
|
||||
- OQ-36: Concrete Adapter Shapes (tracked by this ADR — deferred for
|
||||
exploration; the trait shapes are committed, the adapter shapes are not)
|
||||
- OQ-36: Concrete Adapter Shapes (resolved by ADR-035 — the concrete
|
||||
SQLite+honker adapter shape is committed; the trait shapes committed
|
||||
by this ADR and ADR-030/031 are the one-way doors ADR-035 builds on)
|
||||
- `docs/research/alknet-storage-strategy/findings.md` §3-4 (the
|
||||
SQLite+honker foundation and the repo/adapter pattern)
|
||||
- `/workspace/keypal` — TypeScript repo-pattern reference (the Storage
|
||||
|
||||
Reference in New Issue
Block a user