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:
@@ -4,7 +4,12 @@
|
||||
|
||||
Accepted (establishes the second repo-trait in core, alongside
|
||||
`IdentityProvider`; resolves the credential-persistence dimension of
|
||||
OQ-34)
|
||||
OQ-34). **Refined by [ADR-035](035-concrete-persistence-adapter-shapes.md)**:
|
||||
`put`/`delete` are async (the sketch below showed them sync; ADR-035
|
||||
refines this within the one-way door this ADR committed — "there IS a
|
||||
`CredentialStore` trait with `get`/`put`/`delete` keyed by provider,
|
||||
persisting `EncryptedData`, never decrypting" stands). `get` stays
|
||||
sync (cached read). See ADR-035 §3.
|
||||
|
||||
## Context
|
||||
|
||||
@@ -47,20 +52,35 @@ lives alongside it, and a future persistence adapter is a separate crate
|
||||
```rust
|
||||
pub trait CredentialStore: Send + Sync {
|
||||
fn get(&self, provider: &str) -> Option<EncryptedData>;
|
||||
fn put(&self, provider: &str, data: &EncryptedData) -> Result<(), CredentialStoreError>;
|
||||
fn delete(&self, provider: &str) -> Result<(), CredentialStoreError>;
|
||||
// put/delete refined to async by ADR-035 (within the one-way door
|
||||
// this ADR committed). The sketch below showed them sync; the
|
||||
// refinement is that a SQLite-backed adapter cannot do a sync write
|
||||
// without blocking, and the in-memory default trivially satisfies
|
||||
// an async trait (no .await points). get stays sync (cached read).
|
||||
async fn put(&self, provider: &str, data: &EncryptedData) -> Result<(), StoreError>;
|
||||
async fn delete(&self, provider: &str) -> Result<(), StoreError>;
|
||||
}
|
||||
```
|
||||
|
||||
The error type was sketched here as `CredentialStoreError`;
|
||||
**[ADR-035](035-concrete-persistence-adapter-shapes.md) §7 renames it
|
||||
to `StoreError`** — a single shared type for both the
|
||||
`CredentialStore` and `IdentityStore` traits, so both adapters and all
|
||||
consumers reference one error type. The rename is within this ADR's
|
||||
one-way door (the contract was "a `#[non_exhaustive]` error enum for
|
||||
store failures"; the name was unspecified detail).
|
||||
|
||||
- `provider: &str` — the provider identifier (`"openai"`, `"anthropic"`,
|
||||
`"github"`, etc.). The key the assembly layer uses to look up a
|
||||
credential when populating `Capabilities`.
|
||||
- `EncryptedData` — the vault's encrypted-blob type (ADR-020, defined in
|
||||
`alknet-vault`). The store persists the blob as-is; it does not decrypt.
|
||||
Decryption is the vault's job (ADR-025, local-only by construction).
|
||||
- `CredentialStoreError` — a crate-level error enum for store failures
|
||||
(backend unreachable, serialization, etc.). `#[non_exhaustive]` so
|
||||
adapter crates can extend without breaking match arms.
|
||||
- `StoreError` — a crate-level error enum for store failures (backend
|
||||
unreachable, serialization, etc.). `#[non_exhaustive]` so adapter
|
||||
crates can extend without breaking match arms. (Sketched here as
|
||||
`CredentialStoreError`; renamed to `StoreError` by ADR-035 §7 — a
|
||||
single shared type for both `CredentialStore` and `IdentityStore`.)
|
||||
|
||||
The trait returns `Option<EncryptedData>` from `get` (not `Result`): a
|
||||
missing credential is the common case (the provider isn't configured),
|
||||
@@ -204,6 +224,9 @@ returns `vec![]` from the in-memory adapter until overridden).
|
||||
is the sole decryption boundary)
|
||||
- ADR-033: Storage Boundary and Repo/Adapter Pattern (the overarching
|
||||
pattern this ADR follows)
|
||||
- ADR-035: Concrete Persistence Adapter Shapes (refines this ADR's
|
||||
`put`/`delete` to async; commits the `alknet-store-sqlite` adapter
|
||||
design and the honker cache-invalidation mechanism)
|
||||
- OQ-34: Persistent Peer Registry (resolved by this ADR + ADR-030 + ADR-033
|
||||
— the storage boundary is `config + in-memory adapter` now, persistence
|
||||
adapters additive)
|
||||
|
||||
Reference in New Issue
Block a user