feat(call): add ScopedPeerEnv peer-pinned reachability (ADR-029 §4, call/scoped-peer-env)
This commit is contained in:
@@ -581,7 +581,7 @@ mod tests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn test_context(identity: Option<Identity>) -> OperationContext {
|
fn test_context(identity: Option<Identity>) -> OperationContext {
|
||||||
use crate::registry::context::{AbortPolicy, ScopedOperationEnv};
|
use crate::registry::context::{AbortPolicy, ScopedPeerEnv};
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::time::{Duration, Instant};
|
use std::time::{Duration, Instant};
|
||||||
OperationContext {
|
OperationContext {
|
||||||
@@ -592,7 +592,7 @@ mod tests {
|
|||||||
forwarded_for: None,
|
forwarded_for: None,
|
||||||
capabilities: Capabilities::new(),
|
capabilities: Capabilities::new(),
|
||||||
metadata: HashMap::new(),
|
metadata: HashMap::new(),
|
||||||
scoped_env: ScopedOperationEnv::empty(),
|
scoped_env: ScopedPeerEnv::empty(),
|
||||||
env: Arc::new(NoopEnv),
|
env: Arc::new(NoopEnv),
|
||||||
abort_policy: AbortPolicy::default(),
|
abort_policy: AbortPolicy::default(),
|
||||||
deadline: Some(Instant::now() + Duration::from_secs(30)),
|
deadline: Some(Instant::now() + Duration::from_secs(30)),
|
||||||
|
|||||||
@@ -73,7 +73,7 @@ impl OperationAdapter for FromJsonSchema {
|
|||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::client::from_jsonschema as from_jsonschema_fn;
|
use crate::client::from_jsonschema as from_jsonschema_fn;
|
||||||
use crate::registry::context::{AbortPolicy, ScopedOperationEnv};
|
use crate::registry::context::{AbortPolicy, ScopedPeerEnv};
|
||||||
use crate::registry::env::OperationEnv;
|
use crate::registry::env::OperationEnv;
|
||||||
use crate::registry::spec::{AccessControl, OperationType, Visibility};
|
use crate::registry::spec::{AccessControl, OperationType, Visibility};
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
@@ -117,7 +117,7 @@ mod tests {
|
|||||||
forwarded_for: None,
|
forwarded_for: None,
|
||||||
capabilities: Capabilities::new(),
|
capabilities: Capabilities::new(),
|
||||||
metadata: HashMap::new(),
|
metadata: HashMap::new(),
|
||||||
scoped_env: ScopedOperationEnv::empty(),
|
scoped_env: ScopedPeerEnv::empty(),
|
||||||
env: Arc::new(NoopEnv),
|
env: Arc::new(NoopEnv),
|
||||||
abort_policy: AbortPolicy::default(),
|
abort_policy: AbortPolicy::default(),
|
||||||
deadline: Some(std::time::Instant::now() + Duration::from_secs(30)),
|
deadline: Some(std::time::Instant::now() + Duration::from_secs(30)),
|
||||||
|
|||||||
@@ -164,7 +164,7 @@ mod tests {
|
|||||||
use crate::protocol::wire::{
|
use crate::protocol::wire::{
|
||||||
CallError, EventEnvelope, EVENT_COMPLETED, EVENT_ERROR, EVENT_RESPONDED,
|
CallError, EventEnvelope, EVENT_COMPLETED, EVENT_ERROR, EVENT_RESPONDED,
|
||||||
};
|
};
|
||||||
use crate::registry::context::{AbortPolicy, OperationContext, ScopedOperationEnv};
|
use crate::registry::context::{AbortPolicy, OperationContext, ScopedPeerEnv};
|
||||||
use crate::registry::env::OperationEnv;
|
use crate::registry::env::OperationEnv;
|
||||||
use crate::registry::registration::{make_handler, HandlerRegistration, OperationProvenance};
|
use crate::registry::registration::{make_handler, HandlerRegistration, OperationProvenance};
|
||||||
use crate::registry::spec::{AccessControl, OperationSpec, OperationType, Visibility};
|
use crate::registry::spec::{AccessControl, OperationSpec, OperationType, Visibility};
|
||||||
@@ -419,7 +419,7 @@ mod tests {
|
|||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn build_root_context_carries_capabilities_and_scoped_env() {
|
async fn build_root_context_carries_capabilities_and_scoped_env() {
|
||||||
let mut registry = OperationRegistry::new();
|
let mut registry = OperationRegistry::new();
|
||||||
let scoped = ScopedOperationEnv::new(["fs/readFile"]);
|
let scoped = ScopedPeerEnv::new(["fs/readFile"]);
|
||||||
let caps = Capabilities::new().with_api_key("google", "k".to_string());
|
let caps = Capabilities::new().with_api_key("google", "k".to_string());
|
||||||
registry.register(HandlerRegistration::new(
|
registry.register(HandlerRegistration::new(
|
||||||
external_spec("agent/run", AccessControl::default()),
|
external_spec("agent/run", AccessControl::default()),
|
||||||
@@ -562,7 +562,7 @@ mod tests {
|
|||||||
let context =
|
let context =
|
||||||
adapter.build_root_context("req-5".to_string(), "fs/readFile", None, None, &conn);
|
adapter.build_root_context("req-5".to_string(), "fs/readFile", None, None, &conn);
|
||||||
|
|
||||||
let scoped = ScopedOperationEnv::new(["worker/exec"]);
|
let scoped = ScopedPeerEnv::new(["worker/exec"]);
|
||||||
let invoke_ctx = OperationContext {
|
let invoke_ctx = OperationContext {
|
||||||
request_id: "req-5".to_string(),
|
request_id: "req-5".to_string(),
|
||||||
parent_request_id: None,
|
parent_request_id: None,
|
||||||
@@ -620,7 +620,7 @@ mod tests {
|
|||||||
let context =
|
let context =
|
||||||
adapter.build_root_context("req-6".to_string(), "fs/readFile", None, None, &conn);
|
adapter.build_root_context("req-6".to_string(), "fs/readFile", None, None, &conn);
|
||||||
|
|
||||||
let scoped = ScopedOperationEnv::new(["worker/exec"]);
|
let scoped = ScopedPeerEnv::new(["worker/exec"]);
|
||||||
let invoke_ctx = OperationContext {
|
let invoke_ctx = OperationContext {
|
||||||
request_id: "req-6".to_string(),
|
request_id: "req-6".to_string(),
|
||||||
parent_request_id: None,
|
parent_request_id: None,
|
||||||
|
|||||||
@@ -23,9 +23,7 @@ use super::wire::{
|
|||||||
EVENT_ERROR, EVENT_RESPONDED,
|
EVENT_ERROR, EVENT_RESPONDED,
|
||||||
};
|
};
|
||||||
use crate::protocol::wire::ResponseEnvelope;
|
use crate::protocol::wire::ResponseEnvelope;
|
||||||
use crate::registry::context::{
|
use crate::registry::context::{generate_request_id, AbortPolicy, OperationContext, ScopedPeerEnv};
|
||||||
generate_request_id, AbortPolicy, OperationContext, ScopedOperationEnv,
|
|
||||||
};
|
|
||||||
use crate::registry::env::OperationEnv;
|
use crate::registry::env::OperationEnv;
|
||||||
use crate::registry::registration::{Handler, HandlerRegistration};
|
use crate::registry::registration::{Handler, HandlerRegistration};
|
||||||
|
|
||||||
@@ -280,7 +278,7 @@ impl OperationEnv for OverlayOperationEnv {
|
|||||||
scoped_env = registration
|
scoped_env = registration
|
||||||
.scoped_env
|
.scoped_env
|
||||||
.clone()
|
.clone()
|
||||||
.unwrap_or_else(ScopedOperationEnv::empty);
|
.unwrap_or_else(ScopedPeerEnv::empty);
|
||||||
}
|
}
|
||||||
|
|
||||||
let context = OperationContext {
|
let context = OperationContext {
|
||||||
@@ -432,7 +430,7 @@ mod tests {
|
|||||||
|
|
||||||
fn root_context(
|
fn root_context(
|
||||||
request_id: &str,
|
request_id: &str,
|
||||||
scoped_env: ScopedOperationEnv,
|
scoped_env: ScopedPeerEnv,
|
||||||
env: Arc<dyn OperationEnv + Send + Sync>,
|
env: Arc<dyn OperationEnv + Send + Sync>,
|
||||||
) -> OperationContext {
|
) -> OperationContext {
|
||||||
OperationContext {
|
OperationContext {
|
||||||
@@ -487,7 +485,7 @@ mod tests {
|
|||||||
conn.register_imported(imported_registration("worker/exec"));
|
conn.register_imported(imported_registration("worker/exec"));
|
||||||
let env = conn.overlay_env();
|
let env = conn.overlay_env();
|
||||||
|
|
||||||
let scoped = ScopedOperationEnv::new(["worker/exec"]);
|
let scoped = ScopedPeerEnv::new(["worker/exec"]);
|
||||||
let ctx = root_context("root-1", scoped, env.clone());
|
let ctx = root_context("root-1", scoped, env.clone());
|
||||||
|
|
||||||
let response = env
|
let response = env
|
||||||
@@ -506,7 +504,7 @@ mod tests {
|
|||||||
|
|
||||||
assert!(!env.contains("worker/missing"));
|
assert!(!env.contains("worker/missing"));
|
||||||
|
|
||||||
let scoped = ScopedOperationEnv::new(["worker/missing"]);
|
let scoped = ScopedPeerEnv::new(["worker/missing"]);
|
||||||
let ctx = root_context("root-2", scoped, env.clone());
|
let ctx = root_context("root-2", scoped, env.clone());
|
||||||
|
|
||||||
let response = env
|
let response = env
|
||||||
@@ -525,7 +523,7 @@ mod tests {
|
|||||||
conn.register_imported(imported_registration("worker/exec"));
|
conn.register_imported(imported_registration("worker/exec"));
|
||||||
let env = conn.overlay_env();
|
let env = conn.overlay_env();
|
||||||
|
|
||||||
let scoped = ScopedOperationEnv::empty();
|
let scoped = ScopedPeerEnv::empty();
|
||||||
let ctx = root_context("root-3", scoped, env.clone());
|
let ctx = root_context("root-3", scoped, env.clone());
|
||||||
|
|
||||||
let response = env
|
let response = env
|
||||||
@@ -562,7 +560,7 @@ mod tests {
|
|||||||
));
|
));
|
||||||
let env = conn.overlay_env();
|
let env = conn.overlay_env();
|
||||||
|
|
||||||
let scoped = ScopedOperationEnv::new(["worker/exec"]);
|
let scoped = ScopedPeerEnv::new(["worker/exec"]);
|
||||||
let ctx = root_context("root-4", scoped, env.clone());
|
let ctx = root_context("root-4", scoped, env.clone());
|
||||||
|
|
||||||
let response = env
|
let response = env
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ use super::wire::{
|
|||||||
EVENT_ABORTED, EVENT_REQUESTED,
|
EVENT_ABORTED, EVENT_REQUESTED,
|
||||||
};
|
};
|
||||||
use crate::protocol::adapter::SessionOverlaySource;
|
use crate::protocol::adapter::SessionOverlaySource;
|
||||||
use crate::registry::context::{AbortPolicy, OperationContext, ScopedOperationEnv};
|
use crate::registry::context::{AbortPolicy, OperationContext, ScopedPeerEnv};
|
||||||
use crate::registry::env::{LocalOperationEnv, OperationEnv, PeerCompositeEnv};
|
use crate::registry::env::{LocalOperationEnv, OperationEnv, PeerCompositeEnv};
|
||||||
use crate::registry::registration::OperationRegistry;
|
use crate::registry::registration::OperationRegistry;
|
||||||
|
|
||||||
@@ -135,14 +135,12 @@ impl Dispatcher {
|
|||||||
Some(r) => (
|
Some(r) => (
|
||||||
r.composition_authority.clone(),
|
r.composition_authority.clone(),
|
||||||
r.capabilities.clone(),
|
r.capabilities.clone(),
|
||||||
r.scoped_env
|
r.scoped_env.clone().unwrap_or_else(ScopedPeerEnv::empty),
|
||||||
.clone()
|
|
||||||
.unwrap_or_else(ScopedOperationEnv::empty),
|
|
||||||
),
|
),
|
||||||
None => (
|
None => (
|
||||||
None,
|
None,
|
||||||
alknet_core::types::Capabilities::new(),
|
alknet_core::types::Capabilities::new(),
|
||||||
ScopedOperationEnv::empty(),
|
ScopedPeerEnv::empty(),
|
||||||
),
|
),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ use alknet_core::auth::Identity;
|
|||||||
use alknet_core::types::Capabilities;
|
use alknet_core::types::Capabilities;
|
||||||
use serde_json::Value;
|
use serde_json::Value;
|
||||||
|
|
||||||
use super::env::OperationEnv;
|
use super::env::{OperationEnv, PeerId, PeerRef};
|
||||||
|
|
||||||
pub struct OperationContext {
|
pub struct OperationContext {
|
||||||
pub request_id: String,
|
pub request_id: String,
|
||||||
@@ -25,7 +25,7 @@ pub struct OperationContext {
|
|||||||
pub forwarded_for: Option<Identity>,
|
pub forwarded_for: Option<Identity>,
|
||||||
pub capabilities: Capabilities,
|
pub capabilities: Capabilities,
|
||||||
pub metadata: HashMap<String, Value>,
|
pub metadata: HashMap<String, Value>,
|
||||||
pub scoped_env: ScopedOperationEnv,
|
pub scoped_env: ScopedPeerEnv,
|
||||||
pub env: Arc<dyn OperationEnv + Send + Sync>,
|
pub env: Arc<dyn OperationEnv + Send + Sync>,
|
||||||
pub abort_policy: AbortPolicy,
|
pub abort_policy: AbortPolicy,
|
||||||
pub deadline: Option<Instant>,
|
pub deadline: Option<Instant>,
|
||||||
@@ -75,29 +75,65 @@ impl CompositionAuthority {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct ScopedOperationEnv {
|
pub struct ScopedPeerEnv {
|
||||||
allowed: HashSet<String>,
|
/// Peer-agnostic reachability — reachable via `PeerRef::Any` or
|
||||||
|
/// `PeerRef::Specific(any)`. The common case (peer-agnostic composition).
|
||||||
|
pub allowed_ops: HashSet<String>,
|
||||||
|
/// Peer-pinned reachability — `"peer-id/op-name"`, reachable only via
|
||||||
|
/// `PeerRef::Specific(that peer)`. Additive to `allowed_ops`; opt-in for
|
||||||
|
/// the disambiguation case (ADR-029 §4).
|
||||||
|
pub peer_pinned: HashSet<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ScopedOperationEnv {
|
impl ScopedPeerEnv {
|
||||||
pub fn empty() -> Self {
|
pub fn empty() -> Self {
|
||||||
Self {
|
Self {
|
||||||
allowed: HashSet::new(),
|
allowed_ops: HashSet::new(),
|
||||||
|
peer_pinned: HashSet::new(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn new(ops: impl IntoIterator<Item = impl Into<String>>) -> Self {
|
pub fn new(ops: impl IntoIterator<Item = impl Into<String>>) -> Self {
|
||||||
Self {
|
Self {
|
||||||
allowed: ops.into_iter().map(|s| s.into()).collect(),
|
allowed_ops: ops.into_iter().map(|s| s.into()).collect(),
|
||||||
|
peer_pinned: HashSet::new(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Peer-pinned reachability: `"peer-id/op-name"`. Reachable only via
|
||||||
|
/// `PeerRef::Specific(that peer)`. Additive to `new` — call `new` for the
|
||||||
|
/// peer-agnostic set, then `with_pinned` for the pinned set.
|
||||||
|
pub fn with_pinned(mut self, pinned: impl IntoIterator<Item = impl Into<String>>) -> Self {
|
||||||
|
self.peer_pinned = pinned.into_iter().map(|s| s.into()).collect();
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Peer-agnostic reachability — unchanged from `ScopedOperationEnv::allows`.
|
||||||
|
/// A name here is reachable via any routing path (`PeerRef::Any` or
|
||||||
|
/// `Specific`).
|
||||||
pub fn allows(&self, name: &str) -> bool {
|
pub fn allows(&self, name: &str) -> bool {
|
||||||
self.allowed.contains(name)
|
self.allowed_ops.contains(name)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Peer-pinned reachability — reachable only via `PeerRef::Specific(peer)`.
|
||||||
|
/// The entry shape is `"peer-id/op-name"` (ADR-029 §4, OQ-33).
|
||||||
|
pub fn allows_pinned(&self, peer: &PeerId, name: &str) -> bool {
|
||||||
|
self.peer_pinned.contains(&format!("{peer}/{name}"))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Does this scoped env permit `name` via `peer`? Used by the reachability
|
||||||
|
/// gate in `invoke_peer` / `invoke_with_policy`.
|
||||||
|
/// - `PeerRef::Any` → `allows(name)`
|
||||||
|
/// - `PeerRef::Specific(peer)` → `allows(name) || allows_pinned(peer, name)`
|
||||||
|
pub fn allows_via(&self, peer: &PeerRef, name: &str) -> bool {
|
||||||
|
match peer {
|
||||||
|
PeerRef::Any => self.allows(name),
|
||||||
|
PeerRef::Specific(p) => self.allows(name) || self.allows_pinned(p, name),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for ScopedOperationEnv {
|
impl Default for ScopedPeerEnv {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self::empty()
|
Self::empty()
|
||||||
}
|
}
|
||||||
@@ -114,24 +150,108 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn scoped_env_allows_in_set() {
|
fn scoped_env_allows_in_set() {
|
||||||
let env = ScopedOperationEnv::new(["fs/readFile", "agent/chat"]);
|
let env = ScopedPeerEnv::new(["fs/readFile", "agent/chat"]);
|
||||||
assert!(env.allows("fs/readFile"));
|
assert!(env.allows("fs/readFile"));
|
||||||
assert!(env.allows("agent/chat"));
|
assert!(env.allows("agent/chat"));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn scoped_env_disallows_not_in_set() {
|
fn scoped_env_disallows_not_in_set() {
|
||||||
let env = ScopedOperationEnv::new(["fs/readFile"]);
|
let env = ScopedPeerEnv::new(["fs/readFile"]);
|
||||||
assert!(!env.allows("agent/chat"));
|
assert!(!env.allows("agent/chat"));
|
||||||
assert!(!env.allows(""));
|
assert!(!env.allows(""));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn scoped_env_empty_allows_nothing() {
|
fn scoped_env_empty_allows_nothing() {
|
||||||
let env = ScopedOperationEnv::empty();
|
let env = ScopedPeerEnv::empty();
|
||||||
assert!(!env.allows("fs/readFile"));
|
assert!(!env.allows("fs/readFile"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn scoped_peer_env_new_with_pinned_populates_both_fields() {
|
||||||
|
let env = ScopedPeerEnv::new(["fs/readFile"]).with_pinned(["worker-a/container/exec"]);
|
||||||
|
assert!(env.allowed_ops.contains("fs/readFile"));
|
||||||
|
assert!(env.peer_pinned.contains("worker-a/container/exec"));
|
||||||
|
assert!(!env.allowed_ops.contains("worker-a/container/exec"));
|
||||||
|
assert!(!env.peer_pinned.contains("fs/readFile"));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn scoped_peer_env_allows_checks_allowed_ops_only() {
|
||||||
|
let env = ScopedPeerEnv::empty().with_pinned(["worker-a/container/exec"]);
|
||||||
|
assert!(
|
||||||
|
!env.allows("container/exec"),
|
||||||
|
"pinned-only op not in allowed_ops"
|
||||||
|
);
|
||||||
|
let env2 = ScopedPeerEnv::new(["container/exec"]).with_pinned(["worker-a/container/exec"]);
|
||||||
|
assert!(
|
||||||
|
env2.allows("container/exec"),
|
||||||
|
"op in allowed_ops is allowed"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn scoped_peer_env_allows_pinned_checks_peer_pinned_shape() {
|
||||||
|
let env = ScopedPeerEnv::empty().with_pinned(["worker-a/container/exec"]);
|
||||||
|
assert!(env.allows_pinned(&"worker-a".to_string(), "container/exec"));
|
||||||
|
assert!(
|
||||||
|
!env.allows_pinned(&"worker-b".to_string(), "container/exec"),
|
||||||
|
"wrong peer"
|
||||||
|
);
|
||||||
|
assert!(
|
||||||
|
!env.allows_pinned(&"worker-a".to_string(), "other/op"),
|
||||||
|
"wrong op"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn scoped_peer_env_allows_via_any_uses_allowed_ops_only() {
|
||||||
|
let env = ScopedPeerEnv::new(["fs/readFile"]).with_pinned(["worker-a/container/exec"]);
|
||||||
|
assert!(
|
||||||
|
env.allows_via(&PeerRef::Any, "fs/readFile"),
|
||||||
|
"allowed op via Any"
|
||||||
|
);
|
||||||
|
assert!(
|
||||||
|
!env.allows_via(&PeerRef::Any, "container/exec"),
|
||||||
|
"pinned-only op NOT reachable via Any"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn scoped_peer_env_allows_via_specific_uses_allowed_ops_or_peer_pinned() {
|
||||||
|
let env = ScopedPeerEnv::new(["fs/readFile"]).with_pinned(["worker-a/container/exec"]);
|
||||||
|
assert!(
|
||||||
|
env.allows_via(&PeerRef::Specific("worker-a".to_string()), "container/exec"),
|
||||||
|
"pinned-only op reachable via Specific(pinned peer)"
|
||||||
|
);
|
||||||
|
assert!(
|
||||||
|
env.allows_via(&PeerRef::Specific("worker-a".to_string()), "fs/readFile"),
|
||||||
|
"allowed op reachable via Specific(any peer)"
|
||||||
|
);
|
||||||
|
assert!(
|
||||||
|
!env.allows_via(&PeerRef::Specific("worker-b".to_string()), "container/exec"),
|
||||||
|
"pinned-only op NOT reachable via Specific(wrong peer)"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn scoped_peer_env_op_in_both_sets_reachable_via_both_any_and_specific() {
|
||||||
|
let env = ScopedPeerEnv::new(["container/exec"]).with_pinned(["worker-a/container/exec"]);
|
||||||
|
assert!(
|
||||||
|
env.allows_via(&PeerRef::Any, "container/exec"),
|
||||||
|
"op in allowed_ops reachable via Any"
|
||||||
|
);
|
||||||
|
assert!(
|
||||||
|
env.allows_via(&PeerRef::Specific("worker-a".to_string()), "container/exec"),
|
||||||
|
"op in both sets reachable via Specific(peer)"
|
||||||
|
);
|
||||||
|
assert!(
|
||||||
|
env.allows_via(&PeerRef::Specific("worker-b".to_string()), "container/exec"),
|
||||||
|
"op in allowed_ops reachable via Specific(other peer) too"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn composition_authority_as_identity_correct() {
|
fn composition_authority_as_identity_correct() {
|
||||||
let mut resources = HashMap::new();
|
let mut resources = HashMap::new();
|
||||||
|
|||||||
@@ -323,7 +323,7 @@ pub fn services_schema_handler(registry: Arc<OperationRegistry>) -> Handler {
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::registry::context::{CompositionAuthority, ScopedOperationEnv};
|
use crate::registry::context::{CompositionAuthority, ScopedPeerEnv};
|
||||||
use crate::registry::registration::{make_handler, HandlerRegistration, OperationProvenance};
|
use crate::registry::registration::{make_handler, HandlerRegistration, OperationProvenance};
|
||||||
use alknet_core::types::Capabilities;
|
use alknet_core::types::Capabilities;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
@@ -389,7 +389,7 @@ mod tests {
|
|||||||
forwarded_for: None,
|
forwarded_for: None,
|
||||||
capabilities: Capabilities::new(),
|
capabilities: Capabilities::new(),
|
||||||
metadata: HashMap::new(),
|
metadata: HashMap::new(),
|
||||||
scoped_env: ScopedOperationEnv::empty(),
|
scoped_env: ScopedPeerEnv::empty(),
|
||||||
env: noop_env(),
|
env: noop_env(),
|
||||||
abort_policy: crate::registry::context::AbortPolicy::default(),
|
abort_policy: crate::registry::context::AbortPolicy::default(),
|
||||||
deadline: Some(std::time::Instant::now() + Duration::from_secs(30)),
|
deadline: Some(std::time::Instant::now() + Duration::from_secs(30)),
|
||||||
@@ -409,7 +409,7 @@ mod tests {
|
|||||||
forwarded_for: None,
|
forwarded_for: None,
|
||||||
capabilities: Capabilities::new(),
|
capabilities: Capabilities::new(),
|
||||||
metadata: HashMap::new(),
|
metadata: HashMap::new(),
|
||||||
scoped_env: ScopedOperationEnv::empty(),
|
scoped_env: ScopedPeerEnv::empty(),
|
||||||
env: noop_env(),
|
env: noop_env(),
|
||||||
abort_policy: crate::registry::context::AbortPolicy::default(),
|
abort_policy: crate::registry::context::AbortPolicy::default(),
|
||||||
deadline: Some(std::time::Instant::now() + Duration::from_secs(30)),
|
deadline: Some(std::time::Instant::now() + Duration::from_secs(30)),
|
||||||
@@ -674,7 +674,7 @@ mod tests {
|
|||||||
list_handler,
|
list_handler,
|
||||||
OperationProvenance::Local,
|
OperationProvenance::Local,
|
||||||
CompositionAuthority::none(),
|
CompositionAuthority::none(),
|
||||||
ScopedOperationEnv::empty().into(),
|
ScopedPeerEnv::empty().into(),
|
||||||
Capabilities::new(),
|
Capabilities::new(),
|
||||||
));
|
));
|
||||||
discovery_registry.register(HandlerRegistration::new(
|
discovery_registry.register(HandlerRegistration::new(
|
||||||
@@ -682,7 +682,7 @@ mod tests {
|
|||||||
schema_handler,
|
schema_handler,
|
||||||
OperationProvenance::Local,
|
OperationProvenance::Local,
|
||||||
CompositionAuthority::none(),
|
CompositionAuthority::none(),
|
||||||
ScopedOperationEnv::empty().into(),
|
ScopedPeerEnv::empty().into(),
|
||||||
Capabilities::new(),
|
Capabilities::new(),
|
||||||
));
|
));
|
||||||
let discovery = Arc::new(discovery_registry);
|
let discovery = Arc::new(discovery_registry);
|
||||||
@@ -836,7 +836,7 @@ mod tests {
|
|||||||
forwarded_for: None,
|
forwarded_for: None,
|
||||||
capabilities: Capabilities::new(),
|
capabilities: Capabilities::new(),
|
||||||
metadata: HashMap::new(),
|
metadata: HashMap::new(),
|
||||||
scoped_env: ScopedOperationEnv::empty(),
|
scoped_env: ScopedPeerEnv::empty(),
|
||||||
env,
|
env,
|
||||||
abort_policy: crate::registry::context::AbortPolicy::default(),
|
abort_policy: crate::registry::context::AbortPolicy::default(),
|
||||||
deadline: Some(std::time::Instant::now() + Duration::from_secs(30)),
|
deadline: Some(std::time::Instant::now() + Duration::from_secs(30)),
|
||||||
@@ -919,7 +919,7 @@ mod tests {
|
|||||||
forwarded_for: None,
|
forwarded_for: None,
|
||||||
capabilities: Capabilities::new(),
|
capabilities: Capabilities::new(),
|
||||||
metadata: HashMap::new(),
|
metadata: HashMap::new(),
|
||||||
scoped_env: ScopedOperationEnv::empty(),
|
scoped_env: ScopedPeerEnv::empty(),
|
||||||
env,
|
env,
|
||||||
abort_policy: crate::registry::context::AbortPolicy::default(),
|
abort_policy: crate::registry::context::AbortPolicy::default(),
|
||||||
deadline: Some(std::time::Instant::now() + Duration::from_secs(30)),
|
deadline: Some(std::time::Instant::now() + Duration::from_secs(30)),
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ use std::sync::Arc;
|
|||||||
|
|
||||||
use serde_json::Value;
|
use serde_json::Value;
|
||||||
|
|
||||||
use super::context::{generate_request_id, AbortPolicy, OperationContext, ScopedOperationEnv};
|
use super::context::{generate_request_id, AbortPolicy, OperationContext, ScopedPeerEnv};
|
||||||
use super::registration::OperationRegistry;
|
use super::registration::OperationRegistry;
|
||||||
use crate::protocol::wire::ResponseEnvelope;
|
use crate::protocol::wire::ResponseEnvelope;
|
||||||
|
|
||||||
@@ -136,7 +136,7 @@ impl OperationEnv for LocalOperationEnv {
|
|||||||
scoped_env: registration
|
scoped_env: registration
|
||||||
.scoped_env
|
.scoped_env
|
||||||
.clone()
|
.clone()
|
||||||
.unwrap_or_else(ScopedOperationEnv::empty),
|
.unwrap_or_else(ScopedPeerEnv::empty),
|
||||||
env: parent.env.clone(),
|
env: parent.env.clone(),
|
||||||
internal: true,
|
internal: true,
|
||||||
};
|
};
|
||||||
@@ -263,19 +263,27 @@ impl OperationEnv for PeerCompositeEnv {
|
|||||||
policy: AbortPolicy,
|
policy: AbortPolicy,
|
||||||
) -> ResponseEnvelope {
|
) -> ResponseEnvelope {
|
||||||
let name = format!("{namespace}/{operation}");
|
let name = format!("{namespace}/{operation}");
|
||||||
if !parent.scoped_env.allows(&name) {
|
|
||||||
return ResponseEnvelope::not_found(parent.request_id.clone(), &name);
|
|
||||||
}
|
|
||||||
match peer {
|
match peer {
|
||||||
PeerRef::Specific(peer_id) => match self.connections.get(peer_id) {
|
PeerRef::Specific(peer_id) => {
|
||||||
Some(conn_env) if conn_env.contains(&name) => {
|
if !parent
|
||||||
conn_env
|
.scoped_env
|
||||||
.invoke_with_policy(namespace, operation, input, parent, policy)
|
.allows_via(&PeerRef::Specific(peer_id.clone()), &name)
|
||||||
.await
|
{
|
||||||
|
return ResponseEnvelope::not_found(parent.request_id.clone(), &name);
|
||||||
}
|
}
|
||||||
_ => ResponseEnvelope::not_found(parent.request_id.clone(), &name),
|
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 => {
|
PeerRef::Any => {
|
||||||
|
if !parent.scoped_env.allows(&name) {
|
||||||
|
return ResponseEnvelope::not_found(parent.request_id.clone(), &name);
|
||||||
|
}
|
||||||
self.invoke_with_policy(namespace, operation, input, parent, policy)
|
self.invoke_with_policy(namespace, operation, input, parent, policy)
|
||||||
.await
|
.await
|
||||||
}
|
}
|
||||||
@@ -353,7 +361,7 @@ mod tests {
|
|||||||
request_id: &str,
|
request_id: &str,
|
||||||
identity: Option<Identity>,
|
identity: Option<Identity>,
|
||||||
handler_identity: Option<CompositionAuthority>,
|
handler_identity: Option<CompositionAuthority>,
|
||||||
scoped_env: ScopedOperationEnv,
|
scoped_env: ScopedPeerEnv,
|
||||||
env: Arc<dyn OperationEnv + Send + Sync>,
|
env: Arc<dyn OperationEnv + Send + Sync>,
|
||||||
) -> OperationContext {
|
) -> OperationContext {
|
||||||
root_context_with_forwarded_for(
|
root_context_with_forwarded_for(
|
||||||
@@ -371,7 +379,7 @@ mod tests {
|
|||||||
identity: Option<Identity>,
|
identity: Option<Identity>,
|
||||||
handler_identity: Option<CompositionAuthority>,
|
handler_identity: Option<CompositionAuthority>,
|
||||||
forwarded_for: Option<Identity>,
|
forwarded_for: Option<Identity>,
|
||||||
scoped_env: ScopedOperationEnv,
|
scoped_env: ScopedPeerEnv,
|
||||||
env: Arc<dyn OperationEnv + Send + Sync>,
|
env: Arc<dyn OperationEnv + Send + Sync>,
|
||||||
) -> OperationContext {
|
) -> OperationContext {
|
||||||
OperationContext {
|
OperationContext {
|
||||||
@@ -395,7 +403,7 @@ mod tests {
|
|||||||
spec_visibility: Visibility,
|
spec_visibility: Visibility,
|
||||||
handler: crate::registry::registration::Handler,
|
handler: crate::registry::registration::Handler,
|
||||||
composition_authority: Option<CompositionAuthority>,
|
composition_authority: Option<CompositionAuthority>,
|
||||||
scoped_env: Option<ScopedOperationEnv>,
|
scoped_env: Option<ScopedPeerEnv>,
|
||||||
) -> Arc<OperationRegistry> {
|
) -> Arc<OperationRegistry> {
|
||||||
let mut registry = OperationRegistry::new();
|
let mut registry = OperationRegistry::new();
|
||||||
registry.register(HandlerRegistration::new(
|
registry.register(HandlerRegistration::new(
|
||||||
@@ -421,7 +429,7 @@ mod tests {
|
|||||||
async fn local_env_invoke_allowed_op_dispatches() {
|
async fn local_env_invoke_allowed_op_dispatches() {
|
||||||
let registry = registry_with("echo/run", Visibility::External, echo_handler(), None, None);
|
let registry = registry_with("echo/run", Visibility::External, echo_handler(), None, None);
|
||||||
let env = Arc::new(LocalOperationEnv::new(Arc::clone(®istry)));
|
let env = Arc::new(LocalOperationEnv::new(Arc::clone(®istry)));
|
||||||
let scoped = ScopedOperationEnv::new(["echo/run"]);
|
let scoped = ScopedPeerEnv::new(["echo/run"]);
|
||||||
let ctx = root_context("root-1", None, None, scoped, env.clone());
|
let ctx = root_context("root-1", None, None, scoped, env.clone());
|
||||||
let response = env
|
let response = env
|
||||||
.invoke("echo", "run", serde_json::json!({"hi": 1}), &ctx)
|
.invoke("echo", "run", serde_json::json!({"hi": 1}), &ctx)
|
||||||
@@ -434,7 +442,7 @@ mod tests {
|
|||||||
async fn local_env_invoke_disallowed_op_returns_not_found() {
|
async fn local_env_invoke_disallowed_op_returns_not_found() {
|
||||||
let registry = registry_with("echo/run", Visibility::External, echo_handler(), None, None);
|
let registry = registry_with("echo/run", Visibility::External, echo_handler(), None, None);
|
||||||
let env = Arc::new(LocalOperationEnv::new(Arc::clone(®istry)));
|
let env = Arc::new(LocalOperationEnv::new(Arc::clone(®istry)));
|
||||||
let scoped = ScopedOperationEnv::new(["other/op"]);
|
let scoped = ScopedPeerEnv::new(["other/op"]);
|
||||||
let ctx = root_context("root-2", None, None, scoped, env.clone());
|
let ctx = root_context("root-2", None, None, scoped, env.clone());
|
||||||
let response = env.invoke("echo", "run", serde_json::json!({}), &ctx).await;
|
let response = env.invoke("echo", "run", serde_json::json!({}), &ctx).await;
|
||||||
match response.result {
|
match response.result {
|
||||||
@@ -453,7 +461,7 @@ mod tests {
|
|||||||
None,
|
None,
|
||||||
);
|
);
|
||||||
let env = Arc::new(LocalOperationEnv::new(Arc::clone(®istry)));
|
let env = Arc::new(LocalOperationEnv::new(Arc::clone(®istry)));
|
||||||
let scoped = ScopedOperationEnv::new(["secret/op"]);
|
let scoped = ScopedPeerEnv::new(["secret/op"]);
|
||||||
let ctx = root_context("root-3", None, None, scoped, env.clone());
|
let ctx = root_context("root-3", None, None, scoped, env.clone());
|
||||||
let response = env
|
let response = env
|
||||||
.invoke("secret", "op", serde_json::json!({}), &ctx)
|
.invoke("secret", "op", serde_json::json!({}), &ctx)
|
||||||
@@ -474,7 +482,7 @@ mod tests {
|
|||||||
None,
|
None,
|
||||||
);
|
);
|
||||||
let env = Arc::new(LocalOperationEnv::new(Arc::clone(®istry)));
|
let env = Arc::new(LocalOperationEnv::new(Arc::clone(®istry)));
|
||||||
let scoped = ScopedOperationEnv::new(["child/run"]);
|
let scoped = ScopedPeerEnv::new(["child/run"]);
|
||||||
let ctx = root_context(
|
let ctx = root_context(
|
||||||
"root-4",
|
"root-4",
|
||||||
Some(Identity {
|
Some(Identity {
|
||||||
@@ -503,7 +511,7 @@ mod tests {
|
|||||||
None,
|
None,
|
||||||
);
|
);
|
||||||
let env = Arc::new(LocalOperationEnv::new(Arc::clone(®istry)));
|
let env = Arc::new(LocalOperationEnv::new(Arc::clone(®istry)));
|
||||||
let scoped = ScopedOperationEnv::new(["child/run"]);
|
let scoped = ScopedPeerEnv::new(["child/run"]);
|
||||||
let mut ctx = root_context("root-5", None, None, scoped, env.clone());
|
let mut ctx = root_context("root-5", None, None, scoped, env.clone());
|
||||||
ctx.metadata
|
ctx.metadata
|
||||||
.insert("secret".to_string(), Value::String("leak".into()));
|
.insert("secret".to_string(), Value::String("leak".into()));
|
||||||
@@ -524,7 +532,7 @@ mod tests {
|
|||||||
None,
|
None,
|
||||||
);
|
);
|
||||||
let env = Arc::new(LocalOperationEnv::new(Arc::clone(®istry)));
|
let env = Arc::new(LocalOperationEnv::new(Arc::clone(®istry)));
|
||||||
let scoped = ScopedOperationEnv::new(["child/run"]);
|
let scoped = ScopedPeerEnv::new(["child/run"]);
|
||||||
let forwarded = Identity {
|
let forwarded = Identity {
|
||||||
id: "alice".to_string(),
|
id: "alice".to_string(),
|
||||||
scopes: vec![],
|
scopes: vec![],
|
||||||
@@ -584,7 +592,7 @@ mod tests {
|
|||||||
});
|
});
|
||||||
let composite = PeerCompositeEnv::new(base).with_session(session.clone());
|
let composite = PeerCompositeEnv::new(base).with_session(session.clone());
|
||||||
let env: Arc<dyn OperationEnv + Send + Sync> = Arc::new(composite);
|
let env: Arc<dyn OperationEnv + Send + Sync> = Arc::new(composite);
|
||||||
let scoped = ScopedOperationEnv::new(["agent/chat"]);
|
let scoped = ScopedPeerEnv::new(["agent/chat"]);
|
||||||
let ctx = root_context("root-6", None, None, scoped, env.clone());
|
let ctx = root_context("root-6", None, None, scoped, env.clone());
|
||||||
let response = env
|
let response = env
|
||||||
.invoke("agent", "chat", serde_json::json!({}), &ctx)
|
.invoke("agent", "chat", serde_json::json!({}), &ctx)
|
||||||
@@ -617,7 +625,7 @@ mod tests {
|
|||||||
composite.attach_peer("worker-a".to_string(), worker_a.clone());
|
composite.attach_peer("worker-a".to_string(), worker_a.clone());
|
||||||
composite.attach_peer("worker-b".to_string(), worker_b.clone());
|
composite.attach_peer("worker-b".to_string(), worker_b.clone());
|
||||||
let env: Arc<dyn OperationEnv + Send + Sync> = Arc::new(composite);
|
let env: Arc<dyn OperationEnv + Send + Sync> = Arc::new(composite);
|
||||||
let scoped = ScopedOperationEnv::new(["worker/exec"]);
|
let scoped = ScopedPeerEnv::new(["worker/exec"]);
|
||||||
let ctx = root_context("root-7", None, None, scoped, env.clone());
|
let ctx = root_context("root-7", None, None, scoped, env.clone());
|
||||||
let response = env
|
let response = env
|
||||||
.invoke("worker", "exec", serde_json::json!({}), &ctx)
|
.invoke("worker", "exec", serde_json::json!({}), &ctx)
|
||||||
@@ -650,7 +658,7 @@ mod tests {
|
|||||||
let mut composite = PeerCompositeEnv::new(base.clone()).with_session(session);
|
let mut composite = PeerCompositeEnv::new(base.clone()).with_session(session);
|
||||||
composite.attach_peer("worker-a".to_string(), connection);
|
composite.attach_peer("worker-a".to_string(), connection);
|
||||||
let env: Arc<dyn OperationEnv + Send + Sync> = Arc::new(composite);
|
let env: Arc<dyn OperationEnv + Send + Sync> = Arc::new(composite);
|
||||||
let scoped = ScopedOperationEnv::new(["fs/readFile"]);
|
let scoped = ScopedPeerEnv::new(["fs/readFile"]);
|
||||||
let ctx = root_context("root-8", None, None, scoped, env.clone());
|
let ctx = root_context("root-8", None, None, scoped, env.clone());
|
||||||
let response = env
|
let response = env
|
||||||
.invoke("fs", "readFile", serde_json::json!({}), &ctx)
|
.invoke("fs", "readFile", serde_json::json!({}), &ctx)
|
||||||
@@ -667,7 +675,7 @@ mod tests {
|
|||||||
let base = Arc::new(NoopEnv { contains_op: true });
|
let base = Arc::new(NoopEnv { contains_op: true });
|
||||||
let composite = PeerCompositeEnv::new(base);
|
let composite = PeerCompositeEnv::new(base);
|
||||||
let env: Arc<dyn OperationEnv + Send + Sync> = Arc::new(composite);
|
let env: Arc<dyn OperationEnv + Send + Sync> = Arc::new(composite);
|
||||||
let scoped = ScopedOperationEnv::empty();
|
let scoped = ScopedPeerEnv::empty();
|
||||||
let ctx = root_context("root-9", None, None, scoped, env.clone());
|
let ctx = root_context("root-9", None, None, scoped, env.clone());
|
||||||
let response = env
|
let response = env
|
||||||
.invoke("agent", "chat", serde_json::json!({}), &ctx)
|
.invoke("agent", "chat", serde_json::json!({}), &ctx)
|
||||||
@@ -716,7 +724,7 @@ mod tests {
|
|||||||
composite.attach_peer("worker-a".to_string(), connection.clone());
|
composite.attach_peer("worker-a".to_string(), connection.clone());
|
||||||
composite.detach_peer(&"worker-a".to_string());
|
composite.detach_peer(&"worker-a".to_string());
|
||||||
let env: Arc<dyn OperationEnv + Send + Sync> = Arc::new(composite);
|
let env: Arc<dyn OperationEnv + Send + Sync> = Arc::new(composite);
|
||||||
let scoped = ScopedOperationEnv::new(["worker/exec"]);
|
let scoped = ScopedPeerEnv::new(["worker/exec"]);
|
||||||
let ctx = root_context("root-10", None, None, scoped, env.clone());
|
let ctx = root_context("root-10", None, None, scoped, env.clone());
|
||||||
let response = env
|
let response = env
|
||||||
.invoke("worker", "exec", serde_json::json!({}), &ctx)
|
.invoke("worker", "exec", serde_json::json!({}), &ctx)
|
||||||
@@ -742,7 +750,7 @@ mod tests {
|
|||||||
composite.detach_peer(&"worker-a".to_string());
|
composite.detach_peer(&"worker-a".to_string());
|
||||||
composite.attach_peer("worker-a".to_string(), connection.clone());
|
composite.attach_peer("worker-a".to_string(), connection.clone());
|
||||||
let env: Arc<dyn OperationEnv + Send + Sync> = Arc::new(composite);
|
let env: Arc<dyn OperationEnv + Send + Sync> = Arc::new(composite);
|
||||||
let scoped = ScopedOperationEnv::new(["worker/exec"]);
|
let scoped = ScopedPeerEnv::new(["worker/exec"]);
|
||||||
let ctx = root_context("root-10b", None, None, scoped, env.clone());
|
let ctx = root_context("root-10b", None, None, scoped, env.clone());
|
||||||
let response = env
|
let response = env
|
||||||
.invoke("worker", "exec", serde_json::json!({}), &ctx)
|
.invoke("worker", "exec", serde_json::json!({}), &ctx)
|
||||||
@@ -795,7 +803,7 @@ mod tests {
|
|||||||
let mut composite = PeerCompositeEnv::new(base).with_session(session);
|
let mut composite = PeerCompositeEnv::new(base).with_session(session);
|
||||||
composite.attach_peer("worker-a".to_string(), connection.clone());
|
composite.attach_peer("worker-a".to_string(), connection.clone());
|
||||||
let env: Arc<dyn OperationEnv + Send + Sync> = Arc::new(composite);
|
let env: Arc<dyn OperationEnv + Send + Sync> = Arc::new(composite);
|
||||||
let scoped = ScopedOperationEnv::new(["worker/exec"]);
|
let scoped = ScopedPeerEnv::new(["worker/exec"]);
|
||||||
let ctx = root_context("root-11", None, None, scoped, env.clone());
|
let ctx = root_context("root-11", None, None, scoped, env.clone());
|
||||||
let response = env
|
let response = env
|
||||||
.invoke("worker", "exec", serde_json::json!({}), &ctx)
|
.invoke("worker", "exec", serde_json::json!({}), &ctx)
|
||||||
@@ -811,7 +819,7 @@ mod tests {
|
|||||||
async fn local_env_unknown_op_after_reachability_pass_returns_not_found() {
|
async fn local_env_unknown_op_after_reachability_pass_returns_not_found() {
|
||||||
let registry = Arc::new(OperationRegistry::new());
|
let registry = Arc::new(OperationRegistry::new());
|
||||||
let env = Arc::new(LocalOperationEnv::new(Arc::clone(®istry)));
|
let env = Arc::new(LocalOperationEnv::new(Arc::clone(®istry)));
|
||||||
let scoped = ScopedOperationEnv::new(["fs/readFile"]);
|
let scoped = ScopedPeerEnv::new(["fs/readFile"]);
|
||||||
let ctx = root_context("root-12", None, None, scoped, env.clone());
|
let ctx = root_context("root-12", None, None, scoped, env.clone());
|
||||||
let response = env
|
let response = env
|
||||||
.invoke("fs", "readFile", serde_json::json!({}), &ctx)
|
.invoke("fs", "readFile", serde_json::json!({}), &ctx)
|
||||||
@@ -832,7 +840,7 @@ mod tests {
|
|||||||
None,
|
None,
|
||||||
);
|
);
|
||||||
let env = Arc::new(LocalOperationEnv::new(Arc::clone(®istry)));
|
let env = Arc::new(LocalOperationEnv::new(Arc::clone(®istry)));
|
||||||
let scoped = ScopedOperationEnv::new(["child/run"]);
|
let scoped = ScopedPeerEnv::new(["child/run"]);
|
||||||
let deadline = Instant::now() + Duration::from_secs(5);
|
let deadline = Instant::now() + Duration::from_secs(5);
|
||||||
let mut ctx = root_context("root-13", None, None, scoped, env.clone());
|
let mut ctx = root_context("root-13", None, None, scoped, env.clone());
|
||||||
ctx.deadline = Some(deadline);
|
ctx.deadline = Some(deadline);
|
||||||
@@ -917,7 +925,7 @@ mod tests {
|
|||||||
composite.attach_peer("worker-a".to_string(), worker_a.clone());
|
composite.attach_peer("worker-a".to_string(), worker_a.clone());
|
||||||
composite.attach_peer("worker-b".to_string(), worker_b.clone());
|
composite.attach_peer("worker-b".to_string(), worker_b.clone());
|
||||||
let env: Arc<dyn OperationEnv + Send + Sync> = Arc::new(composite);
|
let env: Arc<dyn OperationEnv + Send + Sync> = Arc::new(composite);
|
||||||
let scoped = ScopedOperationEnv::new(["worker/exec"]);
|
let scoped = ScopedPeerEnv::new(["worker/exec"]);
|
||||||
let ctx = root_context("root-pr-1", None, None, scoped, env.clone());
|
let ctx = root_context("root-pr-1", None, None, scoped, env.clone());
|
||||||
let response = env
|
let response = env
|
||||||
.invoke_peer(
|
.invoke_peer(
|
||||||
@@ -952,7 +960,7 @@ mod tests {
|
|||||||
let mut composite = PeerCompositeEnv::new(base.clone());
|
let mut composite = PeerCompositeEnv::new(base.clone());
|
||||||
composite.attach_peer("worker-a".to_string(), worker_a.clone());
|
composite.attach_peer("worker-a".to_string(), worker_a.clone());
|
||||||
let env: Arc<dyn OperationEnv + Send + Sync> = Arc::new(composite);
|
let env: Arc<dyn OperationEnv + Send + Sync> = Arc::new(composite);
|
||||||
let scoped = ScopedOperationEnv::new(["worker/exec"]);
|
let scoped = ScopedPeerEnv::new(["worker/exec"]);
|
||||||
let ctx = root_context("root-pr-2", None, None, scoped, env.clone());
|
let ctx = root_context("root-pr-2", None, None, scoped, env.clone());
|
||||||
let response = env
|
let response = env
|
||||||
.invoke_peer(
|
.invoke_peer(
|
||||||
@@ -992,7 +1000,7 @@ mod tests {
|
|||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
let env: Arc<dyn OperationEnv + Send + Sync> = Arc::new(composite);
|
let env: Arc<dyn OperationEnv + Send + Sync> = Arc::new(composite);
|
||||||
let scoped = ScopedOperationEnv::new(["worker/exec"]);
|
let scoped = ScopedPeerEnv::new(["worker/exec"]);
|
||||||
let ctx = root_context("root-pr-3", None, None, scoped, env.clone());
|
let ctx = root_context("root-pr-3", None, None, scoped, env.clone());
|
||||||
let response = env
|
let response = env
|
||||||
.invoke_peer(
|
.invoke_peer(
|
||||||
@@ -1032,7 +1040,7 @@ mod tests {
|
|||||||
composite.attach_peer("worker-a".to_string(), worker_a.clone());
|
composite.attach_peer("worker-a".to_string(), worker_a.clone());
|
||||||
composite.attach_peer("worker-b".to_string(), worker_b.clone());
|
composite.attach_peer("worker-b".to_string(), worker_b.clone());
|
||||||
let env: Arc<dyn OperationEnv + Send + Sync> = Arc::new(composite);
|
let env: Arc<dyn OperationEnv + Send + Sync> = Arc::new(composite);
|
||||||
let scoped = ScopedOperationEnv::new(["worker/exec"]);
|
let scoped = ScopedPeerEnv::new(["worker/exec"]);
|
||||||
let ctx = root_context("root-pr-4", None, None, scoped, env.clone());
|
let ctx = root_context("root-pr-4", None, None, scoped, env.clone());
|
||||||
let response = env
|
let response = env
|
||||||
.invoke_peer(
|
.invoke_peer(
|
||||||
@@ -1063,7 +1071,7 @@ mod tests {
|
|||||||
let mut composite = PeerCompositeEnv::new(base);
|
let mut composite = PeerCompositeEnv::new(base);
|
||||||
composite.attach_peer("worker-a".to_string(), worker_a.clone());
|
composite.attach_peer("worker-a".to_string(), worker_a.clone());
|
||||||
let env: Arc<dyn OperationEnv + Send + Sync> = Arc::new(composite);
|
let env: Arc<dyn OperationEnv + Send + Sync> = Arc::new(composite);
|
||||||
let scoped = ScopedOperationEnv::empty();
|
let scoped = ScopedPeerEnv::empty();
|
||||||
let ctx = root_context("root-pr-5", None, None, scoped, env.clone());
|
let ctx = root_context("root-pr-5", None, None, scoped, env.clone());
|
||||||
let response = env
|
let response = env
|
||||||
.invoke_peer(
|
.invoke_peer(
|
||||||
@@ -1111,7 +1119,7 @@ mod tests {
|
|||||||
async fn default_invoke_peer_delegates_to_invoke_with_policy() {
|
async fn default_invoke_peer_delegates_to_invoke_with_policy() {
|
||||||
let registry = registry_with("echo/run", Visibility::External, echo_handler(), None, None);
|
let registry = registry_with("echo/run", Visibility::External, echo_handler(), None, None);
|
||||||
let env = Arc::new(LocalOperationEnv::new(Arc::clone(®istry)));
|
let env = Arc::new(LocalOperationEnv::new(Arc::clone(®istry)));
|
||||||
let scoped = ScopedOperationEnv::new(["echo/run"]);
|
let scoped = ScopedPeerEnv::new(["echo/run"]);
|
||||||
let ctx = root_context("root-pr-6", None, None, scoped, env.clone());
|
let ctx = root_context("root-pr-6", None, None, scoped, env.clone());
|
||||||
let response = env
|
let response = env
|
||||||
.invoke_peer(
|
.invoke_peer(
|
||||||
@@ -1133,4 +1141,178 @@ mod tests {
|
|||||||
let env = LocalOperationEnv::new(registry);
|
let env = LocalOperationEnv::new(registry);
|
||||||
assert!(env.peer_contains(&"any-peer".to_string(), "anything"));
|
assert!(env.peer_contains(&"any-peer".to_string(), "anything"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// --- ADR-029 §4: peer-pinned reachability gate -------------------------
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn invoke_peer_specific_pinned_only_op_reaches_pinned_peer() {
|
||||||
|
let base = Arc::new(NoopEnv { contains_op: true });
|
||||||
|
let worker_a = Arc::new(ProbeEnv {
|
||||||
|
name: "worker-a".to_string(),
|
||||||
|
contains_set: vec!["container/exec".to_string()],
|
||||||
|
dispatched: std::sync::Mutex::new(None),
|
||||||
|
});
|
||||||
|
let mut composite = PeerCompositeEnv::new(base);
|
||||||
|
composite.attach_peer("worker-a".to_string(), worker_a.clone());
|
||||||
|
let env: Arc<dyn OperationEnv + Send + Sync> = Arc::new(composite);
|
||||||
|
let scoped = ScopedPeerEnv::empty().with_pinned(["worker-a/container/exec"]);
|
||||||
|
let ctx = root_context("root-pin-1", None, None, scoped, env.clone());
|
||||||
|
let response = env
|
||||||
|
.invoke_peer(
|
||||||
|
&PeerRef::Specific("worker-a".to_string()),
|
||||||
|
"container",
|
||||||
|
"exec",
|
||||||
|
serde_json::json!({}),
|
||||||
|
&ctx,
|
||||||
|
AbortPolicy::default(),
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
assert_eq!(response.result.unwrap(), Value::String("worker-a".into()));
|
||||||
|
assert_eq!(
|
||||||
|
worker_a.dispatched.lock().unwrap().as_deref(),
|
||||||
|
Some("container/exec")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn invoke_peer_any_pinned_only_op_returns_not_found() {
|
||||||
|
let base = Arc::new(NoopEnv { contains_op: true });
|
||||||
|
let worker_a = Arc::new(ProbeEnv {
|
||||||
|
name: "worker-a".to_string(),
|
||||||
|
contains_set: vec!["container/exec".to_string()],
|
||||||
|
dispatched: std::sync::Mutex::new(None),
|
||||||
|
});
|
||||||
|
let mut composite = PeerCompositeEnv::new(base);
|
||||||
|
composite.attach_peer("worker-a".to_string(), worker_a.clone());
|
||||||
|
let env: Arc<dyn OperationEnv + Send + Sync> = Arc::new(composite);
|
||||||
|
let scoped = ScopedPeerEnv::empty().with_pinned(["worker-a/container/exec"]);
|
||||||
|
let ctx = root_context("root-pin-2", None, None, scoped, env.clone());
|
||||||
|
let response = env
|
||||||
|
.invoke_peer(
|
||||||
|
&PeerRef::Any,
|
||||||
|
"container",
|
||||||
|
"exec",
|
||||||
|
serde_json::json!({}),
|
||||||
|
&ctx,
|
||||||
|
AbortPolicy::default(),
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
match response.result {
|
||||||
|
Err(e) => assert_eq!(e.code, "NOT_FOUND", "pinned-only op NOT reachable via Any"),
|
||||||
|
other => panic!("expected NOT_FOUND via Any, got {other:?}"),
|
||||||
|
}
|
||||||
|
assert!(worker_a.dispatched.lock().unwrap().is_none());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn invoke_with_policy_does_not_pick_up_pinned_only_ops() {
|
||||||
|
let base = Arc::new(NoopEnv { contains_op: true });
|
||||||
|
let worker_a = Arc::new(ProbeEnv {
|
||||||
|
name: "worker-a".to_string(),
|
||||||
|
contains_set: vec!["container/exec".to_string()],
|
||||||
|
dispatched: std::sync::Mutex::new(None),
|
||||||
|
});
|
||||||
|
let mut composite = PeerCompositeEnv::new(base);
|
||||||
|
composite.attach_peer("worker-a".to_string(), worker_a.clone());
|
||||||
|
let env: Arc<dyn OperationEnv + Send + Sync> = Arc::new(composite);
|
||||||
|
let scoped = ScopedPeerEnv::empty().with_pinned(["worker-a/container/exec"]);
|
||||||
|
let ctx = root_context("root-pin-3", None, None, scoped, env.clone());
|
||||||
|
let response = env
|
||||||
|
.invoke("container", "exec", serde_json::json!({}), &ctx)
|
||||||
|
.await;
|
||||||
|
match response.result {
|
||||||
|
Err(e) => assert_eq!(
|
||||||
|
e.code, "NOT_FOUND",
|
||||||
|
"invoke_with_policy (Any path) must NOT pick up pinned-only ops"
|
||||||
|
),
|
||||||
|
other => panic!("expected NOT_FOUND, got {other:?}"),
|
||||||
|
}
|
||||||
|
assert!(worker_a.dispatched.lock().unwrap().is_none());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn invoke_peer_specific_wrong_peer_for_pinned_only_op_returns_not_found() {
|
||||||
|
let base = Arc::new(NoopEnv { contains_op: true });
|
||||||
|
let worker_a = Arc::new(ProbeEnv {
|
||||||
|
name: "worker-a".to_string(),
|
||||||
|
contains_set: vec!["container/exec".to_string()],
|
||||||
|
dispatched: std::sync::Mutex::new(None),
|
||||||
|
});
|
||||||
|
let worker_b = Arc::new(ProbeEnv {
|
||||||
|
name: "worker-b".to_string(),
|
||||||
|
contains_set: vec!["container/exec".to_string()],
|
||||||
|
dispatched: std::sync::Mutex::new(None),
|
||||||
|
});
|
||||||
|
let mut composite = PeerCompositeEnv::new(base);
|
||||||
|
composite.attach_peer("worker-a".to_string(), worker_a.clone());
|
||||||
|
composite.attach_peer("worker-b".to_string(), worker_b.clone());
|
||||||
|
let env: Arc<dyn OperationEnv + Send + Sync> = Arc::new(composite);
|
||||||
|
let scoped = ScopedPeerEnv::empty().with_pinned(["worker-a/container/exec"]);
|
||||||
|
let ctx = root_context("root-pin-4", None, None, scoped, env.clone());
|
||||||
|
let response = env
|
||||||
|
.invoke_peer(
|
||||||
|
&PeerRef::Specific("worker-b".to_string()),
|
||||||
|
"container",
|
||||||
|
"exec",
|
||||||
|
serde_json::json!({}),
|
||||||
|
&ctx,
|
||||||
|
AbortPolicy::default(),
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
match response.result {
|
||||||
|
Err(e) => assert_eq!(
|
||||||
|
e.code, "NOT_FOUND",
|
||||||
|
"pinned to worker-a, routed to worker-b → NOT_FOUND"
|
||||||
|
),
|
||||||
|
other => panic!("expected NOT_FOUND, got {other:?}"),
|
||||||
|
}
|
||||||
|
assert!(worker_a.dispatched.lock().unwrap().is_none());
|
||||||
|
assert!(worker_b.dispatched.lock().unwrap().is_none());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn invoke_peer_op_in_both_sets_reachable_via_both_any_and_specific() {
|
||||||
|
let base = Arc::new(NoopEnv { contains_op: true });
|
||||||
|
let worker_a = Arc::new(ProbeEnv {
|
||||||
|
name: "worker-a".to_string(),
|
||||||
|
contains_set: vec!["container/exec".to_string()],
|
||||||
|
dispatched: std::sync::Mutex::new(None),
|
||||||
|
});
|
||||||
|
let mut composite = PeerCompositeEnv::new(base);
|
||||||
|
composite.attach_peer("worker-a".to_string(), worker_a.clone());
|
||||||
|
let env: Arc<dyn OperationEnv + Send + Sync> = Arc::new(composite);
|
||||||
|
let scoped =
|
||||||
|
ScopedPeerEnv::new(["container/exec"]).with_pinned(["worker-a/container/exec"]);
|
||||||
|
let ctx = root_context("root-pin-5", None, None, scoped, env.clone());
|
||||||
|
|
||||||
|
let response_any = env
|
||||||
|
.invoke_peer(
|
||||||
|
&PeerRef::Any,
|
||||||
|
"container",
|
||||||
|
"exec",
|
||||||
|
serde_json::json!({}),
|
||||||
|
&ctx,
|
||||||
|
AbortPolicy::default(),
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
assert!(
|
||||||
|
response_any.result.is_ok(),
|
||||||
|
"op in allowed_ops reachable via Any"
|
||||||
|
);
|
||||||
|
|
||||||
|
let response_specific = env
|
||||||
|
.invoke_peer(
|
||||||
|
&PeerRef::Specific("worker-a".to_string()),
|
||||||
|
"container",
|
||||||
|
"exec",
|
||||||
|
serde_json::json!({}),
|
||||||
|
&ctx,
|
||||||
|
AbortPolicy::default(),
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
assert!(
|
||||||
|
response_specific.result.is_ok(),
|
||||||
|
"op in both sets reachable via Specific(peer)"
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ use std::sync::Arc;
|
|||||||
use alknet_core::types::Capabilities;
|
use alknet_core::types::Capabilities;
|
||||||
use serde_json::Value;
|
use serde_json::Value;
|
||||||
|
|
||||||
use super::context::{CompositionAuthority, OperationContext, ScopedOperationEnv};
|
use super::context::{CompositionAuthority, OperationContext, ScopedPeerEnv};
|
||||||
use super::spec::{AccessResult, OperationSpec, Visibility};
|
use super::spec::{AccessResult, OperationSpec, Visibility};
|
||||||
use crate::protocol::wire::ResponseEnvelope;
|
use crate::protocol::wire::ResponseEnvelope;
|
||||||
|
|
||||||
@@ -31,7 +31,7 @@ pub struct HandlerRegistration {
|
|||||||
pub handler: Handler,
|
pub handler: Handler,
|
||||||
pub provenance: OperationProvenance,
|
pub provenance: OperationProvenance,
|
||||||
pub composition_authority: Option<CompositionAuthority>,
|
pub composition_authority: Option<CompositionAuthority>,
|
||||||
pub scoped_env: Option<ScopedOperationEnv>,
|
pub scoped_env: Option<ScopedPeerEnv>,
|
||||||
pub capabilities: Capabilities,
|
pub capabilities: Capabilities,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -41,7 +41,7 @@ impl HandlerRegistration {
|
|||||||
handler: Handler,
|
handler: Handler,
|
||||||
provenance: OperationProvenance,
|
provenance: OperationProvenance,
|
||||||
composition_authority: Option<CompositionAuthority>,
|
composition_authority: Option<CompositionAuthority>,
|
||||||
scoped_env: Option<ScopedOperationEnv>,
|
scoped_env: Option<ScopedPeerEnv>,
|
||||||
capabilities: Capabilities,
|
capabilities: Capabilities,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
Self {
|
Self {
|
||||||
@@ -146,7 +146,7 @@ impl OperationRegistryBuilder {
|
|||||||
spec: OperationSpec,
|
spec: OperationSpec,
|
||||||
handler: Handler,
|
handler: Handler,
|
||||||
composition_authority: Option<CompositionAuthority>,
|
composition_authority: Option<CompositionAuthority>,
|
||||||
scoped_env: Option<ScopedOperationEnv>,
|
scoped_env: Option<ScopedPeerEnv>,
|
||||||
capabilities: Capabilities,
|
capabilities: Capabilities,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
let registration = HandlerRegistration::new(
|
let registration = HandlerRegistration::new(
|
||||||
@@ -247,7 +247,7 @@ mod tests {
|
|||||||
identity: Option<Identity>,
|
identity: Option<Identity>,
|
||||||
handler_identity: Option<CompositionAuthority>,
|
handler_identity: Option<CompositionAuthority>,
|
||||||
internal: bool,
|
internal: bool,
|
||||||
scoped_env: ScopedOperationEnv,
|
scoped_env: ScopedPeerEnv,
|
||||||
) -> OperationContext {
|
) -> OperationContext {
|
||||||
OperationContext {
|
OperationContext {
|
||||||
request_id: request_id.to_string(),
|
request_id: request_id.to_string(),
|
||||||
@@ -320,7 +320,7 @@ mod tests {
|
|||||||
None,
|
None,
|
||||||
Capabilities::new(),
|
Capabilities::new(),
|
||||||
));
|
));
|
||||||
let ctx = root_context("req-1", None, None, false, ScopedOperationEnv::empty());
|
let ctx = root_context("req-1", None, None, false, ScopedPeerEnv::empty());
|
||||||
let response = registry
|
let response = registry
|
||||||
.invoke("echo", serde_json::json!({"hi": 1}), ctx)
|
.invoke("echo", serde_json::json!({"hi": 1}), ctx)
|
||||||
.await;
|
.await;
|
||||||
@@ -339,7 +339,7 @@ mod tests {
|
|||||||
None,
|
None,
|
||||||
Capabilities::new(),
|
Capabilities::new(),
|
||||||
));
|
));
|
||||||
let ctx = root_context("req-2", None, None, false, ScopedOperationEnv::empty());
|
let ctx = root_context("req-2", None, None, false, ScopedPeerEnv::empty());
|
||||||
let response = registry.invoke("secret", serde_json::json!({}), ctx).await;
|
let response = registry.invoke("secret", serde_json::json!({}), ctx).await;
|
||||||
match response.result {
|
match response.result {
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
@@ -361,7 +361,7 @@ mod tests {
|
|||||||
None,
|
None,
|
||||||
Capabilities::new(),
|
Capabilities::new(),
|
||||||
));
|
));
|
||||||
let ctx = root_context("req-3", None, None, true, ScopedOperationEnv::empty());
|
let ctx = root_context("req-3", None, None, true, ScopedPeerEnv::empty());
|
||||||
let response = registry
|
let response = registry
|
||||||
.invoke("secret", serde_json::json!({"x": 2}), ctx)
|
.invoke("secret", serde_json::json!({"x": 2}), ctx)
|
||||||
.await;
|
.await;
|
||||||
@@ -372,7 +372,7 @@ mod tests {
|
|||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn unknown_op_returns_not_found() {
|
async fn unknown_op_returns_not_found() {
|
||||||
let registry = OperationRegistry::new();
|
let registry = OperationRegistry::new();
|
||||||
let ctx = root_context("req-4", None, None, false, ScopedOperationEnv::empty());
|
let ctx = root_context("req-4", None, None, false, ScopedPeerEnv::empty());
|
||||||
let response = registry.invoke("missing", serde_json::json!({}), ctx).await;
|
let response = registry.invoke("missing", serde_json::json!({}), ctx).await;
|
||||||
match response.result {
|
match response.result {
|
||||||
Err(e) => assert_eq!(e.code, "NOT_FOUND"),
|
Err(e) => assert_eq!(e.code, "NOT_FOUND"),
|
||||||
@@ -402,7 +402,7 @@ mod tests {
|
|||||||
Some(identity_with_scopes("caller", &["admin"])),
|
Some(identity_with_scopes("caller", &["admin"])),
|
||||||
None,
|
None,
|
||||||
false,
|
false,
|
||||||
ScopedOperationEnv::empty(),
|
ScopedPeerEnv::empty(),
|
||||||
);
|
);
|
||||||
let response = registry.invoke("admin", serde_json::json!({}), ctx).await;
|
let response = registry.invoke("admin", serde_json::json!({}), ctx).await;
|
||||||
assert!(response.result.is_ok());
|
assert!(response.result.is_ok());
|
||||||
@@ -430,7 +430,7 @@ mod tests {
|
|||||||
Some(identity_with_scopes("caller", &["user"])),
|
Some(identity_with_scopes("caller", &["user"])),
|
||||||
None,
|
None,
|
||||||
false,
|
false,
|
||||||
ScopedOperationEnv::empty(),
|
ScopedPeerEnv::empty(),
|
||||||
);
|
);
|
||||||
let response = registry.invoke("admin", serde_json::json!({}), ctx).await;
|
let response = registry.invoke("admin", serde_json::json!({}), ctx).await;
|
||||||
match response.result {
|
match response.result {
|
||||||
@@ -459,7 +459,7 @@ mod tests {
|
|||||||
None,
|
None,
|
||||||
Capabilities::new(),
|
Capabilities::new(),
|
||||||
));
|
));
|
||||||
let ctx = root_context("req-7", None, None, false, ScopedOperationEnv::empty());
|
let ctx = root_context("req-7", None, None, false, ScopedPeerEnv::empty());
|
||||||
let response = registry.invoke("admin", serde_json::json!({}), ctx).await;
|
let response = registry.invoke("admin", serde_json::json!({}), ctx).await;
|
||||||
match response.result {
|
match response.result {
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
@@ -493,7 +493,7 @@ mod tests {
|
|||||||
Some(identity_with_scopes("user", &["user"])),
|
Some(identity_with_scopes("user", &["user"])),
|
||||||
Some(composing_authority),
|
Some(composing_authority),
|
||||||
true,
|
true,
|
||||||
ScopedOperationEnv::empty(),
|
ScopedPeerEnv::empty(),
|
||||||
);
|
);
|
||||||
let response = registry.invoke("secret", serde_json::json!({}), ctx).await;
|
let response = registry.invoke("secret", serde_json::json!({}), ctx).await;
|
||||||
assert!(
|
assert!(
|
||||||
@@ -525,7 +525,7 @@ mod tests {
|
|||||||
Some(identity_with_scopes("user", &["admin"])),
|
Some(identity_with_scopes("user", &["admin"])),
|
||||||
Some(weak_authority),
|
Some(weak_authority),
|
||||||
true,
|
true,
|
||||||
ScopedOperationEnv::empty(),
|
ScopedPeerEnv::empty(),
|
||||||
);
|
);
|
||||||
let response = registry.invoke("secret", serde_json::json!({}), ctx).await;
|
let response = registry.invoke("secret", serde_json::json!({}), ctx).await;
|
||||||
match response.result {
|
match response.result {
|
||||||
@@ -560,7 +560,7 @@ mod tests {
|
|||||||
Some(identity_with_scopes("user", &["user"])),
|
Some(identity_with_scopes("user", &["user"])),
|
||||||
Some(CompositionAuthority::new("agent", ["admin".to_string()])),
|
Some(CompositionAuthority::new("agent", ["admin".to_string()])),
|
||||||
false,
|
false,
|
||||||
ScopedOperationEnv::empty(),
|
ScopedPeerEnv::empty(),
|
||||||
);
|
);
|
||||||
let response = registry.invoke("gate", serde_json::json!({}), ctx).await;
|
let response = registry.invoke("gate", serde_json::json!({}), ctx).await;
|
||||||
match response.result {
|
match response.result {
|
||||||
@@ -604,7 +604,7 @@ mod tests {
|
|||||||
None,
|
None,
|
||||||
Capabilities::new(),
|
Capabilities::new(),
|
||||||
));
|
));
|
||||||
let ctx = root_context("req-11", None, None, false, ScopedOperationEnv::empty());
|
let ctx = root_context("req-11", None, None, false, ScopedPeerEnv::empty());
|
||||||
let response = registry.invoke("boom", serde_json::json!({}), ctx).await;
|
let response = registry.invoke("boom", serde_json::json!({}), ctx).await;
|
||||||
match response.result {
|
match response.result {
|
||||||
Err(e) => assert_eq!(e.code, "INTERNAL"),
|
Err(e) => assert_eq!(e.code, "INTERNAL"),
|
||||||
@@ -619,7 +619,7 @@ mod tests {
|
|||||||
external_spec("echo", AccessControl::default()),
|
external_spec("echo", AccessControl::default()),
|
||||||
echo_handler(),
|
echo_handler(),
|
||||||
CompositionAuthority::none(),
|
CompositionAuthority::none(),
|
||||||
ScopedOperationEnv::empty().into(),
|
ScopedPeerEnv::empty().into(),
|
||||||
Capabilities::new(),
|
Capabilities::new(),
|
||||||
)
|
)
|
||||||
.build();
|
.build();
|
||||||
@@ -636,7 +636,7 @@ mod tests {
|
|||||||
external_spec("agent", AccessControl::default()),
|
external_spec("agent", AccessControl::default()),
|
||||||
echo_handler(),
|
echo_handler(),
|
||||||
Some(CompositionAuthority::new("agent", ["fs:read".to_string()])),
|
Some(CompositionAuthority::new("agent", ["fs:read".to_string()])),
|
||||||
Some(ScopedOperationEnv::new(["fs/readFile"])),
|
Some(ScopedPeerEnv::new(["fs/readFile"])),
|
||||||
Capabilities::new(),
|
Capabilities::new(),
|
||||||
)
|
)
|
||||||
.build();
|
.build();
|
||||||
@@ -687,7 +687,7 @@ mod tests {
|
|||||||
echo_handler(),
|
echo_handler(),
|
||||||
OperationProvenance::Session,
|
OperationProvenance::Session,
|
||||||
Some(CompositionAuthority::new("sandbox", [])),
|
Some(CompositionAuthority::new("sandbox", [])),
|
||||||
Some(ScopedOperationEnv::new(["fs/readFile"])),
|
Some(ScopedPeerEnv::new(["fs/readFile"])),
|
||||||
Capabilities::new(),
|
Capabilities::new(),
|
||||||
);
|
);
|
||||||
let registry = OperationRegistryBuilder::new().with(registration).build();
|
let registry = OperationRegistryBuilder::new().with(registration).build();
|
||||||
@@ -715,7 +715,7 @@ mod tests {
|
|||||||
fn registration_lookup_returns_bundle_fields() {
|
fn registration_lookup_returns_bundle_fields() {
|
||||||
let mut registry = OperationRegistry::new();
|
let mut registry = OperationRegistry::new();
|
||||||
let authority = CompositionAuthority::new("agent", ["fs:read".to_string()]);
|
let authority = CompositionAuthority::new("agent", ["fs:read".to_string()]);
|
||||||
let scoped = ScopedOperationEnv::new(["fs/readFile"]);
|
let scoped = ScopedPeerEnv::new(["fs/readFile"]);
|
||||||
let caps = Capabilities::new().with_api_key("google", "k".to_string());
|
let caps = Capabilities::new().with_api_key("google", "k".to_string());
|
||||||
registry.register(HandlerRegistration::new(
|
registry.register(HandlerRegistration::new(
|
||||||
external_spec("agent", AccessControl::default()),
|
external_spec("agent", AccessControl::default()),
|
||||||
|
|||||||
@@ -233,7 +233,7 @@ async fn two_node_call_round_trip() {
|
|||||||
#[tokio::test(flavor = "multi_thread", worker_threads = 4)]
|
#[tokio::test(flavor = "multi_thread", worker_threads = 4)]
|
||||||
async fn from_call_discovers_and_forwards_over_quic_loopback() {
|
async fn from_call_discovers_and_forwards_over_quic_loopback() {
|
||||||
use alknet_call::client::{from_call, FromCallConfig};
|
use alknet_call::client::{from_call, FromCallConfig};
|
||||||
use alknet_call::registry::context::ScopedOperationEnv;
|
use alknet_call::registry::context::ScopedPeerEnv;
|
||||||
|
|
||||||
let server_registry = build_server_registry();
|
let server_registry = build_server_registry();
|
||||||
let (server_addr, server_fingerprint, _server_join) =
|
let (server_addr, server_fingerprint, _server_join) =
|
||||||
@@ -284,7 +284,7 @@ async fn from_call_discovers_and_forwards_over_quic_loopback() {
|
|||||||
|
|
||||||
// Build a minimal parent context to invoke the overlay env (mirrors how a
|
// Build a minimal parent context to invoke the overlay env (mirrors how a
|
||||||
// composing handler dispatches a child).
|
// composing handler dispatches a child).
|
||||||
let scoped = ScopedOperationEnv::new(["server/echo"]);
|
let scoped = ScopedPeerEnv::new(["server/echo"]);
|
||||||
let parent = alknet_call::registry::context::OperationContext {
|
let parent = alknet_call::registry::context::OperationContext {
|
||||||
request_id: "parent-1".to_string(),
|
request_id: "parent-1".to_string(),
|
||||||
parent_request_id: None,
|
parent_request_id: None,
|
||||||
|
|||||||
Reference in New Issue
Block a user