tasks: decompose ADR-029/030/031/032/034/035 source sync into 17 tasks
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.
This commit is contained in:
156
tasks/call/operation-env-invoke-peer.md
Normal file
156
tasks/call/operation-env-invoke-peer.md
Normal file
@@ -0,0 +1,156 @@
|
||||
---
|
||||
id: call/operation-env-invoke-peer
|
||||
name: Add invoke_peer/peer_contains/PeerRef to OperationEnv trait for peer-keyed routing (ADR-029 §2)
|
||||
status: pending
|
||||
depends_on: [call/peer-composite-env]
|
||||
scope: moderate
|
||||
risk: medium
|
||||
impact: component
|
||||
level: implementation
|
||||
---
|
||||
|
||||
## Description
|
||||
|
||||
Add the `invoke_peer` and `peer_contains` methods to the `OperationEnv` trait,
|
||||
with the `PeerRef` selector enum. Per ADR-029 §2. `PeerCompositeEnv` (built in
|
||||
`call/peer-composite-env`) overrides these with real peer-keyed routing; the
|
||||
default-impl preserves back-compat for single-layer envs
|
||||
(`LocalOperationEnv`, connection overlays) that don't route by peer.
|
||||
|
||||
### PeerRef enum
|
||||
|
||||
```rust
|
||||
pub enum PeerRef {
|
||||
Specific(PeerId), // route to this peer; NOT_FOUND if it doesn't serve the op
|
||||
Any, // first peer (insertion order) that serves it
|
||||
}
|
||||
```
|
||||
|
||||
`PeerRef::Specific(PeerId)` routes to the named peer's overlay only — no
|
||||
fallthrough (explicit routing must be honored or fail loudly, ADR-029 §2).
|
||||
`PeerRef::Any` reuses `invoke_with_policy` (the insertion-order fan-out built
|
||||
in `call/peer-composite-env`).
|
||||
|
||||
### OperationEnv trait additions
|
||||
|
||||
```rust
|
||||
#[async_trait]
|
||||
pub trait OperationEnv: Send + Sync {
|
||||
// ... existing invoke, invoke_with_policy, contains ...
|
||||
|
||||
/// Peer-routing composition (ADR-029 §2). Routes to a specific peer
|
||||
/// (`PeerRef::Specific`) or to the first peer that serves the op
|
||||
/// (`PeerRef::Any`). The default impl ignores the peer selector and
|
||||
/// delegates to `invoke_with_policy`, preserving back-compat for
|
||||
/// single-layer envs that don't route by peer. `PeerCompositeEnv`
|
||||
/// overrides with real peer-keyed routing.
|
||||
async fn invoke_peer(
|
||||
&self,
|
||||
peer: &PeerRef,
|
||||
namespace: &str,
|
||||
operation: &str,
|
||||
input: Value,
|
||||
parent: &OperationContext,
|
||||
policy: AbortPolicy,
|
||||
) -> ResponseEnvelope {
|
||||
let _ = peer; // unused — single-layer envs don't route by peer
|
||||
self.invoke_with_policy(namespace, operation, input, parent, policy).await
|
||||
}
|
||||
|
||||
/// Does this env contain the named op *on the named peer*? Used by
|
||||
/// `PeerCompositeEnv` to probe a specific peer's sub-overlay before
|
||||
/// dispatching via `invoke_peer` with `PeerRef::Specific`. Default impl
|
||||
/// delegates to `contains` (single-layer envs ignore the peer dimension).
|
||||
fn peer_contains(&self, _peer: &PeerId, name: &str) -> bool {
|
||||
self.contains(name)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### PeerCompositeEnv overrides
|
||||
|
||||
```rust
|
||||
#[async_trait]
|
||||
impl OperationEnv for PeerCompositeEnv {
|
||||
// ... invoke_with_policy, contains from call/peer-composite-env ...
|
||||
|
||||
async fn invoke_peer(
|
||||
&self,
|
||||
peer: &PeerRef,
|
||||
namespace: &str,
|
||||
operation: &str,
|
||||
input: Value,
|
||||
parent: &OperationContext,
|
||||
policy: AbortPolicy,
|
||||
) -> ResponseEnvelope {
|
||||
let name = format!("{namespace}/{operation}");
|
||||
if !parent.scoped_env.allows(&name) {
|
||||
return ResponseEnvelope::not_found(parent.request_id.clone(), &name);
|
||||
}
|
||||
match peer {
|
||||
PeerRef::Specific(peer_id) => {
|
||||
// Route to this peer's sub-overlay only. No fallthrough —
|
||||
// explicit routing must be honored or fail loudly (ADR-029 §2).
|
||||
match self.connections.get(peer_id) {
|
||||
Some(conn_env) if conn_env.contains(&name) => {
|
||||
conn_env.invoke_with_policy(namespace, operation, input, parent, policy).await
|
||||
}
|
||||
_ => ResponseEnvelope::not_found(parent.request_id.clone(), &name),
|
||||
}
|
||||
}
|
||||
PeerRef::Any => {
|
||||
// Same as invoke_with_policy: session → peers in order → base.
|
||||
self.invoke_with_policy(namespace, operation, input, parent, policy).await
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn peer_contains(&self, peer: &PeerId, name: &str) -> bool {
|
||||
self.connections.get(peer).map_or(false, |c| c.contains(name))
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Back-compat
|
||||
|
||||
Existing impls (`LocalOperationEnv`, connection overlay envs) use the default
|
||||
`invoke_peer` (delegates to `invoke_with_policy`, ignores peer selector) and
|
||||
default `peer_contains` (delegates to `contains`). No changes needed to those
|
||||
impls — the trait surface grows, the behavior is preserved.
|
||||
|
||||
## Acceptance Criteria
|
||||
|
||||
- [ ] `PeerRef` enum with `Specific(PeerId)` and `Any` variants
|
||||
- [ ] `OperationEnv::invoke_peer` method with default-impl (delegates to `invoke_with_policy`)
|
||||
- [ ] `OperationEnv::peer_contains` method with default-impl (delegates to `contains`)
|
||||
- [ ] `PeerCompositeEnv` overrides `invoke_peer` with real peer-keyed routing
|
||||
- [ ] `PeerRef::Specific` routes to named peer only (no fallthrough → NOT_FOUND if peer doesn't serve op)
|
||||
- [ ] `PeerRef::Any` reuses `invoke_with_policy` (insertion-order fan-out)
|
||||
- [ ] `PeerCompositeEnv` overrides `peer_contains` (checks specific peer's sub-overlay)
|
||||
- [ ] Reachability check (`scoped_env.allows`) gates before peer routing
|
||||
- [ ] `LocalOperationEnv` and overlay envs use default-impls (no changes)
|
||||
- [ ] Unit test: `PeerRef::Specific` routes to the named peer
|
||||
- [ ] Unit test: `PeerRef::Specific` → NOT_FOUND when peer doesn't serve the op (no fallthrough)
|
||||
- [ ] Unit test: `PeerRef::Any` routes to first peer (insertion order) that serves it
|
||||
- [ ] Unit test: `peer_contains` checks specific peer's overlay
|
||||
- [ ] Unit test: default-impl `invoke_peer` delegates to `invoke_with_policy` (back-compat)
|
||||
- [ ] `cargo test -p alknet-call` succeeds
|
||||
- [ ] `cargo clippy -p alknet-call` succeeds with no warnings
|
||||
|
||||
## References
|
||||
|
||||
- docs/architecture/crates/call/operation-registry.md — OperationEnv, invoke_peer, PeerRef
|
||||
- docs/architecture/decisions/029-peer-graph-routing-model.md — ADR-029 §2 (PeerRef routing)
|
||||
|
||||
## Notes
|
||||
|
||||
> The default-impl preserves back-compat — existing single-layer envs
|
||||
> (`LocalOperationEnv`, connection overlays) work unchanged. `PeerCompositeEnv`
|
||||
> overrides with real peer-keyed routing. `PeerRef::Specific` has no
|
||||
> fallthrough (explicit routing must be honored or fail loudly). `PeerRef::Any`
|
||||
> reuses the `invoke_with_policy` fan-out. The reachability check
|
||||
> (`scoped_env.allows`) gates before peer routing, same as before.
|
||||
|
||||
## Summary
|
||||
|
||||
> To be filled on completion
|
||||
Reference in New Issue
Block a user