Decompose the source-to-spec sync for the core and call crates into atomic, dependency-ordered tasks for implementation agents: Core (7 tasks + review): - peer-entry-model: PeerEntry struct, AuthPolicy.peers (ADR-030 keystone) - credential-store-trait: CredentialStore/InMemoryCredentialStore/StoreError (ADR-031/035) - identity-store-trait: IdentityStore async write trait (ADR-035) - config-identity-provider-peerentry: ConfigIdentityProvider PeerEntry resolution (ADR-030) - fingerprint-normalization: ed25519:hex for raw keys across quinn/iroh (ADR-030 §6) - three-remote-roles-docs: document ADR-034 roles and verifier selection - review-core-sync: phase gate before call consumes new identity semantics Call (9 tasks + review): - retire-remote-safe: remove ADR-028 machinery, AccessControl is the gate (ADR-029 §3) - operation-context-forwarded-for: forwarded_for field, wire-ingress only (ADR-032) - peer-composite-env: PeerCompositeEnv, PeerId=Identity.id, remove UUID (ADR-029/030) - operation-env-invoke-peer: invoke_peer/peer_contains/PeerRef (ADR-029 §2) - services-list-accesscontrol-filtered: AccessControl filter, list-peers opt-in (ADR-029 §6) - call-client-verifier-selection: TLS client-auth, verifier by PeerEntry (OQ-29, ADR-034) - from-call-forwarded-for: populate forwarded_for, peer-keyed registration (ADR-029 §5, ADR-032) - dispatch-peer-identity: AccessControl::check(peer_identity), PeerId from resolution (ADR-029 §3, ADR-030 §5) - review-call-sync: phase gate for the call sync Validated: 58 tasks, no cycles, logical topo order, two review checkpoints.
4.4 KiB
id, name, status, depends_on, scope, risk, impact, level
| id | name | status | depends_on | scope | risk | impact | level | |
|---|---|---|---|---|---|---|---|---|
| core/identity-store-trait | Add IdentityStore async write trait extending IdentityProvider (ADR-035) | pending |
|
single | low | component | implementation |
Description
Add the IdentityStore async write trait for peer management, extending the
read-only IdentityProvider trait. Per ADR-035 §2.
IdentityProvider is read-only today and stays read-only — it is the hot-path
trait called on every incoming connection (sync, no .await). Peer mutations
(add/update/remove a PeerEntry) go through this separate async trait.
IdentityStore trait
/// Write trait — management path, async (ADR-035). ConfigIdentityProvider
/// does NOT implement this (config reload is its write path — see below).
/// SqliteIdentityProvider does: writes hit SQLite, emit honker NOTIFY,
/// and the local LISTEN refreshes the in-memory read index.
#[async_trait]
pub trait IdentityStore: IdentityProvider {
async fn put_peer(&self, peer: &PeerEntry) -> Result<(), StoreError>;
async fn update_peer(&self, peer_id: &str, peer: &PeerEntry) -> Result<(), StoreError>;
async fn remove_peer(&self, peer_id: &str) -> Result<(), StoreError>;
}
put_peer— insert or replace aPeerEntry(upsert bypeer_id).update_peer— update an existingPeerEntry(error ifpeer_idnot found; for upsert semantics useput_peer).remove_peer— delete aPeerEntrybypeer_id.
Why a separate trait, not async methods on IdentityProvider
- The hot-path read trait is consumed by the accept loop and every handler —
those call sites are sync and must not gain
.await. Ifput_peerwere onIdentityProvider, every consumer would see the async method even though only the management path calls it. A separateIdentityStore: IdentityProvidersupertrait keeps the read surface lean and makes the write surface opt-in. ConfigIdentityProviderdoes not implementIdentityStore. Its write path is config reload (ConfigReloadHandle::reload), not a method call. This preserves the config-is-source-of-truth model. ImplementingIdentityStore
for
ConfigIdentityProvider"for symmetry" would violate that model — the constraint is the absence of a backend, not a type-system constraint.
ConfigIdentityProvider posture
ConfigIdentityProvider deliberately does NOT implement IdentityStore. This
task does not change ConfigIdentityProvider — it only adds the trait. The
trait is defined for future adapters (SqliteIdentityProvider in
alknet-store-sqlite) to implement. StoreError is already defined by
core/credential-store-trait.
Module placement
Add IdentityStore alongside IdentityProvider in alknet-core/src/auth.rs
(or a new store module if CredentialStore landed there). Re-export from
lib.rs.
Acceptance Criteria
IdentityStoretrait withput_peer,update_peer,remove_peer(all async)IdentityStore: IdentityProvider(supertrait)StoreErrorused as the error type (fromcore/credential-store-trait)ConfigIdentityProviderdoes NOT implementIdentityStore#[async_trait]on the trait- No changes to
IdentityProvidertrait (stays read-only, sync) - Unit test: a mock/test impl of
IdentityStorecompiles and works (verify the trait is implementable) - Unit test:
ConfigIdentityProviderdoes not implementIdentityStore(compile-time or trait-bound assertion) cargo test -p alknet-coresucceedscargo clippy -p alknet-coresucceeds with no warnings
References
- docs/architecture/crates/core/auth.md — IdentityStore write trait, ConfigIdentityProvider posture
- docs/architecture/decisions/035-concrete-persistence-adapter-shapes.md — ADR-035 §2 (the trait, read/write split rationale)
- docs/architecture/decisions/033-storage-boundary-and-repo-adapter-pattern.md — ADR-033 (the pattern)
Notes
Small task but locks the trait shape — a one-way door. The read/write split keeps the hot path sync (no
.awaitin the accept loop).ConfigIdentityProvidernot implementingIdentityStoreis a design posture, not a type-system constraint: it holds no backend, and its write path is config reload. A deployment that wants method-call peer management wires the SQLite adapter (a separate crate, not built in this sync).
Summary
To be filled on completion