5.8 KiB
id, name, status, depends_on, scope, risk, impact, level
| id | name | status | depends_on | scope | risk | impact | level | ||
|---|---|---|---|---|---|---|---|---|---|
| call/from-call-forwarded-for | Wire from_call forwarding handler to populate forwarded_for and use peer-keyed registration (ADR-029 §5, ADR-032) | completed |
|
narrow | low | component | implementation |
Description
Update the from_call forwarding handler to populate forwarded_for on the
call.requested payload it constructs, and update from_call registration to
use the peer-keyed overlay model. Per ADR-029 §5 (peer-keyed registration,
collision rule change) and ADR-032 §3 (from_call populates forwarded_for).
forwarded_for population (ADR-032 §3)
The hub's from_call forwarding handler constructs the call.requested payload
to send to the spoke. It populates forwarded_for with the end user's identity
— read from the hub's OperationContext.identity (the caller the hub
authenticated) when the hub forwards the call.
// In the from_call forwarding handler:
let mut payload = serde_json::json!({
"operationId": operation_id,
"input": input,
});
// Populate forwarded_for from the hub's context.identity (ADR-032)
if let Some(originator) = &context.identity {
payload["forwarded_for"] = serde_json::to_value(originator).ok().unwrap_or(Value::Null);
}
// The hub authenticates as itself (its own auth_token)
if let Some(token) = &credentials.auth_token {
payload["auth_token"] = serde_json::Value::String(token);
}
The hub may set forwarded_for: None (omit the field) if it doesn't want to
disclose the originator. The spoke receives it as metadata on its
OperationContext — available for logging, auditing, per-user rate limiting,
but never used by AccessControl::check (the spoke authorizes the hub, its
direct caller).
Peer-keyed registration (ADR-029 §5)
from_call registers into the specific peer's sub-overlay (via
CallConnection::register_imported), not a flat overlay. Cross-peer collision
dissolves: same name on different peers is fine (separate sub-overlays, no
collision, no prefix needed). Same-peer collision stays an error (a peer
shouldn't expose two ops with the same name).
FromCallConfig::namespace_prefix becomes optional local-naming sugar for
when the importing node wants to expose a peer's ops under a different name
locally — a local-naming concern, not a disambiguation concern. It defaults
to None.
Collision rule change
- Same-peer collision = error (a peer shouldn't expose two ops with the
same name).
AdapterError::SamePeerCollision. - Cross-peer collision dissolves (same name on different peers is fine — separate sub-overlays, ADR-029 §5).
The existing from_call namespace-collision check (which was flat-namespace)
changes to same-peer-only. The AdapterError::Conflict variant (if it exists)
renames to SamePeerCollision (OQ-26).
What this task does NOT do
- Does NOT build
PeerCompositeEnv(that'scall/peer-composite-env, a dependency) —from_callregisters into the connection's overlay, whichPeerCompositeEnvaggregates by peer. - Does NOT add
forwarded_fortoOperationContext(that'scall/operation-context-forwarded-for, a dependency) — this task populates the wire field.
Acceptance Criteria
from_callforwarding handler populatesforwarded_foron thecall.requestedpayload fromcontext.identityforwarded_foromitted (None) when the hub chooses not to disclose the originatorfrom_callregisters into the connection's overlay (peer-keyed viaPeerCompositeEnv)- Same-peer collision = error (
AdapterError::SamePeerCollision) - Cross-peer collision dissolves (same name on different peers is fine)
FromCallConfig::namespace_prefixdefaults toNone(optional local-naming sugar)AdapterError::Conflictrenamed toSamePeerCollision(OQ-26)- Unit test:
forwarded_forpopulated fromcontext.identityon forwarding - Unit test:
forwarded_foromitted when context.identity is None - Unit test: same-peer collision returns
SamePeerCollisionerror - Unit test: cross-peer same name does not collide (separate sub-overlays)
cargo test -p alknet-callsucceedscargo clippy -p alknet-callsucceeds with no warnings
References
- docs/architecture/crates/call/client-and-adapters.md — from_call, forwarded_for, namespace collision
- docs/architecture/decisions/029-peer-graph-routing-model.md — ADR-029 §5 (peer-keyed registration, collision rule)
- docs/architecture/decisions/032-forwarded-for-identity.md — ADR-032 §3 (from_call populates forwarded_for)
Notes
The
from_callhandler is the hub's forwarding path. It populatesforwarded_forfrom the hub'scontext.identity(the end user) so the spoke has the originator as metadata. The spoke authorizes the hub (its direct caller), not the end user —AccessControl::checknever readsforwarded_for. Cross-peer collision dissolves under the peer-keyed model (separate sub-overlays); same-peer collision stays an error.
Summary
Wired the from_call forwarding handler to populate forwarded_for on the call.requested payload from context.identity (ADR-032 §3), added CallConnection::call_with_payload for caller-constructed payloads, renamed AdapterError::Conflict → SamePeerCollision (OQ-26, ADR-029 §5 same-peer-only collision rule), updated FromCallConfig::namespace_prefix doc to reflect it's optional local-naming sugar (defaults None), and extracted build_bundles/build_forwarded_payload for unit-testable collision and forwarding logic. 9 new unit tests covering forwarded_for populated/omitted, same-peer collision (with and without prefix), cross-peer same-name no-collision, distinct names, prefix application, and operation filter. 241 unit + 2 integration tests pass, clippy clean, fmt clean.