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.
5.5 KiB
id, name, status, depends_on, scope, risk, impact, level
| id | name | status | depends_on | scope | risk | impact | level |
|---|---|---|---|---|---|---|---|
| core/credential-store-trait | Add CredentialStore trait, InMemoryCredentialStore, EncryptedData mirror, and StoreError (ADR-031/035) | pending | narrow | low | component | implementation |
Description
Add the second repo trait to alknet-core: CredentialStore for encrypted-
credential persistence, alongside its in-memory default adapter and the shared
StoreError type. Per ADR-031 (the trait) and ADR-035 (refines put/delete
to async, renames the error to StoreError).
This task is standalone — it has no dependency on core/peer-entry-model. The
CredentialStore trait persists EncryptedData blobs (the vault's encrypted
output); it never decrypts (ADR-025 — the vault is the sole decryption
boundary).
CredentialStore trait
pub trait CredentialStore: Send + Sync {
fn get(&self, provider: &str) -> Option<EncryptedData>;
async fn put(&self, provider: &str, data: &EncryptedData) -> Result<(), StoreError>;
async fn delete(&self, provider: &str) -> Result<(), StoreError>;
}
getis sync (cached read — the hot path; ADR-035 §1).put/deleteare async (they hit the backend; ADR-035 §3 refines ADR-031's sync sketch to async within the one-way door).getreturnsOption<EncryptedData>(missing credential is the common case, not an error).- No
listmethod (ADR-031 §4 — additive if needed later).
InMemoryCredentialStore
pub struct InMemoryCredentialStore {
entries: RwLock<HashMap<String, EncryptedData>>,
}
impl InMemoryCredentialStore {
pub fn new() -> Self;
pub fn with_entries(entries: HashMap<String, EncryptedData>) -> Self;
}
impl CredentialStore for InMemoryCredentialStore { ... }
The default adapter covers tests and config-loaded deployments. put/delete
are async with no .await points (trivially satisfy an async trait — ADR-035
§3). Same posture as ConfigIdentityProvider — no persistence, no backend
dependency, no env vars.
EncryptedData core mirror
A thin serializable struct mirroring the vault's EncryptedData (ADR-020),
so the trait can reference it without a vault dependency (ADR-018 — vault is
standalone with zero alknet-crate dependencies):
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct EncryptedData {
pub key_version: u32,
pub salt: Vec<u8>, // wire-format compat (OQ-20); unused in v2 but kept
pub iv: Vec<u8>, // AES-GCM IV (OsRng-generated)
pub data: Vec<u8>, // ciphertext
}
The salt field is kept for wire-format compatibility with the TS predecessor
(OQ-20) — a core mirror that omitted it could not round-trip the vault's
EncryptedData. v2 may write a zero-length salt but must not drop the field
(ADR-035 §6).
StoreError
#[non_exhaustive]
#[derive(Debug, thiserror::Error)]
pub enum StoreError {
#[error("backend error: {message}")]
Backend { message: String },
#[error("not found: {entity}")]
NotFound { entity: String },
#[error("serialization error: {message}")]
Serialization { message: String },
}
Shared by both CredentialStore and IdentityStore (ADR-035 §7 renames
ADR-031's CredentialStoreError to StoreError). #[non_exhaustive] so
adapter crates can extend without breaking match arms. Lives in alknet-core
(where the traits live).
Module placement
Add a new store module (or credential_store module) in alknet-core/src/.
Re-export CredentialStore, InMemoryCredentialStore, EncryptedData, and
StoreError from lib.rs.
Acceptance Criteria
CredentialStoretrait with syncget, asyncput/deleteInMemoryCredentialStorewithnew()andwith_entries()InMemoryCredentialStoreimplementsCredentialStore(async put/delete with no .await points)EncryptedDatacore mirror with 4 fields (key_version,salt,iv,data), derives Serialize/Deserialize/Clone/DebugStoreErrorenum with 3 variants,#[non_exhaustive],thiserror::Error- No vault dependency added to alknet-core (EncryptedData is a core-owned mirror)
- No
listmethod on the trait - Unit test: InMemoryCredentialStore get/put/delete round-trip
- Unit test: get returns None for missing provider
- Unit test: EncryptedData serializes and deserializes (round-trip)
- Unit test: StoreError Display formatting
cargo test -p alknet-coresucceedscargo clippy -p alknet-coresucceeds with no warnings
References
- docs/architecture/crates/core/auth.md — CredentialStore, StoreError, EncryptedData mirror
- docs/architecture/decisions/031-credentialstore-repo-trait.md — ADR-031 (the trait)
- docs/architecture/decisions/035-concrete-persistence-adapter-shapes.md — ADR-035 (async put/delete, StoreError rename, schema)
- docs/architecture/decisions/033-storage-boundary-and-repo-adapter-pattern.md — ADR-033 (the pattern)
Notes
Standalone task — no dependency on PeerEntry. The
CredentialStoretrait is the second repo trait in core (alongsideIdentityProvider), establishing the repo/adapter pattern concretely (ADR-033). The trait is the one-way door; the in-memory default is the reference implementation; persistence adapters (alknet-store-sqlite, ADR-035) are separate crates, not built in this sync.getstays sync because the credential load happens at startup intoCapabilities(ADR-031);put/deleteare async because a SQLite-backed adapter cannot do a sync write without blocking (ADR-035 §3).
Summary
To be filled on completion