refactor: 移除 Python upstream 依赖,清理全部 legacy/Python 兼容层

- 移除 upstream_base_url 参数及 AETHER_GATEWAY_UPSTREAM 环境变量,gateway 不再需要指向 Python 宿主
- 删除所有 LEGACY_*/PYTHON_* 常量、路由组、header 定义及 sunset/phaseout 机制
- 将 legacy_gateway_bridge 重命名为 internal_gateway,executor 相关命名统一为 execution_runtime
- dev.sh 新增 Postgres/Redis 预检查,移除 upstream 相关启动参数和提示
- 新增 ai_public 路由处理器
- 全量适配 handler、test、state、control 等模块的命名和接口变更
This commit is contained in:
fawney19
2026-04-04 01:40:24 +08:00
parent 1d9c77522a
commit cbc811f6ce
484 changed files with 11046 additions and 3925 deletions

View File

@@ -97,18 +97,9 @@ client -> rust frontdoor (aether-gateway) -> execution_runtime/provider transpor
其中: 其中:
- `aether-gateway` 负责公开入口、健康检查、格式转换、本地执行 runtime以及当前已迁到 Rust 的 frontdoor/control/background 路径。 - `aether-gateway` 负责公开入口、健康检查、格式转换、本地执行 runtime以及当前已迁到 Rust 的 frontdoor/control/background 路径。
- `./dev.sh` 不再启动 Python 宿主;未下沉到 Rust 的 legacy 路由会直接失败,除非你手动提供 `AETHER_GATEWAY_UPSTREAM` - `./dev.sh` 不再启动 Python 宿主;未下沉到 Rust 的 legacy 路由会直接失败。
- `./dev.sh` 默认把 `AETHER_GATEWAY_VIDEO_TASK_TRUTH_SOURCE_MODE` 设为 `rust-authoritative`,避免本地还依赖 Python sync report 语义。 - `./dev.sh` 默认把 `AETHER_GATEWAY_VIDEO_TASK_TRUTH_SOURCE_MODE` 设为 `rust-authoritative`,避免本地还依赖 Python sync report 语义。
如果你需要把 gateway 指到一个外部 legacy 宿主,可显式设置:
```bash
AETHER_GATEWAY_UPSTREAM=http://127.0.0.1:18084 \
./dev.sh
```
如果不提供上述变量,`./dev.sh` 会把 legacy upstream 指到一个不可用的本地地址,让未迁移路径快速失败,而不是悄悄回退到 Python。
## Aether Proxy (可选) ## Aether Proxy (可选)
Aether Proxy 是配套的正向代理节点,部署在海外 VPS 上,为墙内的 Aether 实例中转 API 流量。或者部署在其他服务器为指定的提供商、账号、Key使用不同的节点访问。支持 TUI 向导一键配置、systemd 服务管理、TLS 加密、DNS 缓存及连接池调优。 Aether Proxy 是配套的正向代理节点,部署在海外 VPS 上,为墙内的 Aether 实例中转 API 流量。或者部署在其他服务器为指定的提供商、账号、Key使用不同的节点访问。支持 TUI 向导一键配置、systemd 服务管理、TLS 加密、DNS 缓存及连接池调优。

View File

