Files
alknet/docs/architecture/questions/036-concrete-persistence-adapter-shapes.md
T
glm-5.2 1baa619ce9 docs(arch): decompose open-questions.md into per-OQ files under questions/
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.
2026-07-06 16:07:59 +00:00

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)