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.
64 lines
3.7 KiB
Markdown
64 lines
3.7 KiB
Markdown
# 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](decisions/035-concrete-persistence-adapter-shapes.md)
|
|
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 for
|
|
`ConfigIdentityProvider` via `ArcSwap` config 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 honker `NOTIFY`.
|
|
Same `ArcSwap`-backed full-reload pattern as `ConfigIdentityProvider`,
|
|
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 `IdentityProvider` for peer mutations.
|
|
`ConfigIdentityProvider` does 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`/`delete` become async** (refines ADR-031's
|
|
sync sketch — within the one-way door ADR-031 committed; `get` stays
|
|
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 of `alknet-core`. honker's SQLite
|
|
`NOTIFY`/`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 `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.
|
|
- **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.
|
|
- **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](crates/core/auth.md), [config.md](crates/core/config.md)
|