The monolithic open-questions.md (1310 lines, 47 OQs) was large enough to be unmanageable, with high size variance (OQ-42 at 220 lines next to OQ-06 at 8). Decomposed into one file per OQ under docs/architecture/questions/ (NNN-slug.md, mirroring the ADR convention), with open-questions.md retained as the index: theme-grouped tables plus a cross-theme Deferred/Blocked section that surfaces the 6 deferred OQs with their Blocked-on conditions inline (the safe-exit visibility surface). Per-OQ content moved verbatim; all 62 inbound links stay valid (none used anchors). README's curated OQ summary dropped (now redundant with the index tables). Also seeds tasks/architecture/ with this task plus two follow-ups found during the decompose: OQ-09/10 missing structured Blocked-on fields, and the tasks/architecture/ blocker-task half of the Safe Exit protocol being unenforced.
3.7 KiB
OQ-36: Concrete Persistence Adapter Shapes
-
Origin: ADR-033 §"What this does NOT do" (concrete adapter shapes not specified), the project's note that the repo pattern is a tool to reach for, not a one-size-fits-all mold
-
Status: resolved (2026-06-28 by ADR-035)
-
Door type: Two-way (adapter shapes are implementation details; the trait shapes are the one-way doors, already committed by ADR-030/031/033)
-
Priority: medium → resolved
-
Resolution: ADR-035 commits the concrete adapter shape. The design is driven by two constraints: the hot-path read trait (
IdentityProvider::resolve_from_ fingerprint,CredentialStore::get) is sync (called in the accept loop, no.await), and auth changes must take effect without a restart (an early issue the project already fixed forConfigIdentityProviderviaArcSwapconfig reload).The resolution:
- Read trait stays sync; persistence adapters cache in memory. A
SQLite-backed adapter serves sync reads from an in-memory index
(
HashMap<fingerprint, PeerEntry>/HashMap<String, EncryptedData>), loaded from SQLite at construction and refreshed on honkerNOTIFY. SameArcSwap-backed full-reload pattern asConfigIdentityProvider, generalized from "config file is source of truth" to "SQLite is source of truth, honker signals when it changed." - New async
IdentityStorewrite trait (put_peer/update_peer/remove_peer) extendsIdentityProviderfor peer mutations.ConfigIdentityProviderdoes NOT implement it (config reload is its write path); the SQLite adapter does. The read trait stays lean; the write surface is opt-in. CredentialStore::put/deletebecome async (refines ADR-031's sync sketch — within the one-way door ADR-031 committed;getstays sync/cached).InMemoryCredentialStore's write methods are async-with-no-awaits (signature change only).- honker is the cache-invalidation mechanism — a hard dependency of
alknet-store-sqlite, NOT ofalknet-core. honker's SQLiteNOTIFY/LISTEN(single-digit-ms wake, no polling) is what makes the sync-read + cached-index + no-restart combination work. Without it, the adapter either polls (stale window) or requires restart (the bug already fixed). Not optional for the SQLite adapter. alknet-store-sqlite— one crate, both adapters (SqliteIdentityProvider: IdentityProvider + IdentityStore,SqliteCredentialStore: CredentialStore), shared SQLite connection pool + honker LISTEN loop + bootstrap migrations. Splitting into two crates later is a two-way door (additive).- Schema shape committed (one row per
PeerEntrywith JSON columns forfingerprints/scopes/resources; one row perEncryptedDatablob keyed byprovider); exact DDL is an implementation-detail two-way door in the adapter crate. - Shared
StoreError(#[non_exhaustive],thiserror::Error) in alknet-core for both adapters.
The keypal adapter-factory pattern is intentionally not ported to Rust (runtime column-mapping/type-coercion is a TS affordance; in Rust each adapter is a concrete type, cross-cutting concerns are a shared helper module). Two trait families (not one generic
Storage<T>) preserved per ADR-033 §4. Redis / Postgres / on-chain adapters are not needed for current scope — the trait shapes make them possible; the adapter crates get built when a use case forces them. - Read trait stays sync; persistence adapters cache in memory. A
SQLite-backed adapter serves sync reads from an in-memory index
(
-
Cross-references: ADR-004, ADR-011, ADR-014, ADR-020, ADR-025, ADR-030, ADR-031, ADR-033, ADR-035, OQ-33, OQ-34, auth.md, config.md