Merge branch 'wt/review-002-cov13-dead-code'

# Conflicts:
#	src/gateway/dispatch.rs
This commit is contained in:
2026-08-30 23:07:39 +00:00
6 changed files with 40 additions and 164 deletions
+16 -58
View File
@@ -3,8 +3,9 @@
//!
//! Thin concrete struct (not a trait — the alknet research ruled out a
//! trait with an associated output type). Holds `Arc<OperationRegistry>`
//! and `Arc<dyn IdentityProvider>` and exposes a `resolve_bearer()` and
//! `invoke()` method pair returning the neutral `ResponseEnvelope`. Each
//! and exposes an `invoke()` family returning the neutral
//! `ResponseEnvelope` — identity is supplied per-call, resolved upstream
//! in the auth middleware. Each
//! gateway maps the envelope to its own wire shape (`to_openapi` → HTTP
//! `Response`, `to_mcp` → `CallToolResult`).
//!
@@ -34,7 +35,7 @@ use std::sync::atomic::{AtomicUsize, Ordering};
use std::sync::Arc;
use std::time::{Duration, Instant};
use alkcall::core::auth::{AuthToken, Identity, IdentityProvider};
use alkcall::core::auth::Identity;
use alkcall::core::types::Capabilities;
use alkcall::protocol::wire::{CallError, ResponseEnvelope};
use alkcall::registry::context::{AbortPolicy, OperationContext, ScopedPeerEnv};
@@ -45,25 +46,21 @@ use serde_json::Value;
const DEFAULT_TIMEOUT: Duration = Duration::from_secs(30);
/// The shared dispatch spine: registry + identity provider, wired for
/// The shared dispatch spine over the registry, invoking operations for
/// the neutral `ResponseEnvelope` result shape both gateway projections
/// map to their wire formats.
/// map to their wire formats. Identity arrives per-call as
/// `Option<Identity>` — bearer resolution happens upstream in the auth
/// middleware, not here.
pub struct GatewayDispatch {
registry: Arc<OperationRegistry>,
identity_provider: Arc<dyn IdentityProvider>,
invoke_count: AtomicUsize,
}
impl GatewayDispatch {
/// Assemble a dispatch spine over a registry and an identity
/// provider.
pub fn new(
registry: Arc<OperationRegistry>,
identity_provider: Arc<dyn IdentityProvider>,
) -> Self {
/// Assemble a dispatch spine over a registry.
pub fn new(registry: Arc<OperationRegistry>) -> Self {
Self {
registry,
identity_provider,
invoke_count: AtomicUsize::new(0),
}
}
@@ -73,11 +70,6 @@ impl GatewayDispatch {
&self.registry
}
/// The identity provider bearer tokens resolve against.
pub fn identity_provider(&self) -> &Arc<dyn IdentityProvider> {
&self.identity_provider
}
/// How many [`GatewayDispatch::invoke`] calls this spine has
/// served. A test-spy accessor: the over-cap batch tests assert it
/// stays at zero to prove no dispatch happened before the cap
@@ -86,11 +78,6 @@ impl GatewayDispatch {
self.invoke_count.load(Ordering::Relaxed)
}
/// Resolve a bearer token to an identity (the auth-middleware hook).
pub fn resolve_bearer(&self, token: &AuthToken) -> Option<Identity> {
self.identity_provider.resolve_from_token(token)
}
/// Invoke a Query/Mutation op under the 30 s gateway deadline; a
/// hung handler surfaces as a `TIMEOUT` error envelope (504).
pub async fn invoke(
@@ -233,33 +220,10 @@ mod tests {
};
use alkcall::registry::spec::{AccessControl, OperationSpec, OperationType, Visibility};
use futures::StreamExt;
use std::sync::Mutex as StdMutex;
// The tests below run against the module's real 30 s deadline; only
// the elapsed-time bound (< 60 s, anti-flake) is asserted by hand.
struct StaticIdentityProvider {
tokens: StdMutex<HashMap<String, Identity>>,
}
impl StaticIdentityProvider {
fn new() -> Self {
Self {
tokens: StdMutex::new(HashMap::new()),
}
}
}
impl IdentityProvider for StaticIdentityProvider {
fn resolve_from_fingerprint(&self, _fp: &str) -> Option<Identity> {
None
}
fn resolve_from_token(&self, token: &AuthToken) -> Option<Identity> {
let token_str = String::from_utf8_lossy(&token.raw);
self.tokens.lock().unwrap().get(token_str.as_ref()).cloned()
}
}
fn spec(name: &str, visibility: Visibility, op_type: OperationType) -> OperationSpec {
OperationSpec::new(
name,
@@ -292,8 +256,7 @@ mod tests {
#[tokio::test]
async fn invoke_external_op_round_trips() {
let dispatch =
GatewayDispatch::new(echo_registry(), Arc::new(StaticIdentityProvider::new()));
let dispatch = GatewayDispatch::new(echo_registry());
let envelope = dispatch
.invoke(None, "/echo/run", serde_json::json!({ "x": 1 }))
.await;
@@ -302,8 +265,7 @@ mod tests {
#[tokio::test]
async fn invoke_unknown_op_returns_not_found() {
let dispatch =
GatewayDispatch::new(echo_registry(), Arc::new(StaticIdentityProvider::new()));
let dispatch = GatewayDispatch::new(echo_registry());
let envelope = dispatch
.invoke(None, "/missing/op", serde_json::json!({}))
.await;
@@ -328,8 +290,7 @@ mod tests {
Capabilities::new(),
))
.unwrap();
let dispatch =
GatewayDispatch::new(Arc::new(registry), Arc::new(StaticIdentityProvider::new()));
let dispatch = GatewayDispatch::new(Arc::new(registry));
let envelope = dispatch
.invoke(None, "/internal/op", serde_json::json!({}))
.await;
@@ -357,8 +318,7 @@ mod tests {
Capabilities::new(),
))
.unwrap();
let dispatch =
GatewayDispatch::new(Arc::new(registry), Arc::new(StaticIdentityProvider::new()));
let dispatch = GatewayDispatch::new(Arc::new(registry));
let started = std::time::Instant::now();
let envelope = dispatch
.invoke(None, "/hung/op", serde_json::json!({}))
@@ -379,8 +339,7 @@ mod tests {
#[tokio::test]
async fn invoke_completes_within_the_deadline_for_a_fast_handler() {
let dispatch =
GatewayDispatch::new(echo_registry(), Arc::new(StaticIdentityProvider::new()));
let dispatch = GatewayDispatch::new(echo_registry());
let envelope = dispatch
.invoke(None, "/echo/run", serde_json::json!({}))
.await;
@@ -406,8 +365,7 @@ mod tests {
Capabilities::new(),
))
.unwrap();
let dispatch =
GatewayDispatch::new(Arc::new(registry), Arc::new(StaticIdentityProvider::new()));
let dispatch = GatewayDispatch::new(Arc::new(registry));
let mut stream =
dispatch.invoke_streaming(None, "/tick/stream", serde_json::json!({ "count": 3 }));
let mut ticks = Vec::new();