feat: server foundation (phase 1 core) — state, auth, healthz/decoy, gateway dispatch, HttpAdapter

Tasks completed: server-core-types, server-auth, server-healthz-decoy,
gateway-dispatch, server-adapter (5 of 17).

- src/server/state.rs: DecoyConfig + RouterState (alkcall type paths,
  6-endpoint reserved-path docs)
- src/server/auth.rs: bearer middleware + ResolvedIdentity extractor
  (10 tests: missing/malformed/basic/failed-resolution matrix)
- src/server/healthz.rs + decoy.rs: raw healthz; nginx-style 404,
  static site (path-traversal guarded), redirect decoys
- src/gateway/dispatch.rs: GatewayDispatch invoke/invoke_streaming
  (internal:false, forwarded_for:None, bounded deadline) +
  src/gateway/error.rs: CallError→HTTP status mapping (HTTP_<status>
  passthrough, retryable→Retry-After)
- src/server/adapter.rs: HttpAdapter ProtocolHandler — accept_bi →
  BiStream → TokioIo → hyper auto builder (h2 CONNECT enabled);
  integration tests over DuplexStream (request/response cycle, healthz,
  decoy 404)

Verified: cargo test (46 lib tests), clippy -D warnings, fmt,
test --all-features.
This commit is contained in:
2026-08-28 07:35:02 +00:00
parent a85500d3d9
commit d070e548ad
15 changed files with 1669 additions and 31 deletions
+88
View File
@@ -0,0 +1,88 @@
//! Shared server state and configuration for the `HttpAdapter` router.
use std::path::PathBuf;
use std::sync::Arc;
use alkcall::core::auth::IdentityProvider;
use alkcall::registry::registration::OperationRegistry;
/// The stealth decoy surface for paths that are not registered
/// operations and not part of the reserved default surface (the 6
/// gateway endpoints, `/healthz`, `/openapi.json`, the MCP route, the
/// WS upgrade path). Set by the assembly layer at `HttpAdapter`
/// construction. The existence of the decoy path is fixed by ADR-010;
/// the variant is a two-way-door config default.
#[derive(Clone, Default, Debug)]
pub enum DecoyConfig {
/// Serve a fake `404 Not Found` (the default — a fake nginx 404).
#[default]
NotFound,
/// Serve a static site from a configured directory.
StaticSite { root: PathBuf },
/// Redirect to a configured URL.
Redirect { to: String },
}
/// State embedded in the axum `Router`: the registry and identity
/// provider every request handler reaches through the router state, plus
/// the decoy config for the fallback.
#[derive(Clone)]
pub(crate) struct RouterState {
pub(crate) registry: Arc<OperationRegistry>,
pub(crate) identity_provider: Arc<dyn IdentityProvider>,
pub(crate) decoy: DecoyConfig,
}
impl axum::extract::FromRef<RouterState> for DecoyConfig {
fn from_ref(state: &RouterState) -> Self {
state.decoy.clone()
}
}
impl axum::extract::FromRef<RouterState> for Arc<OperationRegistry> {
fn from_ref(state: &RouterState) -> Self {
Arc::clone(&state.registry)
}
}
impl axum::extract::FromRef<RouterState> for Arc<dyn IdentityProvider> {
fn from_ref(state: &RouterState) -> Self {
Arc::clone(&state.identity_provider)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn decoy_config_default_is_not_found() {
assert!(matches!(DecoyConfig::default(), DecoyConfig::NotFound));
}
#[test]
fn router_state_from_ref_extracts_decoy() {
let state = RouterState {
registry: Arc::new(OperationRegistry::new()),
identity_provider: Arc::new(NoopProvider),
decoy: DecoyConfig::Redirect {
to: "https://example.com".to_string(),
},
};
let extracted: DecoyConfig = axum::extract::FromRef::from_ref(&state);
assert!(matches!(extracted, DecoyConfig::Redirect { .. }));
}
struct NoopProvider;
impl IdentityProvider for NoopProvider {
fn resolve_from_fingerprint(&self, _: &str) -> Option<alkcall::core::auth::Identity> {
None
}
fn resolve_from_token(
&self,
_: &alkcall::core::auth::AuthToken,
) -> Option<alkcall::core::auth::Identity> {
None
}
}
}