mirror of
https://github.com/fawney19/Aether.git
synced 2026-10-07 18:07:47 +08:00
refactor(workspace): enforce layered crate boundaries
This commit is contained in:
@@ -213,6 +213,7 @@ async fn estimate_execution_plan_cost_upper_bound_usd(
|
||||
);
|
||||
let ttl = state.frontdoor_runtime_guards.auth_capacity_cache_ttl;
|
||||
if ttl.is_zero() {
|
||||
let _permit = state.acquire_auth_snapshot_load_gate().await?;
|
||||
return calculate_execution_plan_cost_upper_bound(
|
||||
state,
|
||||
plan,
|
||||
@@ -230,6 +231,7 @@ async fn estimate_execution_plan_cost_upper_bound_usd(
|
||||
state
|
||||
.auth_request_cost_upper_bound_cache
|
||||
.get_or_load(cache_key, ttl, || async {
|
||||
let _permit = state.acquire_auth_snapshot_load_gate().await?;
|
||||
calculate_execution_plan_cost_upper_bound(
|
||||
state,
|
||||
plan,
|
||||
@@ -499,9 +501,16 @@ async fn request_model_resolves_to_allowed_model(
|
||||
.model_directive_policy
|
||||
.resolve_reasoning(&api_format, Some(requested_model));
|
||||
let routing_model = resolution.base_model().unwrap_or(requested_model);
|
||||
let rows = state
|
||||
.list_minimal_candidate_selection_rows_for_api_format(&api_format)
|
||||
.await?;
|
||||
let rows = {
|
||||
// Model alias authorization runs before candidate planning, so its database read must
|
||||
// participate in the same foreground DB admission budget as the rest of auth. Keep
|
||||
// the permit scoped to this one read; callers do not hold this gate, and releasing it
|
||||
// here avoids carrying a DB permit through pure filtering or subsequent formats.
|
||||
let _permit = state.acquire_auth_snapshot_load_gate().await?;
|
||||
state
|
||||
.list_minimal_candidate_selection_rows_for_api_format(&api_format)
|
||||
.await?
|
||||
};
|
||||
let matching_rows = rows
|
||||
.into_iter()
|
||||
.filter(|row| {
|
||||
@@ -553,6 +562,7 @@ mod tests {
|
||||
use std::collections::BTreeMap;
|
||||
use std::sync::atomic::{AtomicUsize, Ordering};
|
||||
use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
|
||||
use aether_contracts::{ExecutionPlan, RequestBody};
|
||||
use aether_data::repository::candidate_selection::InMemoryMinimalCandidateSelectionReadRepository;
|
||||
@@ -564,6 +574,7 @@ mod tests {
|
||||
StoredMinimalCandidateSelectionRow, StoredProviderModelMapping,
|
||||
};
|
||||
use aether_data_contracts::DataLayerError;
|
||||
use aether_runtime::ConcurrencyGate;
|
||||
use async_trait::async_trait;
|
||||
use axum::body::Bytes;
|
||||
use axum::http::{HeaderMap, Uri};
|
||||
@@ -896,6 +907,44 @@ mod tests {
|
||||
assert_eq!(rejection, None);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn model_alias_resolution_waits_for_auth_database_gate() {
|
||||
let mut state = state_with_model_mapping();
|
||||
state.auth_snapshot_load_gate = Some(Arc::new(ConcurrencyGate::new(
|
||||
"test_auth_model_resolution",
|
||||
1,
|
||||
)));
|
||||
let held = state
|
||||
.acquire_auth_snapshot_load_gate()
|
||||
.await
|
||||
.expect("auth gate acquisition should succeed")
|
||||
.expect("auth gate should be configured");
|
||||
let decision = decision_with_allowed_models(vec!["gpt-5".to_string()]);
|
||||
let uri: Uri = "/v1/chat/completions".parse().expect("uri should parse");
|
||||
let headers = json_headers();
|
||||
let body = Bytes::from_static(br#"{"model":"gpt-5.2","messages":[]}"#);
|
||||
|
||||
let blocked = tokio::time::timeout(
|
||||
Duration::from_millis(25),
|
||||
request_model_local_rejection(&state, Some(&decision), &uri, &headers, &body),
|
||||
)
|
||||
.await;
|
||||
assert!(
|
||||
blocked.is_err(),
|
||||
"model alias candidate reads must wait for the auth DB gate"
|
||||
);
|
||||
|
||||
drop(held);
|
||||
let rejection = tokio::time::timeout(
|
||||
Duration::from_secs(1),
|
||||
request_model_local_rejection(&state, Some(&decision), &uri, &headers, &body),
|
||||
)
|
||||
.await
|
||||
.expect("model alias resolution should resume after releasing the auth gate")
|
||||
.expect("model rejection should resolve");
|
||||
assert_eq!(rejection, None);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn model_rejection_allows_cross_format_provider_mapping_to_allowed_global_model() {
|
||||
let mut row = sample_row_for_api_format("gemini:generate_content");
|
||||
|
||||
@@ -1,59 +1,11 @@
|
||||
use axum::http::Uri;
|
||||
|
||||
use crate::headers::header_value_str;
|
||||
use crate::{AppState, GatewayError};
|
||||
|
||||
use super::{resolve_control_route, GatewayControlDecision};
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub(crate) struct GatewayPublicRequestContext {
|
||||
pub(crate) trace_id: String,
|
||||
pub(crate) request_method: http::Method,
|
||||
pub(crate) request_path: String,
|
||||
pub(crate) request_query_string: Option<String>,
|
||||
pub(crate) request_content_type: Option<String>,
|
||||
pub(crate) host_header: Option<String>,
|
||||
pub(crate) control_decision: Option<GatewayControlDecision>,
|
||||
}
|
||||
|
||||
impl GatewayPublicRequestContext {
|
||||
pub(crate) fn from_request_parts(
|
||||
trace_id: impl Into<String>,
|
||||
method: &http::Method,
|
||||
uri: &Uri,
|
||||
headers: &http::HeaderMap,
|
||||
control_decision: Option<GatewayControlDecision>,
|
||||
) -> Self {
|
||||
let request_path = if uri.path().starts_with('/') {
|
||||
uri.path().to_string()
|
||||
} else {
|
||||
format!("/{}", uri.path())
|
||||
};
|
||||
let request_query_string = uri.query().map(ToOwned::to_owned);
|
||||
|
||||
Self {
|
||||
trace_id: trace_id.into(),
|
||||
request_method: method.clone(),
|
||||
request_path,
|
||||
request_query_string,
|
||||
request_content_type: header_value_str(headers, http::header::CONTENT_TYPE.as_str()),
|
||||
host_header: header_value_str(headers, http::header::HOST.as_str()),
|
||||
control_decision,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn request_path_and_query(&self) -> String {
|
||||
if let Some(query) = self
|
||||
.request_query_string
|
||||
.as_deref()
|
||||
.filter(|value| !value.is_empty())
|
||||
{
|
||||
format!("{}?{query}", self.request_path)
|
||||
} else {
|
||||
self.request_path.clone()
|
||||
}
|
||||
}
|
||||
}
|
||||
pub(crate) type GatewayPublicRequestContext =
|
||||
aether_gateway_control::PublicRequestContext<GatewayControlDecision>;
|
||||
|
||||
pub(crate) async fn resolve_public_request_context(
|
||||
state: &AppState,
|
||||
|
||||
Reference in New Issue
Block a user