From 37a13e356620af5f8ae143f472023335f8bc59a3 Mon Sep 17 00:00:00 2001 From: Hare Date: Fri, 9 Oct 2026 02:22:18 +0900 Subject: [PATCH] fix: resolve target-qualified ancestor interfaces through live publications Add a provider-owned routing hint without granting authority. Fetch checks provider ownership and re-resolves one coherent target/scope publication; default self/shared scope behavior remains unchanged. Validation: 7 ancestor regression tests, 901 Worker unit and 110 integration tests, root cargo check, formatting and diff checks passed. --- crates/worker/src/wip.rs | 30 ++++++++- crates/worker/src/wip/ancestor_tests.rs | 81 +++++++++++++++++++++++++ 2 files changed, 110 insertions(+), 1 deletion(-) diff --git a/crates/worker/src/wip.rs b/crates/worker/src/wip.rs index 3a3687f90..eaeeece80 100644 --- a/crates/worker/src/wip.rs +++ b/crates/worker/src/wip.rs @@ -288,6 +288,13 @@ pub trait WipSubtreeProvider: Send + Sync { /// looking up a mutable ancestor after resolving the target. Self-scoped /// publishers can use `WipPublication::self_scoped` on their pinned snapshot. async fn publication(&self, path: &str) -> Result, ProtocolError>; + /// Routing hint for target-qualified local Interface names in an ancestor + /// scope. This grants no authority: Host re-resolves one coherent current + /// publication, checks the exact reference, and forbids escaping this owner. + /// Self/shared-scope providers retain the ordinary scope Object route. + fn interface_target(&self, reference: &InterfaceReference) -> Option { + Some(reference.scope.clone()) + } async fn children(&self, path: &str) -> Result, ProtocolError>; } @@ -1248,8 +1255,29 @@ impl WipHost { reference .validate() .map_err(|e| protocol_error(ProtocolErrorCode::InvalidRequest, e.to_string()))?; + let target = if let Some(mount) = self.subtree(&reference.scope) { + let target = mount.provider.interface_target(reference).ok_or_else(|| { + protocol_error( + ProtocolErrorCode::InterfaceNotFound, + "interface is not published", + ) + })?; + if wip_protocol::validate_path(&target).is_err() + || !self + .subtree(&target) + .is_some_and(|owner| owner.root == mount.root) + { + return Err(protocol_error( + ProtocolErrorCode::InterfaceNotFound, + "interface target is outside its provider", + )); + } + target + } else { + reference.scope.clone() + }; if let Some(publication) = self - .publication_for_interface_live(&reference.scope, reference) + .publication_for_interface_live(&target, reference) .await? && publication.projection.interface == *reference && let Some(scope) = publication.scope diff --git a/crates/worker/src/wip/ancestor_tests.rs b/crates/worker/src/wip/ancestor_tests.rs index ab1ac929a..d645a3436 100644 --- a/crates/worker/src/wip/ancestor_tests.rs +++ b/crates/worker/src/wip/ancestor_tests.rs @@ -538,3 +538,84 @@ async fn same_name_scope_replacement_or_loss_of_ref_rejects_old_metadata_without ); } } + +struct TargetQualifiedProvider { + inner: Arc, + target_hint: Mutex>, +} +#[async_trait] +impl WipSubtreeProvider for TargetQualifiedProvider { + fn interface_target(&self, _: &InterfaceReference) -> Option { + self.target_hint.lock().unwrap().clone() + } + async fn publication(&self, path: &str) -> Result, ProtocolError> { + self.inner.publication(path).await + } + async fn children(&self, path: &str) -> Result, ProtocolError> { + self.inner.children(path).await + } +} + +#[tokio::test] +async fn ancestor_interface_target_hint_fetches_one_current_publication_and_never_grants_authority() +{ + let (_, inner) = fixture("/github", true, false); + let provider = Arc::new(TargetQualifiedProvider { + inner: inner.clone(), + target_hint: Mutex::new(Some(inner.target_path.clone())), + }); + let mut mounts = WipMountRegistry::new(); + mounts.allocate_namespace("scope", "github").unwrap(); + mounts + .mount(projection( + "/github", + "/github", + "read", + inner.calls.clone(), + )) + .unwrap(); + mounts + .mount_subtree(WipSubtreeMount { + root: "/github".into(), + provider: provider.clone(), + }) + .unwrap(); + let host = WipHost::new(mounts); + let selected = reference("/github", "read"); + let fetched = host.fetch_interface_live(&selected).await.unwrap(); + assert_eq!(fetched.scope_ref, Some("live-scope".into())); + assert_eq!( + inner.state.lock().unwrap().lookups, + [inner.target_path.clone()] + ); + // A hint must not fabricate a Descriptor or restore a deleted scope lifetime. + inner.state.lock().unwrap().visible = false; + assert_eq!( + host.fetch_interface_live(&selected).await.unwrap_err().code, + ProtocolErrorCode::InterfaceNotFound + ); + inner.state.lock().unwrap().visible = true; + let forged = reference("/github", "not-published"); + assert_eq!( + host.fetch_interface_live(&forged).await.unwrap_err().code, + ProtocolErrorCode::InterfaceNotFound + ); + for hint in [ + None, + Some("/outside"), + Some("/github-extra/item"), + Some("/github/../item"), + Some("/github/missing"), + ] { + *provider.target_hint.lock().unwrap() = hint.map(str::to_owned); + inner.state.lock().unwrap().lookups.clear(); + assert_eq!( + host.fetch_interface_live(&selected).await.unwrap_err().code, + ProtocolErrorCode::InterfaceNotFound + ); + if hint != Some("/github/missing") { + assert!(inner.state.lock().unwrap().lookups.is_empty()); + } + } + assert_eq!(inner.calls.load(Ordering::SeqCst), 0); +}