fix(会员): 模型权限读链路失败改报独立 503 错误码
- 新增 MODEL_ACCESS_UNAVAILABLE(503),与 403 档位不足 / 422 模型不可用区分,不再把付费账号静默降级成 Basic - resolve_owner_agc_model_access 返回 Result 并由 load_owner_llm_catalog 透传;缺会员行 / 缺目录行仍由 module 内失败关闭并返回成功 - 入口注释列出三类错误码语义;补错误码映射单测
This commit is contained in:
@@ -126,6 +126,15 @@ mod model_catalog_tests {
|
||||
agc_model_resolve_error(module_runtime::AgcModelResolveError::Unavailable);
|
||||
assert_eq!(unavailable.status_code(), StatusCode::UNPROCESSABLE_ENTITY);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn access_lookup_failure_maps_to_service_unavailable_with_dedicated_code() {
|
||||
// 读链路失败必须是独立的 503 + 专用错误码,不能与 403 档位不足 / 422 模型不可用混用,
|
||||
// 也不能把付费账号静默降级成 Basic。
|
||||
let error = model_access::model_access_unavailable_error();
|
||||
assert_eq!(error.status_code(), StatusCode::SERVICE_UNAVAILABLE);
|
||||
assert_eq!(error.code(), model_access::MODEL_ACCESS_UNAVAILABLE);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
@@ -357,7 +366,7 @@ async fn load_owner_llm_catalog(
|
||||
);
|
||||
Ok(OwnerAgcModelCatalog {
|
||||
catalog: catalog?,
|
||||
access,
|
||||
access: access?,
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -3,26 +3,47 @@
|
||||
//! 模型目录本身是全局的,权限判定在 `module-runtime`;这里只负责把「当前账号」解析成
|
||||
//! [`AgcModelAccess`],再把全局目录与该档位打包给路由层使用。
|
||||
//!
|
||||
//! 失败关闭:会员行缺失、目录行缺失、或 SpacetimeDB 读失败时都按 `Normal`(`Basic`)处理,
|
||||
//! 只放宽到基础模型,不放开高性能模型。
|
||||
//! LLM 路由对外错误码(三条互不重叠,客户端据此区分处理):
|
||||
//! - `MODEL_NOT_AVAILABLE_FOR_PLAN`(403):模型存在且启用,但当前会员档位不够(`module-runtime` 解析)。
|
||||
//! - `MODEL_UNAVAILABLE`(422,列表无可用模型时 503):模型不在目录、已停用,或该档在目录里没有任何可用模型。
|
||||
//! - `MODEL_ACCESS_UNAVAILABLE`(503):读取账号权限档本身失败(SpacetimeDB 传输 / procedure / 鉴权)。
|
||||
//! 注意**缺会员行 / 缺档位目录行不算这条**:module procedure 已按 `Basic` 失败关闭并返回 `ok: true`。
|
||||
//! 能走到这条说明读链路不可用,不能把付费账号静默降级成 `Basic`。
|
||||
|
||||
use crate::state::AppState;
|
||||
use axum::http::StatusCode;
|
||||
use module_runtime::{AgcModelAccess, AgcModelCatalog};
|
||||
|
||||
use crate::{http_error::AppError, state::AppState};
|
||||
|
||||
/// 读取账号权限档失败(基础设施)时的专用错误码,与档位不足 / 模型不可用区分。
|
||||
pub(crate) const MODEL_ACCESS_UNAVAILABLE: &str = "MODEL_ACCESS_UNAVAILABLE";
|
||||
|
||||
/// 账号视角的 AGC 模型目录:全局目录 + 该账号的权限档。
|
||||
pub(crate) struct OwnerAgcModelCatalog {
|
||||
pub(crate) catalog: AgcModelCatalog,
|
||||
pub(crate) access: AgcModelAccess,
|
||||
}
|
||||
|
||||
/// 读链路失败时的统一错误:503 + [`MODEL_ACCESS_UNAVAILABLE`]。
|
||||
///
|
||||
/// 独立成函数是为了让「基础设施失败」这件事可被单测直接断言,而不是散落的构造。
|
||||
pub(crate) fn model_access_unavailable_error() -> AppError {
|
||||
AppError::from_status(StatusCode::SERVICE_UNAVAILABLE)
|
||||
.with_code(MODEL_ACCESS_UNAVAILABLE)
|
||||
.with_message("会员权限档暂不可用,请稍后重试")
|
||||
}
|
||||
|
||||
/// 解析账号的 AGC 模型权限档。
|
||||
///
|
||||
/// 走专用只读 procedure `get_profile_agc_model_access`:它只读会员账期投影与档位目录,
|
||||
/// 不触发换期 / 免费点刷新,适合 LLM 代理热路径。
|
||||
///
|
||||
/// 成功返回档位;读链路失败返回 503 + [`MODEL_ACCESS_UNAVAILABLE`],由路由层透传,不再静默降级成 `Basic`。
|
||||
/// 缺会员行 / 缺档位目录行仍由 module 内失败关闭到 `Basic` 并返回成功。
|
||||
pub(crate) async fn resolve_owner_agc_model_access(
|
||||
state: &AppState,
|
||||
owner_user_id: &str,
|
||||
) -> AgcModelAccess {
|
||||
) -> Result<AgcModelAccess, AppError> {
|
||||
#[cfg(test)]
|
||||
if super::test_provisioned_router_credentials()
|
||||
.lock()
|
||||
@@ -30,21 +51,21 @@ pub(crate) async fn resolve_owner_agc_model_access(
|
||||
.contains_key(owner_user_id)
|
||||
{
|
||||
// 测试夹具目录里的模型全是缺省 `Basic`,返回 `Full` 以避免夹具再去连 SpacetimeDB。
|
||||
return AgcModelAccess::Full;
|
||||
return Ok(AgcModelAccess::Full);
|
||||
}
|
||||
match state
|
||||
.spacetime_client()
|
||||
.get_profile_agc_model_access(owner_user_id.to_string())
|
||||
.await
|
||||
{
|
||||
Ok(record) => AgcModelAccess::from(record.model_access),
|
||||
Ok(record) => Ok(AgcModelAccess::from(record.model_access)),
|
||||
Err(error) => {
|
||||
tracing::warn!(
|
||||
tracing::error!(
|
||||
user_id = %owner_user_id,
|
||||
error = %error,
|
||||
"读取会员档位失败,AGC 模型权限按 Basic 失败关闭"
|
||||
"读取会员档位失败,AGC 模型权限按 503 上报"
|
||||
);
|
||||
AgcModelAccess::Basic
|
||||
Err(model_access_unavailable_error())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user