@@ -1,7 +1,8 @@
use super::util::crc32; use super::util::crc32;
use super::*;
use serde_json::{json, Value}; use serde_json::{json, Value};
use super::KiroToClaudeCliStreamState;
fn encode_string_header(name: &str, value: &str) -> Vec<u8> { fn encode_string_header(name: &str, value: &str) -> Vec<u8> {
let mut out = Vec::new(); let mut out = Vec::new();
out.push(name.len() as u8); out.push(name.len() as u8);

View File

@@ -139,7 +139,14 @@ pub(crate) fn provider_adaptation_should_unwrap_stream_envelope(
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::{
provider_adaptation_allows_sync_finalize_envelope,
provider_adaptation_anchor_api_format,
provider_adaptation_requires_eventstream_accept,
provider_adaptation_should_unwrap_stream_envelope,
ANTIGRAVITY_V1INTERNAL_ENVELOPE_NAME, GEMINI_CLI_V1INTERNAL_ENVELOPE_NAME,
KIRO_ENVELOPE_NAME,
};
#[test] #[test]
fn resolves_private_surface_anchor_contracts() { fn resolves_private_surface_anchor_contracts() {

View File

@@ -151,7 +151,11 @@ fn is_standard_api_format(api_format: &str) -> bool {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::{
request_conversion_kind, sync_chat_response_conversion_kind,
sync_cli_response_conversion_kind, RequestConversionKind, SyncChatResponseConversionKind,
SyncCliResponseConversionKind,
};
#[test] #[test]
fn request_conversion_registry_supports_bidirectional_standard_matrix() { fn request_conversion_registry_supports_bidirectional_standard_matrix() {

View File

@@ -1,11 +1,14 @@
use base64::Engine as _; use base64::Engine as _;
use std::collections::BTreeMap;
use serde_json::{json, Map, Value};
use super::*;
use crate::gateway::ai_pipeline::finalize::common::{ use crate::gateway::ai_pipeline::finalize::common::{
build_generated_tool_call_id, build_local_success_outcome, canonicalize_tool_arguments, build_generated_tool_call_id, build_local_success_outcome, canonicalize_tool_arguments,
local_finalize_allows_envelope, parse_stream_json_events, unwrap_local_finalize_response_value, local_finalize_allows_envelope, parse_stream_json_events, unwrap_local_finalize_response_value,
LocalCoreSyncFinalizeOutcome, LocalCoreSyncFinalizeOutcome,
}; };
use crate::gateway::{GatewayControlDecision, GatewayError, GatewaySyncReportRequest};
#[derive(Debug, Default)] #[derive(Debug, Default)]
struct ClaudeContentBlockState { struct ClaudeContentBlockState {

View File

@@ -1,13 +1,15 @@
use base64::Engine as _; use base64::Engine as _;
use super::aggregate_claude_stream_sync_response; use super::aggregate_claude_stream_sync_response;
use super::*; use serde_json::{json, Value};
use crate::gateway::ai_pipeline::finalize::common::{ use crate::gateway::ai_pipeline::finalize::common::{
build_generated_tool_call_id, build_local_success_outcome, canonicalize_tool_arguments, build_generated_tool_call_id, build_local_success_outcome, canonicalize_tool_arguments,
local_finalize_allows_envelope, unwrap_local_finalize_response_value, local_finalize_allows_envelope, unwrap_local_finalize_response_value,
LocalCoreSyncFinalizeOutcome, LocalCoreSyncFinalizeOutcome,
}; };
use crate::gateway::ai_pipeline::finalize::standard::build_openai_cli_response; use crate::gateway::ai_pipeline::finalize::standard::build_openai_cli_response;
use crate::gateway::{GatewayControlDecision, GatewayError, GatewaySyncReportRequest};
pub(crate) fn maybe_build_local_claude_cli_stream_sync_response( pub(crate) fn maybe_build_local_claude_cli_stream_sync_response(
trace_id: &str, trace_id: &str,

View File

@@ -1,11 +1,14 @@
use base64::Engine as _; use base64::Engine as _;
use std::collections::BTreeMap;
use serde_json::{json, Map, Value};
use super::*;
use crate::gateway::ai_pipeline::finalize::common::{ use crate::gateway::ai_pipeline::finalize::common::{
build_generated_tool_call_id, build_local_success_outcome, canonicalize_tool_arguments, build_generated_tool_call_id, build_local_success_outcome, canonicalize_tool_arguments,
local_finalize_allows_envelope, parse_stream_json_events, unwrap_local_finalize_response_value, local_finalize_allows_envelope, parse_stream_json_events, unwrap_local_finalize_response_value,
LocalCoreSyncFinalizeOutcome, LocalCoreSyncFinalizeOutcome,
}; };
use crate::gateway::{GatewayControlDecision, GatewayError, GatewaySyncReportRequest};
pub(crate) fn maybe_build_local_gemini_stream_sync_response( pub(crate) fn maybe_build_local_gemini_stream_sync_response(
trace_id: &str, trace_id: &str,

View File

@@ -1,13 +1,15 @@
use base64::Engine as _; use base64::Engine as _;
use super::aggregate_gemini_stream_sync_response; use super::aggregate_gemini_stream_sync_response;
use super::*; use serde_json::{json, Value};
use crate::gateway::ai_pipeline::finalize::common::{ use crate::gateway::ai_pipeline::finalize::common::{
build_generated_tool_call_id, build_local_success_outcome, canonicalize_tool_arguments, build_generated_tool_call_id, build_local_success_outcome, canonicalize_tool_arguments,
local_finalize_allows_envelope, unwrap_local_finalize_response_value, local_finalize_allows_envelope, unwrap_local_finalize_response_value,
LocalCoreSyncFinalizeOutcome, LocalCoreSyncFinalizeOutcome,
}; };
use crate::gateway::ai_pipeline::finalize::standard::build_openai_cli_response; use crate::gateway::ai_pipeline::finalize::standard::build_openai_cli_response;
use crate::gateway::{GatewayControlDecision, GatewayError, GatewaySyncReportRequest};
pub(crate) fn maybe_build_local_gemini_cli_stream_sync_response( pub(crate) fn maybe_build_local_gemini_cli_stream_sync_response(
trace_id: &str, trace_id: &str,

View File

@@ -1,6 +1,8 @@
use base64::Engine as _; use base64::Engine as _;
use std::collections::BTreeMap;
use serde_json::{json, Map, Value};
use super::*;
use super::{ use super::{
aggregate_claude_stream_sync_response, aggregate_gemini_stream_sync_response, aggregate_claude_stream_sync_response, aggregate_gemini_stream_sync_response,
aggregate_openai_cli_stream_sync_response, convert_claude_chat_response_to_openai_chat, aggregate_openai_cli_stream_sync_response, convert_claude_chat_response_to_openai_chat,
@@ -13,6 +15,7 @@ use crate::gateway::ai_pipeline::finalize::common::{
local_finalize_allows_envelope, unwrap_local_finalize_response_value, local_finalize_allows_envelope, unwrap_local_finalize_response_value,
LocalCoreSyncFinalizeOutcome, LocalCoreSyncFinalizeOutcome,
}; };
use crate::gateway::{GatewayControlDecision, GatewayError, GatewaySyncReportRequest};
#[derive(Debug, Default)] #[derive(Debug, Default)]
struct OpenAIChatChoiceState { struct OpenAIChatChoiceState {

View File

@@ -1,6 +1,7 @@
use base64::Engine as _; use base64::Engine as _;
use super::*; use serde_json::{json, Value};
use super::{ use super::{
aggregate_claude_stream_sync_response, aggregate_gemini_stream_sync_response, aggregate_claude_stream_sync_response, aggregate_gemini_stream_sync_response,
convert_claude_cli_response_to_openai_cli, convert_gemini_cli_response_to_openai_cli, convert_claude_cli_response_to_openai_cli, convert_gemini_cli_response_to_openai_cli,
@@ -11,6 +12,7 @@ use crate::gateway::ai_pipeline::finalize::common::{
canonicalize_tool_arguments, local_finalize_allows_envelope, canonicalize_tool_arguments, local_finalize_allows_envelope,
unwrap_local_finalize_response_value, LocalCoreSyncFinalizeOutcome, unwrap_local_finalize_response_value, LocalCoreSyncFinalizeOutcome,
}; };
use crate::gateway::{GatewayControlDecision, GatewayError, GatewaySyncReportRequest};
pub(crate) fn maybe_build_local_openai_cli_stream_sync_response( pub(crate) fn maybe_build_local_openai_cli_stream_sync_response(
trace_id: &str, trace_id: &str,

View File

@@ -103,7 +103,9 @@ mod tests {
}; };
use serde_json::json; use serde_json::json;
use super::*; use super::{
prefer_local_tunnel_owner_candidates, GatewayMinimalCandidateSelectionCandidate, AppState,
};
use crate::gateway::tunnel::TunnelAttachmentRecord; use crate::gateway::tunnel::TunnelAttachmentRecord;
use crate::gateway::GatewayDataState; use crate::gateway::GatewayDataState;
@@ -245,7 +247,7 @@ mod tests {
.expect("local attachment should serialize"), .expect("local attachment should serialize"),
), ),
]); ]);
let state = AppState::new("http://127.0.0.1:1") let state = AppState::new()
.expect("state should build") .expect("state should build")
.with_data_state_for_tests(data_state) .with_data_state_for_tests(data_state)
.with_tunnel_identity_for_tests("gateway-a", Some("http://gateway-a:8080")); .with_tunnel_identity_for_tests("gateway-a", Some("http://gateway-a:8080"));
@@ -274,7 +276,7 @@ mod tests {
std::sync::Arc::new(provider_catalog), std::sync::Arc::new(provider_catalog),
"development-key", "development-key",
); );
let state = AppState::new("http://127.0.0.1:1") let state = AppState::new()
.expect("state should build") .expect("state should build")
.with_data_state_for_tests(data_state) .with_data_state_for_tests(data_state)
.with_tunnel_identity_for_tests("gateway-a", Some("http://gateway-a:8080")); .with_tunnel_identity_for_tests("gateway-a", Some("http://gateway-a:8080"));

View File

@@ -1,4 +1,16 @@
use super::*; use std::collections::BTreeMap;
use serde_json::Value;
use url::form_urlencoded;
use super::{
apply_local_body_rules, build_antigravity_v1internal_url, build_claude_code_messages_url,
build_claude_messages_url, build_gemini_content_url,
build_kiro_generate_assistant_response_url, build_kiro_provider_request_body,
build_passthrough_path_url, build_vertex_api_key_gemini_content_url,
resolve_local_vertex_api_key_query_auth, sanitize_claude_code_request_body,
AntigravityRequestUrlAction, LocalSameFormatProviderFamily, LocalSameFormatProviderSpec,
};
pub(super) fn build_same_format_provider_request_body( pub(super) fn build_same_format_provider_request_body(
body_json: &Value, body_json: &Value,

View File

@@ -156,7 +156,8 @@ fn build_openai_chat_request_body(
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::build_standard_request_body;
use serde_json::json;
#[test] #[test]
fn builds_openai_chat_request_from_claude_chat_source() { fn builds_openai_chat_request_from_claude_chat_source() {

View File

@@ -306,7 +306,8 @@ pub(crate) fn build_cross_format_openai_cli_upstream_url(
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::build_cross_format_openai_cli_request_body;
use serde_json::json;
#[test] #[test]
fn builds_openai_family_cross_format_request_body_from_compact_source() { fn builds_openai_family_cross_format_request_body_from_compact_source() {

View File

@@ -1,9 +1,10 @@
use aether_contracts::RequestBody; use aether_contracts::{ExecutionPlan, RequestBody};
use super::augment_sync_report_context; use super::{augment_sync_report_context, LocalStreamPlanAndReport, LocalSyncPlanAndReport};
use super::*;
use crate::gateway::ai_pipeline::adaptation::surfaces::provider_adaptation_requires_eventstream_accept; use crate::gateway::ai_pipeline::adaptation::surfaces::provider_adaptation_requires_eventstream_accept;
use crate::gateway::ai_pipeline::planner::generic_decision_missing_exact_provider_request;
use crate::gateway::provider_transport::ensure_upstream_auth_header; use crate::gateway::provider_transport::ensure_upstream_auth_header;
use crate::gateway::{GatewayControlSyncDecisionResponse, GatewayError};
pub(crate) fn build_standard_sync_plan_from_decision( pub(crate) fn build_standard_sync_plan_from_decision(
_parts: &http::request::Parts, _parts: &http::request::Parts,

View File

@@ -415,7 +415,10 @@ fn truncate_body(body: &str) -> String {
mod tests { mod tests {
use std::sync::{Arc, Mutex}; use std::sync::{Arc, Mutex};
use super::*; use super::{IDC_AMZ_USER_AGENT, KiroOAuthRefreshAdapter};
use crate::gateway::provider_transport::oauth_refresh::{
LocalOAuthRefreshAdapter, LocalResolvedOAuthRequestAuth,
};
use crate::gateway::provider_transport::snapshot::{ use crate::gateway::provider_transport::snapshot::{
GatewayProviderTransportEndpoint, GatewayProviderTransportKey, GatewayProviderTransportEndpoint, GatewayProviderTransportKey,
GatewayProviderTransportProvider, GatewayProviderTransportSnapshot, GatewayProviderTransportProvider, GatewayProviderTransportSnapshot,
@@ -426,6 +429,7 @@ mod tests {
use axum::routing::any; use axum::routing::any;
use axum::{Json, Router}; use axum::{Json, Router};
use http::StatusCode; use http::StatusCode;
use serde_json::{json, Value};
use tokio::task::JoinHandle; use tokio::task::JoinHandle;
#[derive(Debug, Clone)] #[derive(Debug, Clone)]

View File

@@ -9,7 +9,7 @@ use crate::gateway::{
pub(crate) fn mount_internal_routes(router: Router<AppState>) -> Router<AppState> { pub(crate) fn mount_internal_routes(router: Router<AppState>) -> Router<AppState> {
router router
.route( .route(
"/api/internal/gateway/{*legacy_gateway_path}", "/api/internal/gateway/{*internal_gateway_path}",
any(proxy_request), any(proxy_request),
) )
.route(PROXY_TUNNEL_PATH, get(proxy_tunnel)) .route(PROXY_TUNNEL_PATH, get(proxy_tunnel))

View File

@@ -6,13 +6,9 @@ use axum::Router;
use serde_json::json; use serde_json::json;
use crate::gateway::constants::{ use crate::gateway::constants::{
FRONTDOOR_COMPAT_ROUTE_PATTERNS, FRONTDOOR_MANIFEST_PATH, FRONTDOOR_MANIFEST_VERSION, FRONTDOOR_MANIFEST_PATH, FRONTDOOR_MANIFEST_VERSION, INTERNAL_FRONTDOOR_MANIFEST_PATH,
FRONTDOOR_REPLACEABLE_MIDDLEWARE_GROUPS, FRONTDOOR_REPLACEABLE_ROUTE_GROUPS, INTERNAL_GATEWAY_PATH_PREFIXES, INTERNAL_GATEWAY_ROUTE_GROUPS, READYZ_PATH,
INTERNAL_FRONTDOOR_MANIFEST_PATH, LEGACY_GATEWAY_BRIDGE_PATH_PREFIXES, RUST_FRONTDOOR_OWNED_ROUTE_PATTERNS,
LEGACY_GATEWAY_BRIDGE_ROUTE_GROUPS, LEGACY_INTERNAL_GATEWAY_PHASEOUT_STATUS,
LEGACY_INTERNAL_GATEWAY_SUNSET_DATE, LEGACY_INTERNAL_GATEWAY_SUNSET_HTTP_DATE,
PYTHON_ONLY_MIDDLEWARE_GROUPS, PYTHON_ONLY_ROUTE_GROUPS, PYTHON_ONLY_RUNTIME_COMPONENTS,
READYZ_PATH, RUST_FRONTDOOR_OWNED_ROUTE_PATTERNS,
}; };
use crate::gateway::AppState; use crate::gateway::AppState;
@@ -100,27 +96,10 @@ pub(crate) async fn frontdoor_manifest(State(state): State<AppState>) -> impl In
"trace_id_injection": true, "trace_id_injection": true,
"compatibility_proxy": true, "compatibility_proxy": true,
}, },
}, "internal_gateway": {
"python_host_boundary": { "route_groups": INTERNAL_GATEWAY_ROUTE_GROUPS,
"replaceable_shell": { "path_prefixes": INTERNAL_GATEWAY_PATH_PREFIXES,
"route_groups": FRONTDOOR_REPLACEABLE_ROUTE_GROUPS, "status": "rust_native_control_plane",
"middleware_groups": FRONTDOOR_REPLACEABLE_MIDDLEWARE_GROUPS,
"route_patterns": FRONTDOOR_COMPAT_ROUTE_PATTERNS,
"status": "should_move_to_rust_frontdoor",
},
"python_only": {
"route_groups": PYTHON_ONLY_ROUTE_GROUPS,
"middleware_groups": PYTHON_ONLY_MIDDLEWARE_GROUPS,
"runtime_components": PYTHON_ONLY_RUNTIME_COMPONENTS,
"status": "remain_on_python_host",
},
"legacy_bridge": {
"route_groups": LEGACY_GATEWAY_BRIDGE_ROUTE_GROUPS,
"path_prefixes": LEGACY_GATEWAY_BRIDGE_PATH_PREFIXES,
"status": LEGACY_INTERNAL_GATEWAY_PHASEOUT_STATUS,
"sunset_date": LEGACY_INTERNAL_GATEWAY_SUNSET_DATE,
"sunset_http_date": LEGACY_INTERNAL_GATEWAY_SUNSET_HTTP_DATE,
"replacement": "public_proxy_or_local_rust_control_plane",
}, },
}, },
"features": { "features": {

View File

@@ -26,7 +26,6 @@ fn insert_execution_runtime_candidate_headers(
decision: &GatewayControlDecision, decision: &GatewayControlDecision,
) -> Result<(), GatewayError> { ) -> Result<(), GatewayError> {
let value = execution_runtime_candidate_header_value(decision); let value = execution_runtime_candidate_header_value(decision);
insert_header_if_missing(headers, CONTROL_LEGACY_EXECUTION_RUNTIME_HEADER, value)?;
insert_header_if_missing(headers, CONTROL_EXECUTION_RUNTIME_HEADER, value) insert_header_if_missing(headers, CONTROL_EXECUTION_RUNTIME_HEADER, value)
} }

View File

@@ -154,7 +154,7 @@ mod tests {
#[tokio::test] #[tokio::test]
async fn records_shadow_result_for_ai_public_response() { async fn records_shadow_result_for_ai_public_response() {
let repository = Arc::new(InMemoryShadowResultRepository::default()); let repository = Arc::new(InMemoryShadowResultRepository::default());
let state = AppState::new("http://127.0.0.1:18084") let state = AppState::new()
.expect("app state should build") .expect("app state should build")
.with_shadow_result_data_writer_for_tests(repository.clone()); .with_shadow_result_data_writer_for_tests(repository.clone());
let response = Response::builder() let response = Response::builder()
@@ -205,7 +205,7 @@ mod tests {
#[tokio::test] #[tokio::test]
async fn merges_rust_and_python_shadow_samples_into_match() { async fn merges_rust_and_python_shadow_samples_into_match() {
let repository = Arc::new(InMemoryShadowResultRepository::default()); let repository = Arc::new(InMemoryShadowResultRepository::default());
let state = AppState::new("http://127.0.0.1:18084") let state = AppState::new()
.expect("app state should build") .expect("app state should build")
.with_shadow_result_data_repository_for_tests(repository.clone()); .with_shadow_result_data_repository_for_tests(repository.clone());
let response = Response::builder() let response = Response::builder()

View File

@@ -8,9 +8,7 @@ pub(crate) const FORWARDED_FOR_HEADER: &str = "x-forwarded-for";
pub(crate) const FORWARDED_PROTO_HEADER: &str = "x-forwarded-proto"; pub(crate) const FORWARDED_PROTO_HEADER: &str = "x-forwarded-proto";
pub(crate) const GATEWAY_HEADER: &str = "x-aether-gateway"; pub(crate) const GATEWAY_HEADER: &str = "x-aether-gateway";
pub(crate) const EXECUTION_PATH_HEADER: &str = "x-aether-execution-path"; pub(crate) const EXECUTION_PATH_HEADER: &str = "x-aether-execution-path";
pub(crate) const PYTHON_DEPENDENCY_REASON_HEADER: &str = "x-aether-python-dependency-reason"; pub(crate) const DEPENDENCY_REASON_HEADER: &str = "x-aether-dependency-reason";
pub(crate) const LOCAL_LEGACY_EXECUTION_RUNTIME_MISS_REASON_HEADER: &str =
"x-aether-local-executor-miss-reason";
pub(crate) const LOCAL_EXECUTION_RUNTIME_MISS_REASON_HEADER: &str = pub(crate) const LOCAL_EXECUTION_RUNTIME_MISS_REASON_HEADER: &str =
"x-aether-local-execution-runtime-miss-reason"; "x-aether-local-execution-runtime-miss-reason";
pub(crate) const TUNNEL_AFFINITY_FORWARDED_BY_HEADER: &str = pub(crate) const TUNNEL_AFFINITY_FORWARDED_BY_HEADER: &str =
@@ -18,28 +16,24 @@ pub(crate) const TUNNEL_AFFINITY_FORWARDED_BY_HEADER: &str =
pub(crate) const TUNNEL_AFFINITY_OWNER_INSTANCE_HEADER: &str = pub(crate) const TUNNEL_AFFINITY_OWNER_INSTANCE_HEADER: &str =
"x-aether-tunnel-affinity-owner-instance-id"; "x-aether-tunnel-affinity-owner-instance-id";
pub(crate) const EXECUTION_PATH_PUBLIC_PROXY_PASSTHROUGH: &str = "public_proxy_passthrough"; pub(crate) const EXECUTION_PATH_PUBLIC_PROXY_PASSTHROUGH: &str = "public_proxy_passthrough";
pub(crate) const LEGACY_EXECUTION_PATH_PUBLIC_PROXY_AFTER_EXECUTION_RUNTIME_MISS: &str = pub(crate) const EXECUTION_PATH_LOCAL_PROXY_PASSTHROUGH_REMOVED: &str =
"public_proxy_after_executor_miss"; "local_proxy_passthrough_removed";
pub(crate) const EXECUTION_PATH_PUBLIC_PROXY_AFTER_EXECUTION_RUNTIME_MISS: &str =
"public_proxy_after_execution_runtime_miss";
pub(crate) const EXECUTION_PATH_EXECUTION_RUNTIME_SYNC: &str = "execution_runtime_sync"; pub(crate) const EXECUTION_PATH_EXECUTION_RUNTIME_SYNC: &str = "execution_runtime_sync";
pub(crate) const EXECUTION_PATH_EXECUTION_RUNTIME_STREAM: &str = "execution_runtime_stream"; pub(crate) const EXECUTION_PATH_EXECUTION_RUNTIME_STREAM: &str = "execution_runtime_stream";
pub(crate) const EXECUTION_PATH_CONTROL_EXECUTE_SYNC: &str = "control_execute_sync"; pub(crate) const EXECUTION_PATH_CONTROL_EXECUTE_SYNC: &str = "control_execute_sync";
pub(crate) const EXECUTION_PATH_CONTROL_EXECUTE_STREAM: &str = "control_execute_stream"; pub(crate) const EXECUTION_PATH_CONTROL_EXECUTE_STREAM: &str = "control_execute_stream";
pub(crate) const LEGACY_EXECUTION_PATH_LOCAL_EXECUTION_RUNTIME_MISS: &str = "local_executor_miss";
pub(crate) const EXECUTION_PATH_LOCAL_EXECUTION_RUNTIME_MISS: &str = "local_execution_runtime_miss"; pub(crate) const EXECUTION_PATH_LOCAL_EXECUTION_RUNTIME_MISS: &str = "local_execution_runtime_miss";
pub(crate) const EXECUTION_PATH_LOCAL_AUTH_DENIED: &str = "local_auth_denied"; pub(crate) const EXECUTION_PATH_LOCAL_AUTH_DENIED: &str = "local_auth_denied";
pub(crate) const EXECUTION_PATH_LOCAL_RATE_LIMITED: &str = "local_rate_limited"; pub(crate) const EXECUTION_PATH_LOCAL_RATE_LIMITED: &str = "local_rate_limited";
pub(crate) const EXECUTION_PATH_LOCAL_ROUTE_NOT_FOUND: &str = "local_route_not_found";
pub(crate) const EXECUTION_PATH_LOCAL_OVERLOADED: &str = "local_overloaded"; pub(crate) const EXECUTION_PATH_LOCAL_OVERLOADED: &str = "local_overloaded";
pub(crate) const EXECUTION_PATH_DISTRIBUTED_OVERLOADED: &str = "distributed_overloaded"; pub(crate) const EXECUTION_PATH_DISTRIBUTED_OVERLOADED: &str = "distributed_overloaded";
pub(crate) const EXECUTION_PATH_LOCAL_AI_PUBLIC: &str = "local_ai_public";
pub(crate) const CONTROL_ROUTE_CLASS_HEADER: &str = "x-aether-control-route-class"; pub(crate) const CONTROL_ROUTE_CLASS_HEADER: &str = "x-aether-control-route-class";
pub(crate) const CONTROL_ROUTE_FAMILY_HEADER: &str = "x-aether-control-route-family"; pub(crate) const CONTROL_ROUTE_FAMILY_HEADER: &str = "x-aether-control-route-family";
pub(crate) const CONTROL_ROUTE_KIND_HEADER: &str = "x-aether-control-route-kind"; pub(crate) const CONTROL_ROUTE_KIND_HEADER: &str = "x-aether-control-route-kind";
pub(crate) const CONTROL_LEGACY_EXECUTION_RUNTIME_HEADER: &str =
"x-aether-control-executor-candidate";
pub(crate) const CONTROL_EXECUTION_RUNTIME_HEADER: &str = pub(crate) const CONTROL_EXECUTION_RUNTIME_HEADER: &str =
"x-aether-control-execution-runtime-candidate"; "x-aether-control-execution-runtime-candidate";
pub(crate) const CONTROL_LEGACY_EXECUTION_RUNTIME_CANDIDATE_KEY: &str = "executor_candidate";
pub(crate) const CONTROL_EXECUTION_RUNTIME_CANDIDATE_KEY: &str = "execution_runtime_candidate"; pub(crate) const CONTROL_EXECUTION_RUNTIME_CANDIDATE_KEY: &str = "execution_runtime_candidate";
pub(crate) const CONTROL_REQUEST_ID_HEADER: &str = "x-aether-control-request-id"; pub(crate) const CONTROL_REQUEST_ID_HEADER: &str = "x-aether-control-request-id";
pub(crate) const CONTROL_CANDIDATE_ID_HEADER: &str = "x-aether-control-candidate-id"; pub(crate) const CONTROL_CANDIDATE_ID_HEADER: &str = "x-aether-control-candidate-id";
@@ -48,14 +42,6 @@ pub(crate) const CONTROL_EXECUTED_HEADER: &str = "x-aether-control-executed";
pub(crate) const CONTROL_ACTION_HEADER: &str = "x-aether-control-action"; pub(crate) const CONTROL_ACTION_HEADER: &str = "x-aether-control-action";
pub(crate) const CONTROL_ACTION_PROXY_PUBLIC: &str = "proxy_public"; pub(crate) const CONTROL_ACTION_PROXY_PUBLIC: &str = "proxy_public";
pub(crate) const CONTROL_EXECUTE_FALLBACK_HEADER: &str = "x-aether-control-execute-fallback"; pub(crate) const CONTROL_EXECUTE_FALLBACK_HEADER: &str = "x-aether-control-execute-fallback";
pub(crate) const LEGACY_INTERNAL_GATEWAY_HEADER: &str = "x-aether-legacy-internal-gateway";
pub(crate) const LEGACY_INTERNAL_GATEWAY_PHASEOUT_STATUS: &str = "scheduled_for_removal";
pub(crate) const LEGACY_INTERNAL_GATEWAY_SUNSET_DATE: &str = "2026-06-01";
pub(crate) const LEGACY_INTERNAL_GATEWAY_SUNSET_HTTP_DATE: &str = "Mon, 01 Jun 2026 00:00:00 GMT";
pub(crate) const LEGACY_INTERNAL_GATEWAY_PHASEOUT_HEADER: &str =
"x-aether-legacy-internal-gateway-phaseout";
pub(crate) const LEGACY_INTERNAL_GATEWAY_SUNSET_DATE_HEADER: &str =
"x-aether-legacy-internal-gateway-sunset-date";
pub(crate) const TRUSTED_AUTH_USER_ID_HEADER: &str = "x-aether-auth-user-id"; pub(crate) const TRUSTED_AUTH_USER_ID_HEADER: &str = "x-aether-auth-user-id";
pub(crate) const TRUSTED_AUTH_API_KEY_ID_HEADER: &str = "x-aether-auth-api-key-id"; pub(crate) const TRUSTED_AUTH_API_KEY_ID_HEADER: &str = "x-aether-auth-api-key-id";
pub(crate) const TRUSTED_AUTH_BALANCE_HEADER: &str = "x-aether-auth-balance-remaining"; pub(crate) const TRUSTED_AUTH_BALANCE_HEADER: &str = "x-aether-auth-balance-remaining";
@@ -69,47 +55,8 @@ pub(crate) const TRUSTED_RATE_LIMIT_PREFLIGHT_HEADER: &str = "x-aether-rate-limi
pub(crate) const FRONTDOOR_REPLACEABLE_ROUTE_GROUPS: &[&str] = &["frontdoor_compat_router"]; pub(crate) const FRONTDOOR_REPLACEABLE_ROUTE_GROUPS: &[&str] = &["frontdoor_compat_router"];
pub(crate) const FRONTDOOR_REPLACEABLE_MIDDLEWARE_GROUPS: &[&str] = &["cors"]; pub(crate) const FRONTDOOR_REPLACEABLE_MIDDLEWARE_GROUPS: &[&str] = &["cors"];
// These manifest/reporting inventories intentionally remain explicit instead of being generated pub(crate) const INTERNAL_GATEWAY_ROUTE_GROUPS: &[&str] = &["internal_gateway_router"];
// from api::ai::registry router mounts. The manifest describes operational compatibility surfaces pub(crate) const INTERNAL_GATEWAY_PATH_PREFIXES: &[&str] = &["/api/internal/gateway"];
// and wildcard ownership, which is related to but not identical to the concrete axum route list.
pub(crate) const FRONTDOOR_COMPAT_ROUTE_PATTERNS: &[&str] = &[
"/v1/chat/completions",
"/v1/messages",
"/v1/messages/count_tokens",
"/v1/responses",
"/v1/responses/compact",
"/v1/videos*",
"/v1/models/{model}:generateContent",
"/v1/models/{model}:streamGenerateContent",
"/v1/models/{model}:predictLongRunning",
"/v1beta/models/{model}:generateContent",
"/v1beta/models/{model}:streamGenerateContent",
"/v1beta/models/{model}:predictLongRunning",
"/v1beta/models/{model}/operations/{id}",
"/v1beta/operations*",
"/upload/v1beta/files",
"/v1beta/files*",
];
pub(crate) const PYTHON_ONLY_ROUTE_GROUPS: &[&str] = &[
"auth_router",
"python_admin_router",
"me_router",
"wallet_router",
"payment_router",
"announcement_router",
"dashboard_router",
"python_public_support_router",
"monitoring_router",
"python_internal_router",
];
pub(crate) const PYTHON_ONLY_MIDDLEWARE_GROUPS: &[&str] = &["plugin_middleware"];
pub(crate) const PYTHON_ONLY_RUNTIME_COMPONENTS: &[&str] = &[
"python_host_lifespan",
"plugin_and_module_bootstrap",
"background_workers",
];
pub(crate) const LEGACY_GATEWAY_BRIDGE_ROUTE_GROUPS: &[&str] = &["legacy_gateway_bridge_router"];
pub(crate) const LEGACY_GATEWAY_BRIDGE_PATH_PREFIXES: &[&str] = &["/api/internal/gateway"];
pub(crate) const RUST_FRONTDOOR_OWNED_ROUTE_PATTERNS: &[&str] = &[ pub(crate) const RUST_FRONTDOOR_OWNED_ROUTE_PATTERNS: &[&str] = &[
FRONTDOOR_MANIFEST_PATH, FRONTDOOR_MANIFEST_PATH,
INTERNAL_FRONTDOOR_MANIFEST_PATH, INTERNAL_FRONTDOOR_MANIFEST_PATH,

View File

@@ -400,7 +400,11 @@ pub(super) fn current_unix_secs() -> u64 {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::{
build_auth_context_cache_key, extract_request_credentials, GatewayCredentialCarrier,
GatewayPrimaryCredential, GatewayTrustedAdminHeaders, GatewayTrustedAuthHeaders,
};
use axum::http::{self, Uri};
fn uri(path: &str) -> Uri { fn uri(path: &str) -> Uri {
path.parse().expect("uri should parse") path.parse().expect("uri should parse")

View File

@@ -34,7 +34,7 @@ pub(super) fn derive_principal_candidate(
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::{derive_principal_candidate, GatewayPrincipalCandidate};
use crate::gateway::control::auth::types::{ use crate::gateway::control::auth::types::{
GatewayCredentialBundle, GatewayCredentialCarrier, GatewayExtractedCredentials, GatewayCredentialBundle, GatewayCredentialCarrier, GatewayExtractedCredentials,
GatewayPrimaryCredential, GatewayTrustedAuthHeaders, GatewayPrimaryCredential, GatewayTrustedAuthHeaders,

View File

@@ -106,14 +106,14 @@ pub(in super::super) async fn resolve_control_decision_auth(
return Ok(ControlDecisionAuthResolution::Resolved(decision)); return Ok(ControlDecisionAuthResolution::Resolved(decision));
} }
if skips_legacy_python_auth_context(&decision) { if allows_missing_data_backed_auth_context(&decision) {
return Ok(ControlDecisionAuthResolution::Resolved(decision)); return Ok(ControlDecisionAuthResolution::Resolved(decision));
} }
Ok(ControlDecisionAuthResolution::Resolved(decision)) Ok(ControlDecisionAuthResolution::Resolved(decision))
} }
fn skips_legacy_python_auth_context(decision: &GatewayControlDecision) -> bool { fn allows_missing_data_backed_auth_context(decision: &GatewayControlDecision) -> bool {
matches!( matches!(
decision.route_kind.as_deref(), decision.route_kind.as_deref(),
Some("chat" | "cli" | "compact") Some("chat" | "cli" | "compact")
@@ -593,7 +593,7 @@ mod tests {
sample_snapshot("key-1", "user-1"), sample_snapshot("key-1", "user-1"),
)])); )]));
let data = GatewayDataState::with_auth_api_key_repository_for_tests(repository.clone()); let data = GatewayDataState::with_auth_api_key_repository_for_tests(repository.clone());
let state = AppState::new("http://127.0.0.1:9") let state = AppState::new()
.expect("state should build") .expect("state should build")
.with_data_state_for_tests(data); .with_data_state_for_tests(data);

View File

@@ -1,4 +1,6 @@
use super::*; use axum::http;
use super::{classified, ClassifiedRoute};
#[path = "admin/basic_families.rs"] #[path = "admin/basic_families.rs"]
mod basic_families; mod basic_families;

View File

@@ -1,4 +1,6 @@
use super::*; use axum::http;
use super::{classified, ClassifiedRoute};
pub(super) fn classify_admin_basic_family_route( pub(super) fn classify_admin_basic_family_route(
method: &http::Method, method: &http::Method,

View File

@@ -1,4 +1,6 @@
use super::*; use axum::http;
use super::{classified, ClassifiedRoute};
pub(super) fn classify_admin_endpoints_family_route( pub(super) fn classify_admin_endpoints_family_route(
method: &http::Method, method: &http::Method,

View File

@@ -1,4 +1,6 @@
use super::*; use axum::http;
use super::{classified, ClassifiedRoute};
pub(super) fn classify_admin_model_provider_family_route( pub(super) fn classify_admin_model_provider_family_route(
method: &http::Method, method: &http::Method,

View File

@@ -1,4 +1,6 @@
use super::*; use axum::http;
use super::{classified, ClassifiedRoute};
pub(super) fn classify_admin_observability_family_route( pub(super) fn classify_admin_observability_family_route(
method: &http::Method, method: &http::Method,

View File

@@ -1,4 +1,6 @@
use super::*; use axum::http;
use super::{classified, ClassifiedRoute};
pub(super) fn classify_admin_operations_family_route( pub(super) fn classify_admin_operations_family_route(
method: &http::Method, method: &http::Method,

View File

@@ -1,4 +1,6 @@
use super::*; use axum::http;
use super::{classified, ClassifiedRoute};
pub(super) fn classify_admin_provider_ops_routes( pub(super) fn classify_admin_provider_ops_routes(
method: &http::Method, method: &http::Method,

View File

@@ -1,4 +1,6 @@
use super::*; use axum::http;
use super::{classified, ClassifiedRoute};
pub(super) fn classify_admin_system_family_route( pub(super) fn classify_admin_system_family_route(
method: &http::Method, method: &http::Method,

View File

@@ -18,11 +18,11 @@ pub(super) fn classify_internal_route(
"/api/internal/gateway/finalize-sync" => "finalize_sync", "/api/internal/gateway/finalize-sync" => "finalize_sync",
"/api/internal/gateway/execute-sync" => "execute_sync", "/api/internal/gateway/execute-sync" => "execute_sync",
"/api/internal/gateway/execute-stream" => "execute_stream", "/api/internal/gateway/execute-stream" => "execute_stream",
_ => "legacy_gateway", _ => "unhandled",
}; };
Some(classified( Some(classified(
"internal_proxy", "internal_proxy",
"gateway_legacy", "internal_gateway",
route_kind, route_kind,
"", "",
false, false,

View File

@@ -4,82 +4,7 @@ pub(super) fn classify_oauth_route(
method: &http::Method, method: &http::Method,
normalized_path: &str, normalized_path: &str,
) -> Option<ClassifiedRoute> { ) -> Option<ClassifiedRoute> {
if method == http::Method::GET && normalized_path == "/api/oauth/providers" { if method == http::Method::GET && normalized_path == "/api/admin/oauth/supported-types" {
Some(classified(
"public_support",
"oauth_public_legacy",
"providers",
"",
false,
))
} else if method == http::Method::GET && normalized_path.starts_with("/api/oauth/") {
if normalized_path.ends_with("/authorize") {
Some(classified(
"public_support",
"oauth_public_legacy",
"authorize",
"",
false,
))
} else if normalized_path.ends_with("/callback") {
Some(classified(
"public_support",
"oauth_public_legacy",
"callback",
"",
false,
))
} else {
None
}
} else if method == http::Method::GET && normalized_path == "/api/user/oauth/bindable-providers"
{
Some(classified(
"public_support",
"oauth_user_legacy",
"bindable_providers",
"",
false,
))
} else if method == http::Method::GET && normalized_path == "/api/user/oauth/links" {
Some(classified(
"public_support",
"oauth_user_legacy",
"links",
"",
false,
))
} else if method == http::Method::POST
&& normalized_path.starts_with("/api/user/oauth/")
&& normalized_path.ends_with("/bind-token")
{
Some(classified(
"public_support",
"oauth_user_legacy",
"bind_token",
"",
false,
))
} else if method == http::Method::GET
&& normalized_path.starts_with("/api/user/oauth/")
&& normalized_path.ends_with("/bind")
{
Some(classified(
"public_support",
"oauth_user_legacy",
"bind",
"",
false,
))
} else if method == http::Method::DELETE && normalized_path.starts_with("/api/user/oauth/") {
Some(classified(
"public_support",
"oauth_user_legacy",
"unbind",
"",
false,
))
} else if method == http::Method::GET && normalized_path == "/api/admin/oauth/supported-types" {
Some(classified( Some(classified(
"admin_proxy", "admin_proxy",
"oauth_manage", "oauth_manage",

View File

@@ -1,5 +1,24 @@
use super::{classified, is_gemini_models_route, is_gemini_operation_route, ClassifiedRoute}; use super::{classified, is_gemini_models_route, is_gemini_operation_route, ClassifiedRoute};
fn has_single_segment_after_prefix(path: &str, prefix: &str) -> bool {
let trimmed = path.trim_end_matches('/');
let Some(segment) = trimmed.strip_prefix(prefix) else {
return false;
};
!segment.is_empty() && !segment.contains('/')
}
fn has_single_nested_suffix_after_prefix(path: &str, prefix: &str, suffix: &str) -> bool {
let trimmed = path.trim_end_matches('/');
let Some(rest) = trimmed.strip_prefix(prefix) else {
return false;
};
let Some((segment, actual_suffix)) = rest.split_once('/') else {
return false;
};
!segment.is_empty() && !segment.contains('/') && actual_suffix == suffix
}
pub(super) fn classify_public_support_route( pub(super) fn classify_public_support_route(
method: &http::Method, method: &http::Method,
normalized_path: &str, normalized_path: &str,
@@ -118,6 +137,7 @@ pub(super) fn classify_public_support_route(
&& normalized_path != "/api/announcements/users/me/unread-count" && normalized_path != "/api/announcements/users/me/unread-count"
&& normalized_path != "/api/announcements/users/me/unread-count/" && normalized_path != "/api/announcements/users/me/unread-count/"
&& !normalized_path.ends_with("/read-status") && !normalized_path.ends_with("/read-status")
&& has_single_segment_after_prefix(normalized_path, "/api/announcements/")
{ {
Some(classified( Some(classified(
"public_support", "public_support",
@@ -199,7 +219,7 @@ pub(super) fn classify_public_support_route(
}; };
Some(classified( Some(classified(
"public_support", "public_support",
"auth_legacy", "auth",
route_kind, route_kind,
"user:auth", "user:auth",
false, false,
@@ -222,7 +242,7 @@ pub(super) fn classify_public_support_route(
}; };
Some(classified( Some(classified(
"public_support", "public_support",
"dashboard_legacy", "dashboard",
route_kind, route_kind,
"user:dashboard", "user:dashboard",
false, false,
@@ -240,7 +260,7 @@ pub(super) fn classify_public_support_route(
}; };
Some(classified( Some(classified(
"public_support", "public_support",
"monitoring_user_legacy", "monitoring_user",
route_kind, route_kind,
"user:monitoring", "user:monitoring",
false, false,
@@ -254,7 +274,7 @@ pub(super) fn classify_public_support_route(
{ {
Some(classified( Some(classified(
"public_support", "public_support",
"announcement_user_legacy", "announcement_user",
"unread_count", "unread_count",
"user:announcements", "user:announcements",
false, false,
@@ -267,7 +287,7 @@ pub(super) fn classify_public_support_route(
{ {
Some(classified( Some(classified(
"public_support", "public_support",
"announcement_user_legacy", "announcement_user",
"read_all", "read_all",
"user:announcements", "user:announcements",
false, false,
@@ -275,10 +295,15 @@ pub(super) fn classify_public_support_route(
} else if method == http::Method::PATCH } else if method == http::Method::PATCH
&& normalized_path.starts_with("/api/announcements/") && normalized_path.starts_with("/api/announcements/")
&& (normalized_path.ends_with("/read-status") || normalized_path.ends_with("/read-status/")) && (normalized_path.ends_with("/read-status") || normalized_path.ends_with("/read-status/"))
&& has_single_nested_suffix_after_prefix(
normalized_path,
"/api/announcements/",
"read-status",
)
{ {
Some(classified( Some(classified(
"public_support", "public_support",
"announcement_user_legacy", "announcement_user",
"read_status", "read_status",
"user:announcements", "user:announcements",
false, false,
@@ -305,23 +330,27 @@ pub(super) fn classify_public_support_route(
}; };
Some(classified( Some(classified(
"public_support", "public_support",
"wallet_legacy", "wallet",
route_kind, route_kind,
"user:wallet", "user:wallet",
false, false,
)) ))
} else if method == http::Method::GET && normalized_path.starts_with("/api/wallet/recharge/") { } else if method == http::Method::GET
&& has_single_segment_after_prefix(normalized_path, "/api/wallet/recharge/")
{
Some(classified( Some(classified(
"public_support", "public_support",
"wallet_legacy", "wallet",
"recharge_detail", "recharge_detail",
"user:wallet", "user:wallet",
false, false,
)) ))
} else if method == http::Method::GET && normalized_path.starts_with("/api/wallet/refunds/") { } else if method == http::Method::GET
&& has_single_segment_after_prefix(normalized_path, "/api/wallet/refunds/")
{
Some(classified( Some(classified(
"public_support", "public_support",
"wallet_legacy", "wallet",
"refund_detail", "refund_detail",
"user:wallet", "user:wallet",
false, false,
@@ -339,16 +368,17 @@ pub(super) fn classify_public_support_route(
}; };
Some(classified( Some(classified(
"public_support", "public_support",
"wallet_legacy", "wallet",
route_kind, route_kind,
"user:wallet", "user:wallet",
false, false,
)) ))
} else if method == http::Method::POST && normalized_path.starts_with("/api/payment/callback/") } else if method == http::Method::POST
&& has_single_segment_after_prefix(normalized_path, "/api/payment/callback/")
{ {
Some(classified( Some(classified(
"public_support", "public_support",
"payment_callback_legacy", "payment_callback",
"callback", "callback",
"public:payment", "public:payment",
false, false,
@@ -387,7 +417,7 @@ pub(super) fn classify_public_support_route(
}; };
Some(classified( Some(classified(
"public_support", "public_support",
"users_me_legacy", "users_me",
route_kind, route_kind,
"user:self", "user:self",
false, false,
@@ -400,7 +430,7 @@ pub(super) fn classify_public_support_route(
{ {
Some(classified( Some(classified(
"public_support", "public_support",
"users_me_legacy", "users_me",
"management_tokens_list", "management_tokens_list",
"user:self", "user:self",
false, false,
@@ -419,7 +449,7 @@ pub(super) fn classify_public_support_route(
}; };
Some(classified( Some(classified(
"public_support", "public_support",
"users_me_legacy", "users_me",
route_kind, route_kind,
"user:self", "user:self",
false, false,
@@ -432,7 +462,7 @@ pub(super) fn classify_public_support_route(
{ {
Some(classified( Some(classified(
"public_support", "public_support",
"users_me_legacy", "users_me",
"management_tokens_create", "management_tokens_create",
"user:self", "user:self",
false, false,
@@ -443,7 +473,7 @@ pub(super) fn classify_public_support_route(
{ {
Some(classified( Some(classified(
"public_support", "public_support",
"users_me_legacy", "users_me",
"management_token_regenerate", "management_token_regenerate",
"user:self", "user:self",
false, false,
@@ -451,7 +481,7 @@ pub(super) fn classify_public_support_route(
} else if method == http::Method::PATCH && normalized_path == "/api/users/me/password" { } else if method == http::Method::PATCH && normalized_path == "/api/users/me/password" {
Some(classified( Some(classified(
"public_support", "public_support",
"users_me_legacy", "users_me",
"password", "password",
"user:self", "user:self",
false, false,
@@ -462,7 +492,7 @@ pub(super) fn classify_public_support_route(
{ {
Some(classified( Some(classified(
"public_support", "public_support",
"users_me_legacy", "users_me",
"management_token_toggle", "management_token_toggle",
"user:self", "user:self",
false, false,
@@ -470,13 +500,13 @@ pub(super) fn classify_public_support_route(
} else if method == http::Method::DELETE && normalized_path == "/api/users/me/sessions/others" { } else if method == http::Method::DELETE && normalized_path == "/api/users/me/sessions/others" {
Some(classified( Some(classified(
"public_support", "public_support",
"users_me_legacy", "users_me",
"sessions_others_delete", "sessions_others_delete",
"user:self", "user:self",
false, false,
)) ))
} else if matches!(method, &http::Method::PATCH | &http::Method::DELETE) } else if matches!(method, &http::Method::PATCH | &http::Method::DELETE)
&& normalized_path.starts_with("/api/users/me/sessions/") && has_single_segment_after_prefix(normalized_path, "/api/users/me/sessions/")
{ {
let route_kind = if method == http::Method::PATCH { let route_kind = if method == http::Method::PATCH {
"session_update" "session_update"
@@ -485,7 +515,7 @@ pub(super) fn classify_public_support_route(
}; };
Some(classified( Some(classified(
"public_support", "public_support",
"users_me_legacy", "users_me",
route_kind, route_kind,
"user:self", "user:self",
false, false,
@@ -500,14 +530,21 @@ pub(super) fn classify_public_support_route(
}; };
Some(classified( Some(classified(
"public_support", "public_support",
"users_me_legacy", "users_me",
route_kind, route_kind,
"user:self", "user:self",
false, false,
)) ))
} else if method == http::Method::PUT } else if method == http::Method::PUT
&& (normalized_path.ends_with("/providers") || normalized_path.ends_with("/capabilities")) && (has_single_nested_suffix_after_prefix(
&& normalized_path.starts_with("/api/users/me/api-keys/") normalized_path,
"/api/users/me/api-keys/",
"providers",
) || has_single_nested_suffix_after_prefix(
normalized_path,
"/api/users/me/api-keys/",
"capabilities",
))
{ {
let route_kind = if normalized_path.ends_with("/providers") { let route_kind = if normalized_path.ends_with("/providers") {
"api_key_providers_update" "api_key_providers_update"
@@ -516,7 +553,7 @@ pub(super) fn classify_public_support_route(
}; };
Some(classified( Some(classified(
"public_support", "public_support",
"users_me_legacy", "users_me",
route_kind, route_kind,
"user:self", "user:self",
false, false,
@@ -524,7 +561,7 @@ pub(super) fn classify_public_support_route(
} else if matches!( } else if matches!(
method, method,
&http::Method::GET | &http::Method::PUT | &http::Method::PATCH | &http::Method::DELETE &http::Method::GET | &http::Method::PUT | &http::Method::PATCH | &http::Method::DELETE
) && normalized_path.starts_with("/api/users/me/api-keys/") ) && has_single_segment_after_prefix(normalized_path, "/api/users/me/api-keys/")
{ {
let route_kind = match *method { let route_kind = match *method {
http::Method::GET => "api_key_detail", http::Method::GET => "api_key_detail",
@@ -535,7 +572,7 @@ pub(super) fn classify_public_support_route(
}; };
Some(classified( Some(classified(
"public_support", "public_support",
"users_me_legacy", "users_me",
route_kind, route_kind,
"user:self", "user:self",
false, false,
@@ -543,7 +580,7 @@ pub(super) fn classify_public_support_route(
} else if matches!( } else if matches!(
method, method,
&http::Method::GET | &http::Method::PUT | &http::Method::DELETE &http::Method::GET | &http::Method::PUT | &http::Method::DELETE
) && normalized_path.starts_with("/api/me/management-tokens/") ) && has_single_segment_after_prefix(normalized_path, "/api/me/management-tokens/")
{ {
let route_kind = match *method { let route_kind = match *method {
http::Method::GET => "management_token_detail", http::Method::GET => "management_token_detail",
@@ -553,7 +590,7 @@ pub(super) fn classify_public_support_route(
}; };
Some(classified( Some(classified(
"public_support", "public_support",
"users_me_legacy", "users_me",
route_kind, route_kind,
"user:self", "user:self",
false, false,

View File

@@ -1,4 +1,6 @@
use super::*; use http::Uri;
use super::{classify_control_route, headers};
#[test] #[test]
fn classifies_admin_adaptive_keys_list_as_admin_proxy_route() { fn classifies_admin_adaptive_keys_list_as_admin_proxy_route() {

View File

@@ -1,4 +1,6 @@
use super::*; use http::Uri;
use super::{classify_control_route, headers};
#[test] #[test]
fn classifies_admin_api_keys_list_as_admin_proxy_route() { fn classifies_admin_api_keys_list_as_admin_proxy_route() {

View File

@@ -1,4 +1,6 @@
use super::*; use http::Uri;
use super::{classify_control_route, headers};
#[test] #[test]
fn classifies_admin_billing_presets_as_admin_proxy_route() { fn classifies_admin_billing_presets_as_admin_proxy_route() {

View File

@@ -1,4 +1,6 @@
use super::*; use http::Uri;
use super::{classify_control_route, headers};
#[test] #[test]
fn classifies_admin_endpoint_health_api_formats_as_admin_proxy_route() { fn classifies_admin_endpoint_health_api_formats_as_admin_proxy_route() {

View File

@@ -1,4 +1,6 @@
use super::*; use http::Uri;
use super::{classify_control_route, headers};
#[test] #[test]
fn classifies_admin_endpoint_health_summary_as_admin_proxy_route() { fn classifies_admin_endpoint_health_summary_as_admin_proxy_route() {

View File

@@ -1,4 +1,6 @@
use super::*; use http::Uri;
use super::{classify_control_route, headers};
#[test] #[test]
fn classifies_admin_monitoring_audit_logs_as_admin_proxy_route() { fn classifies_admin_monitoring_audit_logs_as_admin_proxy_route() {

View File

@@ -1,4 +1,6 @@
use super::*; use http::Uri;
use super::{classify_control_route, headers};
#[test] #[test]
fn classifies_admin_provider_oauth_start_key_as_admin_proxy_route() { fn classifies_admin_provider_oauth_start_key_as_admin_proxy_route() {

View File

@@ -1,4 +1,6 @@
use super::*; use http::Uri;
use super::{classify_control_route, headers};
#[test] #[test]
fn classifies_admin_payments_list_orders_as_admin_proxy_route() { fn classifies_admin_payments_list_orders_as_admin_proxy_route() {

View File

@@ -1,4 +1,6 @@
use super::*; use http::Uri;
use super::{classify_control_route, headers};
#[test] #[test]
fn classifies_admin_pool_overview_as_admin_proxy_route() { fn classifies_admin_pool_overview_as_admin_proxy_route() {

View File

@@ -1,4 +1,6 @@
use super::*; use http::Uri;
use super::{classify_control_route, headers};
#[test] #[test]
fn classifies_admin_provider_ops_architectures_list_as_admin_proxy_route() { fn classifies_admin_provider_ops_architectures_list_as_admin_proxy_route() {

View File

@@ -1,4 +1,6 @@
use super::*; use http::Uri;
use super::{classify_control_route, headers};
#[test] #[test]
fn classifies_admin_provider_query_models_as_admin_proxy_route() { fn classifies_admin_provider_query_models_as_admin_proxy_route() {

View File

@@ -1,4 +1,6 @@
use super::*; use http::Uri;
use super::{classify_control_route, headers};
#[test] #[test]
fn classifies_admin_provider_strategy_list_as_admin_proxy_route() { fn classifies_admin_provider_strategy_list_as_admin_proxy_route() {

View File

@@ -1,4 +1,6 @@
use super::*; use http::Uri;
use super::{classify_control_route, headers};
#[test] #[test]
fn classifies_admin_create_provider_as_admin_proxy_route() { fn classifies_admin_create_provider_as_admin_proxy_route() {

View File

@@ -1,4 +1,6 @@
use super::*; use http::Uri;
use super::{classify_control_route, headers};
#[test] #[test]
fn classifies_admin_proxy_nodes_list_as_admin_proxy_route() { fn classifies_admin_proxy_nodes_list_as_admin_proxy_route() {

View File

@@ -1,4 +1,6 @@
use super::*; use http::Uri;
use super::{classify_control_route, headers};
#[test] #[test]
fn classifies_admin_security_blacklist_add_as_admin_proxy_route() { fn classifies_admin_security_blacklist_add_as_admin_proxy_route() {

View File

@@ -1,4 +1,6 @@
use super::*; use http::Uri;
use super::{classify_control_route, headers};
#[test] #[test]
fn classifies_admin_stats_provider_quota_usage_as_admin_proxy_route() { fn classifies_admin_stats_provider_quota_usage_as_admin_proxy_route() {

View File

@@ -1,4 +1,6 @@
use super::*; use http::Uri;
use super::{classify_control_route, headers};
#[test] #[test]
fn classifies_admin_usage_stats_as_admin_proxy_route() { fn classifies_admin_usage_stats_as_admin_proxy_route() {

View File

@@ -1,4 +1,6 @@
use super::*; use http::Uri;
use super::{classify_control_route, headers};
#[test] #[test]
fn classifies_admin_users_list_as_admin_proxy_route() { fn classifies_admin_users_list_as_admin_proxy_route() {

View File

@@ -1,4 +1,6 @@
use super::*; use http::Uri;
use super::{classify_control_route, headers};
#[test] #[test]
fn classifies_admin_video_tasks_list_as_admin_proxy_route() { fn classifies_admin_video_tasks_list_as_admin_proxy_route() {

View File

@@ -1,4 +1,6 @@
use super::*; use http::Uri;
use super::{classify_control_route, headers};
#[test] #[test]
fn classifies_admin_wallets_list_as_admin_proxy_route() { fn classifies_admin_wallets_list_as_admin_proxy_route() {

View File

@@ -1,4 +1,6 @@
use super::*; use http::Uri;
use super::{classify_control_route, headers};
#[test] #[test]
fn classifies_claude_count_tokens_as_non_execution_runtime_public_route() { fn classifies_claude_count_tokens_as_non_execution_runtime_public_route() {

View File

@@ -1,4 +1,6 @@
use super::*; use http::Uri;
use super::{classify_control_route, headers};
#[test] #[test]
fn classifies_internal_tunnel_heartbeat_as_internal_proxy_route() { fn classifies_internal_tunnel_heartbeat_as_internal_proxy_route() {
@@ -29,7 +31,7 @@ fn classifies_internal_gateway_resolve_as_internal_proxy_route() {
classify_control_route(&http::Method::POST, &uri, &headers).expect("route should classify"); classify_control_route(&http::Method::POST, &uri, &headers).expect("route should classify");
assert_eq!(decision.route_class.as_deref(), Some("internal_proxy")); assert_eq!(decision.route_class.as_deref(), Some("internal_proxy"));
assert_eq!(decision.route_family.as_deref(), Some("gateway_legacy")); assert_eq!(decision.route_family.as_deref(), Some("internal_gateway"));
assert_eq!(decision.route_kind.as_deref(), Some("resolve")); assert_eq!(decision.route_kind.as_deref(), Some("resolve"));
assert_eq!(decision.auth_endpoint_signature.as_deref(), Some("")); assert_eq!(decision.auth_endpoint_signature.as_deref(), Some(""));
assert!(!decision.is_execution_runtime_candidate()); assert!(!decision.is_execution_runtime_candidate());

View File

@@ -1,4 +1,6 @@
use super::*; use http::Uri;
use super::{classify_control_route, GatewayPublicRequestContext};
fn headers(items: &[(&str, &str)]) -> http::HeaderMap { fn headers(items: &[(&str, &str)]) -> http::HeaderMap {
let mut headers = http::HeaderMap::new(); let mut headers = http::HeaderMap::new();

View File

@@ -1,4 +1,6 @@
use super::*; use http::Uri;
use super::{classify_control_route, headers};
#[test] #[test]
fn classifies_models_list_as_public_support_route() { fn classifies_models_list_as_public_support_route() {
@@ -174,14 +176,14 @@ fn classifies_public_announcement_detail_as_public_support_route() {
} }
#[test] #[test]
fn classifies_dashboard_stats_as_public_support_legacy_route() { fn classifies_dashboard_stats_as_public_support_route() {
let headers = headers(&[]); let headers = headers(&[]);
let uri: Uri = "/api/dashboard/stats".parse().expect("uri should parse"); let uri: Uri = "/api/dashboard/stats".parse().expect("uri should parse");
let decision = let decision =
classify_control_route(&http::Method::GET, &uri, &headers).expect("route should classify"); classify_control_route(&http::Method::GET, &uri, &headers).expect("route should classify");
assert_eq!(decision.route_class.as_deref(), Some("public_support")); assert_eq!(decision.route_class.as_deref(), Some("public_support"));
assert_eq!(decision.route_family.as_deref(), Some("dashboard_legacy")); assert_eq!(decision.route_family.as_deref(), Some("dashboard"));
assert_eq!(decision.route_kind.as_deref(), Some("stats")); assert_eq!(decision.route_kind.as_deref(), Some("stats"));
assert_eq!( assert_eq!(
decision.auth_endpoint_signature.as_deref(), decision.auth_endpoint_signature.as_deref(),
@@ -191,7 +193,7 @@ fn classifies_dashboard_stats_as_public_support_legacy_route() {
} }
#[test] #[test]
fn classifies_user_monitoring_audit_logs_as_public_support_legacy_route() { fn classifies_user_monitoring_audit_logs_as_public_support_route() {
let headers = headers(&[]); let headers = headers(&[]);
let uri: Uri = "/api/monitoring/my-audit-logs" let uri: Uri = "/api/monitoring/my-audit-logs"
.parse() .parse()
@@ -200,10 +202,7 @@ fn classifies_user_monitoring_audit_logs_as_public_support_legacy_route() {
classify_control_route(&http::Method::GET, &uri, &headers).expect("route should classify"); classify_control_route(&http::Method::GET, &uri, &headers).expect("route should classify");
assert_eq!(decision.route_class.as_deref(), Some("public_support")); assert_eq!(decision.route_class.as_deref(), Some("public_support"));
assert_eq!( assert_eq!(decision.route_family.as_deref(), Some("monitoring_user"));
decision.route_family.as_deref(),
Some("monitoring_user_legacy")
);
assert_eq!(decision.route_kind.as_deref(), Some("audit_logs")); assert_eq!(decision.route_kind.as_deref(), Some("audit_logs"));
assert_eq!( assert_eq!(
decision.auth_endpoint_signature.as_deref(), decision.auth_endpoint_signature.as_deref(),
@@ -213,7 +212,7 @@ fn classifies_user_monitoring_audit_logs_as_public_support_legacy_route() {
} }
#[test] #[test]
fn classifies_announcement_unread_count_as_public_support_legacy_route() { fn classifies_announcement_unread_count_as_public_support_route() {
let headers = headers(&[]); let headers = headers(&[]);
let uri: Uri = "/api/announcements/users/me/unread-count" let uri: Uri = "/api/announcements/users/me/unread-count"
.parse() .parse()
@@ -222,10 +221,7 @@ fn classifies_announcement_unread_count_as_public_support_legacy_route() {
classify_control_route(&http::Method::GET, &uri, &headers).expect("route should classify"); classify_control_route(&http::Method::GET, &uri, &headers).expect("route should classify");
assert_eq!(decision.route_class.as_deref(), Some("public_support")); assert_eq!(decision.route_class.as_deref(), Some("public_support"));
assert_eq!( assert_eq!(decision.route_family.as_deref(), Some("announcement_user"));
decision.route_family.as_deref(),
Some("announcement_user_legacy")
);
assert_eq!(decision.route_kind.as_deref(), Some("unread_count")); assert_eq!(decision.route_kind.as_deref(), Some("unread_count"));
assert_eq!( assert_eq!(
decision.auth_endpoint_signature.as_deref(), decision.auth_endpoint_signature.as_deref(),
@@ -235,7 +231,7 @@ fn classifies_announcement_unread_count_as_public_support_legacy_route() {
} }
#[test] #[test]
fn classifies_announcement_read_status_as_public_support_legacy_route() { fn classifies_announcement_read_status_as_public_support_route() {
let headers = headers(&[]); let headers = headers(&[]);
let uri: Uri = "/api/announcements/announcement-1/read-status" let uri: Uri = "/api/announcements/announcement-1/read-status"
.parse() .parse()
@@ -244,10 +240,7 @@ fn classifies_announcement_read_status_as_public_support_legacy_route() {
.expect("route should classify"); .expect("route should classify");
assert_eq!(decision.route_class.as_deref(), Some("public_support")); assert_eq!(decision.route_class.as_deref(), Some("public_support"));
assert_eq!( assert_eq!(decision.route_family.as_deref(), Some("announcement_user"));
decision.route_family.as_deref(),
Some("announcement_user_legacy")
);
assert_eq!(decision.route_kind.as_deref(), Some("read_status")); assert_eq!(decision.route_kind.as_deref(), Some("read_status"));
assert_eq!( assert_eq!(
decision.auth_endpoint_signature.as_deref(), decision.auth_endpoint_signature.as_deref(),
@@ -257,7 +250,7 @@ fn classifies_announcement_read_status_as_public_support_legacy_route() {
} }
#[test] #[test]
fn classifies_announcement_read_all_as_public_support_legacy_route() { fn classifies_announcement_read_all_as_public_support_route() {
let headers = headers(&[]); let headers = headers(&[]);
let uri: Uri = "/api/announcements/read-all" let uri: Uri = "/api/announcements/read-all"
.parse() .parse()
@@ -266,10 +259,7 @@ fn classifies_announcement_read_all_as_public_support_legacy_route() {
classify_control_route(&http::Method::POST, &uri, &headers).expect("route should classify"); classify_control_route(&http::Method::POST, &uri, &headers).expect("route should classify");
assert_eq!(decision.route_class.as_deref(), Some("public_support")); assert_eq!(decision.route_class.as_deref(), Some("public_support"));
assert_eq!( assert_eq!(decision.route_family.as_deref(), Some("announcement_user"));
decision.route_family.as_deref(),
Some("announcement_user_legacy")
);
assert_eq!(decision.route_kind.as_deref(), Some("read_all")); assert_eq!(decision.route_kind.as_deref(), Some("read_all"));
assert_eq!( assert_eq!(
decision.auth_endpoint_signature.as_deref(), decision.auth_endpoint_signature.as_deref(),
@@ -279,7 +269,7 @@ fn classifies_announcement_read_all_as_public_support_legacy_route() {
} }
#[test] #[test]
fn classifies_wallet_legacy_routes_as_public_support_legacy_route() { fn classifies_wallet_routes_as_public_support_route() {
let headers = headers(&[]); let headers = headers(&[]);
for (method, uri, route_kind) in [ for (method, uri, route_kind) in [
(http::Method::GET, "/api/wallet/balance", "balance"), (http::Method::GET, "/api/wallet/balance", "balance"),
@@ -322,7 +312,7 @@ fn classifies_wallet_legacy_routes_as_public_support_legacy_route() {
classify_control_route(&method, &uri, &headers).expect("route should classify"); classify_control_route(&method, &uri, &headers).expect("route should classify");
assert_eq!(decision.route_class.as_deref(), Some("public_support")); assert_eq!(decision.route_class.as_deref(), Some("public_support"));
assert_eq!(decision.route_family.as_deref(), Some("wallet_legacy")); assert_eq!(decision.route_family.as_deref(), Some("wallet"));
assert_eq!(decision.route_kind.as_deref(), Some(route_kind)); assert_eq!(decision.route_kind.as_deref(), Some(route_kind));
assert_eq!( assert_eq!(
decision.auth_endpoint_signature.as_deref(), decision.auth_endpoint_signature.as_deref(),
@@ -333,7 +323,7 @@ fn classifies_wallet_legacy_routes_as_public_support_legacy_route() {
} }
#[test] #[test]
fn classifies_users_me_legacy_routes_as_public_support_legacy_route() { fn classifies_users_me_routes_as_public_support_route() {
let headers = headers(&[]); let headers = headers(&[]);
for (method, uri, route_kind) in [ for (method, uri, route_kind) in [
(http::Method::GET, "/api/users/me", "detail"), (http::Method::GET, "/api/users/me", "detail"),
@@ -436,7 +426,7 @@ fn classifies_users_me_legacy_routes_as_public_support_legacy_route() {
classify_control_route(&method, &uri, &headers).expect("route should classify"); classify_control_route(&method, &uri, &headers).expect("route should classify");
assert_eq!(decision.route_class.as_deref(), Some("public_support")); assert_eq!(decision.route_class.as_deref(), Some("public_support"));
assert_eq!(decision.route_family.as_deref(), Some("users_me_legacy")); assert_eq!(decision.route_family.as_deref(), Some("users_me"));
assert_eq!(decision.route_kind.as_deref(), Some(route_kind)); assert_eq!(decision.route_kind.as_deref(), Some(route_kind));
assert_eq!( assert_eq!(
decision.auth_endpoint_signature.as_deref(), decision.auth_endpoint_signature.as_deref(),
@@ -447,7 +437,7 @@ fn classifies_users_me_legacy_routes_as_public_support_legacy_route() {
} }
#[test] #[test]
fn classifies_payment_callback_as_public_support_legacy_route() { fn classifies_payment_callback_as_public_support_route() {
let headers = headers(&[]); let headers = headers(&[]);
let uri: Uri = "/api/payment/callback/alipay" let uri: Uri = "/api/payment/callback/alipay"
.parse() .parse()
@@ -456,10 +446,7 @@ fn classifies_payment_callback_as_public_support_legacy_route() {
classify_control_route(&http::Method::POST, &uri, &headers).expect("route should classify"); classify_control_route(&http::Method::POST, &uri, &headers).expect("route should classify");
assert_eq!(decision.route_class.as_deref(), Some("public_support")); assert_eq!(decision.route_class.as_deref(), Some("public_support"));
assert_eq!( assert_eq!(decision.route_family.as_deref(), Some("payment_callback"));
decision.route_family.as_deref(),
Some("payment_callback_legacy")
);
assert_eq!(decision.route_kind.as_deref(), Some("callback")); assert_eq!(decision.route_kind.as_deref(), Some("callback"));
assert_eq!( assert_eq!(
decision.auth_endpoint_signature.as_deref(), decision.auth_endpoint_signature.as_deref(),
@@ -620,7 +607,7 @@ fn classifies_auth_settings_as_public_support_route() {
} }
#[test] #[test]
fn classifies_auth_legacy_routes_as_public_support_route() { fn classifies_auth_routes_as_public_support_route() {
for (method, path, route_kind) in [ for (method, path, route_kind) in [
(http::Method::POST, "/api/auth/login", "login"), (http::Method::POST, "/api/auth/login", "login"),
(http::Method::POST, "/api/auth/refresh", "refresh"), (http::Method::POST, "/api/auth/refresh", "refresh"),
@@ -645,7 +632,7 @@ fn classifies_auth_legacy_routes_as_public_support_route() {
classify_control_route(&method, &uri, &headers).expect("route should classify"); classify_control_route(&method, &uri, &headers).expect("route should classify");
assert_eq!(decision.route_class.as_deref(), Some("public_support")); assert_eq!(decision.route_class.as_deref(), Some("public_support"));
assert_eq!(decision.route_family.as_deref(), Some("auth_legacy")); assert_eq!(decision.route_family.as_deref(), Some("auth"));
assert_eq!(decision.route_kind.as_deref(), Some(route_kind)); assert_eq!(decision.route_kind.as_deref(), Some(route_kind));
assert_eq!( assert_eq!(
decision.auth_endpoint_signature.as_deref(), decision.auth_endpoint_signature.as_deref(),
@@ -656,71 +643,45 @@ fn classifies_auth_legacy_routes_as_public_support_route() {
} }
#[test] #[test]
fn classifies_oauth_public_providers_as_public_support_route() { fn does_not_classify_oauth_public_providers_route() {
let headers = headers(&[]); let headers = headers(&[]);
let uri: Uri = "/api/oauth/providers".parse().expect("uri should parse"); let uri: Uri = "/api/oauth/providers".parse().expect("uri should parse");
let decision = let decision = classify_control_route(&http::Method::GET, &uri, &headers);
classify_control_route(&http::Method::GET, &uri, &headers).expect("route should classify");
assert_eq!(decision.route_class.as_deref(), Some("public_support")); assert!(decision.is_none());
assert_eq!(
decision.route_family.as_deref(),
Some("oauth_public_legacy")
);
assert_eq!(decision.route_kind.as_deref(), Some("providers"));
assert_eq!(decision.auth_endpoint_signature.as_deref(), Some(""));
assert!(!decision.is_execution_runtime_candidate());
} }
#[test] #[test]
fn classifies_oauth_public_authorize_as_public_support_route() { fn does_not_classify_oauth_public_authorize_route() {
let headers = headers(&[]); let headers = headers(&[]);
let uri: Uri = "/api/oauth/linuxdo/authorize?client_device_id=device-1" let uri: Uri = "/api/oauth/linuxdo/authorize?client_device_id=device-1"
.parse() .parse()
.expect("uri should parse"); .expect("uri should parse");
let decision = let decision = classify_control_route(&http::Method::GET, &uri, &headers);
classify_control_route(&http::Method::GET, &uri, &headers).expect("route should classify");
assert_eq!(decision.route_class.as_deref(), Some("public_support")); assert!(decision.is_none());
assert_eq!(
decision.route_family.as_deref(),
Some("oauth_public_legacy")
);
assert_eq!(decision.route_kind.as_deref(), Some("authorize"));
assert_eq!(decision.auth_endpoint_signature.as_deref(), Some(""));
assert!(!decision.is_execution_runtime_candidate());
} }
#[test] #[test]
fn classifies_oauth_user_bindable_providers_as_public_support_route() { fn does_not_classify_oauth_user_bindable_providers_route() {
let headers = headers(&[]); let headers = headers(&[]);
let uri: Uri = "/api/user/oauth/bindable-providers" let uri: Uri = "/api/user/oauth/bindable-providers"
.parse() .parse()
.expect("uri should parse"); .expect("uri should parse");
let decision = let decision = classify_control_route(&http::Method::GET, &uri, &headers);
classify_control_route(&http::Method::GET, &uri, &headers).expect("route should classify");
assert_eq!(decision.route_class.as_deref(), Some("public_support")); assert!(decision.is_none());
assert_eq!(decision.route_family.as_deref(), Some("oauth_user_legacy"));
assert_eq!(decision.route_kind.as_deref(), Some("bindable_providers"));
assert_eq!(decision.auth_endpoint_signature.as_deref(), Some(""));
assert!(!decision.is_execution_runtime_candidate());
} }
#[test] #[test]
fn classifies_oauth_user_bind_token_as_public_support_route() { fn does_not_classify_oauth_user_bind_token_route() {
let headers = headers(&[]); let headers = headers(&[]);
let uri: Uri = "/api/user/oauth/linuxdo/bind-token" let uri: Uri = "/api/user/oauth/linuxdo/bind-token"
.parse() .parse()
.expect("uri should parse"); .expect("uri should parse");
let decision = let decision = classify_control_route(&http::Method::POST, &uri, &headers);
classify_control_route(&http::Method::POST, &uri, &headers).expect("route should classify");
assert_eq!(decision.route_class.as_deref(), Some("public_support")); assert!(decision.is_none());
assert_eq!(decision.route_family.as_deref(), Some("oauth_user_legacy"));
assert_eq!(decision.route_kind.as_deref(), Some("bind_token"));
assert_eq!(decision.auth_endpoint_signature.as_deref(), Some(""));
assert!(!decision.is_execution_runtime_candidate());
} }
#[test] #[test]

View File

@@ -19,10 +19,10 @@ impl IntoResponse for GatewayError {
fn into_response(self) -> Response<Body> { fn into_response(self) -> Response<Body> {
match self { match self {
Self::UpstreamUnavailable { trace_id, message } => { Self::UpstreamUnavailable { trace_id, message } => {
warn!(trace_id = %trace_id, error = %message, "gateway upstream unavailable"); warn!(trace_id = %trace_id, error = %message, "gateway proxy unavailable");
let body = Json(json!({ let body = Json(json!({
"error": { "error": {
"message": "gateway upstream unavailable", "message": "gateway proxy unavailable",
"trace_id": trace_id, "trace_id": trace_id,
} }
})); }));

View File

@@ -251,7 +251,9 @@ pub(crate) fn append_execution_contract_fields_to_value(
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::{
append_execution_contract_fields_to_value, ConversionMode, ExecutionStrategy,
};
use serde_json::json; use serde_json::json;
#[test] #[test]

View File

@@ -32,7 +32,7 @@ pub(crate) async fn maybe_execute_via_execution_runtime_stream(
#[cfg(test)] #[cfg(test)]
{ {
if state if state
.test_remote_execution_runtime_base_url() .execution_runtime_override_base_url()
.unwrap_or_default() .unwrap_or_default()
.is_empty() .is_empty()
&& parts.method != http::Method::POST && parts.method != http::Method::POST

View File

@@ -98,7 +98,7 @@ pub(crate) async fn execute_execution_runtime_stream(
#[cfg(test)] #[cfg(test)]
{ {
let remote_execution_runtime_base_url = state let remote_execution_runtime_base_url = state
.test_remote_execution_runtime_base_url() .execution_runtime_override_base_url()
.unwrap_or_default(); .unwrap_or_default();
if remote_execution_runtime_base_url.trim().is_empty() { if remote_execution_runtime_base_url.trim().is_empty() {
let execution = match DirectSyncExecutionRuntime::new() let execution = match DirectSyncExecutionRuntime::new()

View File

@@ -38,7 +38,7 @@ pub(crate) async fn maybe_execute_via_execution_runtime_sync(
#[cfg(test)] #[cfg(test)]
{ {
if state if state
.test_remote_execution_runtime_base_url() .execution_runtime_override_base_url()
.unwrap_or_default() .unwrap_or_default()
.is_empty() .is_empty()
&& parts.method != http::Method::POST && parts.method != http::Method::POST

View File

@@ -101,7 +101,7 @@ pub(crate) async fn execute_execution_runtime_sync(
#[cfg(test)] #[cfg(test)]
let result = { let result = {
let remote_execution_runtime_base_url = state let remote_execution_runtime_base_url = state
.test_remote_execution_runtime_base_url() .execution_runtime_override_base_url()
.unwrap_or_default(); .unwrap_or_default();
if remote_execution_runtime_base_url.trim().is_empty() { if remote_execution_runtime_base_url.trim().is_empty() {
match DirectSyncExecutionRuntime::new() match DirectSyncExecutionRuntime::new()

View File

@@ -216,7 +216,7 @@ pub(crate) async fn execute_sync_plan(
#[cfg(test)] #[cfg(test)]
{ {
let remote_execution_runtime_base_url = state let remote_execution_runtime_base_url = state
.test_remote_execution_runtime_base_url() .execution_runtime_override_base_url()
.unwrap_or_default(); .unwrap_or_default();
if !remote_execution_runtime_base_url.trim().is_empty() { if !remote_execution_runtime_base_url.trim().is_empty() {
return execute_sync_plan_via_remote_execution_runtime( return execute_sync_plan_via_remote_execution_runtime(

View File

@@ -3,7 +3,6 @@ use std::sync::Mutex;
use aether_runtime::{MetricKind, MetricLabel, MetricSample}; use aether_runtime::{MetricKind, MetricLabel, MetricSample};
use crate::gateway::constants::LEGACY_INTERNAL_GATEWAY_SUNSET_DATE;
use crate::gateway::GatewayControlDecision; use crate::gateway::GatewayControlDecision;
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
@@ -12,10 +11,7 @@ pub(crate) enum GatewayFallbackMetricKind {
PlanFallback, PlanFallback,
ControlExecuteFallback, ControlExecuteFallback,
LocalExecutionRuntimeMiss, LocalExecutionRuntimeMiss,
PublicProxyAfterExecutionRuntimeMiss, RemoteExecuteEmergency,
PublicProxyPassthrough,
LegacyInternalBridge,
PythonExecuteEmergency,
} }
impl GatewayFallbackMetricKind { impl GatewayFallbackMetricKind {
@@ -25,12 +21,7 @@ impl GatewayFallbackMetricKind {
Self::PlanFallback => "plan_fallback_total", Self::PlanFallback => "plan_fallback_total",
Self::ControlExecuteFallback => "control_execute_fallback_total", Self::ControlExecuteFallback => "control_execute_fallback_total",
Self::LocalExecutionRuntimeMiss => "local_execution_runtime_miss_total", Self::LocalExecutionRuntimeMiss => "local_execution_runtime_miss_total",
Self::PublicProxyAfterExecutionRuntimeMiss => { Self::RemoteExecuteEmergency => "remote_execute_emergency_total",
"public_proxy_after_execution_runtime_miss_total"
}
Self::PublicProxyPassthrough => "public_proxy_passthrough_total",
Self::LegacyInternalBridge => "legacy_internal_bridge_total",
Self::PythonExecuteEmergency => "python_execute_emergency_total",
} }
} }
@@ -44,25 +35,10 @@ impl GatewayFallbackMetricKind {
"Number of requests that fell back to Python control execution." "Number of requests that fell back to Python control execution."
} }
Self::LocalExecutionRuntimeMiss => { Self::LocalExecutionRuntimeMiss => {
"Number of requests that were terminated locally after execution runtime miss because Python fallback was removed." "Number of requests that were terminated locally after execution runtime miss because no proxy fallback exists."
} }
Self::PublicProxyAfterExecutionRuntimeMiss => { Self::RemoteExecuteEmergency => {
"Number of requests that fell through to Python public proxy after execution runtime miss." "Number of requests that used remote emergency execution fallback."
}
Self::PublicProxyPassthrough => {
"Number of requests that were proxied to Python public routes without local execution."
}
Self::LegacyInternalBridge => {
Box::leak(
format!(
"Number of requests that still used the legacy internal gateway bridge scheduled to sunset on {}.",
LEGACY_INTERNAL_GATEWAY_SUNSET_DATE
)
.into_boxed_str(),
)
}
Self::PythonExecuteEmergency => {
"Number of requests that used Python emergency execution fallback."
} }
} }
} }
@@ -75,8 +51,7 @@ pub(crate) enum GatewayFallbackReason {
SchedulerDecisionUnsupported, SchedulerDecisionUnsupported,
ExecutionRuntimeMiss, ExecutionRuntimeMiss,
ProxyPassthrough, ProxyPassthrough,
PythonFallbackRemoved, LocalExecutionPathRequired,
LegacyInternalGateway,
ControlExecuteEmergency, ControlExecuteEmergency,
ExecutionRuntimeMissing, ExecutionRuntimeMissing,
} }
@@ -89,8 +64,7 @@ impl GatewayFallbackReason {
Self::SchedulerDecisionUnsupported => "scheduler_decision_unsupported", Self::SchedulerDecisionUnsupported => "scheduler_decision_unsupported",
Self::ExecutionRuntimeMiss => "execution_runtime_miss", Self::ExecutionRuntimeMiss => "execution_runtime_miss",
Self::ProxyPassthrough => "proxy_passthrough", Self::ProxyPassthrough => "proxy_passthrough",
Self::PythonFallbackRemoved => "python_fallback_removed", Self::LocalExecutionPathRequired => "local_execution_path_required",
Self::LegacyInternalGateway => "legacy_internal_gateway",
Self::ControlExecuteEmergency => "control_execute_emergency", Self::ControlExecuteEmergency => "control_execute_emergency",
Self::ExecutionRuntimeMissing => "execution_runtime_missing", Self::ExecutionRuntimeMissing => "execution_runtime_missing",
} }

View File

@@ -1,4 +1,8 @@
use super::*; use super::{
ai_pipeline, async_task, audit, auth, control, error, execution_runtime, fallback_metrics,
gateway_cache, gateway_data, handlers, hooks, intent, maintenance, middleware, model_fetch,
rate_limit, router, state, tunnel, usage, wallet_runtime,
};
pub(crate) use aether_data::repository::proxy_nodes::{ pub(crate) use aether_data::repository::proxy_nodes::{
ProxyNodeHeartbeatMutation, ProxyNodeTunnelStatusMutation, StoredProxyNode, ProxyNodeHeartbeatMutation, ProxyNodeTunnelStatusMutation, StoredProxyNode,

View File

@@ -1,7 +1,13 @@
use super::*; use axum::routing::any;
use axum::Router;
pub fn build_router(upstream_base_url: impl Into<String>) -> Result<Router, reqwest::Error> { use super::{
Ok(build_router_with_state(AppState::new(upstream_base_url)?)) api, middleware, prometheus_response, proxy_request, AppState, ConcurrencyError,
DistributedConcurrencyError,
};
pub fn build_router() -> Result<Router, reqwest::Error> {
Ok(build_router_with_state(AppState::new()?))
} }
pub fn build_router_with_state(state: AppState) -> Router { pub fn build_router_with_state(state: AppState) -> Router {
@@ -38,12 +44,9 @@ pub(crate) enum RequestAdmissionError {
Distributed(DistributedConcurrencyError), Distributed(DistributedConcurrencyError),
} }
pub async fn serve_tcp( pub async fn serve_tcp(bind: &str) -> Result<(), Box<dyn std::error::Error>> {
bind: &str,
upstream_base_url: &str,
) -> Result<(), Box<dyn std::error::Error>> {
let listener = tokio::net::TcpListener::bind(bind).await?; let listener = tokio::net::TcpListener::bind(bind).await?;
let router = build_router(upstream_base_url.to_string())?; let router = build_router()?;
axum::serve( axum::serve(
listener, listener,
router.into_make_service_with_connect_info::<std::net::SocketAddr>(), router.into_make_service_with_connect_info::<std::net::SocketAddr>(),

View File

@@ -1,4 +1,19 @@
use super::*; use std::collections::HashMap;
use std::sync::Arc;
use std::sync::Mutex as StdMutex;
use std::time::Duration;
use aether_runtime::{ConcurrencyGate, DistributedConcurrencyGate};
use super::async_task::{VideoTaskPollerConfig, VideoTaskService};
use super::error::GatewayError;
use super::fallback_metrics;
use super::gateway_cache::{
AuthApiKeyLastUsedCache, AuthContextCache, DirectPlanBypassCache, SchedulerAffinityCache,
};
use super::gateway_data::GatewayDataState;
use super::rate_limit::FrontdoorUserRpmLimiter;
use super::{provider_transport, usage};
#[path = "state/catalog.rs"] #[path = "state/catalog.rs"]
mod catalog; mod catalog;
#[path = "state/core.rs"] #[path = "state/core.rs"]
@@ -338,9 +353,8 @@ impl FrontdoorCorsConfig {
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct AppState { pub struct AppState {
pub(in crate::gateway) upstream_base_url: String,
#[cfg(test)] #[cfg(test)]
pub(in crate::gateway) test_remote_execution_runtime_base_url: Option<String>, pub(in crate::gateway) execution_runtime_override_base_url: Option<String>,
pub(in crate::gateway) data: Arc<GatewayDataState>, pub(in crate::gateway) data: Arc<GatewayDataState>,
pub(in crate::gateway) usage_runtime: Arc<usage::UsageRuntime>, pub(in crate::gateway) usage_runtime: Arc<usage::UsageRuntime>,
pub(in crate::gateway) video_tasks: Arc<VideoTaskService>, pub(in crate::gateway) video_tasks: Arc<VideoTaskService>,
@@ -429,10 +443,6 @@ pub struct AppState {
Arc<StdMutex<HashMap<String, String>>>, Arc<StdMutex<HashMap<String, String>>>,
} }
pub(super) fn normalize_upstream_base_url(upstream_base_url: String) -> String {
upstream_base_url.trim_end_matches('/').to_string()
}
pub(super) fn provider_transport_snapshot_looks_refreshed( pub(super) fn provider_transport_snapshot_looks_refreshed(
current: &provider_transport::GatewayProviderTransportSnapshot, current: &provider_transport::GatewayProviderTransportSnapshot,
refreshed: &provider_transport::GatewayProviderTransportSnapshot, refreshed: &provider_transport::GatewayProviderTransportSnapshot,

View File

@@ -1,4 +1,4 @@
use super::*; use super::{AppState, GatewayError, LocalMutationOutcome, LocalProviderDeleteTaskState};
impl AppState { impl AppState {
pub fn has_provider_catalog_data_reader(&self) -> bool { pub fn has_provider_catalog_data_reader(&self) -> bool {

View File

@@ -1,6 +1,44 @@
use super::*; use std::collections::HashMap;
use std::sync::Arc;
use std::sync::Mutex as StdMutex;
use std::time::Duration;
use aether_data::repository::proxy_nodes::{
ProxyNodeHeartbeatMutation, ProxyNodeTunnelStatusMutation, StoredProxyNode,
StoredProxyNodeEvent,
};
use aether_http::{build_http_client, HttpClientConfig};
use aether_runtime::{
service_up_sample, AdmissionPermit, ConcurrencyGate, ConcurrencySnapshot,
DistributedConcurrencyError, DistributedConcurrencyGate, DistributedConcurrencySnapshot,
MetricKind, MetricLabel, MetricSample,
};
use tokio::task::JoinHandle;
use super::{AppState, FrontdoorCorsConfig, LocalExecutionRuntimeMissDiagnostic};
use super::super::async_task::{
spawn_video_task_poller, VideoTaskPollerConfig, VideoTaskService, VideoTaskTruthSourceMode,
};
use super::super::fallback_metrics;
use super::super::fallback_metrics::{GatewayFallbackMetricKind, GatewayFallbackReason};
use super::super::gateway_cache::{
AuthApiKeyLastUsedCache, AuthContextCache, DirectPlanBypassCache, SchedulerAffinityCache,
};
use super::super::gateway_data::{GatewayDataConfig, GatewayDataState};
use super::super::model_fetch::spawn_model_fetch_worker;
use super::super::rate_limit::{FrontdoorUserRpmConfig, FrontdoorUserRpmLimiter};
use super::super::router::RequestAdmissionError;
use super::super::{control::GatewayControlDecision, error::GatewayError};
use super::super::{provider_transport, scheduler, usage};
use crate::gateway::maintenance::spawn_audit_cleanup_worker;
use crate::gateway::maintenance::spawn_db_maintenance_worker;
use crate::gateway::maintenance::spawn_gemini_file_mapping_cleanup_worker;
use crate::gateway::maintenance::spawn_pending_cleanup_worker; use crate::gateway::maintenance::spawn_pending_cleanup_worker;
use crate::gateway::maintenance::spawn_pool_monitor_worker; use crate::gateway::maintenance::spawn_pool_monitor_worker;
use crate::gateway::maintenance::spawn_provider_checkin_worker;
use crate::gateway::maintenance::spawn_request_candidate_cleanup_worker;
use crate::gateway::maintenance::spawn_stats_aggregation_worker; use crate::gateway::maintenance::spawn_stats_aggregation_worker;
use crate::gateway::maintenance::spawn_stats_hourly_aggregation_worker; use crate::gateway::maintenance::spawn_stats_hourly_aggregation_worker;
use crate::gateway::maintenance::spawn_usage_cleanup_worker; use crate::gateway::maintenance::spawn_usage_cleanup_worker;
@@ -17,22 +55,26 @@ impl AppState {
self.tunnel.request_close_all_proxies() self.tunnel.request_close_all_proxies()
} }
pub fn new(upstream_base_url: impl Into<String>) -> Result<Self, reqwest::Error> { pub fn new() -> Result<Self, reqwest::Error> {
Self::build(upstream_base_url, None) Self::build(None)
} }
#[cfg(test)] #[cfg(test)]
pub(crate) fn new_with_test_remote_execution_runtime( pub(crate) fn with_execution_runtime_override_base_url(
upstream_base_url: impl Into<String>, mut self,
test_remote_execution_runtime_base_url: Option<String>, execution_runtime_override_base_url: impl Into<String>,
) -> Result<Self, reqwest::Error> { ) -> Self {
Self::build(upstream_base_url, test_remote_execution_runtime_base_url) self.execution_runtime_override_base_url = Some(
execution_runtime_override_base_url
.into()
.trim_end_matches('/')
.to_string(),
)
.filter(|value| !value.is_empty());
self
} }
fn build( fn build(execution_runtime_override_base_url: Option<String>) -> Result<Self, reqwest::Error> {
upstream_base_url: impl Into<String>,
test_remote_execution_runtime_base_url: Option<String>,
) -> Result<Self, reqwest::Error> {
let data = Arc::new(GatewayDataState::disabled()); let data = Arc::new(GatewayDataState::disabled());
let client = build_http_client(&HttpClientConfig { let client = build_http_client(&HttpClientConfig {
connect_timeout_ms: Some(10_000), connect_timeout_ms: Some(10_000),
@@ -41,10 +83,9 @@ impl AppState {
..HttpClientConfig::default() ..HttpClientConfig::default()
})?; })?;
Ok(Self { Ok(Self {
upstream_base_url: normalize_upstream_base_url(upstream_base_url.into()),
#[cfg(test)] #[cfg(test)]
test_remote_execution_runtime_base_url: test_remote_execution_runtime_base_url execution_runtime_override_base_url: execution_runtime_override_base_url
.map(normalize_upstream_base_url) .map(|value| value.trim_end_matches('/').to_string())
.filter(|value| !value.is_empty()), .filter(|value| !value.is_empty()),
data: Arc::clone(&data), data: Arc::clone(&data),
usage_runtime: Arc::new(usage::UsageRuntime::disabled()), usage_runtime: Arc::new(usage::UsageRuntime::disabled()),
@@ -121,8 +162,8 @@ impl AppState {
} }
#[cfg(test)] #[cfg(test)]
pub(crate) fn test_remote_execution_runtime_base_url(&self) -> Option<&str> { pub(crate) fn execution_runtime_override_base_url(&self) -> Option<&str> {
self.test_remote_execution_runtime_base_url.as_deref() self.execution_runtime_override_base_url.as_deref()
} }
pub fn with_data_config( pub fn with_data_config(
@@ -160,6 +201,14 @@ impl AppState {
Ok(self) Ok(self)
} }
pub async fn run_postgres_migrations(&self) -> Result<bool, sqlx::migrate::MigrateError> {
let Some(pool) = self.postgres_pool() else {
return Ok(false);
};
aether_data::migrate::run_migrations(&pool).await?;
Ok(true)
}
pub fn with_video_task_poller_config(mut self, interval: Duration, batch_size: usize) -> Self { pub fn with_video_task_poller_config(mut self, interval: Duration, batch_size: usize) -> Self {
self.video_task_poller = Some(VideoTaskPollerConfig { self.video_task_poller = Some(VideoTaskPollerConfig {
interval, interval,

View File

@@ -1,4 +1,13 @@
use super::*; use super::{
provider_transport_snapshot_looks_refreshed, AppState, CachedProviderTransportSnapshot,
GatewayError, ProviderTransportSnapshotCacheKey, PROVIDER_TRANSPORT_SNAPSHOT_CACHE_MAX_ENTRIES,
PROVIDER_TRANSPORT_SNAPSHOT_CACHE_TTL,
};
use super::super::provider_transport;
use std::time::Duration;
use aether_crypto::encrypt_python_fernet_plaintext;
impl AppState { impl AppState {
pub(in crate::gateway) fn clear_provider_transport_snapshot_cache(&self) { pub(in crate::gateway) fn clear_provider_transport_snapshot_cache(&self) {

View File

@@ -1,4 +1,13 @@
use super::*; use super::super::error::GatewayError;
use super::super::{scheduler, usage};
use super::{
AdminBillingCollectorRecord, AdminBillingCollectorWriteInput, AdminBillingPresetApplyResult,
AdminBillingRuleRecord, AdminBillingRuleWriteInput, AdminPaymentCallbackRecord,
AdminSecurityBlacklistEntry, AdminWalletMutationOutcome, AdminWalletPaymentOrderRecord,
AdminWalletRefundRecord, AdminWalletTransactionRecord, AppState, LocalMutationOutcome,
AUTH_API_KEY_LAST_USED_MAX_ENTRIES, AUTH_API_KEY_LAST_USED_TTL,
};
use sqlx::Row; use sqlx::Row;
#[path = "runtime/admin_finance_queries.rs"] #[path = "runtime/admin_finance_queries.rs"]

View File

@@ -1,4 +1,12 @@
use super::*; use sqlx::Row;
use crate::gateway::state::AdminPaymentCallbackRecord;
use crate::gateway::{
AdminWalletMutationOutcome, AdminWalletPaymentOrderRecord, AdminWalletRefundRecord,
AdminWalletTransactionRecord, AppState, GatewayError,
};
use super::admin_wallet_payment_order_from_row;
impl AppState { impl AppState {
pub(crate) async fn list_admin_payment_orders( pub(crate) async fn list_admin_payment_orders(

View File

@@ -1,4 +1,13 @@
use super::*; use sqlx::Row;
use crate::gateway::{
AdminWalletMutationOutcome, AdminWalletPaymentOrderRecord, AppState, GatewayError,
};
use super::{
admin_payment_gateway_response_map, admin_wallet_payment_order_from_row,
admin_wallet_snapshot_from_row,
};
impl AppState { impl AppState {
pub(crate) async fn admin_expire_payment_order( pub(crate) async fn admin_expire_payment_order(

View File

@@ -1,4 +1,5 @@
use super::*; use crate::gateway::state::AdminSecurityBlacklistEntry;
use crate::gateway::{AppState, GatewayError};
impl AppState { impl AppState {
pub(crate) async fn add_admin_security_blacklist( pub(crate) async fn add_admin_security_blacklist(

View File

@@ -1,4 +1,10 @@
use super::*; use sqlx::Row;
use crate::gateway::{
AdminWalletPaymentOrderRecord, AdminWalletTransactionRecord, AppState, GatewayError,
};
use super::{admin_wallet_build_order_no, admin_wallet_snapshot_from_row};
impl AppState { impl AppState {
pub(crate) async fn admin_adjust_wallet_balance( pub(crate) async fn admin_adjust_wallet_balance(

View File

@@ -1,4 +1,9 @@
use super::*; use sqlx::Row;
use super::{
admin_wallet_refund_from_row, admin_wallet_snapshot_from_row, AdminWalletMutationOutcome,
AdminWalletRefundRecord, AdminWalletTransactionRecord, AppState, GatewayError,
};
impl AppState { impl AppState {
pub(crate) async fn admin_process_wallet_refund( pub(crate) async fn admin_process_wallet_refund(

View File

@@ -1,4 +1,4 @@
use super::*; use crate::gateway::{AppState, GatewayError};
impl AppState { impl AppState {
pub(crate) async fn list_announcements( pub(crate) async fn list_announcements(

View File

@@ -1,4 +1,4 @@
use super::*; use crate::gateway::{AppState, GatewayError};
impl AppState { impl AppState {
pub(crate) async fn list_auth_api_key_export_records_by_user_ids( pub(crate) async fn list_auth_api_key_export_records_by_user_ids(

View File

@@ -1,4 +1,6 @@
use super::*; use crate::gateway::{scheduler, usage, AppState, GatewayError};
use super::super::{AUTH_API_KEY_LAST_USED_MAX_ENTRIES, AUTH_API_KEY_LAST_USED_TTL};
impl AppState { impl AppState {
pub(crate) async fn read_request_candidate_trace( pub(crate) async fn read_request_candidate_trace(

View File

@@ -1,4 +1,4 @@
use super::*; use crate::gateway::{AppState, GatewayError};
impl AppState { impl AppState {
pub(crate) async fn find_user_session( pub(crate) async fn find_user_session(

View File

@@ -1,4 +1,4 @@
use super::*; use crate::gateway::{AppState, GatewayError};
impl AppState { impl AppState {
pub(crate) async fn find_user_auth_by_id( pub(crate) async fn find_user_auth_by_id(

View File

@@ -1,4 +1,4 @@
use super::*; use crate::gateway::{AppState, GatewayError};
impl AppState { impl AppState {
pub(crate) async fn read_user_model_capability_settings( pub(crate) async fn read_user_model_capability_settings(

View File

@@ -1,4 +1,8 @@
use super::*; use super::{
admin_billing_collector_from_row, admin_billing_rule_from_row, AdminBillingCollectorRecord,
AdminBillingCollectorWriteInput, AdminBillingPresetApplyResult, AdminBillingRuleRecord,
AdminBillingRuleWriteInput, AppState, GatewayError, LocalMutationOutcome,
};
impl AppState { impl AppState {
pub(crate) async fn admin_billing_enabled_default_value_exists( pub(crate) async fn admin_billing_enabled_default_value_exists(

View File

@@ -1,4 +1,4 @@
use super::*; use crate::gateway::{AppState, GatewayError};
impl AppState { impl AppState {
pub(crate) async fn list_minimal_candidate_selection_rows_for_api_format( pub(crate) async fn list_minimal_candidate_selection_rows_for_api_format(

View File

@@ -1,4 +1,4 @@
use super::*; use crate::gateway::{AppState, GatewayError};
impl AppState { impl AppState {
pub(crate) async fn upsert_gemini_file_mapping( pub(crate) async fn upsert_gemini_file_mapping(

View File

@@ -1,4 +1,4 @@
use super::*; use crate::gateway::{AppState, GatewayError};
impl AppState { impl AppState {
pub(crate) async fn record_shadow_result_sample( pub(crate) async fn record_shadow_result_sample(

View File

@@ -1,4 +1,4 @@
use super::*; use crate::gateway::{AppState, GatewayError};
impl AppState { impl AppState {
pub(crate) async fn read_request_candidates_by_request_id( pub(crate) async fn read_request_candidates_by_request_id(

View File

@@ -1,4 +1,4 @@
use super::*; use crate::gateway::{AppState, GatewayError};
impl AppState { impl AppState {
pub(crate) async fn read_user_preferences( pub(crate) async fn read_user_preferences(

View File

@@ -1,4 +1,9 @@
use super::*; use sqlx::Row;
use super::{
AdminBillingCollectorRecord, AdminBillingRuleRecord, AdminWalletPaymentOrderRecord,
AdminWalletRefundRecord, GatewayError,
};
pub(super) fn admin_wallet_build_order_no(now: chrono::DateTime<chrono::Utc>) -> String { pub(super) fn admin_wallet_build_order_no(now: chrono::DateTime<chrono::Utc>) -> String {
format!( format!(

View File

@@ -1,4 +1,4 @@
use super::*; use crate::gateway::{AppState, GatewayError};
impl AppState { impl AppState {
pub(crate) async fn find_wallet( pub(crate) async fn find_wallet(

View File

@@ -1,6 +1,11 @@
use super::*; use std::collections::HashMap;
use std::sync::{Arc, Mutex as StdMutex};
use serde_json::json; use serde_json::json;
use super::{AppState, GatewayDataState};
use crate::gateway::{provider_transport, usage};
#[cfg(test)] #[cfg(test)]
impl AppState { impl AppState {
pub(crate) fn with_data_state_for_tests(mut self, data_state: GatewayDataState) -> Self { pub(crate) fn with_data_state_for_tests(mut self, data_state: GatewayDataState) -> Self {
@@ -269,6 +274,16 @@ impl AppState {
self self
} }
pub(crate) fn without_auth_user_store_for_tests(mut self) -> Self {
self.auth_user_store = None;
self
}
pub(crate) fn without_auth_user_model_capability_store_for_tests(mut self) -> Self {
self.auth_user_model_capability_store = None;
self
}
pub(crate) fn with_auth_wallets_for_tests<I>(mut self, wallets: I) -> Self pub(crate) fn with_auth_wallets_for_tests<I>(mut self, wallets: I) -> Self
where where
I: IntoIterator<Item = aether_data::repository::wallet::StoredWalletSnapshot>, I: IntoIterator<Item = aether_data::repository::wallet::StoredWalletSnapshot>,
@@ -303,7 +318,7 @@ impl AppState {
pub(crate) fn with_admin_payment_callbacks_for_tests<I>(mut self, callbacks: I) -> Self pub(crate) fn with_admin_payment_callbacks_for_tests<I>(mut self, callbacks: I) -> Self
where where
I: IntoIterator<Item = AdminPaymentCallbackRecord>, I: IntoIterator<Item = crate::gateway::state::AdminPaymentCallbackRecord>,
{ {
let store = self let store = self
.admin_payment_callback_store .admin_payment_callback_store

View File

@@ -1,4 +1,7 @@
use super::*; use super::{AppState, GatewayError};
use super::super::async_task;
use super::super::async_task::video as video_tasks;
impl AppState { impl AppState {
pub(crate) async fn read_data_backed_video_task_response( pub(crate) async fn read_data_backed_video_task_response(
@@ -22,6 +25,18 @@ impl AppState {
.map_err(|err| GatewayError::Internal(err.to_string())) .map_err(|err| GatewayError::Internal(err.to_string()))
} }
pub(crate) async fn find_video_task_by_short_id(
&self,
short_id: &str,
) -> Result<Option<aether_data::repository::video_tasks::StoredVideoTask>, GatewayError> {
self.data
.find_video_task(
aether_data::repository::video_tasks::VideoTaskLookupKey::ShortId(short_id),
)
.await
.map_err(|err| GatewayError::Internal(err.to_string()))
}
pub(crate) async fn upsert_video_task_snapshot( pub(crate) async fn upsert_video_task_snapshot(
&self, &self,
snapshot: &video_tasks::LocalVideoTaskSnapshot, snapshot: &video_tasks::LocalVideoTaskSnapshot,

View File

@@ -1,4 +1,10 @@
use super::*; use super::{
any, build_router, build_router_with_execution_runtime_override, json, start_server, Arc,
Body, Bytes, HeaderName, HeaderValue, Infallible, Json, Mutex, Request, Response, Router,
StatusCode,
CONTROL_EXECUTED_HEADER, CONTROL_EXECUTE_FALLBACK_HEADER, DEPENDENCY_REASON_HEADER,
EXECUTION_PATH_HEADER, EXECUTION_PATH_LOCAL_EXECUTION_RUNTIME_MISS, TRACE_ID_HEADER,
};
#[tokio::test] #[tokio::test]
async fn gateway_locally_denies_sync_ai_control_execute_when_opted_in_and_execution_runtime_missing( async fn gateway_locally_denies_sync_ai_control_execute_when_opted_in_and_execution_runtime_missing(
@@ -20,7 +26,6 @@ async fn gateway_locally_denies_sync_ai_control_execute_when_opted_in_and_execut
"route_family": "openai", "route_family": "openai",
"route_kind": "chat", "route_kind": "chat",
"auth_endpoint_signature": "openai:chat", "auth_endpoint_signature": "openai:chat",
"executor_candidate": true,
"execution_runtime_candidate": true, "execution_runtime_candidate": true,
"auth_context": { "auth_context": {
"user_id": "user-sync-123", "user_id": "user-sync-123",
@@ -84,7 +89,7 @@ async fn gateway_locally_denies_sync_ai_control_execute_when_opted_in_and_execut
); );
let (upstream_url, upstream_handle) = start_server(upstream).await; let (upstream_url, upstream_handle) = start_server(upstream).await;
let gateway = build_router(upstream_url.clone()).expect("gateway should build"); let gateway = build_router().expect("gateway should build");
let (gateway_url, gateway_handle) = start_server(gateway).await; let (gateway_url, gateway_handle) = start_server(gateway).await;
let response = reqwest::Client::new() let response = reqwest::Client::new()
@@ -103,12 +108,12 @@ async fn gateway_locally_denies_sync_ai_control_execute_when_opted_in_and_execut
.get(EXECUTION_PATH_HEADER) .get(EXECUTION_PATH_HEADER)
.and_then(|value| value.to_str().ok()) .and_then(|value| value.to_str().ok())
.map(ToOwned::to_owned); .map(ToOwned::to_owned);
let python_dependency_reason = response let dependency_reason = response
.headers() .headers()
.get(PYTHON_DEPENDENCY_REASON_HEADER) .get(DEPENDENCY_REASON_HEADER)
.and_then(|value| value.to_str().ok()) .and_then(|value| value.to_str().ok())
.map(ToOwned::to_owned); .map(ToOwned::to_owned);
assert_eq!(python_dependency_reason.as_deref(), None); assert_eq!(dependency_reason.as_deref(), None);
let payload: serde_json::Value = response.json().await.expect("body should parse"); let payload: serde_json::Value = response.json().await.expect("body should parse");
assert_eq!( assert_eq!(
execution_path.as_deref(), execution_path.as_deref(),
@@ -116,7 +121,7 @@ async fn gateway_locally_denies_sync_ai_control_execute_when_opted_in_and_execut
); );
assert_eq!( assert_eq!(
payload["error"]["message"], payload["error"]["message"],
"OpenAI chat execution runtime miss did not match a Rust execution path, and Python fallback has been removed" "OpenAI chat execution runtime miss did not match a Rust execution path"
); );
assert_eq!(*execute_hits.lock().expect("mutex should lock"), 0); assert_eq!(*execute_hits.lock().expect("mutex should lock"), 0);
assert_eq!(*public_hits.lock().expect("mutex should lock"), 0); assert_eq!(*public_hits.lock().expect("mutex should lock"), 0);
@@ -152,7 +157,6 @@ async fn gateway_locally_denies_stream_ai_control_execute_when_opted_in_and_exec
"route_family": "openai", "route_family": "openai",
"route_kind": "chat", "route_kind": "chat",
"auth_endpoint_signature": "openai:chat", "auth_endpoint_signature": "openai:chat",
"executor_candidate": true,
"execution_runtime_candidate": true, "execution_runtime_candidate": true,
"auth_context": { "auth_context": {
"user_id": "user-stream-123", "user_id": "user-stream-123",
@@ -220,7 +224,7 @@ async fn gateway_locally_denies_stream_ai_control_execute_when_opted_in_and_exec
); );
let (upstream_url, upstream_handle) = start_server(upstream).await; let (upstream_url, upstream_handle) = start_server(upstream).await;
let gateway = build_router(upstream_url.clone()).expect("gateway should build"); let gateway = build_router().expect("gateway should build");
let (gateway_url, gateway_handle) = start_server(gateway).await; let (gateway_url, gateway_handle) = start_server(gateway).await;
let response = reqwest::Client::new() let response = reqwest::Client::new()
@@ -239,12 +243,12 @@ async fn gateway_locally_denies_stream_ai_control_execute_when_opted_in_and_exec
.get(EXECUTION_PATH_HEADER) .get(EXECUTION_PATH_HEADER)
.and_then(|value| value.to_str().ok()) .and_then(|value| value.to_str().ok())
.map(ToOwned::to_owned); .map(ToOwned::to_owned);
let python_dependency_reason = response let dependency_reason = response
.headers() .headers()
.get(PYTHON_DEPENDENCY_REASON_HEADER) .get(DEPENDENCY_REASON_HEADER)
.and_then(|value| value.to_str().ok()) .and_then(|value| value.to_str().ok())
.map(ToOwned::to_owned); .map(ToOwned::to_owned);
assert_eq!(python_dependency_reason.as_deref(), None); assert_eq!(dependency_reason.as_deref(), None);
let payload: serde_json::Value = response.json().await.expect("body should parse"); let payload: serde_json::Value = response.json().await.expect("body should parse");
assert_eq!( assert_eq!(
execution_path.as_deref(), execution_path.as_deref(),
@@ -252,7 +256,7 @@ async fn gateway_locally_denies_stream_ai_control_execute_when_opted_in_and_exec
); );
assert_eq!( assert_eq!(
payload["error"]["message"], payload["error"]["message"],
"OpenAI chat execution runtime miss did not match a Rust execution path, and Python fallback has been removed" "OpenAI chat execution runtime miss did not match a Rust execution path"
); );
assert_eq!(*execute_hits.lock().expect("mutex should lock"), 0); assert_eq!(*execute_hits.lock().expect("mutex should lock"), 0);
assert_eq!(*public_hits.lock().expect("mutex should lock"), 0); assert_eq!(*public_hits.lock().expect("mutex should lock"), 0);
@@ -288,7 +292,6 @@ async fn gateway_does_not_proxy_control_execute_over_http_when_opted_in_and_exec
"route_family": "openai", "route_family": "openai",
"route_kind": "chat", "route_kind": "chat",
"auth_endpoint_signature": "openai:chat", "auth_endpoint_signature": "openai:chat",
"executor_candidate": true,
"execution_runtime_candidate": true, "execution_runtime_candidate": true,
"public_path": "/v1/chat/completions" "public_path": "/v1/chat/completions"
})) }))
@@ -361,10 +364,7 @@ async fn gateway_does_not_proxy_control_execute_over_http_when_opted_in_and_exec
let (upstream_url, upstream_handle) = start_server(upstream).await; let (upstream_url, upstream_handle) = start_server(upstream).await;
let (execution_runtime_url, execution_runtime_handle) = start_server(execution_runtime).await; let (execution_runtime_url, execution_runtime_handle) = start_server(execution_runtime).await;
let gateway = build_router_with_test_remote_execution_runtime( let gateway = build_router_with_execution_runtime_override(execution_runtime_url);
upstream_url.clone(),
execution_runtime_url,
);
let (gateway_url, gateway_handle) = start_server(gateway).await; let (gateway_url, gateway_handle) = start_server(gateway).await;
let response = reqwest::Client::new() let response = reqwest::Client::new()
@@ -387,14 +387,14 @@ async fn gateway_does_not_proxy_control_execute_over_http_when_opted_in_and_exec
assert_eq!( assert_eq!(
response response
.headers() .headers()
.get(PYTHON_DEPENDENCY_REASON_HEADER) .get(DEPENDENCY_REASON_HEADER)
.and_then(|value| value.to_str().ok()), .and_then(|value| value.to_str().ok()),
None None
); );
let payload: serde_json::Value = response.json().await.expect("body should parse"); let payload: serde_json::Value = response.json().await.expect("body should parse");
assert_eq!( assert_eq!(
payload["error"]["message"], payload["error"]["message"],
"OpenAI chat execution runtime miss did not match a Rust execution path, and Python fallback has been removed" "OpenAI chat execution runtime miss did not match a Rust execution path"
); );
assert_eq!(*plan_hits.lock().expect("mutex should lock"), 0); assert_eq!(*plan_hits.lock().expect("mutex should lock"), 0);
assert_eq!(*execute_hits.lock().expect("mutex should lock"), 0); assert_eq!(*execute_hits.lock().expect("mutex should lock"), 0);
@@ -425,7 +425,6 @@ async fn gateway_does_not_proxy_control_execute_over_http_when_opted_in_and_exec
"route_family": "openai", "route_family": "openai",
"route_kind": "chat", "route_kind": "chat",
"auth_endpoint_signature": "openai:chat", "auth_endpoint_signature": "openai:chat",
"executor_candidate": true,
"execution_runtime_candidate": true, "execution_runtime_candidate": true,
"public_path": "/v1/chat/completions" "public_path": "/v1/chat/completions"
})) }))
@@ -498,10 +497,7 @@ async fn gateway_does_not_proxy_control_execute_over_http_when_opted_in_and_exec
let (upstream_url, upstream_handle) = start_server(upstream).await; let (upstream_url, upstream_handle) = start_server(upstream).await;
let (execution_runtime_url, execution_runtime_handle) = start_server(execution_runtime).await; let (execution_runtime_url, execution_runtime_handle) = start_server(execution_runtime).await;
let gateway = build_router_with_test_remote_execution_runtime( let gateway = build_router_with_execution_runtime_override(execution_runtime_url);
upstream_url.clone(),
execution_runtime_url,
);
let (gateway_url, gateway_handle) = start_server(gateway).await; let (gateway_url, gateway_handle) = start_server(gateway).await;
let response = reqwest::Client::new() let response = reqwest::Client::new()
@@ -524,14 +520,14 @@ async fn gateway_does_not_proxy_control_execute_over_http_when_opted_in_and_exec
assert_eq!( assert_eq!(
response response
.headers() .headers()
.get(PYTHON_DEPENDENCY_REASON_HEADER) .get(DEPENDENCY_REASON_HEADER)
.and_then(|value| value.to_str().ok()), .and_then(|value| value.to_str().ok()),
None None
); );
let payload: serde_json::Value = response.json().await.expect("body should parse"); let payload: serde_json::Value = response.json().await.expect("body should parse");
assert_eq!( assert_eq!(
payload["error"]["message"], payload["error"]["message"],
"OpenAI chat execution runtime miss did not match a Rust execution path, and Python fallback has been removed" "OpenAI chat execution runtime miss did not match a Rust execution path"
); );
assert_eq!(*plan_hits.lock().expect("mutex should lock"), 0); assert_eq!(*plan_hits.lock().expect("mutex should lock"), 0);
assert_eq!(*execute_hits.lock().expect("mutex should lock"), 0); assert_eq!(*execute_hits.lock().expect("mutex should lock"), 0);

View File

@@ -1,4 +1,9 @@
use super::*; use super::{
any, build_router, build_router_with_execution_runtime_override, json, start_server, Arc,
Body, HeaderValue, Json, Mutex, Request, Response, Router, StatusCode,
DEPENDENCY_REASON_HEADER, EXECUTION_PATH_HEADER, EXECUTION_PATH_LOCAL_EXECUTION_RUNTIME_MISS,
LOCAL_EXECUTION_RUNTIME_MISS_REASON_HEADER, TRACE_ID_HEADER,
};
#[tokio::test] #[tokio::test]
async fn gateway_locally_denies_openai_chat_after_repeated_execution_runtime_misses_without_control_execute_opt_in( async fn gateway_locally_denies_openai_chat_after_repeated_execution_runtime_misses_without_control_execute_opt_in(
@@ -20,7 +25,6 @@ async fn gateway_locally_denies_openai_chat_after_repeated_execution_runtime_mis
"route_family": "openai", "route_family": "openai",
"route_kind": "chat", "route_kind": "chat",
"auth_endpoint_signature": "openai:chat", "auth_endpoint_signature": "openai:chat",
"executor_candidate": true,
"execution_runtime_candidate": true, "execution_runtime_candidate": true,
"public_path": "/v1/chat/completions" "public_path": "/v1/chat/completions"
})) }))
@@ -79,10 +83,7 @@ async fn gateway_locally_denies_openai_chat_after_repeated_execution_runtime_mis
let (upstream_url, upstream_handle) = start_server(upstream).await; let (upstream_url, upstream_handle) = start_server(upstream).await;
let (execution_runtime_url, execution_runtime_handle) = start_server(execution_runtime).await; let (execution_runtime_url, execution_runtime_handle) = start_server(execution_runtime).await;
let gateway = build_router_with_test_remote_execution_runtime( let gateway = build_router_with_execution_runtime_override(execution_runtime_url);
upstream_url.clone(),
execution_runtime_url,
);
let (gateway_url, gateway_handle) = start_server(gateway).await; let (gateway_url, gateway_handle) = start_server(gateway).await;
let client = reqwest::Client::new(); let client = reqwest::Client::new();
@@ -106,7 +107,7 @@ async fn gateway_locally_denies_openai_chat_after_repeated_execution_runtime_mis
assert_eq!( assert_eq!(
response response
.headers() .headers()
.get(PYTHON_DEPENDENCY_REASON_HEADER) .get(DEPENDENCY_REASON_HEADER)
.and_then(|value| value.to_str().ok()), .and_then(|value| value.to_str().ok()),
None None
); );
@@ -121,7 +122,7 @@ async fn gateway_locally_denies_openai_chat_after_repeated_execution_runtime_mis
assert_eq!(payload["error"]["type"], "http_error"); assert_eq!(payload["error"]["type"], "http_error");
assert_eq!( assert_eq!(
payload["error"]["message"], payload["error"]["message"],
"OpenAI chat execution runtime miss did not match a Rust execution path, and Python fallback has been removed" "OpenAI chat execution runtime miss did not match a Rust execution path"
); );
} }
@@ -156,7 +157,6 @@ async fn gateway_locally_denies_openai_chat_when_control_api_is_configured_witho
"route_family": "openai", "route_family": "openai",
"route_kind": "chat", "route_kind": "chat",
"auth_endpoint_signature": "openai:chat", "auth_endpoint_signature": "openai:chat",
"executor_candidate": true,
"execution_runtime_candidate": true, "execution_runtime_candidate": true,
"public_path": "/v1/chat/completions" "public_path": "/v1/chat/completions"
})) }))
@@ -211,7 +211,7 @@ async fn gateway_locally_denies_openai_chat_when_control_api_is_configured_witho
); );
let (upstream_url, upstream_handle) = start_server(upstream).await; let (upstream_url, upstream_handle) = start_server(upstream).await;
let gateway = build_router(upstream_url.clone()).expect("gateway should build"); let gateway = build_router().expect("gateway should build");
let (gateway_url, gateway_handle) = start_server(gateway).await; let (gateway_url, gateway_handle) = start_server(gateway).await;
let response = reqwest::Client::new() let response = reqwest::Client::new()
@@ -233,7 +233,7 @@ async fn gateway_locally_denies_openai_chat_when_control_api_is_configured_witho
assert_eq!( assert_eq!(
response response
.headers() .headers()
.get(PYTHON_DEPENDENCY_REASON_HEADER) .get(DEPENDENCY_REASON_HEADER)
.and_then(|value| value.to_str().ok()), .and_then(|value| value.to_str().ok()),
None None
); );
@@ -248,7 +248,7 @@ async fn gateway_locally_denies_openai_chat_when_control_api_is_configured_witho
assert_eq!(payload["error"]["type"], "http_error"); assert_eq!(payload["error"]["type"], "http_error");
assert_eq!( assert_eq!(
payload["error"]["message"], payload["error"]["message"],
"OpenAI chat execution runtime miss did not match a Rust execution path, and Python fallback has been removed" "OpenAI chat execution runtime miss did not match a Rust execution path"
); );
assert_eq!(*execute_hits.lock().expect("mutex should lock"), 0); assert_eq!(*execute_hits.lock().expect("mutex should lock"), 0);
assert_eq!(*public_hits.lock().expect("mutex should lock"), 0); assert_eq!(*public_hits.lock().expect("mutex should lock"), 0);
@@ -283,7 +283,6 @@ async fn gateway_locally_denies_openai_chat_stream_after_execution_runtime_miss_
"route_family": "openai", "route_family": "openai",
"route_kind": "chat", "route_kind": "chat",
"auth_endpoint_signature": "openai:chat", "auth_endpoint_signature": "openai:chat",
"executor_candidate": true,
"execution_runtime_candidate": true, "execution_runtime_candidate": true,
"public_path": "/v1/chat/completions" "public_path": "/v1/chat/completions"
})) }))
@@ -323,10 +322,7 @@ async fn gateway_locally_denies_openai_chat_stream_after_execution_runtime_miss_
let (upstream_url, upstream_handle) = start_server(upstream).await; let (upstream_url, upstream_handle) = start_server(upstream).await;
let (execution_runtime_url, execution_runtime_handle) = start_server(execution_runtime).await; let (execution_runtime_url, execution_runtime_handle) = start_server(execution_runtime).await;
let gateway = build_router_with_test_remote_execution_runtime( let gateway = build_router_with_execution_runtime_override(execution_runtime_url);
upstream_url.clone(),
execution_runtime_url,
);
let (gateway_url, gateway_handle) = start_server(gateway).await; let (gateway_url, gateway_handle) = start_server(gateway).await;
let response = reqwest::Client::new() let response = reqwest::Client::new()
@@ -348,7 +344,7 @@ async fn gateway_locally_denies_openai_chat_stream_after_execution_runtime_miss_
assert_eq!( assert_eq!(
response response
.headers() .headers()
.get(PYTHON_DEPENDENCY_REASON_HEADER) .get(DEPENDENCY_REASON_HEADER)
.and_then(|value| value.to_str().ok()), .and_then(|value| value.to_str().ok()),
None None
); );
@@ -363,7 +359,7 @@ async fn gateway_locally_denies_openai_chat_stream_after_execution_runtime_miss_
assert_eq!(payload["error"]["type"], "http_error"); assert_eq!(payload["error"]["type"], "http_error");
assert_eq!( assert_eq!(
payload["error"]["message"], payload["error"]["message"],
"OpenAI chat execution runtime miss did not match a Rust execution path, and Python fallback has been removed" "OpenAI chat execution runtime miss did not match a Rust execution path"
); );
assert_eq!(*execute_hits.lock().expect("mutex should lock"), 0); assert_eq!(*execute_hits.lock().expect("mutex should lock"), 0);
assert_eq!(*public_hits.lock().expect("mutex should lock"), 0); assert_eq!(*public_hits.lock().expect("mutex should lock"), 0);
@@ -428,7 +424,6 @@ async fn assert_ai_route_locally_denied_after_execution_runtime_miss_with_reques
"route_family": route_family, "route_family": route_family,
"route_kind": route_kind, "route_kind": route_kind,
"auth_endpoint_signature": endpoint_signature, "auth_endpoint_signature": endpoint_signature,
"executor_candidate": true,
"execution_runtime_candidate": true, "execution_runtime_candidate": true,
"public_path": route_path "public_path": route_path
})) }))
@@ -488,10 +483,7 @@ async fn assert_ai_route_locally_denied_after_execution_runtime_miss_with_reques
let (upstream_url, upstream_handle) = start_server(upstream).await; let (upstream_url, upstream_handle) = start_server(upstream).await;
let (execution_runtime_url, execution_runtime_handle) = start_server(execution_runtime).await; let (execution_runtime_url, execution_runtime_handle) = start_server(execution_runtime).await;
let gateway = build_router_with_test_remote_execution_runtime( let gateway = build_router_with_execution_runtime_override(execution_runtime_url);
upstream_url.clone(),
execution_runtime_url,
);
let (gateway_url, gateway_handle) = start_server(gateway).await; let (gateway_url, gateway_handle) = start_server(gateway).await;
let mut request = let mut request =
@@ -514,7 +506,7 @@ async fn assert_ai_route_locally_denied_after_execution_runtime_miss_with_reques
assert_eq!( assert_eq!(
response response
.headers() .headers()
.get(PYTHON_DEPENDENCY_REASON_HEADER) .get(DEPENDENCY_REASON_HEADER)
.and_then(|value| value.to_str().ok()), .and_then(|value| value.to_str().ok()),
None None
); );
@@ -545,7 +537,7 @@ async fn gateway_locally_denies_openai_responses_after_execution_runtime_miss_wi
"cli", "cli",
"openai:cli", "openai:cli",
"{\"model\":\"gpt-5\",\"input\":\"hello\"}", "{\"model\":\"gpt-5\",\"input\":\"hello\"}",
"OpenAI responses execution runtime miss did not match a Rust execution path, and Python fallback has been removed", "OpenAI responses execution runtime miss did not match a Rust execution path",
) )
.await; .await;
} }
@@ -559,7 +551,7 @@ async fn gateway_locally_denies_claude_messages_after_execution_runtime_miss_wit
"chat", "chat",
"claude:chat", "claude:chat",
"{\"model\":\"claude-sonnet-4-5\",\"messages\":[]}", "{\"model\":\"claude-sonnet-4-5\",\"messages\":[]}",
"Claude messages execution runtime miss did not match a Rust execution path, and Python fallback has been removed", "Claude messages execution runtime miss did not match a Rust execution path",
) )
.await; .await;
} }
@@ -573,7 +565,7 @@ async fn gateway_locally_denies_openai_responses_stream_after_execution_runtime_
"cli", "cli",
"openai:cli", "openai:cli",
"{\"model\":\"gpt-5\",\"input\":\"hello\",\"stream\":true}", "{\"model\":\"gpt-5\",\"input\":\"hello\",\"stream\":true}",
"OpenAI responses execution runtime miss did not match a Rust execution path, and Python fallback has been removed", "OpenAI responses execution runtime miss did not match a Rust execution path",
) )
.await; .await;
} }
@@ -587,7 +579,7 @@ async fn gateway_locally_denies_claude_messages_stream_after_execution_runtime_m
"chat", "chat",
"claude:chat", "claude:chat",
"{\"model\":\"claude-sonnet-4-5\",\"messages\":[],\"stream\":true}", "{\"model\":\"claude-sonnet-4-5\",\"messages\":[],\"stream\":true}",
"Claude messages execution runtime miss did not match a Rust execution path, and Python fallback has been removed", "Claude messages execution runtime miss did not match a Rust execution path",
) )
.await; .await;
} }
@@ -601,7 +593,7 @@ async fn gateway_locally_denies_openai_compact_after_execution_runtime_miss_with
"compact", "compact",
"openai:compact", "openai:compact",
"{\"model\":\"gpt-5\",\"input\":\"hello\"}", "{\"model\":\"gpt-5\",\"input\":\"hello\"}",
"OpenAI compact execution runtime miss did not match a Rust execution path, and Python fallback has been removed", "OpenAI compact execution runtime miss did not match a Rust execution path",
) )
.await; .await;
} }
@@ -615,7 +607,7 @@ async fn gateway_locally_denies_openai_compact_stream_after_execution_runtime_mi
"compact", "compact",
"openai:compact", "openai:compact",
"{\"model\":\"gpt-5\",\"input\":\"hello\",\"stream\":true}", "{\"model\":\"gpt-5\",\"input\":\"hello\",\"stream\":true}",
"OpenAI compact execution runtime miss did not match a Rust execution path, and Python fallback has been removed", "OpenAI compact execution runtime miss did not match a Rust execution path",
) )
.await; .await;
} }
@@ -629,7 +621,21 @@ async fn gateway_locally_denies_gemini_generate_after_execution_runtime_miss_wit
"chat", "chat",
"gemini:chat", "gemini:chat",
"{\"contents\":[]}", "{\"contents\":[]}",
"Gemini public execution runtime miss did not match a Rust execution path, and Python fallback has been removed", "Gemini public execution runtime miss did not match a Rust execution path",
)
.await;
}
#[tokio::test]
async fn gateway_locally_denies_gemini_v1_generate_after_execution_runtime_miss_without_control_execute_opt_in(
) {
assert_ai_route_locally_denied_after_execution_runtime_miss(
"/v1/models/gemini-2.5-pro:generateContent",
"gemini",
"chat",
"gemini:chat",
"{\"contents\":[]}",
"Gemini public execution runtime miss did not match a Rust execution path",
) )
.await; .await;
} }
@@ -643,7 +649,7 @@ async fn gateway_locally_denies_gemini_stream_after_execution_runtime_miss_witho
"chat", "chat",
"gemini:chat", "gemini:chat",
"{\"contents\":[]}", "{\"contents\":[]}",
"Gemini public execution runtime miss did not match a Rust execution path, and Python fallback has been removed", "Gemini public execution runtime miss did not match a Rust execution path",
) )
.await; .await;
} }
@@ -657,7 +663,7 @@ async fn gateway_locally_denies_openai_video_after_execution_runtime_miss_withou
"video", "video",
"openai:video", "openai:video",
"{\"model\":\"sora-2\"}", "{\"model\":\"sora-2\"}",
"OpenAI video execution runtime miss did not match a Rust execution path, and Python fallback has been removed", "OpenAI video execution runtime miss did not match a Rust execution path",
) )
.await; .await;
} }
@@ -671,7 +677,23 @@ async fn gateway_locally_denies_gemini_video_after_execution_runtime_miss_withou
"video", "video",
"gemini:video", "gemini:video",
"{\"instances\":[]}", "{\"instances\":[]}",
"Gemini public execution runtime miss did not match a Rust execution path, and Python fallback has been removed", "Gemini public execution runtime miss did not match a Rust execution path",
)
.await;
}
#[tokio::test]
async fn gateway_locally_denies_gemini_files_root_after_execution_runtime_miss_without_control_execute_opt_in(
) {
assert_ai_route_locally_denied_after_execution_runtime_miss_with_request(
reqwest::Method::GET,
"/v1beta/files",
"/v1beta/files?view=BASIC",
"gemini",
"files",
"gemini:chat",
None,
"Gemini files execution runtime miss did not match a Rust execution path",
) )
.await; .await;
} }
@@ -687,7 +709,7 @@ async fn gateway_locally_denies_gemini_files_download_after_execution_runtime_mi
"files", "files",
"gemini:chat", "gemini:chat",
None, None,
"Gemini files execution runtime miss did not match a Rust execution path, and Python fallback has been removed", "Gemini files execution runtime miss did not match a Rust execution path",
) )
.await; .await;
} }
@@ -703,7 +725,7 @@ async fn gateway_locally_denies_gemini_files_upload_after_execution_runtime_miss
"files", "files",
"gemini:chat", "gemini:chat",
Some("{\"file\":{}}"), Some("{\"file\":{}}"),
"Gemini files execution runtime miss did not match a Rust execution path, and Python fallback has been removed", "Gemini files execution runtime miss did not match a Rust execution path",
) )
.await; .await;
} }

View File

@@ -1,4 +1,9 @@
use super::*; use super::{
any, build_router_with_state, build_state_with_execution_runtime_override, json,
start_server, to_bytes, Arc, Body, Bytes, HeaderName, HeaderValue, Json, Mutex, Request,
Response, Router, StatusCode, CONTROL_EXECUTED_HEADER, EXECUTION_PATH_HEADER,
LOCAL_EXECUTION_RUNTIME_MISS_REASON_HEADER, TRACE_ID_HEADER,
};
use crate::gateway::gateway_data::GatewayDataState; use crate::gateway::gateway_data::GatewayDataState;
use aether_crypto::{encrypt_python_fernet_plaintext, DEVELOPMENT_ENCRYPTION_KEY}; use aether_crypto::{encrypt_python_fernet_plaintext, DEVELOPMENT_ENCRYPTION_KEY};
use aether_data::repository::auth::{ use aether_data::repository::auth::{
@@ -366,7 +371,7 @@ async fn gateway_executes_openai_chat_sync_upstream_stream_via_local_finalize_re
let (upstream_url, upstream_handle) = start_server(upstream).await; let (upstream_url, upstream_handle) = start_server(upstream).await;
let (execution_runtime_url, execution_runtime_handle) = start_server(execution_runtime).await; let (execution_runtime_url, execution_runtime_handle) = start_server(execution_runtime).await;
let gateway_state = let gateway_state =
build_state_with_test_remote_execution_runtime(upstream_url.clone(), execution_runtime_url.clone()) build_state_with_execution_runtime_override(execution_runtime_url.clone())
.with_data_state_for_tests( .with_data_state_for_tests(
GatewayDataState::with_auth_candidate_selection_provider_catalog_and_request_candidate_repository_for_tests( GatewayDataState::with_auth_candidate_selection_provider_catalog_and_request_candidate_repository_for_tests(
auth_repository, auth_repository,
@@ -827,7 +832,7 @@ async fn gateway_executes_openai_chat_cross_format_upstream_stream_via_local_fin
let (upstream_url, upstream_handle) = start_server(upstream).await; let (upstream_url, upstream_handle) = start_server(upstream).await;
let (execution_runtime_url, execution_runtime_handle) = start_server(execution_runtime).await; let (execution_runtime_url, execution_runtime_handle) = start_server(execution_runtime).await;
let gateway_state = let gateway_state =
build_state_with_test_remote_execution_runtime(upstream_url.clone(), execution_runtime_url.clone()) build_state_with_execution_runtime_override(execution_runtime_url.clone())
.with_data_state_for_tests( .with_data_state_for_tests(
GatewayDataState::with_auth_candidate_selection_provider_catalog_request_candidates_and_usage_for_tests( GatewayDataState::with_auth_candidate_selection_provider_catalog_request_candidates_and_usage_for_tests(
auth_repository, auth_repository,
@@ -1277,7 +1282,7 @@ async fn gateway_executes_openai_chat_cross_format_tool_use_upstream_stream_via_
let (upstream_url, upstream_handle) = start_server(upstream).await; let (upstream_url, upstream_handle) = start_server(upstream).await;
let (execution_runtime_url, execution_runtime_handle) = start_server(execution_runtime).await; let (execution_runtime_url, execution_runtime_handle) = start_server(execution_runtime).await;
let gateway_state = let gateway_state =
build_state_with_test_remote_execution_runtime(upstream_url.clone(), execution_runtime_url.clone()) build_state_with_execution_runtime_override(execution_runtime_url.clone())
.with_data_state_for_tests( .with_data_state_for_tests(
GatewayDataState::with_auth_candidate_selection_provider_catalog_request_candidates_and_usage_for_tests( GatewayDataState::with_auth_candidate_selection_provider_catalog_request_candidates_and_usage_for_tests(
auth_repository, auth_repository,
@@ -1681,7 +1686,7 @@ async fn gateway_skips_openai_chat_antigravity_cross_format_sync_candidate_as_tr
let (upstream_url, upstream_handle) = start_server(upstream).await; let (upstream_url, upstream_handle) = start_server(upstream).await;
let (execution_runtime_url, execution_runtime_handle) = start_server(execution_runtime).await; let (execution_runtime_url, execution_runtime_handle) = start_server(execution_runtime).await;
let gateway_state = let gateway_state =
build_state_with_test_remote_execution_runtime(upstream_url.clone(), execution_runtime_url.clone()) build_state_with_execution_runtime_override(execution_runtime_url.clone())
.with_data_state_for_tests( .with_data_state_for_tests(
GatewayDataState::with_auth_candidate_selection_provider_catalog_request_candidates_and_usage_for_tests( GatewayDataState::with_auth_candidate_selection_provider_catalog_request_candidates_and_usage_for_tests(
auth_repository, auth_repository,
@@ -2030,7 +2035,7 @@ async fn gateway_executes_openai_chat_cross_format_claude_upstream_sync_via_loca
let (upstream_url, upstream_handle) = start_server(upstream).await; let (upstream_url, upstream_handle) = start_server(upstream).await;
let (execution_runtime_url, execution_runtime_handle) = start_server(execution_runtime).await; let (execution_runtime_url, execution_runtime_handle) = start_server(execution_runtime).await;
let gateway_state = let gateway_state =
build_state_with_test_remote_execution_runtime(upstream_url.clone(), execution_runtime_url.clone()) build_state_with_execution_runtime_override(execution_runtime_url.clone())
.with_data_state_for_tests( .with_data_state_for_tests(
GatewayDataState::with_auth_candidate_selection_provider_catalog_request_candidates_and_usage_for_tests( GatewayDataState::with_auth_candidate_selection_provider_catalog_request_candidates_and_usage_for_tests(
auth_repository, auth_repository,
@@ -2383,7 +2388,7 @@ async fn gateway_executes_openai_chat_cross_format_gemini_upstream_sync_via_loca
let (upstream_url, upstream_handle) = start_server(upstream).await; let (upstream_url, upstream_handle) = start_server(upstream).await;
let (execution_runtime_url, execution_runtime_handle) = start_server(execution_runtime).await; let (execution_runtime_url, execution_runtime_handle) = start_server(execution_runtime).await;
let gateway_state = let gateway_state =
build_state_with_test_remote_execution_runtime(upstream_url.clone(), execution_runtime_url.clone()) build_state_with_execution_runtime_override(execution_runtime_url.clone())
.with_data_state_for_tests( .with_data_state_for_tests(
GatewayDataState::with_auth_candidate_selection_provider_catalog_request_candidates_and_usage_for_tests( GatewayDataState::with_auth_candidate_selection_provider_catalog_request_candidates_and_usage_for_tests(
auth_repository, auth_repository,

View File

@@ -1,4 +1,10 @@
use super::*; use super::{
any, build_router_with_execution_runtime_override, build_router_with_state,
build_state_with_execution_runtime_override, json, start_server, to_bytes, Arc, Body, Bytes,
HeaderName, HeaderValue, Json, Mutex, Request, Response, Router, StatusCode,
CONTROL_EXECUTED_HEADER, EXECUTION_PATH_EXECUTION_RUNTIME_SYNC, EXECUTION_PATH_HEADER,
TRACE_ID_HEADER,
};
use crate::gateway::gateway_data::GatewayDataState; use crate::gateway::gateway_data::GatewayDataState;
use aether_crypto::{encrypt_python_fernet_plaintext, DEVELOPMENT_ENCRYPTION_KEY}; use aether_crypto::{encrypt_python_fernet_plaintext, DEVELOPMENT_ENCRYPTION_KEY};
use aether_data::repository::auth::{ use aether_data::repository::auth::{
@@ -347,7 +353,7 @@ async fn gateway_executes_openai_compact_cross_format_upstream_stream_via_local_
let (upstream_url, upstream_handle) = start_server(upstream).await; let (upstream_url, upstream_handle) = start_server(upstream).await;
let (execution_runtime_url, execution_runtime_handle) = start_server(execution_runtime).await; let (execution_runtime_url, execution_runtime_handle) = start_server(execution_runtime).await;
let gateway_state = let gateway_state =
build_state_with_test_remote_execution_runtime(upstream_url.clone(), execution_runtime_url.clone()) build_state_with_execution_runtime_override(execution_runtime_url.clone())
.with_data_state_for_tests( .with_data_state_for_tests(
GatewayDataState::with_auth_candidate_selection_provider_catalog_request_candidates_and_usage_for_tests( GatewayDataState::with_auth_candidate_selection_provider_catalog_request_candidates_and_usage_for_tests(
auth_repository, auth_repository,
@@ -802,7 +808,7 @@ async fn gateway_executes_openai_compact_openai_family_upstream_stream_via_local
let (upstream_url, upstream_handle) = start_server(upstream).await; let (upstream_url, upstream_handle) = start_server(upstream).await;
let (execution_runtime_url, execution_runtime_handle) = start_server(execution_runtime).await; let (execution_runtime_url, execution_runtime_handle) = start_server(execution_runtime).await;
let gateway_state = let gateway_state =
build_state_with_test_remote_execution_runtime(upstream_url.clone(), execution_runtime_url.clone()) build_state_with_execution_runtime_override(execution_runtime_url.clone())
.with_data_state_for_tests( .with_data_state_for_tests(
GatewayDataState::with_auth_candidate_selection_provider_catalog_request_candidates_and_usage_for_tests( GatewayDataState::with_auth_candidate_selection_provider_catalog_request_candidates_and_usage_for_tests(
auth_repository, auth_repository,
@@ -1250,7 +1256,7 @@ async fn gateway_executes_openai_compact_openai_family_upstream_stream_via_local
let (upstream_url, upstream_handle) = start_server(upstream).await; let (upstream_url, upstream_handle) = start_server(upstream).await;
let (execution_runtime_url, execution_runtime_handle) = start_server(execution_runtime).await; let (execution_runtime_url, execution_runtime_handle) = start_server(execution_runtime).await;
let gateway_state = let gateway_state =
build_state_with_test_remote_execution_runtime(upstream_url.clone(), execution_runtime_url.clone()) build_state_with_execution_runtime_override(execution_runtime_url.clone())
.with_data_state_for_tests( .with_data_state_for_tests(
GatewayDataState::with_auth_candidate_selection_provider_catalog_request_candidates_and_usage_for_tests( GatewayDataState::with_auth_candidate_selection_provider_catalog_request_candidates_and_usage_for_tests(
auth_repository, auth_repository,

View File

@@ -1,4 +1,10 @@
use super::*; use super::{
any, build_router_with_execution_runtime_override, build_router_with_state,
build_state_with_execution_runtime_override, json, start_server, to_bytes, Arc, Body, Bytes,
HeaderName, HeaderValue, Json, Mutex, Request, Response, Router, StatusCode,
CONTROL_EXECUTED_HEADER, EXECUTION_PATH_EXECUTION_RUNTIME_SYNC, EXECUTION_PATH_HEADER,
TRACE_ID_HEADER,
};
use crate::gateway::gateway_data::GatewayDataState; use crate::gateway::gateway_data::GatewayDataState;
use aether_crypto::{encrypt_python_fernet_plaintext, DEVELOPMENT_ENCRYPTION_KEY}; use aether_crypto::{encrypt_python_fernet_plaintext, DEVELOPMENT_ENCRYPTION_KEY};
use aether_data::repository::auth::{ use aether_data::repository::auth::{
@@ -346,7 +352,7 @@ async fn gateway_executes_openai_cli_cross_format_upstream_stream_via_local_fina
let (upstream_url, upstream_handle) = start_server(upstream).await; let (upstream_url, upstream_handle) = start_server(upstream).await;
let (execution_runtime_url, execution_runtime_handle) = start_server(execution_runtime).await; let (execution_runtime_url, execution_runtime_handle) = start_server(execution_runtime).await;
let gateway_state = let gateway_state =
build_state_with_test_remote_execution_runtime(upstream_url.clone(), execution_runtime_url.clone()) build_state_with_execution_runtime_override(execution_runtime_url.clone())
.with_data_state_for_tests( .with_data_state_for_tests(
GatewayDataState::with_auth_candidate_selection_provider_catalog_request_candidates_and_usage_for_tests( GatewayDataState::with_auth_candidate_selection_provider_catalog_request_candidates_and_usage_for_tests(
auth_repository, auth_repository,
@@ -798,7 +804,7 @@ async fn gateway_executes_openai_cli_cross_format_function_call_upstream_stream_
let (upstream_url, upstream_handle) = start_server(upstream).await; let (upstream_url, upstream_handle) = start_server(upstream).await;
let (execution_runtime_url, execution_runtime_handle) = start_server(execution_runtime).await; let (execution_runtime_url, execution_runtime_handle) = start_server(execution_runtime).await;
let gateway_state = let gateway_state =
build_state_with_test_remote_execution_runtime(upstream_url.clone(), execution_runtime_url.clone()) build_state_with_execution_runtime_override(execution_runtime_url.clone())
.with_data_state_for_tests( .with_data_state_for_tests(
GatewayDataState::with_auth_candidate_selection_provider_catalog_request_candidates_and_usage_for_tests( GatewayDataState::with_auth_candidate_selection_provider_catalog_request_candidates_and_usage_for_tests(
auth_repository, auth_repository,
@@ -1362,7 +1368,7 @@ async fn gateway_executes_openai_cli_antigravity_cross_format_upstream_stream_vi
)], )],
); );
let gateway_state = let gateway_state =
build_state_with_test_remote_execution_runtime(upstream_url.clone(), execution_runtime_url.clone()) build_state_with_execution_runtime_override(execution_runtime_url.clone())
.with_data_state_for_tests( .with_data_state_for_tests(
GatewayDataState::with_auth_candidate_selection_provider_catalog_request_candidates_and_usage_for_tests( GatewayDataState::with_auth_candidate_selection_provider_catalog_request_candidates_and_usage_for_tests(
auth_repository, auth_repository,

Some files were not shown because too many files have changed in this diff Show More