fix(gateway): review-012 regression-pass cleanup on the loud ownership default

- deduplicate the thrice-repeated refusal block into
  GatewayDispatch::ownership_wiring_refusal(&self, op)
- drop the dead unwrap_or("") branch in the refusal predicate
  (let-else: the internal-visibility exemption and the resource_type
  predicate are now separate lets)
- pin the predicate refinement review 012 ruled on: a list-case op
  (resource_type set, resource_id_path None) is refused on an unwired
  spine and served through owns_any when a provider is wired

Verification: cargo test (697), cargo test --all-features (721),
clippy all-targets both configs, fmt --check
This commit is contained in:
glm-5.3-flash committed 2026-10-17 14:17:06 +00:00
1 parent 9899b37a82
commit 1fda0d8587
1 file changed
+75 -22
+75 -22
View File
@@ -151,12 +151,7 @@ impl GatewayDispatch {
return ResponseEnvelope::error(uuid::Uuid::new_v4().to_string(), error);
}
let request_id = uuid::Uuid::new_v4().to_string();
if let Some(error) = ownership_wiring_refusal(
&self.registry,
&operation_name,
self.ownership_provider.as_ref(),
self.static_ownership_fallback,
) {
if let Some(error) = self.ownership_wiring_refusal(&operation_name) {
return ResponseEnvelope::error(request_id, error);
}
let context = self.build_root_context(&request_id, &operation_name, identity);
@@ -194,12 +189,7 @@ impl GatewayDispatch {
}));
}
let request_id = uuid::Uuid::new_v4().to_string();
if let Some(error) = ownership_wiring_refusal(
&self.registry,
&operation_name,
self.ownership_provider.as_ref(),
self.static_ownership_fallback,
) {
if let Some(error) = self.ownership_wiring_refusal(&operation_name) {
return Box::pin(futures::stream::once(async move {
ResponseEnvelope::error(request_id, error)
}));
@@ -224,12 +214,7 @@ impl GatewayDispatch {
) -> ResponseEnvelope {
let operation_name = strip_leading_slash(op).to_string();
let request_id = uuid::Uuid::new_v4().to_string();
if let Some(error) = ownership_wiring_refusal(
&self.registry,
&operation_name,
self.ownership_provider.as_ref(),
self.static_ownership_fallback,
) {
if let Some(error) = self.ownership_wiring_refusal(&operation_name) {
return ResponseEnvelope::error(request_id, error);
}
let context = self.build_root_context_sink(&request_id, &operation_name, identity);
@@ -250,6 +235,15 @@ impl GatewayDispatch {
}
}
fn ownership_wiring_refusal(&self, operation: &str) -> Option<CallError> {
ownership_wiring_refusal(
&self.registry,
operation,
self.ownership_provider.as_ref(),
self.static_ownership_fallback,
)
}
fn build_root_context_sink(
&self,
request_id: &str,
@@ -368,12 +362,11 @@ fn ownership_wiring_refusal(
if ownership_provider.is_some() || static_ownership_fallback {
return None;
}
let registration = registry.registration(operation)?;
let spec = &registration.spec;
if spec.visibility == Visibility::Internal || spec.access_control.resource_type.is_none() {
let spec = &registry.registration(operation)?.spec;
if spec.visibility == Visibility::Internal {
return None;
}
let resource_type = spec.access_control.resource_type.as_deref().unwrap_or("");
let resource_type = spec.access_control.resource_type.as_deref()?;
Some(CallError::forbidden(format!(
"operation '{operation}' gates on resource ownership ({resource_type}), but no ownership \
provider is wired on this dispatch spine; the deployment must wire \
@@ -1146,6 +1139,66 @@ mod tests {
}
}
#[tokio::test]
async fn gateway_list_case_resource_gated_ops_are_refused_unwired_and_served_via_owns_any() {
let registry = OperationRegistry::new();
let mut list_spec = spec("ns/list", Visibility::External, OperationType::Query);
list_spec.access_control = namespace_acl();
registry
.register(HandlerRegistration::new(
list_spec,
HandlerKind::Once(make_handler(|input, ctx| async move {
ResponseEnvelope::ok(ctx.request_id, input)
})),
OperationProvenance::Local,
None,
None,
Capabilities::new(),
))
.unwrap();
let registry = Arc::new(registry);
let envelope = GatewayDispatch::new(Arc::clone(&registry))
.invoke(
Some(identity_with_static_claim("token-carrier", true)),
"ns/list",
serde_json::json!({}),
)
.await;
match envelope.result {
Err(error) => assert_eq!(
error.code, "FORBIDDEN",
"the list case (resource_id_path None) is refused unwired too, got {error:?}"
),
Ok(v) => panic!(
"a token-carried static claim must not admit the list case on an unwired spine, \
got {v:?}"
),
}
let store = Arc::new(InMemoryOwnershipStore::new());
store
.record(
&identity_with_static_claim("tenant-a", false),
"blob-namespace",
"ns-9",
)
.await
.unwrap();
let envelope = GatewayDispatch::new(Arc::clone(&registry))
.with_ownership_provider(Arc::clone(&store) as Arc<dyn OwnershipProvider>)
.invoke(
Some(identity_with_static_claim("tenant-a", false)),
"ns/list",
serde_json::json!({}),
)
.await;
assert!(
envelope.result.is_ok(),
"the wired spine serves the list case through owns_any, got {envelope:?}"
);
}
#[tokio::test]
async fn gateway_static_fallback_opt_out_serves_resource_gated_ops() {
let dispatch = GatewayDispatch::new(namespace_registry()).with_static_ownership_fallback();