mirror of
https://github.com/zyycn/codex-proxy-rs.git
synced 2026-11-06 08:06:19 +00:00
docs(backend): standardize Rust file comments
Add Chinese responsibility summaries to every backend Rust source, test, and build script. Remove sentence-ending periods from comments while preserving meaningful dots in URLs, versions, identifiers, and examples. Keep generated SDK documentation in sync with its contract generator, group middleware module declarations and exports, and record the comment conventions in the development skill. Validated with Rustfmt, strict Clippy, backend tests, documentation tests, and skill validation. Database-backed integration cases were skipped because dedicated PostgreSQL and Redis test services were not configured. Co-authored-by: Codex <noreply@openai.com>
This commit is contained in:
@@ -40,6 +40,12 @@ description: Codex Proxy RS 仓库的开发、修复、排障、代码审查和
|
||||
3. 按变更范围读取[验证](../../../CONTRIBUTING.md#验证),区分通过、失败、跳过与未执行;提交时补读[提交约定](../../../CONTRIBUTING.md#项目约定)
|
||||
4. 交付说明实际结果与验证缺口;页面证据和后端生命周期检查按上表执行,不把构建通过当作行为验收
|
||||
|
||||
## 后端注释
|
||||
|
||||
- 每个 Rust 文件顶层使用简短的中文 `//!` 注释说明文件职责,覆盖生产源码、测试与构建脚本;测试文件说明测试范围或辅助用途,生成文件同步维护生成器
|
||||
- 注释不使用中文或英文句号,多句说明按语义分行;保留 URL、版本号、标识符和代码示例中有语义的点号
|
||||
- 文件说明聚焦当前功能与职责边界,局部注释解释原因和约束,避免复述代码或记录修改经过
|
||||
|
||||
## 文档硬约束
|
||||
|
||||
**常规文档只描述当前状态,不写变更历史**,这项检查适用于每次开发收尾,不限于专门的文档任务
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
//! 插件帮助只读启动;命令仅组装命令平面和必要写泵。
|
||||
//! 插件帮助只读启动;命令仅组装命令平面和必要写泵
|
||||
|
||||
use super::{
|
||||
BootstrapError, GatewayConfig,
|
||||
@@ -6,7 +6,7 @@ use super::{
|
||||
startup::{Environment, Mode, Providers},
|
||||
};
|
||||
|
||||
/// 这里只接收插件命名空间内的参数,不转交宿主配置或其他启动 secret。
|
||||
/// 这里只接收插件命名空间内的参数,不转交宿主配置或其他启动 secret
|
||||
pub struct PluginCommand {
|
||||
pub instance_id: Option<String>,
|
||||
pub name: Option<String>,
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
//! 网关顶层配置的加载、路径解析与跨模块参数校验
|
||||
|
||||
use gateway_host::{ConfigError, HostConfig, LoadableConfig};
|
||||
use serde::Deserialize;
|
||||
|
||||
const CONFIG_SCHEMA_VERSION: u32 = 1;
|
||||
|
||||
/// 顶层配置只组合各包拥有的配置段,不解释任何业务字段。
|
||||
/// 各启动配置忽略未知字段,允许升级时保留旧配置;已知字段仍按类型和业务约束校验。
|
||||
/// 顶层配置只组合各包拥有的配置段,不解释任何业务字段
|
||||
/// 各启动配置忽略未知字段,允许升级时保留旧配置;已知字段仍按类型和业务约束校验
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct GatewayConfig {
|
||||
schema_version: u32,
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
//! 网关唯一组合根:按启动模式连接各 Bundle。
|
||||
//! 网关唯一组合根:按启动模式连接各 Bundle
|
||||
|
||||
mod command;
|
||||
mod config;
|
||||
@@ -10,7 +10,7 @@ pub use command::{PluginCommand, plugin_command};
|
||||
pub use config::GatewayConfig;
|
||||
pub use server::run;
|
||||
|
||||
/// 组合根只保留包级错误分类,不展开内部实现或敏感配置。
|
||||
/// 组合根只保留包级错误分类,不展开内部实现或敏感配置
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
pub enum BootstrapError {
|
||||
#[error(transparent)]
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
//! 插件 Runtime 的组装与回调资源持有;服务和命令模式复用同一组管理端口。
|
||||
//! 插件 Runtime 的组装与回调资源持有;服务和命令模式复用同一组管理端口
|
||||
|
||||
use std::sync::Arc;
|
||||
|
||||
@@ -15,7 +15,7 @@ use gateway_plugin_runtime::PluginRuntime;
|
||||
|
||||
use super::BootstrapError;
|
||||
|
||||
// 必须在调用方仍持有 Core 和回调资源时异步关闭,不能先退出该作用域再清理。
|
||||
// 必须在调用方仍持有 Core 和回调资源时异步关闭,不能先退出该作用域再清理
|
||||
macro_rules! or_shutdown {
|
||||
($runtime:expr, $result:expr) => {
|
||||
match $result {
|
||||
@@ -31,7 +31,7 @@ macro_rules! or_shutdown {
|
||||
pub(super) use or_shutdown;
|
||||
|
||||
pub(super) struct ManagementPorts {
|
||||
// Runtime 保存 Weak;组合根持有这些 Arc,直到插件全部关闭。
|
||||
// Runtime 保存 Weak;组合根持有这些 Arc,直到插件全部关闭
|
||||
pub(super) accounts: Arc<dyn PluginAccountAccess>,
|
||||
keys: Arc<dyn PluginClientKeyAccess>,
|
||||
resources: Arc<dyn PluginResourceAccess>,
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
//! 服务模式完成全部组装后交给 Host 监听,并在请求和 Worker 排空后关闭插件。
|
||||
//! 服务模式完成全部组装后交给 Host 监听,并在请求和 Worker 排空后关闭插件
|
||||
|
||||
use gateway_host::ConfigError;
|
||||
|
||||
@@ -8,7 +8,7 @@ use super::{
|
||||
startup::{Environment, Mode, Providers},
|
||||
};
|
||||
|
||||
/// 按冻结顺序初始化全部 Bundle,并把进程阻塞权交给 Host。
|
||||
/// 按冻结顺序初始化全部 Bundle,并把进程阻塞权交给 Host
|
||||
pub async fn run() -> Result<(), BootstrapError> {
|
||||
let config = gateway_host::load_config::<GatewayConfig>()?;
|
||||
let environment = Environment::initialize(config.host, config.store, Mode::Server).await?;
|
||||
@@ -126,7 +126,7 @@ pub async fn run() -> Result<(), BootstrapError> {
|
||||
if let Some(snapshot) = snapshot {
|
||||
return middleware.resolve(snapshot.extensions()?);
|
||||
}
|
||||
// 数据面未就绪时,管理与诊断路由仍可使用发布候选中的插件。
|
||||
// 数据面未就绪时,管理与诊断路由仍可使用发布候选中的插件
|
||||
let diagnostic = snapshots.snapshot_for_diagnostics()?;
|
||||
middleware.resolve(diagnostic.extensions()?)
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
//! 启动模式共享的基础设施与 Provider 组装,不启动 HTTP 或 Worker。
|
||||
//! 启动模式共享的基础设施与 Provider 组装,不启动 HTTP 或 Worker
|
||||
|
||||
use std::{path::PathBuf, sync::Arc};
|
||||
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
//! 网关进程组装根。
|
||||
//! 网关进程组装根
|
||||
|
||||
pub mod bootstrap;
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
//! 网关命令行入口,选择启动模式并创建异步运行时
|
||||
|
||||
use std::{
|
||||
env,
|
||||
error::Error,
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
//! docs/architecture.md 依赖 DAG 与生产源码纪律的 workspace 级机器校验。
|
||||
//! docs/architecture.md 依赖 DAG 与生产源码纪律的 workspace 级机器校验
|
||||
|
||||
use std::{
|
||||
collections::{BTreeMap, BTreeSet},
|
||||
@@ -8,7 +8,7 @@ use std::{
|
||||
|
||||
use syn::{Item, visit::Visit};
|
||||
|
||||
/// workspace 成员冻结清单;新增 crate 必须同步扩展本文件的依赖规则。
|
||||
/// workspace 成员冻结清单;新增 crate 必须同步扩展本文件的依赖规则
|
||||
pub(super) const WORKSPACE_MEMBERS: &[&str] = &[
|
||||
"apps/gateway",
|
||||
"apps/plugin-cli",
|
||||
@@ -178,7 +178,7 @@ fn gateway_admin_stays_free_of_infrastructure_dependencies() {
|
||||
);
|
||||
}
|
||||
|
||||
/// workspace 包名到冻结成员路径的映射。
|
||||
/// workspace 包名到冻结成员路径的映射
|
||||
const PACKAGE_TO_MEMBER: &[(&str, &str)] = &[
|
||||
("codex-proxy-rs", "apps/gateway"),
|
||||
("codex-proxy-plugin-cli", "apps/plugin-cli"),
|
||||
@@ -194,7 +194,7 @@ const PACKAGE_TO_MEMBER: &[(&str, &str)] = &[
|
||||
("provider-xai", "crates/providers/xai"),
|
||||
];
|
||||
|
||||
/// SDK 与 Adapter/provider 根门面的稳定合同模块;任何增减都必须同步完成边界审计。
|
||||
/// SDK 与 Adapter/provider 根门面的稳定合同模块;任何增减都必须同步完成边界审计
|
||||
const ADAPTER_PUBLIC_MODULES: &[(&str, &[&str])] = &[
|
||||
("crates/gateway-plugin/sdk", &["call", "client"]),
|
||||
("crates/gateway-plugin/runtime", &[]),
|
||||
@@ -224,13 +224,13 @@ const ADAPTER_PUBLIC_MODULES: &[(&str, &[&str])] = &[
|
||||
("crates/providers/xai", &["credential", "transport"]),
|
||||
];
|
||||
|
||||
/// 不对应单一生产模块、而是校验 crate/workspace 整体契约的根级测试场景。
|
||||
/// 不对应单一生产模块、而是校验 crate/workspace 整体契约的根级测试场景
|
||||
const ROOT_TEST_SCENARIOS: &[(&str, &[&str])] = &[
|
||||
("apps/gateway", &["architecture"]),
|
||||
("crates/gateway-api", &["architecture"]),
|
||||
];
|
||||
|
||||
/// 冻结的 workspace 内部运行时依赖边;新增/删除任何边都必须同步本表。
|
||||
/// 冻结的 workspace 内部运行时依赖边;新增/删除任何边都必须同步本表
|
||||
const ALLOWED_INTERNAL_EDGES: &[(&str, &str)] = &[
|
||||
("codex-proxy-plugin-cli", "gateway-plugin-sdk"),
|
||||
("codex-proxy-rs", "gateway-admin"),
|
||||
@@ -475,7 +475,7 @@ fn integration_tests_mirror_production_module_tree() {
|
||||
}
|
||||
}
|
||||
|
||||
/// Rust 子进程 fixture 是 Cargo 的独立 crate 根,不能要求它由测试模块再次声明。
|
||||
/// Rust 子进程 fixture 是 Cargo 的独立 crate 根,不能要求它由测试模块再次声明
|
||||
fn test_source_roots() -> BTreeMap<PathBuf, Vec<String>> {
|
||||
let metadata = cargo_metadata_json();
|
||||
let mut roots = BTreeMap::<PathBuf, Vec<String>>::new();
|
||||
@@ -529,7 +529,7 @@ fn root_test_scenario_allowed(member: &str, module: &Path) -> bool {
|
||||
|
||||
fn assert_module_tree(root: &Path, crate_roots: &[&str]) {
|
||||
let files = super::rust_files(root);
|
||||
// 同一父模块的所有子文件复用一次语法解析,计数仍保留重复声明检查。
|
||||
// 同一父模块的所有子文件复用一次语法解析,计数仍保留重复声明检查
|
||||
let mut declarations = BTreeMap::<PathBuf, BTreeMap<String, usize>>::new();
|
||||
for relative in &files {
|
||||
if crate_roots
|
||||
@@ -649,7 +649,7 @@ fn cargo_metadata_json() -> serde_json::Value {
|
||||
serde_json::from_slice(&output.stdout).expect("parse cargo metadata")
|
||||
}
|
||||
|
||||
/// 提取成员 `[dependencies]` 段内声明的依赖名;段落以下一个 `[` 表头结束。
|
||||
/// 提取成员 `[dependencies]` 段内声明的依赖名;段落以下一个 `[` 表头结束
|
||||
fn dependency_names(member: &str) -> Vec<String> {
|
||||
let manifest = fs::read_to_string(backend_root().join(member).join("Cargo.toml"))
|
||||
.expect("read member manifest");
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
//! 验证网关基础命令无需加载配置,并拒绝非法或多余参数
|
||||
|
||||
use std::{fs, process::Command};
|
||||
|
||||
#[test]
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
//! 验证顶层配置加载、路径解析、显式覆盖与敏感字段脱敏
|
||||
|
||||
use std::{fs, process::Command};
|
||||
|
||||
use codex_proxy_rs::bootstrap::GatewayConfig;
|
||||
@@ -27,7 +29,7 @@ fn config_loader_should_resolve_paths_relative_to_config_file() {
|
||||
fn config_loader_should_share_resolved_assets_with_system_update() {
|
||||
const CHILD_ENV: &str = "CPR_TEST_UPDATE_ASSETS_CHILD";
|
||||
let Ok(case) = std::env::var(CHILD_ENV) else {
|
||||
// 使用子进程覆盖无环境变量、Docker 路径和相对路径,避免污染并行测试。
|
||||
// 使用子进程覆盖无环境变量、Docker 路径和相对路径,避免污染并行测试
|
||||
for (case, web_dist) in [
|
||||
("binary", None),
|
||||
("docker", Some("/app/web/dist")),
|
||||
@@ -379,7 +381,7 @@ fn valid_config() -> String {
|
||||
}
|
||||
|
||||
fn valid_config_document() -> serde_json::Value {
|
||||
// 按字段修改样例,避免注释或排版变化让测试输入悄悄失效;JSON 仍可由 YAML 文件入口加载。
|
||||
// 按字段修改样例,避免注释或排版变化让测试输入悄悄失效;JSON 仍可由 YAML 文件入口加载
|
||||
config::Config::builder()
|
||||
.add_source(config::File::from_str(
|
||||
&valid_config(),
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
//! 网关启动配置、命令行与服务组装的测试入口
|
||||
|
||||
mod command;
|
||||
mod config;
|
||||
mod server;
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
//! 验证服务组装为 HTTPS 代理探测传递自定义 CA
|
||||
|
||||
use std::{fs, process::Command};
|
||||
|
||||
#[tokio::test]
|
||||
@@ -19,7 +21,7 @@ async fn proxy_probe_should_use_provider_custom_ca_for_https_proxies() {
|
||||
let Ok(directory) = std::env::var(CHILD_ENV) else {
|
||||
let directory = tempfile::tempdir().unwrap();
|
||||
generate_proxy_test_certificates(directory.path());
|
||||
// 使用子进程隔离环境变量,避免并行测试读取到临时 CA 配置。
|
||||
// 使用子进程隔离环境变量,避免并行测试读取到临时 CA 配置
|
||||
for ca_env in ["CODEX_CA_CERTIFICATE", "SSL_CERT_FILE"] {
|
||||
let mut child = Command::new(std::env::current_exe().unwrap());
|
||||
child
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
//! 网关组合根测试入口,检查源码模块纪律与冻结的文件边界
|
||||
|
||||
mod architecture;
|
||||
mod bootstrap;
|
||||
|
||||
// 组合根是否混入业务策略由代码审查判断,行数和标识符/注释关键词不能证明职责越界。
|
||||
// 依赖 DAG、公开模块面和模块镜像继续由 architecture 检查,源码纪律由下方测试维护。
|
||||
// 组合根是否混入业务策略由代码审查判断,行数和标识符/注释关键词不能证明职责越界
|
||||
// 依赖 DAG、公开模块面和模块镜像继续由 architecture 检查,源码纪律由下方测试维护
|
||||
|
||||
use std::{
|
||||
collections::BTreeSet,
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
//! 插件打包命令行,校验输入资源并生成带摘要的平台归档
|
||||
|
||||
use std::{
|
||||
collections::{BTreeMap, BTreeSet},
|
||||
fs::{self, File, OpenOptions},
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
//! 插件打包命令行的参数、归档校验与输出行为测试
|
||||
|
||||
use std::{
|
||||
collections::BTreeMap,
|
||||
fs::{self, File},
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
//! 备份纯策略与 Worker 任务。
|
||||
//! 备份纯策略与 Worker 任务
|
||||
|
||||
pub mod policy;
|
||||
pub mod task;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
//! 备份纯策略:Cron 推进与保留候选。
|
||||
//! 备份纯策略:Cron 推进与保留候选
|
||||
//!
|
||||
//! 本模块不持有任何端口或对象,是 `BackupService` 与 `BackupTask` 共用的纯函数集。
|
||||
//! 本模块不持有任何端口或对象,是 `BackupService` 与 `BackupTask` 共用的纯函数集
|
||||
|
||||
use std::str::FromStr;
|
||||
|
||||
@@ -10,10 +10,11 @@ use cron::Schedule;
|
||||
|
||||
use crate::model::backup::{BackupError, BackupRecord, BackupStatus, code};
|
||||
|
||||
/// 向历史回溯的最大范围(分钟)。覆盖 Feb 29 + 特定星期等罕见组合的最长间隔。
|
||||
/// 向历史回溯的最大范围(分钟)
|
||||
/// 覆盖 Feb 29 + 特定星期等罕见组合的最长间隔
|
||||
const MAX_BACKDATE_MINUTES: i64 = 40 * 366 * 24 * 60;
|
||||
|
||||
/// 已解析并校验的备份计划。
|
||||
/// 已解析并校验的备份计划
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct BackupSchedule {
|
||||
schedule: Schedule,
|
||||
@@ -21,11 +22,11 @@ pub struct BackupSchedule {
|
||||
}
|
||||
|
||||
impl BackupSchedule {
|
||||
/// 按部署时区解析并校验 5 段 Cron。
|
||||
/// 按部署时区解析并校验 5 段 Cron
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// Cron 非法时返回 [`BackupError`]。
|
||||
/// Cron 非法时返回 [`BackupError`]
|
||||
pub fn parse(
|
||||
cron_expression: &str,
|
||||
timezone: gateway_core::time::DeploymentTimeZone,
|
||||
@@ -40,16 +41,16 @@ impl BackupSchedule {
|
||||
})
|
||||
}
|
||||
|
||||
/// 返回最接近 `at`(含 `at` 所在分钟)的最近一次触发时间。
|
||||
/// 返回最接近 `at`(含 `at` 所在分钟)的最近一次触发时间
|
||||
///
|
||||
/// 若过去 `MAX_BACKDATE_MINUTES` 内没有任何触发(极罕见的稀疏计划或超长停机),
|
||||
/// 返回 `None`,调用方不应为历史点补任务。
|
||||
/// 返回 `None`,调用方不应为历史点补任务
|
||||
#[must_use]
|
||||
pub fn last_firing_at_or_before(&self, at: DateTime<Utc>) -> Option<DateTime<Utc>> {
|
||||
let high_min = at.timestamp().div_euclid(60);
|
||||
let low_min = high_min.saturating_sub(MAX_BACKDATE_MINUTES);
|
||||
|
||||
// 检查窗口内是否存在触发;不存在则直接返回 None。
|
||||
// 检查窗口内是否存在触发;不存在则直接返回 None
|
||||
let low_instant = instant(low_min, self.timezone)?;
|
||||
if self
|
||||
.next_after(low_instant.to_utc())
|
||||
@@ -58,7 +59,7 @@ impl BackupSchedule {
|
||||
return None;
|
||||
}
|
||||
|
||||
// 二分查找最小的 u(分钟)使 after(u) > high;prev = after(u - 1 分钟)。
|
||||
// 二分查找最小的 u(分钟)使 after(u) > high;prev = after(u - 1 分钟)
|
||||
let mut lo = low_min;
|
||||
let mut hi = high_min;
|
||||
while lo < hi {
|
||||
@@ -77,7 +78,7 @@ impl BackupSchedule {
|
||||
.and_then(|firing| Utc.timestamp_opt(firing * 60, 0).single())
|
||||
}
|
||||
|
||||
/// 返回第一个严格晚于 `at` 的触发时间。
|
||||
/// 返回第一个严格晚于 `at` 的触发时间
|
||||
#[must_use]
|
||||
pub fn next_after(&self, at: DateTime<Utc>) -> Option<DateTime<Utc>> {
|
||||
let local = at.with_timezone(&self.timezone).naive_local().and_utc();
|
||||
@@ -91,7 +92,7 @@ impl BackupSchedule {
|
||||
})
|
||||
}
|
||||
|
||||
/// `after(u 分钟)` 返回的第一个触发时间(分钟索引),用于二分搜索。
|
||||
/// `after(u 分钟)` 返回的第一个触发时间(分钟索引),用于二分搜索
|
||||
fn firing_after(&self, minute_index: i64) -> Option<i64> {
|
||||
let instant = instant(minute_index, self.timezone)?.to_utc();
|
||||
self.next_after(instant)
|
||||
@@ -99,34 +100,34 @@ impl BackupSchedule {
|
||||
}
|
||||
}
|
||||
|
||||
/// 构造指定分钟索引(UTC epoch 分钟)在目标时区下的 `DateTime`。
|
||||
/// 构造指定分钟索引(UTC epoch 分钟)在目标时区下的 `DateTime`
|
||||
fn instant(minute_index: i64, timezone: Tz) -> Option<DateTime<Tz>> {
|
||||
timezone.timestamp_opt(minute_index * 60, 0).single()
|
||||
}
|
||||
|
||||
/// 一条记录被纳入删除流程的原因。
|
||||
/// 一条记录被纳入删除流程的原因
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum RetentionReason {
|
||||
/// 超过 `retention_days`。
|
||||
/// 超过 `retention_days`
|
||||
Days,
|
||||
/// 超过 `retention_count`。
|
||||
/// 超过 `retention_count`
|
||||
Count,
|
||||
}
|
||||
|
||||
/// 保留决策。
|
||||
/// 保留决策
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct RetentionDecision {
|
||||
pub record_id: String,
|
||||
pub reason: RetentionReason,
|
||||
}
|
||||
|
||||
/// 从按完成时间倒序的 completed 计划备份中决定纳入删除流程的记录。
|
||||
/// 从按完成时间倒序的 completed 计划备份中决定纳入删除流程的记录
|
||||
///
|
||||
/// 自动保留规则:
|
||||
/// - `retentionDays = 0` 时不按天数清理;`retentionCount = 0` 时不按份数清理。
|
||||
/// - 自动计划备份满足任一启用阈值即可进入删除。
|
||||
/// - 无论阈值如何,至少保留最近一个成功的计划备份(倒序下标 0)。
|
||||
/// - 手动备份不进入本规则;手工/计划记录各自的 `expires_at` 由到期扫描处理。
|
||||
/// - `retentionDays = 0` 时不按天数清理;`retentionCount = 0` 时不按份数清理
|
||||
/// - 自动计划备份满足任一启用阈值即可进入删除
|
||||
/// - 无论阈值如何,至少保留最近一个成功的计划备份(倒序下标 0)
|
||||
/// - 手动备份不进入本规则;手工/计划记录各自的 `expires_at` 由到期扫描处理
|
||||
#[must_use]
|
||||
pub fn decide_retention(
|
||||
retention_days: u32,
|
||||
@@ -139,7 +140,7 @@ pub fn decide_retention(
|
||||
if record.status != BackupStatus::Completed {
|
||||
continue;
|
||||
}
|
||||
// 最近一个成功的计划备份永远保留。
|
||||
// 最近一个成功的计划备份永远保留
|
||||
if index == 0 {
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
//! Backup Worker 贡献:单个可取消 `DaemonTask` 承担调度、执行、删除收敛与保留清理。
|
||||
//! Backup Worker 贡献:单个可取消 `DaemonTask` 承担调度、执行、删除收敛与保留清理
|
||||
//!
|
||||
//! 当前部署边界是单副本,因此不需要 Redis lease、fencing token 或 heartbeat。长时间
|
||||
//! `pg_dump` 与上传由本 Daemon 自身持有;Host 只负责 panic 后重启、健康与关闭。
|
||||
//! 当前部署边界是单副本,因此不需要 Redis lease、fencing token 或 heartbeat
|
||||
//! 长时间
|
||||
//! `pg_dump` 与上传由本 Daemon 自身持有;Host 只负责 panic 后重启、健康与关闭
|
||||
|
||||
use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
@@ -33,16 +34,16 @@ impl From<BackupError> for WorkerTaskError {
|
||||
|
||||
use super::policy::{BackupSchedule, decide_retention};
|
||||
|
||||
/// 循环间隔:管理员修改 Cron 后该时长内生效;无工作时可取消等待。
|
||||
/// 循环间隔:管理员修改 Cron 后该时长内生效;无工作时可取消等待
|
||||
const BACKUP_LOOP_INTERVAL: Duration = Duration::from_secs(30);
|
||||
/// 每个循环最多执行的保留删除数量。
|
||||
/// 每个循环最多执行的保留删除数量
|
||||
const RETENTION_BATCH_SIZE: usize = 10;
|
||||
/// 保留扫描一次读取的 completed 计划备份上限。
|
||||
/// 保留扫描一次读取的 completed 计划备份上限
|
||||
const RETENTION_SCAN_LIMIT: u32 = 1000;
|
||||
/// 每个循环最多完成的待删除记录数量。
|
||||
/// 每个循环最多完成的待删除记录数量
|
||||
const PENDING_DELETION_BATCH: u32 = 20;
|
||||
|
||||
/// 备份 Daemon 任务。
|
||||
/// 备份 Daemon 任务
|
||||
pub struct BackupTask {
|
||||
timezone: gateway_core::time::DeploymentTimeZone,
|
||||
repository: Arc<dyn BackupRepository>,
|
||||
@@ -51,7 +52,7 @@ pub struct BackupTask {
|
||||
}
|
||||
|
||||
impl BackupTask {
|
||||
/// 组合仓储、导出器与对象存储适配器。
|
||||
/// 组合仓储、导出器与对象存储适配器
|
||||
#[must_use]
|
||||
pub fn new(
|
||||
repository: Arc<dyn BackupRepository>,
|
||||
@@ -94,13 +95,13 @@ impl BackupTask {
|
||||
}
|
||||
}
|
||||
|
||||
/// 执行一个周期:推进计划、恢复中间态、删除收敛、领取执行一个任务、保留清理。
|
||||
/// 执行一个周期:推进计划、恢复中间态、删除收敛、领取执行一个任务、保留清理
|
||||
///
|
||||
/// 对外暴露以便集成测试逐周期驱动;daemon 循环内部复用。
|
||||
/// 对外暴露以便集成测试逐周期驱动;daemon 循环内部复用
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// 仓储或基础设施不可用、任务执行失败时返回 [`WorkerTaskError`]。
|
||||
/// 仓储或基础设施不可用、任务执行失败时返回 [`WorkerTaskError`]
|
||||
pub async fn run_cycle(&self, cancellation: &CancellationToken) -> Result<(), WorkerTaskError> {
|
||||
if cancellation.is_cancelled() {
|
||||
return Ok(());
|
||||
@@ -113,7 +114,7 @@ impl BackupTask {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// 推进到期计划并幂等插入 scheduled 任务。
|
||||
/// 推进到期计划并幂等插入 scheduled 任务
|
||||
async fn advance_schedule(&self, now: DateTime<Utc>) -> Result<(), WorkerTaskError> {
|
||||
let settings = self.repository.load_settings().await.map_err(repo_error)?;
|
||||
if !settings.schedule_enabled {
|
||||
@@ -128,7 +129,7 @@ impl BackupTask {
|
||||
.map_err(|_| WorkerTaskError::safe("backup schedule is invalid"))?;
|
||||
|
||||
if settings.schedule_timezone.as_deref() != Some(timezone) {
|
||||
// 时区切换只初始化未来游标,不把旧时区计划当作应补跑任务。
|
||||
// 时区切换只初始化未来游标,不把旧时区计划当作应补跑任务
|
||||
if let Some(next_run_at) = schedule.next_after(now) {
|
||||
self.repository
|
||||
.advance_schedule_cursor(
|
||||
@@ -147,7 +148,7 @@ impl BackupTask {
|
||||
Some(next_run_at) if next_run_at <= now => true,
|
||||
Some(_) => false,
|
||||
None => {
|
||||
// 刚启用计划但尚无游标:直接初始化。
|
||||
// 刚启用计划但尚无游标:直接初始化
|
||||
if let Some(next_run_at) = schedule.next_after(now) {
|
||||
let _ = self
|
||||
.repository
|
||||
@@ -171,7 +172,7 @@ impl BackupTask {
|
||||
let scheduled_at = match schedule.last_firing_at_or_before(now) {
|
||||
Some(scheduled_at) => scheduled_at,
|
||||
None => {
|
||||
// 无法从计划推导最近到期时间:推进游标避免反复触发。
|
||||
// 无法从计划推导最近到期时间:推进游标避免反复触发
|
||||
if let Some(next_run_at) = schedule.next_after(now) {
|
||||
let _ = self
|
||||
.repository
|
||||
@@ -221,7 +222,7 @@ impl BackupTask {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// 恢复中间状态(dumping/uploading/deleting)。
|
||||
/// 恢复中间状态(dumping/uploading/deleting)
|
||||
async fn recover_intermediate(
|
||||
&self,
|
||||
cancellation: &CancellationToken,
|
||||
@@ -241,7 +242,7 @@ impl BackupTask {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// dumping:有完整暂存则继续上传,否则清理暂存并标记失败。
|
||||
/// dumping:有完整暂存则继续上传,否则清理暂存并标记失败
|
||||
async fn recover_dumping(
|
||||
&self,
|
||||
record: &BackupRecord,
|
||||
@@ -287,7 +288,7 @@ impl BackupTask {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// uploading:远端匹配则补记完成,有完整暂存则重试上传,否则失败。
|
||||
/// uploading:远端匹配则补记完成,有完整暂存则重试上传,否则失败
|
||||
async fn recover_uploading(
|
||||
&self,
|
||||
record: &BackupRecord,
|
||||
@@ -339,7 +340,7 @@ impl BackupTask {
|
||||
}
|
||||
}
|
||||
|
||||
/// 完成待删除记录(deleting → DeleteObject → 硬删除)。
|
||||
/// 完成待删除记录(deleting → DeleteObject → 硬删除)
|
||||
async fn finish_pending_deletions(&self) -> Result<(), WorkerTaskError> {
|
||||
let settings = self.repository.load_settings().await.map_err(repo_error)?;
|
||||
let Some(config) = BackupStorageConfig::from_settings(&settings) else {
|
||||
@@ -366,7 +367,7 @@ impl BackupTask {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// 领取一个 queued 任务并执行导出、上传与远端校验。
|
||||
/// 领取一个 queued 任务并执行导出、上传与远端校验
|
||||
async fn process_next_task(
|
||||
&self,
|
||||
cancellation: &CancellationToken,
|
||||
@@ -391,7 +392,7 @@ impl BackupTask {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// 执行单个任务;内部处理全部状态迁移。
|
||||
/// 执行单个任务;内部处理全部状态迁移
|
||||
async fn execute_task(
|
||||
&self,
|
||||
record: &BackupRecord,
|
||||
@@ -425,7 +426,7 @@ impl BackupTask {
|
||||
}
|
||||
};
|
||||
|
||||
// dumping → uploading,持久化 size/sha256。
|
||||
// dumping → uploading,持久化 size/sha256
|
||||
let update = StatusTransitionUpdate {
|
||||
size_bytes: Some(artifact.size_bytes),
|
||||
sha256: Some(artifact.sha256.clone()),
|
||||
@@ -458,7 +459,7 @@ impl BackupTask {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// 上传 + 远端校验,内部完成终态迁移。
|
||||
/// 上传 + 远端校验,内部完成终态迁移
|
||||
async fn upload_and_verify(
|
||||
&self,
|
||||
record: &BackupRecord,
|
||||
@@ -563,7 +564,7 @@ impl BackupTask {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// 把活跃任务写入失败终态并清理暂存。
|
||||
/// 把活跃任务写入失败终态并清理暂存
|
||||
async fn fail_task(
|
||||
&self,
|
||||
record: &BackupRecord,
|
||||
@@ -598,11 +599,11 @@ impl BackupTask {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// 执行一小批到期保留清理:先处理 `expires_at` 已到期的记录,再按天数/份数清理计划备份。
|
||||
/// 执行一小批到期保留清理:先处理 `expires_at` 已到期的记录,再按天数/份数清理计划备份
|
||||
async fn run_retention_batch(&self, now: DateTime<Utc>) -> Result<(), WorkerTaskError> {
|
||||
let settings = self.repository.load_settings().await.map_err(repo_error)?;
|
||||
|
||||
// 1. expires_at 已到期的记录(手动或计划)无条件进入删除。
|
||||
// 1. expires_at 已到期的记录(手动或计划)无条件进入删除
|
||||
let expired = self
|
||||
.repository
|
||||
.list_expired_records(RETENTION_BATCH_SIZE as u32)
|
||||
@@ -620,7 +621,7 @@ impl BackupTask {
|
||||
self.finalize_deletion(&record).await?;
|
||||
}
|
||||
|
||||
// 2. 计划备份按 retentionDays / retentionCount 清理。
|
||||
// 2. 计划备份按 retentionDays / retentionCount 清理
|
||||
let records = self
|
||||
.repository
|
||||
.list_scheduled_completed_desc(RETENTION_SCAN_LIMIT)
|
||||
@@ -646,7 +647,7 @@ impl BackupTask {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// 对已进入 deleting 的记录执行对象删除并硬删除记录行。
|
||||
/// 对已进入 deleting 的记录执行对象删除并硬删除记录行
|
||||
async fn finalize_deletion(&self, record: &BackupRecord) -> Result<(), WorkerTaskError> {
|
||||
let settings = self.repository.load_settings().await.map_err(repo_error)?;
|
||||
let Some(config) = BackupStorageConfig::from_settings(&settings) else {
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
//! 容量熔断冻结的恢复编排:到期探测、失败顺延与自适应并发下调。
|
||||
//! 容量熔断冻结的恢复编排:到期探测、失败顺延与自适应并发下调
|
||||
//!
|
||||
//! 冻结事实保存在可丢失的 Redis 冷却中,本服务只组合 Admin 端口:
|
||||
//! 观测(`AccountRuntimeStore`)、探测(`AccountsService`)与写回
|
||||
//! (原子降低并发 / 按冻结代次结束探测)。旧观测不会覆盖管理员操作。
|
||||
//! (原子降低并发 / 按冻结代次结束探测)
|
||||
//! 旧观测不会覆盖管理员操作
|
||||
|
||||
use std::collections::BTreeMap;
|
||||
use std::sync::Arc;
|
||||
@@ -22,21 +23,21 @@ use crate::model::{MutationActor, MutationContext};
|
||||
use crate::ports::store::{AccountRuntimeStore, AccountStore, SettingsStore};
|
||||
use crate::use_case::accounts::AccountsService;
|
||||
|
||||
/// 自适应并发下调保留系数:下调到观测在途峰值的 80%。
|
||||
/// 自适应并发下调保留系数:下调到观测在途峰值的 80%
|
||||
const ADAPTIVE_CONCURRENCY_FACTOR: f64 = 0.8;
|
||||
/// 自适应并发下限:过低的并发让账号几乎不可用,宁可保持冻结。
|
||||
/// 自适应并发下限:过低的并发让账号几乎不可用,宁可保持冻结
|
||||
const ADAPTIVE_CONCURRENCY_FLOOR: u32 = 2;
|
||||
/// worker 周期;冷却观测与探测都依赖该节奏。
|
||||
/// worker 周期;冷却观测与探测都依赖该节奏
|
||||
pub const FREEZE_RECOVERY_INTERVAL: Duration = Duration::from_secs(30);
|
||||
pub const FREEZE_RECOVERY_WORKER_OWNER: &str = "account-freeze-recovery";
|
||||
pub const WORKER_INITIAL_BACKOFF: Duration = Duration::from_secs(1);
|
||||
pub const WORKER_MAXIMUM_BACKOFF: Duration = Duration::from_secs(60);
|
||||
pub const WORKER_LEASE_TTL: Duration = Duration::from_secs(15 * 60);
|
||||
pub const WORKER_LEASE_RENEWAL: Duration = Duration::from_secs(5 * 60);
|
||||
/// 系统审计上下文的固定请求 ID;账号并发更新审计由此溯源到自动冻结编排。
|
||||
/// 系统审计上下文的固定请求 ID;账号并发更新审计由此溯源到自动冻结编排
|
||||
const SYSTEM_REQUEST_ID: &str = "account-freeze-recovery";
|
||||
|
||||
/// 冻结恢复所需的管理端口组合。
|
||||
/// 冻结恢复所需的管理端口组合
|
||||
#[derive(Clone)]
|
||||
pub struct FreezeRecoveryDeps {
|
||||
pub accounts: Arc<dyn AccountsService>,
|
||||
@@ -61,7 +62,7 @@ impl FreezeRecoveryTask {
|
||||
}
|
||||
}
|
||||
|
||||
/// 读取冻结策略;读取或校验失败跳过本轮,不能把未知配置当作允许解冻。
|
||||
/// 读取冻结策略;读取或校验失败跳过本轮,不能把未知配置当作允许解冻
|
||||
async fn freeze_policy(&self) -> Option<gateway_core::provider_ports::ProviderFreezePolicy> {
|
||||
let settings = self.deps.settings.load_runtime_settings().await.ok()?;
|
||||
gateway_core::provider_ports::ProviderFreezePolicy::try_new(
|
||||
@@ -95,7 +96,7 @@ impl FreezeRecoveryTask {
|
||||
}
|
||||
|
||||
/// 对冻结中的账号执行自适应并发下调:目标为观测在途峰值的 80%(下限 2),
|
||||
/// 只降不升;未观测到峰值证据的账号保持现状。
|
||||
/// 只降不升;未观测到峰值证据的账号保持现状
|
||||
async fn adapt_concurrency_limits(
|
||||
&self,
|
||||
freezes: &BTreeMap<String, AccountFreeze>,
|
||||
@@ -146,7 +147,7 @@ impl FreezeRecoveryTask {
|
||||
.ok()
|
||||
}
|
||||
|
||||
/// 到达探测时间后才恢复;关闭自动冻结或探测时,已有冻结仍等待原冷却结束。
|
||||
/// 到达探测时间后才恢复;关闭自动冻结或探测时,已有冻结仍等待原冷却结束
|
||||
async fn recover_due_freezes(
|
||||
&self,
|
||||
freezes: &BTreeMap<String, AccountFreeze>,
|
||||
@@ -179,14 +180,14 @@ impl FreezeRecoveryTask {
|
||||
return;
|
||||
};
|
||||
if !item.account.enabled {
|
||||
// 停用账号没有自动恢复意义;解冻交给管理员手动恢复。
|
||||
// 停用账号没有自动恢复意义;解冻交给管理员手动恢复
|
||||
return;
|
||||
}
|
||||
let Ok(account) = ProviderAccountId::new(account_id.to_owned()) else {
|
||||
return;
|
||||
};
|
||||
let Some(model) = self.resolve_probe_model(&account, policy).await else {
|
||||
// 没有可用探测模型时按失败处理,顺延冻结等待下一轮。
|
||||
// 没有可用探测模型时按失败处理,顺延冻结等待下一轮
|
||||
self.postpone(&account, freeze, policy).await;
|
||||
return;
|
||||
};
|
||||
@@ -218,7 +219,7 @@ impl FreezeRecoveryTask {
|
||||
}
|
||||
}
|
||||
|
||||
/// 探测失败只顺延本次仍存在的冻结,旧探测不能重新冻结已手动恢复的账号。
|
||||
/// 探测失败只顺延本次仍存在的冻结,旧探测不能重新冻结已手动恢复的账号
|
||||
async fn postpone(
|
||||
&self,
|
||||
account: &ProviderAccountId,
|
||||
@@ -281,7 +282,7 @@ impl ScheduledTask for FreezeRecoveryTask {
|
||||
}
|
||||
}
|
||||
|
||||
/// 自适应目标并发:观测峰值 × 0.8 向下取整,下限 2;无证据(峰值为 0)不调整。
|
||||
/// 自适应目标并发:观测峰值 × 0.8 向下取整,下限 2;无证据(峰值为 0)不调整
|
||||
fn adaptive_target(peak_in_flight: u32) -> Option<u32> {
|
||||
if peak_in_flight == 0 {
|
||||
return None;
|
||||
@@ -290,7 +291,7 @@ fn adaptive_target(peak_in_flight: u32) -> Option<u32> {
|
||||
Some(scaled.max(ADAPTIVE_CONCURRENCY_FLOOR))
|
||||
}
|
||||
|
||||
/// 消耗连接测试事件流并返回是否以 `Completed` 终止。
|
||||
/// 消耗连接测试事件流并返回是否以 `Completed` 终止
|
||||
async fn drain_probe(mut events: crate::model::accounts::AccountConnectionTestEventStream) -> bool {
|
||||
let mut completed = false;
|
||||
while let Some(event) = events.next().await {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
//! 管理控制面的语义模型、用例与外部能力端口。
|
||||
//! 管理控制面的语义模型、用例与外部能力端口
|
||||
//!
|
||||
//! 本 crate 不包含 HTTP wire、数据库实现或具体 Provider 实现。
|
||||
//! 本 crate 不包含 HTTP wire、数据库实现或具体 Provider 实现
|
||||
|
||||
use std::{fmt, path::Path, sync::Arc, time::Duration};
|
||||
|
||||
@@ -70,7 +70,7 @@ const WEAK_ADMIN_PASSWORDS: &[&str] = &[
|
||||
const BACKUP_WORKER_OWNER: &str = "backup";
|
||||
const DEFAULT_CLIENT_SESSION_TTL_MINUTES: u64 = 24 * 60;
|
||||
|
||||
/// 只用于首次幂等创建默认管理员的启动密码。
|
||||
/// 只用于首次幂等创建默认管理员的启动密码
|
||||
#[derive(Clone, Deserialize)]
|
||||
#[serde(transparent)]
|
||||
pub struct InitialAdminPassword(SecretString);
|
||||
@@ -100,7 +100,7 @@ impl fmt::Debug for InitialAdminPassword {
|
||||
}
|
||||
}
|
||||
|
||||
/// 管理控制面的启动配置。
|
||||
/// 管理控制面的启动配置
|
||||
#[derive(Clone, Deserialize, PartialEq, Eq)]
|
||||
pub struct AdminConfig {
|
||||
pub session_ttl_minutes: u64,
|
||||
@@ -108,7 +108,7 @@ pub struct AdminConfig {
|
||||
pub default_password: InitialAdminPassword,
|
||||
}
|
||||
|
||||
/// Client 登录域的通用启动配置。
|
||||
/// Client 登录域的通用启动配置
|
||||
#[derive(Debug, Clone, Deserialize, PartialEq, Eq)]
|
||||
pub struct ClientConfig {
|
||||
pub session_ttl_minutes: u64,
|
||||
@@ -123,11 +123,11 @@ impl Default for ClientConfig {
|
||||
}
|
||||
|
||||
impl ClientConfig {
|
||||
/// 校验 Client session TTL;当前配置不含相对路径。
|
||||
/// 校验 Client session TTL;当前配置不含相对路径
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// 会话有效期为零或无法安全换算时返回错误。
|
||||
/// 会话有效期为零或无法安全换算时返回错误
|
||||
pub fn resolve_and_validate(&mut self, _source_dir: &Path) -> Result<(), AdminConfigError> {
|
||||
if self.session_ttl_minutes == 0 || i64::try_from(self.session_ttl_minutes).is_err() {
|
||||
return Err(AdminConfigError::InvalidField("client.session_ttl_minutes"));
|
||||
@@ -137,11 +137,11 @@ impl ClientConfig {
|
||||
}
|
||||
|
||||
impl AdminConfig {
|
||||
/// 校验 Admin-owned 字段;当前配置不含相对路径。
|
||||
/// 校验 Admin-owned 字段;当前配置不含相对路径
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// 用户名、会话有效期或初始密码不满足安全约束时返回错误。
|
||||
/// 用户名、会话有效期或初始密码不满足安全约束时返回错误
|
||||
pub fn resolve_and_validate(&mut self, _source_dir: &Path) -> Result<(), AdminConfigError> {
|
||||
if self.default_username.trim().is_empty()
|
||||
|| self.default_username.chars().any(char::is_control)
|
||||
@@ -173,7 +173,7 @@ impl fmt::Debug for AdminConfig {
|
||||
}
|
||||
}
|
||||
|
||||
/// Admin-owned 启动配置错误;不回显任何配置值。
|
||||
/// Admin-owned 启动配置错误;不回显任何配置值
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, thiserror::Error)]
|
||||
pub enum AdminConfigError {
|
||||
#[error("配置字段 `{0}` 不合法")]
|
||||
@@ -182,9 +182,9 @@ pub enum AdminConfigError {
|
||||
WeakInitialPassword,
|
||||
}
|
||||
|
||||
/// API 持有的管理资源能力集合。
|
||||
/// API 持有的管理资源能力集合
|
||||
///
|
||||
/// 字段全部私有;调用方经 accessor 直接调用能力,不需要命名内部 `use_case` 模块。
|
||||
/// 字段全部私有;调用方经 accessor 直接调用能力,不需要命名内部 `use_case` 模块
|
||||
#[derive(Clone)]
|
||||
pub struct AdminServices {
|
||||
timezone: gateway_core::time::DeploymentTimeZone,
|
||||
@@ -237,7 +237,7 @@ impl AdminServices {
|
||||
self.key_usage.as_ref()
|
||||
}
|
||||
|
||||
/// 取得账号服务的共享句柄;后台编排(冻结恢复 worker)需要持有 Arc。
|
||||
/// 取得账号服务的共享句柄;后台编排(冻结恢复 worker)需要持有 Arc
|
||||
#[must_use]
|
||||
pub fn accounts_handle(&self) -> Arc<dyn AccountsService> {
|
||||
Arc::clone(&self.accounts)
|
||||
@@ -292,7 +292,7 @@ impl AdminServices {
|
||||
self.credentials.as_ref()
|
||||
}
|
||||
|
||||
/// Runtime 只持有该窄端口的 Weak;AdminBundle 保持实际生命周期。
|
||||
/// Runtime 只持有该窄端口的 Weak;AdminBundle 保持实际生命周期
|
||||
#[must_use]
|
||||
pub fn plugin_accounts_handle(&self) -> Arc<dyn PluginAccountAccess> {
|
||||
Arc::clone(&self.plugin_accounts)
|
||||
@@ -304,7 +304,7 @@ impl AdminServices {
|
||||
}
|
||||
}
|
||||
|
||||
/// Admin 初始化完成后的封闭能力包。
|
||||
/// Admin 初始化完成后的封闭能力包
|
||||
pub struct AdminBundle {
|
||||
services: AdminServices,
|
||||
worker_contributions: Vec<WorkerContribution>,
|
||||
@@ -316,13 +316,13 @@ impl AdminBundle {
|
||||
self.services.clone()
|
||||
}
|
||||
|
||||
/// 取出 Admin Worker 贡献;只能调用一次,与其它 Bundle 的贡献一并交给 Host。
|
||||
/// 取出 Admin Worker 贡献;只能调用一次,与其它 Bundle 的贡献一并交给 Host
|
||||
pub fn take_worker_contributions(&mut self) -> Vec<WorkerContribution> {
|
||||
std::mem::take(&mut self.worker_contributions)
|
||||
}
|
||||
}
|
||||
|
||||
/// 组合根提供给控制面的运行能力;与配置和存储端口分别传入。
|
||||
/// 组合根提供给控制面的运行能力;与配置和存储端口分别传入
|
||||
pub struct AdminRuntimePorts {
|
||||
pub timezone: gateway_core::time::DeploymentTimeZone,
|
||||
pub service_middleware: service::PlanSource,
|
||||
@@ -341,11 +341,11 @@ pub struct AdminRuntimePorts {
|
||||
pub client_key_verifier: Arc<dyn ClientKeyVerifier>,
|
||||
}
|
||||
|
||||
/// 校验配置、接入已组装的 Provider 注册表并完成默认管理员幂等初始化。
|
||||
/// 校验配置、接入已组装的 Provider 注册表并完成默认管理员幂等初始化
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// 配置非法或默认管理员初始化失败时返回错误。
|
||||
/// 配置非法或默认管理员初始化失败时返回错误
|
||||
pub async fn initialize(
|
||||
config: AdminConfig,
|
||||
client_config: ClientConfig,
|
||||
@@ -355,7 +355,7 @@ pub async fn initialize(
|
||||
initialize_inner(config, client_config, store, runtime, None).await
|
||||
}
|
||||
|
||||
/// 使用组合根已绑定给 Runtime 的同一账号窄端口,避免为完整 Admin 重建第二实例。
|
||||
/// 使用组合根已绑定给 Runtime 的同一账号窄端口,避免为完整 Admin 重建第二实例
|
||||
pub async fn initialize_with_plugin_accounts(
|
||||
config: AdminConfig,
|
||||
client_config: ClientConfig,
|
||||
@@ -541,7 +541,7 @@ async fn initialize_inner(
|
||||
})
|
||||
}
|
||||
|
||||
/// CLI 只组合账号用例,不初始化管理员、管理服务或后台任务;写入仍复用同一事务与审计。
|
||||
/// CLI 只组合账号用例,不初始化管理员、管理服务或后台任务;写入仍复用同一事务与审计
|
||||
pub fn initialize_plugin_accounts(
|
||||
providers: ports::provider::ProviderAdminRegistry,
|
||||
accounts: Arc<dyn ports::store::AccountStore>,
|
||||
@@ -552,7 +552,7 @@ pub fn initialize_plugin_accounts(
|
||||
))
|
||||
}
|
||||
|
||||
/// 为 Runtime 创建非秘密 Client Key 目录与预算重置的窄端口。
|
||||
/// 为 Runtime 创建非秘密 Client Key 目录与预算重置的窄端口
|
||||
#[must_use]
|
||||
pub fn initialize_plugin_client_keys(
|
||||
providers: ports::provider::ProviderAdminRegistry,
|
||||
@@ -564,7 +564,7 @@ pub fn initialize_plugin_client_keys(
|
||||
Arc::new(use_case::plugin_client_keys::DefaultPluginClientKeyAccess::new(service))
|
||||
}
|
||||
|
||||
/// 为 Runtime 组合实例自有资源写入;权限和归属在同一存储事务复核。
|
||||
/// 为 Runtime 组合实例自有资源写入;权限和归属在同一存储事务复核
|
||||
#[must_use]
|
||||
pub fn initialize_plugin_resources(
|
||||
store: Arc<dyn ports::plugin_resources::PluginResourceStore>,
|
||||
@@ -573,7 +573,7 @@ pub fn initialize_plugin_resources(
|
||||
Arc::new(use_case::plugin_resources::DefaultPluginResourceAccess { store, snapshot })
|
||||
}
|
||||
|
||||
/// Backup Worker 注册:单个可取消 Daemon,owner 固定为 `backup`。
|
||||
/// Backup Worker 注册:单个可取消 Daemon,owner 固定为 `backup`
|
||||
fn backup_worker_contribution(
|
||||
task: backup::task::BackupTask,
|
||||
) -> Result<Vec<WorkerContribution>, AdminError> {
|
||||
@@ -592,7 +592,7 @@ fn backup_worker_contribution(
|
||||
Ok(vec![WorkerContribution::Registration(registration)])
|
||||
}
|
||||
|
||||
/// 冻结恢复 Worker 注册:按固定周期扫描活跃冻结,owner 固定。
|
||||
/// 冻结恢复 Worker 注册:按固定周期扫描活跃冻结,owner 固定
|
||||
fn freeze_recovery_worker_contribution(
|
||||
task: freeze_recovery::FreezeRecoveryTask,
|
||||
) -> Result<Vec<WorkerContribution>, AdminError> {
|
||||
@@ -623,7 +623,7 @@ fn freeze_recovery_worker_contribution(
|
||||
Ok(vec![WorkerContribution::Registration(registration)])
|
||||
}
|
||||
|
||||
/// 设置服务不依赖 Web 管理会话,CLI 与服务器通过同一用例执行事务和发布。
|
||||
/// 设置服务不依赖 Web 管理会话,CLI 与服务器通过同一用例执行事务和发布
|
||||
pub fn initialize_settings(
|
||||
store: Arc<dyn ports::store::SettingsStore>,
|
||||
snapshot: Arc<dyn SnapshotControl>,
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
//! Provider-neutral account group commands and projections.
|
||||
//! 跨 Provider 账号分组的管理命令与查询投影
|
||||
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
@@ -7,7 +7,7 @@ use gateway_core::{account::AccountStatusFacts, routing::AccountGroupId};
|
||||
|
||||
use super::{PageSize, Revision, observability::DecimalAmount};
|
||||
|
||||
/// Canonical `#RRGGBBAA` color persisted with an account group.
|
||||
/// 账号分组持久化使用的标准 `#RRGGBBAA` 颜色
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct AccountGroupColor(String);
|
||||
|
||||
@@ -26,7 +26,7 @@ impl AccountGroupColor {
|
||||
}
|
||||
}
|
||||
|
||||
/// Group member availability at the current observation time.
|
||||
/// 当前观测时刻的分组成员可用性
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub struct AccountGroupAccountSummary {
|
||||
pub available: u64,
|
||||
@@ -34,23 +34,23 @@ pub struct AccountGroupAccountSummary {
|
||||
pub total: u64,
|
||||
}
|
||||
|
||||
/// Group scheduling slots derived from available accounts and runtime leases.
|
||||
/// 根据可用账号与运行时租约计算的分组调度槽位
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub struct AccountGroupCapacity {
|
||||
pub used_slots: Option<u64>,
|
||||
/// `None` 表示可用账号中存在不限制并发的账号。
|
||||
/// `None` 表示可用账号中存在不限制并发的账号
|
||||
pub total_slots: Option<u64>,
|
||||
}
|
||||
|
||||
/// Successful, downstream-committed USD request costs for group accounts.
|
||||
/// 分组账号成功且已向下游交付的请求美元费用
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct AccountGroupUsage {
|
||||
pub today_usd: DecimalAmount,
|
||||
/// 当前 usage retention 窗口内、按请求发生时 routing group 快照归属的累计成本。
|
||||
/// 当前 usage retention 窗口内、按请求发生时 routing group 快照归属的累计成本
|
||||
pub retained_total_usd: DecimalAmount,
|
||||
}
|
||||
|
||||
/// 当前页账号组 membership 对应的持久账号事实;运行态在 Admin query service 合并。
|
||||
/// 当前页账号组 membership 对应的持久账号事实;运行态在 Admin query service 合并
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct AccountGroupMemberFact {
|
||||
pub group_id: AccountGroupId,
|
||||
@@ -59,7 +59,7 @@ pub struct AccountGroupMemberFact {
|
||||
pub total_slots: Option<u64>,
|
||||
}
|
||||
|
||||
/// Lightweight group reference embedded in account and client-key views.
|
||||
/// 账号与 Client Key 视图内嵌的轻量分组引用
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct AccountGroupRef {
|
||||
pub id: AccountGroupId,
|
||||
@@ -68,7 +68,7 @@ pub struct AccountGroupRef {
|
||||
pub enabled: bool,
|
||||
}
|
||||
|
||||
/// Account group list query.
|
||||
/// 账号分组列表查询
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct AccountGroupListQuery {
|
||||
pub page: u32,
|
||||
@@ -77,7 +77,7 @@ pub struct AccountGroupListQuery {
|
||||
pub enabled: Option<bool>,
|
||||
}
|
||||
|
||||
/// Complete account group summary.
|
||||
/// 完整账号分组摘要
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct AccountGroupRecord {
|
||||
pub disable_fast: bool,
|
||||
@@ -96,7 +96,7 @@ pub struct AccountGroupRecord {
|
||||
pub updated_at: DateTime<Utc>,
|
||||
}
|
||||
|
||||
/// Paginated account groups.
|
||||
/// 账号分组分页结果
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct AccountGroupPage {
|
||||
pub config_revision: Revision,
|
||||
@@ -106,7 +106,7 @@ pub struct AccountGroupPage {
|
||||
pub page_size: u16,
|
||||
}
|
||||
|
||||
/// Create an account group.
|
||||
/// 创建账号分组
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct CreateAccountGroup {
|
||||
pub disable_fast: bool,
|
||||
@@ -115,7 +115,7 @@ pub struct CreateAccountGroup {
|
||||
pub color: AccountGroupColor,
|
||||
}
|
||||
|
||||
/// Store-ready create command with a generated stable ID.
|
||||
/// 已生成稳定标识、可直接提交存储的创建命令
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct NewAccountGroup {
|
||||
pub disable_fast: bool,
|
||||
@@ -125,7 +125,7 @@ pub struct NewAccountGroup {
|
||||
pub color: AccountGroupColor,
|
||||
}
|
||||
|
||||
/// Update an account group's descriptive fields.
|
||||
/// 更新账号分组的描述字段
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct UpdateAccountGroup {
|
||||
pub disable_fast: Option<bool>,
|
||||
@@ -135,20 +135,20 @@ pub struct UpdateAccountGroup {
|
||||
pub color: AccountGroupColor,
|
||||
}
|
||||
|
||||
/// Enable or disable an account group.
|
||||
/// 启用或停用账号分组
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct SetAccountGroupEnabled {
|
||||
pub id: AccountGroupId,
|
||||
pub enabled: bool,
|
||||
}
|
||||
|
||||
/// Delete an account group that is not referenced by a client key.
|
||||
/// 删除未被 Client Key 引用的账号分组
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct DeleteAccountGroup {
|
||||
pub id: AccountGroupId,
|
||||
}
|
||||
|
||||
/// Result of any account-group mutation.
|
||||
/// 账号分组变更结果
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct AccountGroupMutation {
|
||||
pub config_revision: Revision,
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
//! 多 Provider 账号目录与连接测试的公共事实。
|
||||
//! 多 Provider 账号目录与连接测试的公共事实
|
||||
|
||||
use std::{collections::BTreeMap, pin::Pin};
|
||||
|
||||
@@ -18,7 +18,7 @@ pub use gateway_core::account::{
|
||||
QuotaState, resolve_account_status,
|
||||
};
|
||||
|
||||
/// 账号可用的管理操作。
|
||||
/// 账号可用的管理操作
|
||||
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
|
||||
pub struct ProviderAccountCapabilities {
|
||||
pub quota: bool,
|
||||
@@ -30,7 +30,7 @@ pub struct ProviderAccountCapabilities {
|
||||
pub consume_reset_credit: bool,
|
||||
}
|
||||
|
||||
/// 导入时统一应用的账号备注、调度与分组设置;缺省时保留原有导入语义。
|
||||
/// 导入时统一应用的账号备注、调度与分组设置;缺省时保留原有导入语义
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct AccountImportSettings {
|
||||
pub notes: Option<String>,
|
||||
@@ -41,7 +41,7 @@ pub struct AccountImportSettings {
|
||||
pub group_ids: Vec<gateway_core::routing::AccountGroupId>,
|
||||
}
|
||||
|
||||
/// 账号列表排序字段。
|
||||
/// 账号列表排序字段
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum AccountSortField {
|
||||
Email,
|
||||
@@ -52,21 +52,21 @@ pub enum AccountSortField {
|
||||
ExpiresAt,
|
||||
}
|
||||
|
||||
/// 账号列表排序方向。
|
||||
/// 账号列表排序方向
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum SortDirection {
|
||||
Asc,
|
||||
Desc,
|
||||
}
|
||||
|
||||
/// 一组完整的账号排序规则。
|
||||
/// 一组完整的账号排序规则
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub struct AccountSort {
|
||||
pub field: AccountSortField,
|
||||
pub direction: SortDirection,
|
||||
}
|
||||
|
||||
/// 账号列表的存储查询条件。
|
||||
/// 账号列表的存储查询条件
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct AccountListQuery {
|
||||
pub page: u32,
|
||||
@@ -78,15 +78,15 @@ pub struct AccountListQuery {
|
||||
pub sort: Option<AccountSort>,
|
||||
}
|
||||
|
||||
/// Admin query service 从运行态存储取得的当前账号冷却快照。
|
||||
/// Admin query service 从运行态存储取得的当前账号冷却快照
|
||||
#[derive(Debug, Clone, Default, PartialEq, Eq)]
|
||||
pub struct AccountRuntimeSnapshot {
|
||||
pub cooldown: BTreeMap<String, gateway_core::account::AccountCooldown>,
|
||||
/// `None` 表示实时 lease 存储不可用;`Some` 中未出现的账号当前使用量为零。
|
||||
/// `None` 表示实时 lease 存储不可用;`Some` 中未出现的账号当前使用量为零
|
||||
pub in_flight: Option<BTreeMap<String, u64>>,
|
||||
}
|
||||
|
||||
/// 恢复任务读取的冻结快照;generation 将异步结果绑定到本次冻结。
|
||||
/// 恢复任务读取的冻结快照;generation 将异步结果绑定到本次冻结
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct AccountFreeze {
|
||||
pub credential_revision: Revision,
|
||||
@@ -95,14 +95,14 @@ pub struct AccountFreeze {
|
||||
pub requires_probe: bool,
|
||||
}
|
||||
|
||||
/// Optional account membership filter.
|
||||
/// 可选的账号分组成员筛选条件
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub enum AccountGroupFilter {
|
||||
Group(gateway_core::routing::AccountGroupId),
|
||||
Ungrouped,
|
||||
}
|
||||
|
||||
/// 账号公共存储投影;Provider 专属字段不进入此结构。
|
||||
/// 账号公共存储投影;Provider 专属字段不进入此结构
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct AccountRecord {
|
||||
pub id: String,
|
||||
@@ -133,14 +133,14 @@ pub struct AccountRecord {
|
||||
pub updated_at: DateTime<Utc>,
|
||||
}
|
||||
|
||||
/// 单一货币的账号成本聚合。
|
||||
/// 单一货币的账号成本聚合
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct AccountCost {
|
||||
pub currency: String,
|
||||
pub amount: super::observability::DecimalAmount,
|
||||
}
|
||||
|
||||
/// 账号在一个模型上的历史用量。
|
||||
/// 账号在一个模型上的历史用量
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct AccountModelUsage {
|
||||
pub model: String,
|
||||
@@ -161,14 +161,14 @@ pub struct AccountModelUsage {
|
||||
pub last_used_at: DateTime<Utc>,
|
||||
}
|
||||
|
||||
/// 账号在一个小时窗口内的请求数。
|
||||
/// 账号在一个小时窗口内的请求数
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct AccountRequestBucket {
|
||||
pub bucket_start: DateTime<Utc>,
|
||||
pub request_count: u64,
|
||||
}
|
||||
|
||||
/// 账号历史用量聚合。
|
||||
/// 账号历史用量聚合
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct AccountUsage {
|
||||
pub account_id: String,
|
||||
@@ -191,9 +191,9 @@ pub struct AccountUsage {
|
||||
pub models: Vec<AccountModelUsage>,
|
||||
}
|
||||
|
||||
/// 某个账号在调用方指定时间窗口内的本地用量查询。
|
||||
/// 某个账号在调用方指定时间窗口内的本地用量查询
|
||||
///
|
||||
/// `key` 只用于把聚合结果关联回调用方的窗口,不承载 Provider 私有语义。
|
||||
/// `key` 只用于把聚合结果关联回调用方的窗口,不承载 Provider 私有语义
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct AccountUsageWindowQuery {
|
||||
pub account_id: String,
|
||||
@@ -201,7 +201,7 @@ pub struct AccountUsageWindowQuery {
|
||||
pub range: TimeRange,
|
||||
}
|
||||
|
||||
/// 一个账号时间窗口用量查询的结果。
|
||||
/// 一个账号时间窗口用量查询的结果
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct AccountUsageWindowResult {
|
||||
pub account_id: String,
|
||||
@@ -209,7 +209,7 @@ pub struct AccountUsageWindowResult {
|
||||
pub usage: AccountUsage,
|
||||
}
|
||||
|
||||
/// 账号列表页所需的完整存储事实。
|
||||
/// 账号列表页所需的完整存储事实
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct AccountPage {
|
||||
pub config_revision: Revision,
|
||||
@@ -218,7 +218,7 @@ pub struct AccountPage {
|
||||
pub summary: AccountSummary,
|
||||
}
|
||||
|
||||
/// 同一状态快照下的账号事实与唯一状态投影。
|
||||
/// 同一状态快照下的账号事实与唯一状态投影
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct AccountPageItem {
|
||||
pub account: AccountRecord,
|
||||
@@ -226,18 +226,18 @@ pub struct AccountPageItem {
|
||||
pub capacity: AccountCapacity,
|
||||
}
|
||||
|
||||
/// 网关配置的账号并发上限与查询时的占用,不包含排队请求或上游隐藏限制。
|
||||
/// 网关配置的账号并发上限与查询时的占用,不包含排队请求或上游隐藏限制
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub struct AccountCapacity {
|
||||
/// 实时租约读取失败时为 `None`,不能当作空闲。
|
||||
/// 实时租约读取失败时为 `None`,不能当作空闲
|
||||
pub used_slots: Option<u64>,
|
||||
/// 应用账号覆盖或全局默认值后的上限;`None` 表示不限。
|
||||
/// 应用账号覆盖或全局默认值后的上限;`None` 表示不限
|
||||
pub total_slots: Option<u64>,
|
||||
}
|
||||
|
||||
/// 统一账号目录的全局状态计数,不受当前筛选和分页影响。
|
||||
/// 统一账号目录的全局状态计数,不受当前筛选和分页影响
|
||||
///
|
||||
/// 计数与 [`AccountStatus`] 一一对应,由 store 按派生状态聚合。
|
||||
/// 计数与 [`AccountStatus`] 一一对应,由 store 按派生状态聚合
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub struct AccountSummary {
|
||||
pub total: u64,
|
||||
@@ -248,11 +248,11 @@ pub struct AccountSummary {
|
||||
pub error: u64,
|
||||
}
|
||||
|
||||
/// 账号可编辑事实的一次性替换命令。
|
||||
/// 账号可编辑事实的一次性替换命令
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct UpdateAccount {
|
||||
pub account_id: String,
|
||||
/// 缺省保留备注;空字符串清空备注。
|
||||
/// 缺省保留备注;空字符串清空备注
|
||||
pub notes: Option<String>,
|
||||
pub enabled: bool,
|
||||
pub concurrency_limit: Option<AccountConcurrencyLimit>,
|
||||
@@ -262,14 +262,14 @@ pub struct UpdateAccount {
|
||||
pub outbound_proxy: Option<super::proxies::AccountProxySelection>,
|
||||
}
|
||||
|
||||
/// 账号更新结果。
|
||||
/// 账号更新结果
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct AccountUpdateResult {
|
||||
pub config_revision: Revision,
|
||||
pub account_id: gateway_core::account::ProviderAccountId,
|
||||
}
|
||||
|
||||
/// 仅修改显式字段的批量账号设置命令。
|
||||
/// 仅修改显式字段的批量账号设置命令
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct BatchUpdateAccounts {
|
||||
pub account_ids: Vec<String>,
|
||||
@@ -281,20 +281,20 @@ pub struct BatchUpdateAccounts {
|
||||
pub outbound_proxy: Option<super::proxies::AccountProxySelection>,
|
||||
}
|
||||
|
||||
/// 批量账号更新结果。
|
||||
/// 批量账号更新结果
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct AccountsUpdateResult {
|
||||
pub config_revision: Revision,
|
||||
pub account_ids: Vec<gateway_core::account::ProviderAccountId>,
|
||||
}
|
||||
|
||||
/// 账号批量删除命令。
|
||||
/// 账号批量删除命令
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct DeleteAccounts {
|
||||
pub account_ids: Vec<String>,
|
||||
}
|
||||
|
||||
/// 账号连接测试的语义事件。
|
||||
/// 账号连接测试的语义事件
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub enum AccountConnectionTestEvent {
|
||||
Started {
|
||||
@@ -323,6 +323,6 @@ pub enum AccountConnectionTestEvent {
|
||||
},
|
||||
}
|
||||
|
||||
/// 每次连接测试独占的有限事件流。
|
||||
/// 每次连接测试独占的有限事件流
|
||||
pub type AccountConnectionTestEventStream =
|
||||
Pin<Box<dyn Stream<Item = AccountConnectionTestEvent> + Send + 'static>>;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
//! 管理写入的审计分类;适配器只能补充事务实际写入的字段与实体标识。
|
||||
//! 管理写入的审计分类;适配器只能补充事务实际写入的字段与实体标识
|
||||
|
||||
use super::{
|
||||
MutationActor, MutationContext,
|
||||
@@ -112,7 +112,7 @@ impl MutationAuditOperation {
|
||||
}
|
||||
}
|
||||
|
||||
/// 分类与稳定实体标识共同组成审计意图,字段差异由同一提交事务补齐。
|
||||
/// 分类与稳定实体标识共同组成审计意图,字段差异由同一提交事务补齐
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct MutationAuditIntent<'a> {
|
||||
pub operation: MutationAuditOperation,
|
||||
|
||||
@@ -1,30 +1,30 @@
|
||||
//! 控制面认证、统一会话和管理员安全审计事实。
|
||||
//! 控制面认证、统一会话和管理员安全审计事实
|
||||
|
||||
use chrono::{DateTime, Utc};
|
||||
use gateway_core::{engine::execution::ClientAuthenticationError, policy::ClientApiKeyId};
|
||||
|
||||
use super::{MutationActor, MutationContext, Revision};
|
||||
|
||||
/// 会话审计使用的稳定管理员标识。
|
||||
/// 会话审计使用的稳定管理员标识
|
||||
#[must_use]
|
||||
pub fn admin_session_actor_ref(admin_user_id: &str) -> String {
|
||||
format!("admin:{admin_user_id}")
|
||||
}
|
||||
|
||||
/// 已认证的管理主体。
|
||||
/// 已认证的管理主体
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub enum AdminPrincipal {
|
||||
Session {
|
||||
admin_user_id: String,
|
||||
},
|
||||
ApiKey,
|
||||
/// 安装者完整信任的插件;身份仅由内部 HTTP 分派端口签发。
|
||||
/// 安装者完整信任的插件;身份仅由内部 HTTP 分派端口签发
|
||||
Plugin {
|
||||
instance_id: String,
|
||||
},
|
||||
}
|
||||
|
||||
/// 传给管理用例的安全请求上下文。
|
||||
/// 传给管理用例的安全请求上下文
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct AdminRequestContext {
|
||||
pub principal: AdminPrincipal,
|
||||
@@ -48,7 +48,7 @@ impl AdminRequestContext {
|
||||
}
|
||||
}
|
||||
|
||||
/// 登录方式只决定凭据验证流程,不能直接授予会话权限。
|
||||
/// 登录方式只决定凭据验证流程,不能直接授予会话权限
|
||||
#[derive(Clone, PartialEq, Eq)]
|
||||
pub enum LoginCommand {
|
||||
Admin {
|
||||
@@ -76,7 +76,7 @@ impl std::fmt::Debug for LoginCommand {
|
||||
}
|
||||
}
|
||||
|
||||
/// 登录成功后返回的会话事实。
|
||||
/// 登录成功后返回的会话事实
|
||||
#[derive(Clone, PartialEq, Eq)]
|
||||
pub struct LoginResult {
|
||||
pub session_id: String,
|
||||
@@ -93,7 +93,7 @@ impl std::fmt::Debug for LoginResult {
|
||||
}
|
||||
}
|
||||
|
||||
/// 登录状态机可被 API 精确映射的失败类型。
|
||||
/// 登录状态机可被 API 精确映射的失败类型
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, thiserror::Error)]
|
||||
pub enum LoginError {
|
||||
#[error("invalid login credentials")]
|
||||
@@ -114,7 +114,7 @@ impl From<ClientAuthenticationError> for LoginError {
|
||||
}
|
||||
}
|
||||
|
||||
/// 服务端已验证的身份绑定,不保存密码或原始 API Key。
|
||||
/// 服务端已验证的身份绑定,不保存密码或原始 API Key
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub enum SessionSubject {
|
||||
Admin {
|
||||
@@ -126,7 +126,7 @@ pub enum SessionSubject {
|
||||
},
|
||||
}
|
||||
|
||||
/// 仅已登录管理员可以修改自己的密码。
|
||||
/// 仅已登录管理员可以修改自己的密码
|
||||
pub struct ChangePassword {
|
||||
pub current_password: String,
|
||||
pub new_password: String,
|
||||
@@ -138,14 +138,14 @@ impl std::fmt::Debug for ChangePassword {
|
||||
}
|
||||
}
|
||||
|
||||
/// 两种登录方式共用的固定有效期会话。
|
||||
/// 两种登录方式共用的固定有效期会话
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct AuthSession {
|
||||
pub subject: SessionSubject,
|
||||
pub expires_at: DateTime<Utc>,
|
||||
}
|
||||
|
||||
/// 安全审计事件类型。
|
||||
/// 安全审计事件类型
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum AuditActorKind {
|
||||
AdminSession,
|
||||
@@ -154,7 +154,7 @@ pub enum AuditActorKind {
|
||||
Anonymous,
|
||||
}
|
||||
|
||||
/// 管理写操作留下的最小审计事实。
|
||||
/// 管理写操作留下的最小审计事实
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct AdminAuditEvent {
|
||||
pub id: String,
|
||||
|
||||
@@ -1,33 +1,33 @@
|
||||
//! 备份领域的稳定错误码。
|
||||
//! 备份领域的稳定错误码
|
||||
|
||||
/// 配置或参数非法。
|
||||
/// 配置或参数非法
|
||||
pub const INVALID_CONFIG: &str = "backup.invalid_config";
|
||||
/// Cron 表达式非法。
|
||||
/// Cron 表达式非法
|
||||
pub const INVALID_CRON: &str = "backup.invalid_cron";
|
||||
/// IANA 时区非法。
|
||||
/// 记录不存在。
|
||||
/// IANA 时区非法
|
||||
/// 记录不存在
|
||||
pub const RECORD_NOT_FOUND: &str = "backup.record_not_found";
|
||||
/// 已存在活跃任务,拒绝创建。
|
||||
/// 已存在活跃任务,拒绝创建
|
||||
pub const ACTIVE_TASK_CONFLICT: &str = "backup.active_task_conflict";
|
||||
/// 记录状态与期望不符,无法迁移。
|
||||
/// 记录状态与期望不符,无法迁移
|
||||
pub const STATE_CONFLICT: &str = "backup.state_conflict";
|
||||
/// 已有记录时禁止切换存储目标。
|
||||
/// 已有记录时禁止切换存储目标
|
||||
pub const STORAGE_IDENTITY_LOCKED: &str = "backup.storage_identity_locked";
|
||||
/// `pg_dump` 非零退出或失败。
|
||||
/// `pg_dump` 非零退出或失败
|
||||
pub const PG_DUMP_FAILED: &str = "backup.pg_dump_failed";
|
||||
/// 暂存磁盘空间不足。
|
||||
/// 暂存磁盘空间不足
|
||||
pub const STAGING_SPACE_EXHAUSTED: &str = "backup.staging_space_exhausted";
|
||||
/// S3 认证失败。
|
||||
/// S3 认证失败
|
||||
pub const S3_AUTH_FAILED: &str = "backup.s3_auth_failed";
|
||||
/// S3 权限拒绝。
|
||||
/// S3 权限拒绝
|
||||
pub const S3_PERMISSION_DENIED: &str = "backup.s3_permission_denied";
|
||||
/// S3 上传失败。
|
||||
/// S3 上传失败
|
||||
pub const S3_UPLOAD_FAILED: &str = "backup.s3_upload_failed";
|
||||
/// 远端对象校验失败。
|
||||
/// 远端对象校验失败
|
||||
pub const REMOTE_VERIFICATION_FAILED: &str = "backup.remote_verification_failed";
|
||||
/// 任务被取消。
|
||||
/// 任务被取消
|
||||
pub const CANCELLED: &str = "backup.cancelled";
|
||||
/// 仓储或对象存储暂不可用。
|
||||
/// 仓储或对象存储暂不可用
|
||||
pub const STORE_UNAVAILABLE: &str = "backup.store_unavailable";
|
||||
/// 上游 S3 服务返回无效或失败响应。
|
||||
/// 上游 S3 服务返回无效或失败响应
|
||||
pub const UPSTREAM_FAILURE: &str = "backup.upstream_failure";
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
//! S3 备份领域模型、状态机与稳定错误。
|
||||
//! S3 备份领域模型、状态机与稳定错误
|
||||
//!
|
||||
//! 领域类型只携带事实,不承载基础设施细节。Secret 使用 [`secrecy::SecretString`],
|
||||
//! 全流程不实现 `Serialize`,避免被错误地写入 wire 或日志。管理员身份等审计事实
|
||||
//! 只存在于 `admin_audit_events`,本模型不重复保存。
|
||||
//! 领域类型只携带事实,不承载基础设施细节
|
||||
//! Secret 使用 [`secrecy::SecretString`],
|
||||
//! 全流程不实现 `Serialize`,避免被错误地写入 wire 或日志
|
||||
//! 管理员身份等审计事实
|
||||
//! 只存在于 `admin_audit_events`,本模型不重复保存
|
||||
|
||||
use std::fmt;
|
||||
|
||||
@@ -12,7 +14,7 @@ use uuid::Uuid;
|
||||
|
||||
use super::PageSize;
|
||||
|
||||
/// 备份触发来源。
|
||||
/// 备份触发来源
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum BackupTriggerKind {
|
||||
Manual,
|
||||
@@ -20,7 +22,7 @@ pub enum BackupTriggerKind {
|
||||
}
|
||||
|
||||
impl BackupTriggerKind {
|
||||
/// 稳定持久化值。
|
||||
/// 稳定持久化值
|
||||
#[must_use]
|
||||
pub const fn as_str(self) -> &'static str {
|
||||
match self {
|
||||
@@ -29,7 +31,7 @@ impl BackupTriggerKind {
|
||||
}
|
||||
}
|
||||
|
||||
/// 解析持久化值。
|
||||
/// 解析持久化值
|
||||
#[must_use]
|
||||
pub fn parse(value: &str) -> Option<Self> {
|
||||
match value {
|
||||
@@ -46,11 +48,11 @@ impl fmt::Display for BackupTriggerKind {
|
||||
}
|
||||
}
|
||||
|
||||
/// 备份任务状态机(六状态)。
|
||||
/// 备份任务状态机(六状态)
|
||||
///
|
||||
/// 迁移 `backup_records_lifecycle_ck` 与字段空值语义共同定义合法持久化行;
|
||||
/// [`Self::allows_transition_to`] 只约束领域内的显式状态迁移,两层都必须满足。
|
||||
/// 删除成功后记录行被硬删除,因此不存在 `deleted` 终态。
|
||||
/// [`Self::allows_transition_to`] 只约束领域内的显式状态迁移,两层都必须满足
|
||||
/// 删除成功后记录行被硬删除,因此不存在 `deleted` 终态
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
pub enum BackupStatus {
|
||||
Queued,
|
||||
@@ -62,7 +64,7 @@ pub enum BackupStatus {
|
||||
}
|
||||
|
||||
impl BackupStatus {
|
||||
/// 稳定持久化值。
|
||||
/// 稳定持久化值
|
||||
#[must_use]
|
||||
pub const fn as_str(self) -> &'static str {
|
||||
match self {
|
||||
@@ -75,7 +77,7 @@ impl BackupStatus {
|
||||
}
|
||||
}
|
||||
|
||||
/// 解析持久化值。
|
||||
/// 解析持久化值
|
||||
#[must_use]
|
||||
pub fn parse(value: &str) -> Option<Self> {
|
||||
match value {
|
||||
@@ -89,19 +91,19 @@ impl BackupStatus {
|
||||
}
|
||||
}
|
||||
|
||||
/// 该状态是否消耗全局活跃名额。
|
||||
/// 该状态是否消耗全局活跃名额
|
||||
#[must_use]
|
||||
pub const fn is_active(self) -> bool {
|
||||
matches!(self, Self::Queued | Self::Dumping | Self::Uploading)
|
||||
}
|
||||
|
||||
/// 该状态是否允许进入删除流程。
|
||||
/// 该状态是否允许进入删除流程
|
||||
#[must_use]
|
||||
pub const fn can_be_deleted(self) -> bool {
|
||||
matches!(self, Self::Completed | Self::Failed)
|
||||
}
|
||||
|
||||
/// 领域内允许的显式迁移。
|
||||
/// 领域内允许的显式迁移
|
||||
#[must_use]
|
||||
pub fn allows_transition_to(self, target: BackupStatus) -> bool {
|
||||
matches!(
|
||||
@@ -124,7 +126,7 @@ impl fmt::Display for BackupStatus {
|
||||
}
|
||||
}
|
||||
|
||||
/// 已通过领域状态机校验的显式迁移。
|
||||
/// 已通过领域状态机校验的显式迁移
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub struct BackupStatusTransition {
|
||||
from: BackupStatus,
|
||||
@@ -132,26 +134,26 @@ pub struct BackupStatusTransition {
|
||||
}
|
||||
|
||||
impl BackupStatusTransition {
|
||||
/// 校验并构造显式迁移;非法状态边返回 `None`。
|
||||
/// 校验并构造显式迁移;非法状态边返回 `None`
|
||||
#[must_use]
|
||||
pub fn try_new(from: BackupStatus, to: BackupStatus) -> Option<Self> {
|
||||
from.allows_transition_to(to).then_some(Self { from, to })
|
||||
}
|
||||
|
||||
/// 迁移要求的当前状态。
|
||||
/// 迁移要求的当前状态
|
||||
#[must_use]
|
||||
pub const fn from(self) -> BackupStatus {
|
||||
self.from
|
||||
}
|
||||
|
||||
/// 迁移完成后的目标状态。
|
||||
/// 迁移完成后的目标状态
|
||||
#[must_use]
|
||||
pub const fn to(self) -> BackupStatus {
|
||||
self.to
|
||||
}
|
||||
}
|
||||
|
||||
/// S3 存储配置更新命令;`secret_access_key` 为 `None` 表示保留旧值。
|
||||
/// S3 存储配置更新命令;`secret_access_key` 为 `None` 表示保留旧值
|
||||
#[derive(Clone)]
|
||||
pub struct UpdateBackupStorageCommand {
|
||||
pub endpoint: String,
|
||||
@@ -178,7 +180,7 @@ impl fmt::Debug for UpdateBackupStorageCommand {
|
||||
}
|
||||
}
|
||||
|
||||
/// 调度配置更新命令。
|
||||
/// 调度配置更新命令
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct UpdateBackupScheduleCommand {
|
||||
pub schedule_enabled: bool,
|
||||
@@ -187,9 +189,9 @@ pub struct UpdateBackupScheduleCommand {
|
||||
pub retention_count: u32,
|
||||
}
|
||||
|
||||
/// 完整保存的备份设置(含 Secret)。
|
||||
/// 完整保存的备份设置(含 Secret)
|
||||
///
|
||||
/// 只从仓储读取;不实现 `Serialize`,防止 Secret 进入 wire 或日志。
|
||||
/// 只从仓储读取;不实现 `Serialize`,防止 Secret 进入 wire 或日志
|
||||
#[derive(Clone)]
|
||||
pub struct BackupSettings {
|
||||
pub storage_revision: u64,
|
||||
@@ -202,7 +204,7 @@ pub struct BackupSettings {
|
||||
pub force_path_style: bool,
|
||||
pub schedule_enabled: bool,
|
||||
pub cron_expression: Option<String>,
|
||||
/// 记录持久化游标采用的时区,由服务端维护,不是独立部署设置。
|
||||
/// 记录持久化游标采用的时区,由服务端维护,不是独立部署设置
|
||||
pub schedule_timezone: Option<String>,
|
||||
pub retention_days: u32,
|
||||
pub retention_count: u32,
|
||||
@@ -236,7 +238,7 @@ impl fmt::Debug for BackupSettings {
|
||||
}
|
||||
|
||||
impl BackupSettings {
|
||||
/// S3 存储配置是否完整(bucket/region/密钥/前缀齐全)。
|
||||
/// S3 存储配置是否完整(bucket/region/密钥/前缀齐全)
|
||||
#[must_use]
|
||||
pub fn storage_configured(&self) -> bool {
|
||||
self.endpoint.is_some()
|
||||
@@ -247,14 +249,14 @@ impl BackupSettings {
|
||||
&& self.prefix.is_some()
|
||||
}
|
||||
|
||||
/// 探测是否已对当前 revision 成功;`last_verified_at` 非空即表示当前配置已通过探针。
|
||||
/// 探测是否已对当前 revision 成功;`last_verified_at` 非空即表示当前配置已通过探针
|
||||
#[must_use]
|
||||
pub fn storage_verified(&self) -> bool {
|
||||
self.last_verified_at.is_some()
|
||||
}
|
||||
}
|
||||
|
||||
/// 已经校验并可用于对象存储操作的存储配置快照。
|
||||
/// 已经校验并可用于对象存储操作的存储配置快照
|
||||
#[derive(Clone)]
|
||||
pub struct BackupStorageConfig {
|
||||
pub storage_revision: u64,
|
||||
@@ -284,7 +286,7 @@ impl fmt::Debug for BackupStorageConfig {
|
||||
}
|
||||
|
||||
impl BackupStorageConfig {
|
||||
/// 从完整设置推导对象存储配置;设置不完整时返回 `None`。
|
||||
/// 从完整设置推导对象存储配置;设置不完整时返回 `None`
|
||||
#[must_use]
|
||||
pub fn from_settings(settings: &BackupSettings) -> Option<Self> {
|
||||
Some(Self {
|
||||
@@ -300,7 +302,7 @@ impl BackupStorageConfig {
|
||||
}
|
||||
}
|
||||
|
||||
/// 一条备份记录。
|
||||
/// 一条备份记录
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct BackupRecord {
|
||||
pub id: String,
|
||||
@@ -315,13 +317,13 @@ pub struct BackupRecord {
|
||||
pub error_message: Option<String>,
|
||||
pub started_at: Option<DateTime<Utc>>,
|
||||
pub completed_at: Option<DateTime<Utc>>,
|
||||
/// 创建时确定的过期时间;手工和计划备份均可设置,到期自动清理。
|
||||
/// 创建时确定的过期时间;手工和计划备份均可设置,到期自动清理
|
||||
pub expires_at: Option<DateTime<Utc>>,
|
||||
pub created_at: DateTime<Utc>,
|
||||
pub updated_at: DateTime<Utc>,
|
||||
}
|
||||
|
||||
/// 备份记录分页查询。
|
||||
/// 备份记录分页查询
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct BackupRecordListQuery {
|
||||
pub page: u32,
|
||||
@@ -330,7 +332,7 @@ pub struct BackupRecordListQuery {
|
||||
pub trigger: Option<BackupTriggerKind>,
|
||||
}
|
||||
|
||||
/// 备份记录分页结果。
|
||||
/// 备份记录分页结果
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct BackupRecordPage {
|
||||
pub items: Vec<BackupRecord>,
|
||||
@@ -340,7 +342,7 @@ pub struct BackupRecordPage {
|
||||
}
|
||||
|
||||
impl BackupRecordPage {
|
||||
/// 计算总页数;空集合视为一页。
|
||||
/// 计算总页数;空集合视为一页
|
||||
#[must_use]
|
||||
pub fn total_pages(&self) -> u32 {
|
||||
let size = u64::from(self.page_size.get());
|
||||
@@ -352,7 +354,7 @@ impl BackupRecordPage {
|
||||
}
|
||||
}
|
||||
|
||||
/// 创建备份任务的持久化数据;id 与对象 key 由领域生成。
|
||||
/// 创建备份任务的持久化数据;id 与对象 key 由领域生成
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct BackupRecordSeed {
|
||||
pub id: String,
|
||||
@@ -362,7 +364,7 @@ pub struct BackupRecordSeed {
|
||||
pub expires_at: Option<DateTime<Utc>>,
|
||||
}
|
||||
|
||||
/// 连接测试结果;只携带稳定阶段与脱敏消息。
|
||||
/// 连接测试结果;只携带稳定阶段与脱敏消息
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct ConnectionTestResult {
|
||||
pub ok: bool,
|
||||
@@ -371,7 +373,7 @@ pub struct ConnectionTestResult {
|
||||
pub message: String,
|
||||
}
|
||||
|
||||
/// 对象存储探测阶段。
|
||||
/// 对象存储探测阶段
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum ConnectionTestStage {
|
||||
PutObject,
|
||||
@@ -381,7 +383,7 @@ pub enum ConnectionTestStage {
|
||||
}
|
||||
|
||||
impl ConnectionTestStage {
|
||||
/// 稳定 wire 值。
|
||||
/// 稳定 wire 值
|
||||
#[must_use]
|
||||
pub const fn as_str(self) -> &'static str {
|
||||
match self {
|
||||
@@ -393,10 +395,10 @@ impl ConnectionTestStage {
|
||||
}
|
||||
}
|
||||
|
||||
/// 上传到对象存储并期望远端校验的归档 metadata。
|
||||
/// 上传到对象存储并期望远端校验的归档 metadata
|
||||
///
|
||||
/// `size_bytes` 在上传时不写入 metadata,但 `HeadObject` 会返回远端大小,
|
||||
/// 用于与本地归档校验一致。
|
||||
/// 用于与本地归档校验一致
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct BackupObjectMetadata {
|
||||
pub backup_id: String,
|
||||
@@ -406,7 +408,7 @@ pub struct BackupObjectMetadata {
|
||||
}
|
||||
|
||||
impl BackupObjectMetadata {
|
||||
/// 构造上传 metadata。
|
||||
/// 构造上传 metadata
|
||||
#[must_use]
|
||||
pub const fn new(
|
||||
backup_id: String,
|
||||
@@ -423,7 +425,7 @@ impl BackupObjectMetadata {
|
||||
}
|
||||
}
|
||||
|
||||
/// 短时下载地址结果。
|
||||
/// 短时下载地址结果
|
||||
#[derive(Clone, PartialEq, Eq)]
|
||||
pub struct DownloadUrlResult {
|
||||
pub url: String,
|
||||
@@ -442,10 +444,11 @@ impl fmt::Debug for DownloadUrlResult {
|
||||
}
|
||||
}
|
||||
|
||||
/// 备份基础设施错误:只携带稳定错误码与脱敏消息。
|
||||
/// 备份基础设施错误:只携带稳定错误码与脱敏消息
|
||||
///
|
||||
/// 该类型不构成第二套 kind 枚举;`gateway-admin` 边界统一映射为既有
|
||||
/// `AdminErrorKind`。稳定 `error_code` 同时用于任务记录、日志与 UI。
|
||||
/// `AdminErrorKind`
|
||||
/// 稳定 `error_code` 同时用于任务记录、日志与 UI
|
||||
#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
|
||||
#[error("{message}")]
|
||||
pub struct BackupError {
|
||||
@@ -454,32 +457,32 @@ pub struct BackupError {
|
||||
}
|
||||
|
||||
impl BackupError {
|
||||
/// 构造基础设施错误。
|
||||
/// 构造基础设施错误
|
||||
#[must_use]
|
||||
pub const fn new(code: &'static str, message: String) -> Self {
|
||||
Self { code, message }
|
||||
}
|
||||
|
||||
/// 稳定错误码,例如 `backup.s3_auth_failed`。
|
||||
/// 稳定错误码,例如 `backup.s3_auth_failed`
|
||||
#[must_use]
|
||||
pub const fn code(&self) -> &'static str {
|
||||
self.code
|
||||
}
|
||||
|
||||
/// 脱敏后的可操作消息。
|
||||
/// 脱敏后的可操作消息
|
||||
#[must_use]
|
||||
pub fn message(&self) -> &str {
|
||||
&self.message
|
||||
}
|
||||
}
|
||||
|
||||
/// 构造稳定错误码常量。
|
||||
/// 构造稳定错误码常量
|
||||
pub mod code;
|
||||
|
||||
/// 构建对象 key:`{prefix}/YYYY/MM/DD/codex-proxy-rs_{UTC seconds}_{backup id}.dump`。
|
||||
/// 构建对象 key:`{prefix}/YYYY/MM/DD/codex-proxy-rs_{UTC seconds}_{backup id}.dump`
|
||||
///
|
||||
/// 规范化规则:prefix 不得为空、不得以前导 `/` 开头、不得包含 `..`、反斜杠或控制字符;
|
||||
/// 输出统一使用 `/` 分隔且不含空段。
|
||||
/// 输出统一使用 `/` 分隔且不含空段
|
||||
pub fn build_object_key(
|
||||
prefix: &str,
|
||||
backup_id: &str,
|
||||
@@ -496,22 +499,22 @@ pub fn build_object_key(
|
||||
))
|
||||
}
|
||||
|
||||
/// 下载文件名:`codex-proxy-rs_{短 id}.dump`,与对象 key 的文件名一致。
|
||||
/// 下载文件名:`codex-proxy-rs_{短 id}.dump`,与对象 key 的文件名一致
|
||||
#[must_use]
|
||||
pub fn build_download_file_name(backup_id: &str) -> String {
|
||||
format!("codex-proxy-rs_{}.dump", short_backup_id(backup_id))
|
||||
}
|
||||
|
||||
/// 截取备份 id 的可读短标识:去掉 `backup_` 前缀,最多保留 8 位 hex。
|
||||
/// 截取备份 id 的可读短标识:去掉 `backup_` 前缀,最多保留 8 位 hex
|
||||
#[must_use]
|
||||
fn short_backup_id(backup_id: &str) -> String {
|
||||
let hex = backup_id.strip_prefix("backup_").unwrap_or(backup_id);
|
||||
hex.get(..8).unwrap_or(hex).to_owned()
|
||||
}
|
||||
|
||||
/// 规范化并校验对象 prefix。
|
||||
/// 规范化并校验对象 prefix
|
||||
///
|
||||
/// 返回去掉尾部 `/` 的规范 prefix;空串、前导 `/`、`..` 段、反斜杠和控制字符均拒绝。
|
||||
/// 返回去掉尾部 `/` 的规范 prefix;空串、前导 `/`、`..` 段、反斜杠和控制字符均拒绝
|
||||
pub fn normalize_prefix(prefix: &str) -> Result<String, BackupError> {
|
||||
if prefix.is_empty() || prefix.starts_with('/') {
|
||||
return Err(BackupError::new(
|
||||
@@ -534,13 +537,13 @@ pub fn normalize_prefix(prefix: &str) -> Result<String, BackupError> {
|
||||
Ok(prefix.trim_end_matches('/').to_owned())
|
||||
}
|
||||
|
||||
/// 生成新的备份记录 id。
|
||||
/// 生成新的备份记录 id
|
||||
#[must_use]
|
||||
pub fn new_backup_id() -> String {
|
||||
format!("backup_{}", Uuid::now_v7().simple())
|
||||
}
|
||||
|
||||
/// 从触发类型与计划时间构造可持久化 seed;id 与对象 key 一次生成保持一致。
|
||||
/// 从触发类型与计划时间构造可持久化 seed;id 与对象 key 一次生成保持一致
|
||||
pub fn build_backup_seed(
|
||||
trigger_kind: BackupTriggerKind,
|
||||
scheduled_at: Option<DateTime<Utc>>,
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
//! 管理设置页使用的 Codex 客户端下载信息。
|
||||
//! 管理设置页使用的 Codex 客户端下载信息
|
||||
|
||||
use chrono::{DateTime, Utc};
|
||||
|
||||
/// Windows 安装包架构。
|
||||
/// Windows 安装包架构
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
|
||||
pub enum ClientArchitecture {
|
||||
X64,
|
||||
@@ -19,7 +19,7 @@ impl ClientArchitecture {
|
||||
}
|
||||
}
|
||||
|
||||
/// 下载地址的可信来源。
|
||||
/// 下载地址的可信来源
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum ClientDownloadSource {
|
||||
MicrosoftStore,
|
||||
@@ -36,7 +36,7 @@ impl ClientDownloadSource {
|
||||
}
|
||||
}
|
||||
|
||||
/// 一个已经过 Host 校验、可以直接交给浏览器下载的安装包。
|
||||
/// 一个已经过 Host 校验、可以直接交给浏览器下载的安装包
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct ClientDownloadPackage {
|
||||
pub architecture: ClientArchitecture,
|
||||
@@ -48,7 +48,7 @@ pub struct ClientDownloadPackage {
|
||||
pub expires_at: Option<DateTime<Utc>>,
|
||||
}
|
||||
|
||||
/// Codex Desktop Windows 下载解析结果。
|
||||
/// Codex Desktop Windows 下载解析结果
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct CodexDesktopWindowsDownloads {
|
||||
pub resolved_at: DateTime<Utc>,
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
//! Client API Key 的 Command、Result 与安全秘密类型。
|
||||
//! Client API Key 的 Command、Result 与安全秘密类型
|
||||
|
||||
use std::{collections::BTreeMap, fmt, num::NonZeroU16};
|
||||
|
||||
@@ -20,16 +20,16 @@ pub type ProviderRequestProfileOverrides =
|
||||
pub type ProviderRequestProfileOverrideUpdates =
|
||||
BTreeMap<ProviderKind, Option<gateway_core::account::OpaqueProviderData>>;
|
||||
|
||||
/// Client Key 列表保持旧 HTTP 合同允许的完整非零 `u16` 页大小。
|
||||
/// Client Key 列表保持旧 HTTP 合同允许的完整非零 `u16` 页大小
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
pub struct ClientKeyPageSize(NonZeroU16);
|
||||
|
||||
impl ClientKeyPageSize {
|
||||
/// 创建 1 至 65535 的 Client Key 页大小。
|
||||
/// 创建 1 至 65535 的 Client Key 页大小
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// `value` 为零时返回 [`AdminModelError::InvalidClientKeyPageSize`]。
|
||||
/// `value` 为零时返回 [`AdminModelError::InvalidClientKeyPageSize`]
|
||||
pub fn new(value: u16) -> Result<Self, AdminModelError> {
|
||||
NonZeroU16::new(value)
|
||||
.map(Self)
|
||||
@@ -42,7 +42,7 @@ impl ClientKeyPageSize {
|
||||
}
|
||||
}
|
||||
|
||||
/// Client Key 列表排序字段。
|
||||
/// Client Key 列表排序字段
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum ClientKeySortField {
|
||||
Name,
|
||||
@@ -51,21 +51,21 @@ pub enum ClientKeySortField {
|
||||
LastUsedAt,
|
||||
}
|
||||
|
||||
/// Client Key 列表排序方向。
|
||||
/// Client Key 列表排序方向
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum SortDirection {
|
||||
Asc,
|
||||
Desc,
|
||||
}
|
||||
|
||||
/// Client Key 列表排序规则。
|
||||
/// Client Key 列表排序规则
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub struct ClientKeySort {
|
||||
pub field: ClientKeySortField,
|
||||
pub direction: SortDirection,
|
||||
}
|
||||
|
||||
/// 与排序字段绑定的游标值。
|
||||
/// 与排序字段绑定的游标值
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub enum ClientKeyCursorValue {
|
||||
Name(String),
|
||||
@@ -74,7 +74,7 @@ pub enum ClientKeyCursorValue {
|
||||
LastUsedAt(Option<DateTime<Utc>>),
|
||||
}
|
||||
|
||||
/// Client Key 的稳定键集游标。
|
||||
/// Client Key 的稳定键集游标
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct ClientKeyCursor {
|
||||
pub sort: ClientKeySort,
|
||||
@@ -82,7 +82,7 @@ pub struct ClientKeyCursor {
|
||||
pub id: ClientApiKeyId,
|
||||
}
|
||||
|
||||
/// Client Key 列表查询。
|
||||
/// Client Key 列表查询
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct ClientKeyListQuery {
|
||||
pub cursor: Option<ClientKeyCursor>,
|
||||
@@ -91,7 +91,7 @@ pub struct ClientKeyListQuery {
|
||||
pub sort: ClientKeySort,
|
||||
}
|
||||
|
||||
/// 不含完整明文 Key 的管理投影。
|
||||
/// 不含完整明文 Key 的管理投影
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct ClientKeyRecord {
|
||||
pub request_profile_overrides: ProviderRequestProfileOverrides,
|
||||
@@ -109,7 +109,7 @@ pub struct ClientKeyRecord {
|
||||
pub updated_at: DateTime<Utc>,
|
||||
}
|
||||
|
||||
/// Client Key 列表页。
|
||||
/// Client Key 列表页
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct ClientKeyPage {
|
||||
pub config_revision: Revision,
|
||||
@@ -118,7 +118,7 @@ pub struct ClientKeyPage {
|
||||
pub next_cursor: Option<ClientKeyCursor>,
|
||||
}
|
||||
|
||||
/// 仅在创建或显式读取密钥配置时返回的明文 Key;Debug 隐去明文。
|
||||
/// 仅在创建或显式读取密钥配置时返回的明文 Key;Debug 隐去明文
|
||||
#[derive(Clone, PartialEq, Eq)]
|
||||
pub struct ClientKeySecret {
|
||||
pub record: ClientKeyRecord,
|
||||
@@ -150,7 +150,7 @@ impl fmt::Debug for ClientKeySecret {
|
||||
}
|
||||
}
|
||||
|
||||
/// API 提交的 Client Key 创建命令。
|
||||
/// API 提交的 Client Key 创建命令
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct CreateClientKey {
|
||||
pub request_profile_overrides: ProviderRequestProfileOverrides,
|
||||
@@ -162,7 +162,7 @@ pub struct CreateClientKey {
|
||||
pub budget: ClientBudgetLimits,
|
||||
}
|
||||
|
||||
/// 管理用例生成 ID 与明文后的持久化命令。
|
||||
/// 管理用例生成 ID 与明文后的持久化命令
|
||||
#[derive(Clone, PartialEq, Eq)]
|
||||
pub struct NewClientKey {
|
||||
pub request_profile_overrides: ProviderRequestProfileOverrides,
|
||||
@@ -187,7 +187,7 @@ impl fmt::Debug for NewClientKey {
|
||||
}
|
||||
}
|
||||
|
||||
/// 修改 Client Key 的公开策略字段。
|
||||
/// 修改 Client Key 的公开策略字段
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct UpdateClientKey {
|
||||
pub request_profile_override_updates: ProviderRequestProfileOverrideUpdates,
|
||||
@@ -200,14 +200,14 @@ pub struct UpdateClientKey {
|
||||
pub weekly_limit_usd: Option<gateway_core::metering::Decimal>,
|
||||
}
|
||||
|
||||
/// 修改 Client Key 启用状态。
|
||||
/// 修改 Client Key 启用状态
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct SetClientKeyEnabled {
|
||||
pub id: ClientApiKeyId,
|
||||
pub enabled: bool,
|
||||
}
|
||||
|
||||
/// 管理员选择的金额重置范围。
|
||||
/// 管理员选择的金额重置范围
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum ClientKeyBudgetPeriod {
|
||||
Daily,
|
||||
@@ -215,14 +215,14 @@ pub enum ClientKeyBudgetPeriod {
|
||||
All,
|
||||
}
|
||||
|
||||
/// 清零所选窗口已用金额,保留限额、到期时间与历史费用。
|
||||
/// 清零所选窗口已用金额,保留限额、到期时间与历史费用
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct ResetClientKeyBudget {
|
||||
pub id: ClientApiKeyId,
|
||||
pub period: ClientKeyBudgetPeriod,
|
||||
}
|
||||
|
||||
/// 仅更新指定金额上限;省略的周期保持不变,不修改已用金额或窗口。
|
||||
/// 仅更新指定金额上限;省略的周期保持不变,不修改已用金额或窗口
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct UpdateClientKeyBudgetLimits {
|
||||
pub id: ClientApiKeyId,
|
||||
@@ -230,20 +230,20 @@ pub struct UpdateClientKeyBudgetLimits {
|
||||
pub weekly_limit_usd: Option<gateway_core::metering::Decimal>,
|
||||
}
|
||||
|
||||
/// 预算变更的调用来源;插件身份由 Runtime 给出,不能从插件请求反序列化。
|
||||
/// 预算变更的调用来源;插件身份由 Runtime 给出,不能从插件请求反序列化
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum ClientKeyBudgetMutationOrigin {
|
||||
Admin,
|
||||
Plugin(PluginResourceOwner),
|
||||
}
|
||||
|
||||
/// 删除 Client Key。
|
||||
/// 删除 Client Key
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct DeleteClientKey {
|
||||
pub id: ClientApiKeyId,
|
||||
}
|
||||
|
||||
/// Client Key 创建结果;完整明文仅存在于该一次性结果中。
|
||||
/// Client Key 创建结果;完整明文仅存在于该一次性结果中
|
||||
pub struct CreatedClientKey {
|
||||
pub config_revision: Revision,
|
||||
pub secret: ClientKeySecret,
|
||||
@@ -259,7 +259,7 @@ impl fmt::Debug for CreatedClientKey {
|
||||
}
|
||||
}
|
||||
|
||||
/// Client Key 普通写操作结果。
|
||||
/// Client Key 普通写操作结果
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct ClientKeyMutation {
|
||||
pub config_revision: Revision,
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
//! 进程内账号导入任务;只保存执行所需输入与可公开的结果。
|
||||
//! 进程内账号导入任务;只保存执行所需输入与可公开的结果
|
||||
|
||||
use chrono::{DateTime, Utc};
|
||||
use gateway_core::{account::ProviderAccountId, routing::ProviderKind};
|
||||
@@ -17,7 +17,7 @@ pub struct ImportTaskInput {
|
||||
#[derive(Debug)]
|
||||
pub struct SubmitImportTask {
|
||||
pub submission_id: Uuid,
|
||||
/// API 对完整输入计算摘要;用于区分丢失响应后的重试与修改后的新提交。
|
||||
/// API 对完整输入计算摘要;用于区分丢失响应后的重试与修改后的新提交
|
||||
pub fingerprint: [u8; 32],
|
||||
pub context: MutationContext,
|
||||
pub items: Vec<ImportTaskInput>,
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
//! 单页 Key 用量的查询合同;身份范围不由调用方提供。
|
||||
//! 单页 Key 用量的查询合同;身份范围不由调用方提供
|
||||
|
||||
use super::{
|
||||
PageSize,
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
//! 管理控制面使用的 Command、Result 与稳定值对象。
|
||||
//! 管理控制面使用的 Command、Result 与稳定值对象
|
||||
|
||||
use std::num::{NonZeroU16, NonZeroU64};
|
||||
|
||||
@@ -24,7 +24,7 @@ pub mod retention;
|
||||
pub mod settings;
|
||||
pub mod system;
|
||||
|
||||
/// 管理用例对外返回的稳定错误分类。
|
||||
/// 管理用例对外返回的稳定错误分类
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum AdminErrorKind {
|
||||
Invalid,
|
||||
@@ -39,7 +39,7 @@ pub enum AdminErrorKind {
|
||||
Internal,
|
||||
}
|
||||
|
||||
/// 不携带基础设施细节、可安全返回给管理员的管理用例错误。
|
||||
/// 不携带基础设施细节、可安全返回给管理员的管理用例错误
|
||||
#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
|
||||
#[error("{message}")]
|
||||
pub struct AdminError {
|
||||
@@ -61,7 +61,7 @@ impl AdminError {
|
||||
self.kind
|
||||
}
|
||||
|
||||
/// 返回已脱敏、可公开给管理员的文案。
|
||||
/// 返回已脱敏、可公开给管理员的文案
|
||||
#[must_use]
|
||||
pub fn message(&self) -> &str {
|
||||
&self.message
|
||||
@@ -108,7 +108,7 @@ impl AdminError {
|
||||
}
|
||||
}
|
||||
|
||||
/// PostgreSQL 中所有正整数 revision 的管理层表示。
|
||||
/// PostgreSQL 中所有正整数 revision 的管理层表示
|
||||
#[derive(
|
||||
Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, serde::Serialize, serde::Deserialize,
|
||||
)]
|
||||
@@ -116,11 +116,11 @@ impl AdminError {
|
||||
pub struct Revision(NonZeroU64);
|
||||
|
||||
impl Revision {
|
||||
/// 创建正整数 revision。
|
||||
/// 创建正整数 revision
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// `value` 为零时返回 [`AdminModelError::ZeroRevision`]。
|
||||
/// `value` 为零时返回 [`AdminModelError::ZeroRevision`]
|
||||
pub fn new(value: u64) -> Result<Self, AdminModelError> {
|
||||
NonZeroU64::new(value)
|
||||
.map(Self)
|
||||
@@ -133,18 +133,18 @@ impl Revision {
|
||||
}
|
||||
}
|
||||
|
||||
/// 管理列表统一使用的受限页大小。
|
||||
/// 管理列表统一使用的受限页大小
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
pub struct PageSize(NonZeroU16);
|
||||
|
||||
impl PageSize {
|
||||
pub const MAX: u16 = 200;
|
||||
|
||||
/// 创建 1 至 200 的页大小。
|
||||
/// 创建 1 至 200 的页大小
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// `value` 不在有效范围时返回 [`AdminModelError::InvalidPageSize`]。
|
||||
/// `value` 不在有效范围时返回 [`AdminModelError::InvalidPageSize`]
|
||||
pub fn new(value: u16) -> Result<Self, AdminModelError> {
|
||||
if value > Self::MAX {
|
||||
return Err(AdminModelError::InvalidPageSize(value));
|
||||
@@ -160,7 +160,7 @@ impl PageSize {
|
||||
}
|
||||
}
|
||||
|
||||
/// 可审计管理写操作的发起者。
|
||||
/// 可审计管理写操作的发起者
|
||||
#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
|
||||
#[serde(rename_all = "snake_case", deny_unknown_fields)]
|
||||
pub enum MutationActor {
|
||||
@@ -169,7 +169,7 @@ pub enum MutationActor {
|
||||
System,
|
||||
}
|
||||
|
||||
/// 管理写操作必须携带的审计上下文。
|
||||
/// 管理写操作必须携带的审计上下文
|
||||
#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
|
||||
#[serde(rename_all = "snake_case", deny_unknown_fields)]
|
||||
pub struct MutationContext {
|
||||
@@ -177,7 +177,7 @@ pub struct MutationContext {
|
||||
pub request_id: String,
|
||||
}
|
||||
|
||||
/// 领域值对象构造失败。
|
||||
/// 领域值对象构造失败
|
||||
#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
|
||||
pub enum AdminModelError {
|
||||
#[error("revision must be greater than zero")]
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
//! 用量、成本、健康与错误诊断的 UTC 语义事实。
|
||||
//! 用量、成本、健康与错误诊断的 UTC 语义事实
|
||||
|
||||
use std::str::FromStr;
|
||||
|
||||
@@ -6,7 +6,7 @@ use chrono::{DateTime, TimeDelta, Utc};
|
||||
|
||||
use super::{AdminModelError, PageSize};
|
||||
|
||||
/// 页面筛选表达自然日范围,具体 UTC 边界由部署时区解析。
|
||||
/// 页面筛选表达自然日范围,具体 UTC 边界由部署时区解析
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub enum CalendarPeriod {
|
||||
Today,
|
||||
@@ -25,7 +25,7 @@ impl CalendarPeriod {
|
||||
}
|
||||
}
|
||||
|
||||
/// 外部观测查询的 UTC 时间范围。
|
||||
/// 外部观测查询的 UTC 时间范围
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub struct TimeRange {
|
||||
pub start: DateTime<Utc>,
|
||||
@@ -46,17 +46,17 @@ impl TimeRange {
|
||||
let start = timezone
|
||||
.days_before(end, days)
|
||||
.ok_or(AdminModelError::InvalidTimeRange)?;
|
||||
// 自然日刚开始时允许空快照,不借用前一天或伪造未来终点。
|
||||
// 自然日刚开始时允许空快照,不借用前一天或伪造未来终点
|
||||
if start == end {
|
||||
return Ok(Self { start, end });
|
||||
}
|
||||
Self::new(start, end)
|
||||
}
|
||||
/// 创建最长 366 天的正时间范围。
|
||||
/// 创建最长 366 天的正时间范围
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// 范围为空、反向或超过 366 天时返回错误。
|
||||
/// 范围为空、反向或超过 366 天时返回错误
|
||||
pub fn new(start: DateTime<Utc>, end: DateTime<Utc>) -> Result<Self, AdminModelError> {
|
||||
let duration = end.signed_duration_since(start);
|
||||
if duration <= TimeDelta::zero() || duration > TimeDelta::days(366) {
|
||||
@@ -66,7 +66,7 @@ impl TimeRange {
|
||||
}
|
||||
}
|
||||
|
||||
/// 请求结果状态。
|
||||
/// 请求结果状态
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub enum RequestOutcome {
|
||||
Running,
|
||||
@@ -77,19 +77,19 @@ pub enum RequestOutcome {
|
||||
Other(OtherRequestOutcome),
|
||||
}
|
||||
|
||||
/// 未知但有界的请求结果值;私有字段禁止绕过 [`RequestOutcome::new`] 构造。
|
||||
/// 未知但有界的请求结果值;私有字段禁止绕过 [`RequestOutcome::new`] 构造
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct OtherRequestOutcome(String);
|
||||
|
||||
impl RequestOutcome {
|
||||
pub const MAX_BYTES: usize = 256;
|
||||
|
||||
/// 从持久化或 wire 字符串创建可扩展结果语义。
|
||||
/// 从持久化或 wire 字符串创建可扩展结果语义
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// 空值、超过 256 字节或含控制字符时返回
|
||||
/// [`AdminModelError::InvalidRequestOutcome`]。
|
||||
/// [`AdminModelError::InvalidRequestOutcome`]
|
||||
pub fn new(value: impl Into<String>) -> Result<Self, AdminModelError> {
|
||||
let value = value.into();
|
||||
match value.as_str() {
|
||||
@@ -128,7 +128,7 @@ impl OtherRequestOutcome {
|
||||
}
|
||||
}
|
||||
|
||||
/// 用量记录过滤条件。
|
||||
/// 用量记录过滤条件
|
||||
#[derive(Debug, Clone, Default, PartialEq, Eq)]
|
||||
pub struct UsageFilter {
|
||||
pub client_api_key_ref: Option<String>,
|
||||
@@ -146,7 +146,7 @@ pub struct UsageFilter {
|
||||
pub search: Option<String>,
|
||||
}
|
||||
|
||||
/// 用量记录分页查询。
|
||||
/// 用量记录分页查询
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct UsageQuery {
|
||||
pub range: TimeRange,
|
||||
@@ -155,7 +155,7 @@ pub struct UsageQuery {
|
||||
pub page_size: PageSize,
|
||||
}
|
||||
|
||||
/// 运维错误过滤条件。
|
||||
/// 运维错误过滤条件
|
||||
#[derive(Debug, Clone, Default, PartialEq, Eq)]
|
||||
pub struct OpsErrorFilter {
|
||||
pub client_api_key_ref: Option<String>,
|
||||
@@ -172,7 +172,7 @@ pub struct OpsErrorFilter {
|
||||
pub search: Option<String>,
|
||||
}
|
||||
|
||||
/// 运维错误分页查询。
|
||||
/// 运维错误分页查询
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct OpsErrorQuery {
|
||||
pub range: TimeRange,
|
||||
@@ -181,7 +181,7 @@ pub struct OpsErrorQuery {
|
||||
pub page_size: PageSize,
|
||||
}
|
||||
|
||||
/// 用量诊断维度。
|
||||
/// 用量诊断维度
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum DiagnosticDimension {
|
||||
Provider,
|
||||
@@ -193,7 +193,7 @@ pub enum DiagnosticDimension {
|
||||
Status,
|
||||
}
|
||||
|
||||
/// `numeric(20,10)` 的非负规范金额。
|
||||
/// `numeric(20,10)` 的非负规范金额
|
||||
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
pub struct DecimalAmount(String);
|
||||
|
||||
@@ -203,13 +203,13 @@ impl DecimalAmount {
|
||||
&self.0
|
||||
}
|
||||
|
||||
/// 精确相加两个 `numeric(20,10)` 金额。
|
||||
/// 精确相加两个 `numeric(20,10)` 金额
|
||||
#[must_use]
|
||||
pub fn checked_add(&self, other: &Self) -> Option<Self> {
|
||||
Self::from_decimal(self.to_decimal()?.checked_add(other.to_decimal()?)?)
|
||||
}
|
||||
|
||||
/// 将金额按非零请求数均分,保留最多十位小数。
|
||||
/// 将金额按非零请求数均分,保留最多十位小数
|
||||
#[must_use]
|
||||
pub fn checked_div_u64(&self, divisor: u64) -> Option<Self> {
|
||||
Self::from_decimal(self.to_decimal()?.checked_div_u64(divisor)?)
|
||||
@@ -262,25 +262,25 @@ impl std::fmt::Display for DecimalAmount {
|
||||
}
|
||||
}
|
||||
|
||||
/// 单一币种的成本合计。
|
||||
/// 单一币种的成本合计
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct CurrencyCost {
|
||||
pub currency: String,
|
||||
pub amount: DecimalAmount,
|
||||
}
|
||||
|
||||
/// PostgreSQL 连续百分位返回的非负有限毫秒值。
|
||||
/// PostgreSQL 连续百分位返回的非负有限毫秒值
|
||||
///
|
||||
/// 以 IEEE-754 bits 保存,既不丢失插值小数,也可安全实现 `Eq`。
|
||||
/// 以 IEEE-754 bits 保存,既不丢失插值小数,也可安全实现 `Eq`
|
||||
#[derive(Clone, Copy, PartialEq, Eq, Hash)]
|
||||
pub struct PercentileMilliseconds(u64);
|
||||
|
||||
impl PercentileMilliseconds {
|
||||
/// 校验并保存一个毫秒百分位。
|
||||
/// 校验并保存一个毫秒百分位
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// 非有限值或负值返回 [`AdminModelError::InvalidLatencyPercentile`]。
|
||||
/// 非有限值或负值返回 [`AdminModelError::InvalidLatencyPercentile`]
|
||||
pub fn new(value: f64) -> Result<Self, AdminModelError> {
|
||||
if !value.is_finite() || value < 0.0 {
|
||||
return Err(AdminModelError::InvalidLatencyPercentile);
|
||||
@@ -300,7 +300,7 @@ impl std::fmt::Debug for PercentileMilliseconds {
|
||||
}
|
||||
}
|
||||
|
||||
/// 延迟分布的三个稳定百分位。
|
||||
/// 延迟分布的三个稳定百分位
|
||||
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
|
||||
pub struct LatencyPercentiles {
|
||||
pub p50_ms: Option<PercentileMilliseconds>,
|
||||
@@ -308,7 +308,7 @@ pub struct LatencyPercentiles {
|
||||
pub p99_ms: Option<PercentileMilliseconds>,
|
||||
}
|
||||
|
||||
/// Provider 价格规则计算所需的持久请求事实。
|
||||
/// Provider 价格规则计算所需的持久请求事实
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct ProviderBillingInput {
|
||||
pub upstream_model_id: String,
|
||||
@@ -320,9 +320,9 @@ pub struct ProviderBillingInput {
|
||||
pub total: CurrencyCost,
|
||||
}
|
||||
|
||||
/// 已完整交付且由 Provider 计算费用的持久请求事实。
|
||||
/// 已完整交付且由 Provider 计算费用的持久请求事实
|
||||
///
|
||||
/// 控制面仅保留通用事实,具体 Provider 负责校验已持久化总额并恢复标准费用。
|
||||
/// 控制面仅保留通用事实,具体 Provider 负责校验已持久化总额并恢复标准费用
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct UsageCalculatedBillingFact {
|
||||
pub breakdown: Option<CalculatedBillingBreakdown>,
|
||||
@@ -337,7 +337,7 @@ pub struct UsageCalculatedBillingFact {
|
||||
pub total: CurrencyCost,
|
||||
}
|
||||
|
||||
/// Provider 已确认的逐项费用与单价。
|
||||
/// Provider 已确认的逐项费用与单价
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct CalculatedBillingBreakdown {
|
||||
pub long_context_billing_applied: bool,
|
||||
@@ -367,16 +367,16 @@ pub struct ImageBillingBreakdown {
|
||||
pub cache_read_price_per_million: CurrencyCost,
|
||||
}
|
||||
|
||||
/// 单次请求的费用语义。
|
||||
/// 单次请求的费用语义
|
||||
///
|
||||
/// Provider 上报费用或无法恢复逐项价格时保留总额;Provider 验证成功后升级为完整分解。
|
||||
/// Provider 上报费用或无法恢复逐项价格时保留总额;Provider 验证成功后升级为完整分解
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub enum UsageBilling {
|
||||
Total { source: String, total: CurrencyCost },
|
||||
Calculated(Box<CalculatedBillingBreakdown>),
|
||||
}
|
||||
|
||||
/// 计费数据覆盖情况。
|
||||
/// 计费数据覆盖情况
|
||||
#[derive(Debug, Clone, Default, PartialEq, Eq)]
|
||||
pub struct CostCoverage {
|
||||
pub provider_reported_count: u64,
|
||||
@@ -387,7 +387,7 @@ pub struct CostCoverage {
|
||||
}
|
||||
|
||||
impl CostCoverage {
|
||||
/// Provider 直接上报或 Provider 规则完整计算的已知成本数。
|
||||
/// Provider 直接上报或 Provider 规则完整计算的已知成本数
|
||||
#[must_use]
|
||||
pub fn known_count(&self) -> u64 {
|
||||
self.provider_reported_count
|
||||
@@ -395,7 +395,7 @@ impl CostCoverage {
|
||||
}
|
||||
}
|
||||
|
||||
/// 请求级聚合指标。
|
||||
/// 请求级聚合指标
|
||||
#[derive(Debug, Clone, Default, PartialEq, Eq)]
|
||||
pub struct RequestMetrics {
|
||||
pub request_count: u64,
|
||||
@@ -432,7 +432,7 @@ pub struct RequestMetrics {
|
||||
pub cache_hit_request_count: u64,
|
||||
}
|
||||
|
||||
/// 上游 attempt 聚合指标。
|
||||
/// 上游 attempt 聚合指标
|
||||
#[derive(Debug, Clone, Default, PartialEq, Eq)]
|
||||
pub struct AttemptMetrics {
|
||||
pub attempt_count: u64,
|
||||
@@ -447,7 +447,7 @@ pub struct AttemptMetrics {
|
||||
pub costs: Vec<CurrencyCost>,
|
||||
}
|
||||
|
||||
/// 时间序列粒度。
|
||||
/// 时间序列粒度
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum Granularity {
|
||||
FifteenMinutes,
|
||||
@@ -455,7 +455,7 @@ pub enum Granularity {
|
||||
Day,
|
||||
}
|
||||
|
||||
/// 一段时间桶内的请求指标。
|
||||
/// 一段时间桶内的请求指标
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct RequestMetricPoint {
|
||||
pub bucket_start: DateTime<Utc>,
|
||||
@@ -465,7 +465,7 @@ pub struct RequestMetricPoint {
|
||||
pub costs: Vec<CurrencyCost>,
|
||||
}
|
||||
|
||||
/// 账号池的统一五态统计。
|
||||
/// 账号池的统一五态统计
|
||||
#[derive(Debug, Clone, Default, PartialEq, Eq)]
|
||||
pub struct AccountPoolMetrics {
|
||||
pub total: u64,
|
||||
@@ -476,7 +476,7 @@ pub struct AccountPoolMetrics {
|
||||
pub error: u64,
|
||||
}
|
||||
|
||||
/// Dashboard 中一个账号的模型级用量事实。
|
||||
/// Dashboard 中一个账号的模型级用量事实
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct DashboardAccountModelUsage {
|
||||
pub model: String,
|
||||
@@ -497,14 +497,14 @@ pub struct DashboardAccountModelUsage {
|
||||
pub last_used_at: DateTime<Utc>,
|
||||
}
|
||||
|
||||
/// Dashboard 中账号的单小时请求数。
|
||||
/// Dashboard 中账号的单小时请求数
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct DashboardAccountRequestBucket {
|
||||
pub bucket_start: DateTime<Utc>,
|
||||
pub request_count: u64,
|
||||
}
|
||||
|
||||
/// Dashboard 中一个账号的完整用量事实。
|
||||
/// Dashboard 中一个账号的完整用量事实
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct DashboardAccountUsage {
|
||||
pub account_id: String,
|
||||
@@ -513,7 +513,7 @@ pub struct DashboardAccountUsage {
|
||||
pub name: String,
|
||||
pub email: Option<String>,
|
||||
pub plan_type: Option<String>,
|
||||
/// Admin 按 Provider 补齐的套餐展示名称。
|
||||
/// Admin 按 Provider 补齐的套餐展示名称
|
||||
pub plan_type_display: Option<String>,
|
||||
pub request_count: u64,
|
||||
pub success_count: u64,
|
||||
@@ -531,27 +531,27 @@ pub struct DashboardAccountUsage {
|
||||
pub costs: Vec<CurrencyCost>,
|
||||
pub last_used_at: Option<DateTime<Utc>>,
|
||||
pub request_buckets: Vec<DashboardAccountRequestBucket>,
|
||||
/// Provider 已持久化额度窗口投影出的代表性已用比例。
|
||||
/// Provider 已持久化额度窗口投影出的代表性已用比例
|
||||
///
|
||||
/// `None` 表示上游未提供可比较的百分比,不应伪造为零。
|
||||
/// `None` 表示上游未提供可比较的百分比,不应伪造为零
|
||||
pub quota_used_percent: Option<f64>,
|
||||
/// Provider 返回的代表窗口;缺失时保留未知语义。
|
||||
/// Provider 返回的代表窗口;缺失时保留未知语义
|
||||
pub quota_window: Option<super::provider_credentials::ProviderQuotaWindow>,
|
||||
pub models: Vec<DashboardAccountModelUsage>,
|
||||
}
|
||||
|
||||
/// Dashboard 当前账号池的可重建运行时槽位事实。
|
||||
/// Dashboard 当前账号池的可重建运行时槽位事实
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub struct DashboardRuntimeSlots {
|
||||
/// 继承运行参数默认并发上限的可调度账号数。
|
||||
/// 继承运行参数默认并发上限的可调度账号数
|
||||
pub inherited_accounts: u64,
|
||||
/// 已配置账号级并发上限的可调度账号槽位总数。
|
||||
/// 已配置账号级并发上限的可调度账号槽位总数
|
||||
pub overridden_slots: u64,
|
||||
/// Redis 可用时的实时 in-flight 槽位总数;不可用时为 `None`。
|
||||
/// Redis 可用时的实时 in-flight 槽位总数;不可用时为 `None`
|
||||
pub used_slots: Option<u64>,
|
||||
}
|
||||
|
||||
/// 仪表盘卡片脚注展示的全历史累计。
|
||||
/// 仪表盘卡片脚注展示的全历史累计
|
||||
#[derive(Debug, Clone, Default, PartialEq)]
|
||||
pub struct DashboardTotals {
|
||||
pub request_count: u64,
|
||||
@@ -561,7 +561,7 @@ pub struct DashboardTotals {
|
||||
pub billing_usd: Option<DecimalAmount>,
|
||||
}
|
||||
|
||||
/// 仪表盘所需的当前区间事实及全历史累计。
|
||||
/// 仪表盘所需的当前区间事实及全历史累计
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct DashboardObservation {
|
||||
pub range: TimeRange,
|
||||
@@ -572,7 +572,7 @@ pub struct DashboardObservation {
|
||||
pub recent_requests: Vec<UsageListRecord>,
|
||||
}
|
||||
|
||||
/// 使用记录表格的窄读模型。
|
||||
/// 使用记录表格的窄读模型
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct UsageListRecord {
|
||||
pub client_api_key_name: Option<String>,
|
||||
@@ -584,9 +584,9 @@ pub struct UsageListRecord {
|
||||
pub provider_account_ref: Option<String>,
|
||||
pub provider_account_name: Option<String>,
|
||||
pub provider_account_email: Option<String>,
|
||||
/// 账号当前备注,按内部账号 ID 关联,不属于请求历史快照。
|
||||
/// 账号当前备注,按内部账号 ID 关联,不属于请求历史快照
|
||||
pub provider_account_notes: Option<String>,
|
||||
/// 账号当前套餐,不属于请求历史快照。
|
||||
/// 账号当前套餐,不属于请求历史快照
|
||||
pub provider_account_plan_type: Option<String>,
|
||||
pub provider_account_plan_type_display: Option<String>,
|
||||
pub provider_account_authentication_kind: Option<String>,
|
||||
@@ -628,7 +628,7 @@ pub struct UsageListRecord {
|
||||
pub started_at: DateTime<Utc>,
|
||||
}
|
||||
|
||||
/// 一次完整模型请求的公共观测记录。
|
||||
/// 一次完整模型请求的公共观测记录
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct UsageRecord {
|
||||
pub id: String,
|
||||
@@ -653,7 +653,7 @@ pub struct UsageRecord {
|
||||
pub websocket_pool: Option<String>,
|
||||
pub upstream_response_model: Option<String>,
|
||||
pub service_tier: Option<String>,
|
||||
/// Provider 已筛选的专有观测 JSON;管理领域保持不透明。
|
||||
/// Provider 已筛选的专有观测 JSON;管理领域保持不透明
|
||||
pub provider_metadata_json: Option<String>,
|
||||
pub attempt_count: u32,
|
||||
pub upstream_send_state: String,
|
||||
@@ -707,7 +707,7 @@ pub struct UsageRecord {
|
||||
pub completed_at: Option<DateTime<Utc>>,
|
||||
}
|
||||
|
||||
/// 用量分页结果。
|
||||
/// 用量分页结果
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct UsagePage {
|
||||
pub items: Vec<UsageListRecord>,
|
||||
@@ -716,7 +716,7 @@ pub struct UsagePage {
|
||||
pub total: u64,
|
||||
}
|
||||
|
||||
/// 请求中的一次上游尝试或运维事件。
|
||||
/// 请求中的一次上游尝试或运维事件
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct UsageAttempt {
|
||||
pub source: String,
|
||||
@@ -753,7 +753,7 @@ pub struct UsageAttempt {
|
||||
pub occurred_at: DateTime<Utc>,
|
||||
}
|
||||
|
||||
/// 一条请求及其全部尝试。
|
||||
/// 一条请求及其全部尝试
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct UsageDetail {
|
||||
pub trace: Option<serde_json::Value>,
|
||||
@@ -762,7 +762,7 @@ pub struct UsageDetail {
|
||||
pub attempts: Vec<UsageAttempt>,
|
||||
}
|
||||
|
||||
/// 一个 Provider 的聚合用量。
|
||||
/// 一个 Provider 的聚合用量
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct ProviderObservation {
|
||||
pub provider_kind: String,
|
||||
@@ -772,7 +772,7 @@ pub struct ProviderObservation {
|
||||
pub total_tokens: u64,
|
||||
}
|
||||
|
||||
/// 用量总览。
|
||||
/// 用量总览
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct UsageOverview {
|
||||
pub range: TimeRange,
|
||||
@@ -781,21 +781,21 @@ pub struct UsageOverview {
|
||||
pub providers: Vec<ProviderObservation>,
|
||||
}
|
||||
|
||||
/// 用量摘要的用例结果。
|
||||
/// 用量摘要的用例结果
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct UsageSummary {
|
||||
pub overview: UsageOverview,
|
||||
pub average_latency_ms: Option<u64>,
|
||||
}
|
||||
|
||||
/// 诊断聚合结果,分母包含截取展示项之前的全部匹配请求。
|
||||
/// 诊断聚合结果,分母包含截取展示项之前的全部匹配请求
|
||||
#[derive(Debug, Clone, Default, PartialEq, Eq)]
|
||||
pub struct DiagnosticsObservation {
|
||||
pub total_request_count: u64,
|
||||
pub items: Vec<DiagnosticObservation>,
|
||||
}
|
||||
|
||||
/// 单个诊断维度值的聚合结果。
|
||||
/// 单个诊断维度值的聚合结果
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct DiagnosticObservation {
|
||||
pub key: String,
|
||||
@@ -817,7 +817,7 @@ pub struct DiagnosticObservation {
|
||||
pub costs: Vec<CurrencyCost>,
|
||||
}
|
||||
|
||||
/// 统一运维错误记录。
|
||||
/// 统一运维错误记录
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct OpsError {
|
||||
pub client_api_key_name: Option<String>,
|
||||
@@ -875,7 +875,7 @@ pub struct OpsError {
|
||||
pub stable_sort_id: String,
|
||||
}
|
||||
|
||||
/// 运维错误分页结果。
|
||||
/// 运维错误分页结果
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct OpsErrorPage {
|
||||
pub items: Vec<OpsError>,
|
||||
@@ -884,7 +884,7 @@ pub struct OpsErrorPage {
|
||||
pub total: u64,
|
||||
}
|
||||
|
||||
/// 仪表盘趋势指标。
|
||||
/// 仪表盘趋势指标
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum TrendKind {
|
||||
Usage,
|
||||
@@ -892,7 +892,7 @@ pub enum TrendKind {
|
||||
Errors,
|
||||
}
|
||||
|
||||
/// 一个趋势桶的全部已计算语义,API 只负责时区标签与展示格式。
|
||||
/// 一个趋势桶的全部已计算语义,API 只负责时区标签与展示格式
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct TrendPoint {
|
||||
pub bucket_start: DateTime<Utc>,
|
||||
@@ -908,7 +908,7 @@ pub struct TrendPoint {
|
||||
pub costs: Vec<CurrencyCost>,
|
||||
}
|
||||
|
||||
/// 趋势的无格式化汇总。
|
||||
/// 趋势的无格式化汇总
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct TrendSummary {
|
||||
pub request_count: u64,
|
||||
@@ -929,7 +929,7 @@ pub struct TrendSummary {
|
||||
pub cost_coverage: CostCoverage,
|
||||
}
|
||||
|
||||
/// 趋势结果;API 再决定图表标签与数字格式。
|
||||
/// 趋势结果;API 再决定图表标签与数字格式
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct Trend {
|
||||
pub kind: TrendKind,
|
||||
@@ -937,7 +937,7 @@ pub struct Trend {
|
||||
pub summary: TrendSummary,
|
||||
}
|
||||
|
||||
/// 健康时间桶状态。
|
||||
/// 健康时间桶状态
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum HealthStatus {
|
||||
Future,
|
||||
@@ -948,7 +948,7 @@ pub enum HealthStatus {
|
||||
Stable,
|
||||
}
|
||||
|
||||
/// 一个 15 分钟健康桶的语义结果。
|
||||
/// 一个 15 分钟健康桶的语义结果
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct HealthTimelinePoint {
|
||||
pub bucket_start: DateTime<Utc>,
|
||||
@@ -961,7 +961,7 @@ pub struct HealthTimelinePoint {
|
||||
pub caller_error_requests: u64,
|
||||
}
|
||||
|
||||
/// 中国自然日的固定 96 槽健康时间线。
|
||||
/// 中国自然日的固定 96 槽健康时间线
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct HealthTimeline {
|
||||
pub reliability_percent: Option<f64>,
|
||||
@@ -974,7 +974,7 @@ pub struct HealthTimeline {
|
||||
pub points: Vec<HealthTimelinePoint>,
|
||||
}
|
||||
|
||||
/// Dashboard 请求画像的目标平台。
|
||||
/// Dashboard 请求画像的目标平台
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct DashboardWireTarget {
|
||||
pub os_type: String,
|
||||
@@ -983,7 +983,7 @@ pub struct DashboardWireTarget {
|
||||
pub terminal: String,
|
||||
}
|
||||
|
||||
/// Desktop 发布检查状态。
|
||||
/// Desktop 发布检查状态
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum DesktopReleaseStatus {
|
||||
Unchecked,
|
||||
@@ -992,7 +992,7 @@ pub enum DesktopReleaseStatus {
|
||||
Failed,
|
||||
}
|
||||
|
||||
/// 与请求画像分离的 Desktop 发布检查事实。
|
||||
/// 与请求画像分离的 Desktop 发布检查事实
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct DashboardDesktopRelease {
|
||||
pub status: DesktopReleaseStatus,
|
||||
@@ -1008,9 +1008,9 @@ pub struct DashboardDesktopRelease {
|
||||
pub error: Option<String>,
|
||||
}
|
||||
|
||||
/// Provider 拥有并实际用于上游请求的身份画像快照。
|
||||
/// Provider 拥有并实际用于上游请求的身份画像快照
|
||||
///
|
||||
/// `attributes` 承载 Provider 特有的可观测字段;公共控制面不解释其语义。
|
||||
/// `attributes` 承载 Provider 特有的可观测字段;公共控制面不解释其语义
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct DashboardWireProfile {
|
||||
pub provider: String,
|
||||
@@ -1024,24 +1024,24 @@ pub struct DashboardWireProfile {
|
||||
pub release: Option<DashboardDesktopRelease>,
|
||||
}
|
||||
|
||||
/// Provider 画像的差异字段;展示层只显示标签和值,不解释 Provider 语义。
|
||||
/// Provider 画像的差异字段;展示层只显示标签和值,不解释 Provider 语义
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct DashboardWireAttribute {
|
||||
pub label: String,
|
||||
pub value: String,
|
||||
}
|
||||
|
||||
/// 当前账号池的并发调度容量。
|
||||
/// 当前账号池的并发调度容量
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct DashboardCapacity {
|
||||
pub max_concurrent_per_account: u64,
|
||||
/// `None` 表示可用账号池不限制并发。
|
||||
/// `None` 表示可用账号池不限制并发
|
||||
pub total_slots: Option<u64>,
|
||||
pub used_slots: Option<u64>,
|
||||
pub available_slots: Option<u64>,
|
||||
}
|
||||
|
||||
/// Dashboard 某个自然日区间内的卡片计数。
|
||||
/// Dashboard 某个自然日区间内的卡片计数
|
||||
#[derive(Debug, Clone, Copy, Default, PartialEq)]
|
||||
pub struct DashboardPeriodMetrics {
|
||||
pub request_count: u64,
|
||||
@@ -1053,7 +1053,7 @@ pub struct DashboardPeriodMetrics {
|
||||
pub observed_cached_token_rate: Option<f64>,
|
||||
}
|
||||
|
||||
/// 仪表盘聚合结果。
|
||||
/// 仪表盘聚合结果
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct DashboardResult {
|
||||
pub observation: DashboardObservation,
|
||||
@@ -1069,7 +1069,7 @@ pub struct DashboardResult {
|
||||
pub rotation_strategy: super::settings::RotationStrategy,
|
||||
}
|
||||
|
||||
/// 洞察健康时间点的语义结果。
|
||||
/// 洞察健康时间点的语义结果
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct UsageInsightsHealthPoint {
|
||||
pub total_requests: u64,
|
||||
@@ -1082,7 +1082,7 @@ pub struct UsageInsightsHealthPoint {
|
||||
pub error_rate: f64,
|
||||
}
|
||||
|
||||
/// 洞察健康汇总;失败数已排除调用方错误。
|
||||
/// 洞察健康汇总;失败数已排除调用方错误
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct UsageInsightsHealth {
|
||||
pub total_requests: u64,
|
||||
@@ -1096,7 +1096,7 @@ pub struct UsageInsightsHealth {
|
||||
pub points: Vec<UsageInsightsHealthPoint>,
|
||||
}
|
||||
|
||||
/// 洞察性能时间点。
|
||||
/// 洞察性能时间点
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct UsageInsightsPerformancePoint {
|
||||
pub bucket_start: DateTime<Utc>,
|
||||
@@ -1111,7 +1111,7 @@ pub struct UsageInsightsPerformancePoint {
|
||||
pub capacity_utilization_p95_basis_points: Option<u64>,
|
||||
}
|
||||
|
||||
/// 洞察性能汇总与可观测覆盖率。
|
||||
/// 洞察性能汇总与可观测覆盖率
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct UsageInsightsPerformance {
|
||||
pub latency_percentiles: LatencyPercentiles,
|
||||
@@ -1131,7 +1131,7 @@ pub struct UsageInsightsPerformance {
|
||||
pub points: Vec<UsageInsightsPerformancePoint>,
|
||||
}
|
||||
|
||||
/// 洞察成本时间点。
|
||||
/// 洞察成本时间点
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct UsageInsightsCostPoint {
|
||||
pub bucket_start: DateTime<Utc>,
|
||||
@@ -1147,7 +1147,7 @@ pub struct UsageInsightsCostPoint {
|
||||
pub cache_hit_request_rate: Option<f64>,
|
||||
}
|
||||
|
||||
/// 洞察成本汇总。
|
||||
/// 洞察成本汇总
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct UsageInsightsCost {
|
||||
pub estimated_cost: Option<DecimalAmount>,
|
||||
@@ -1169,7 +1169,7 @@ pub struct UsageInsightsCost {
|
||||
pub coverage: CostCoverage,
|
||||
}
|
||||
|
||||
/// 用量洞察的完整用例结果;API 只负责标签、时区与字符串格式化。
|
||||
/// 用量洞察的完整用例结果;API 只负责标签、时区与字符串格式化
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct UsageInsights {
|
||||
pub granularity: Granularity,
|
||||
@@ -1180,7 +1180,7 @@ pub struct UsageInsights {
|
||||
pub providers: Vec<ProviderObservation>,
|
||||
}
|
||||
|
||||
/// 已完成分母计算的诊断项。
|
||||
/// 已完成分母计算的诊断项
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct DiagnosticsItem {
|
||||
pub key: String,
|
||||
@@ -1204,7 +1204,7 @@ pub struct DiagnosticsItem {
|
||||
pub total_tokens: u64,
|
||||
}
|
||||
|
||||
/// 诊断结果。
|
||||
/// 诊断结果
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct DiagnosticsResult {
|
||||
pub dimension: DiagnosticDimension,
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
//! 插件可见的 Client Key 非秘密资源投影。
|
||||
//! 插件可见的 Client Key 非秘密资源投影
|
||||
|
||||
use gateway_core::policy::ClientApiKeyId;
|
||||
use gateway_core::routing::AccountGroupId;
|
||||
|
||||
use super::PageSize;
|
||||
|
||||
/// 插件可读取的当前 Key 范围,不含密钥或配置秘密。
|
||||
/// 插件可读取的当前 Key 范围,不含密钥或配置秘密
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct PluginClientKeyFacts {
|
||||
pub id: ClientApiKeyId,
|
||||
@@ -25,7 +25,7 @@ pub struct PluginClientKeyListQuery {
|
||||
pub limit: PageSize,
|
||||
}
|
||||
|
||||
/// 插件只能发现调用所需的公开身份,不取得 Key 前缀、策略细节或明文。
|
||||
/// 插件只能发现调用所需的公开身份,不取得 Key 前缀、策略细节或明文
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct PluginClientKey {
|
||||
pub id: ClientApiKeyId,
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
//! 插件资源归属由宿主调用上下文给出,不能由插件参数选择实例或代次。
|
||||
//! 插件资源归属由宿主调用上下文给出,不能由插件参数选择实例或代次
|
||||
|
||||
use super::Revision;
|
||||
|
||||
@@ -17,7 +17,7 @@ pub struct ManagedResource {
|
||||
}
|
||||
|
||||
pub struct ResourceMutation<T> {
|
||||
/// 没有事实变化时不递增配置 revision,也不触发发布。
|
||||
/// 没有事实变化时不递增配置 revision,也不触发发布
|
||||
pub revision: Option<Revision>,
|
||||
pub value: T,
|
||||
}
|
||||
@@ -34,7 +34,7 @@ pub struct GroupMembersChanged {
|
||||
pub removed: u64,
|
||||
}
|
||||
|
||||
/// 插件 Key 的首次创建设置;分组始终从本实例资源键解析。
|
||||
/// 插件 Key 的首次创建设置;分组始终从本实例资源键解析
|
||||
pub struct ManagedKeyConfig {
|
||||
pub name: String,
|
||||
pub limits: gateway_core::policy::RateLimits,
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
//! 插件分发来源、下载凭据、发行查询与远程安装的数据合同
|
||||
|
||||
use std::{fmt, sync::Arc};
|
||||
|
||||
use chrono::{DateTime, Utc};
|
||||
@@ -17,7 +19,7 @@ pub enum PluginUpdateSource {
|
||||
Github { repository: String },
|
||||
}
|
||||
|
||||
/// 检查策略只决定查询目标,任何策略都不会自动安装或切换运行实例。
|
||||
/// 检查策略只决定查询目标,任何策略都不会自动安装或切换运行实例
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
#[serde(tag = "kind", rename_all = "snake_case", deny_unknown_fields)]
|
||||
pub enum PluginUpdatePolicy {
|
||||
@@ -85,7 +87,7 @@ impl fmt::Debug for PluginDistributionEgress {
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct PluginUpdateCheck {
|
||||
pub binding: PluginSourceBinding,
|
||||
/// Release 元数据不是已校验的插件包,不据此声明平台或 SDK 兼容。
|
||||
/// Release 元数据不是已校验的插件包,不据此声明平台或 SDK 兼容
|
||||
pub release: PluginRelease,
|
||||
}
|
||||
|
||||
@@ -109,7 +111,7 @@ pub enum DownloadPurpose {
|
||||
Artifact,
|
||||
}
|
||||
|
||||
/// 授权目标以完整 origin 和路径段边界匹配;摘要、名称和列表均不包含 secret。
|
||||
/// 授权目标以完整 origin 和路径段边界匹配;摘要、名称和列表均不包含 secret
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase", deny_unknown_fields)]
|
||||
pub struct SourceCredentialInfo {
|
||||
@@ -174,7 +176,7 @@ pub struct PluginRelease {
|
||||
pub expires_at: DateTime<Utc>,
|
||||
}
|
||||
|
||||
/// 下载意图不承载凭据;引用在 Admin 中解析后只交给 Host 的本次调用。
|
||||
/// 下载意图不承载凭据;引用在 Admin 中解析后只交给 Host 的本次调用
|
||||
#[derive(Debug, Clone, Deserialize)]
|
||||
#[serde(tag = "kind", rename_all = "snake_case", deny_unknown_fields)]
|
||||
pub enum RemotePluginLocation {
|
||||
@@ -200,7 +202,7 @@ pub struct RemotePluginVerify {
|
||||
pub credential_ids: Vec<String>,
|
||||
#[serde(default)]
|
||||
pub outbound_proxy_id: Option<String>,
|
||||
/// 更新已有插件时锁定身份;首次安装从包内清单读取。
|
||||
/// 更新已有插件时锁定身份;首次安装从包内清单读取
|
||||
pub expected_plugin_id: Option<String>,
|
||||
}
|
||||
|
||||
@@ -222,7 +224,7 @@ pub struct DownloadedPlugin {
|
||||
pub source: PluginSource,
|
||||
}
|
||||
|
||||
/// 只读包校验结果,不代表实例配置、授权或私有状态已完成运行时准备。
|
||||
/// 只读包校验结果,不代表实例配置、授权或私有状态已完成运行时准备
|
||||
#[derive(Debug, Clone, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct VerifiedPluginArtifact {
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
//! 插件实例配置、能力绑定、运行状态与版本切换的数据合同
|
||||
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
use secrecy::SecretString;
|
||||
@@ -20,7 +22,7 @@ pub struct PluginCapabilityBinding {
|
||||
pub stage: String,
|
||||
pub order: i32,
|
||||
pub failure_policy: PluginFailurePolicy,
|
||||
/// 观察绑定选择事件类型;其他能力不设置事件。
|
||||
/// 观察绑定选择事件类型;其他能力不设置事件
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub event: Option<String>,
|
||||
#[serde(default)]
|
||||
@@ -35,7 +37,7 @@ pub struct PluginCapabilityBinding {
|
||||
pub identity_bindings: Vec<PluginFrontendIdentityBinding>,
|
||||
}
|
||||
|
||||
/// 外部 principal 到既有 Client Key 的显式宿主映射。
|
||||
/// 外部 principal 到既有 Client Key 的显式宿主映射
|
||||
#[derive(Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase", deny_unknown_fields)]
|
||||
pub struct PluginFrontendIdentityBinding {
|
||||
@@ -53,7 +55,7 @@ impl std::fmt::Debug for PluginFrontendIdentityBinding {
|
||||
}
|
||||
}
|
||||
|
||||
/// 配置和 secret 分离;Debug 与 Serialize 不得无意展开实例配置。
|
||||
/// 配置和 secret 分离;Debug 与 Serialize 不得无意展开实例配置
|
||||
#[derive(Clone)]
|
||||
pub struct PluginInstance {
|
||||
pub id: String,
|
||||
@@ -78,7 +80,7 @@ pub struct PluginInstanceMutation {
|
||||
pub instance: PluginInstance,
|
||||
}
|
||||
|
||||
/// 控制面展示的单实例运行状态;它描述当前进程观察到的事实,不替代持久启用配置。
|
||||
/// 控制面展示的单实例运行状态;它描述当前进程观察到的事实,不替代持久启用配置
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum PluginInstanceRuntimeStatus {
|
||||
Disabled,
|
||||
@@ -91,14 +93,14 @@ pub enum PluginInstanceRuntimeStatus {
|
||||
Draining,
|
||||
}
|
||||
|
||||
/// 已脱敏且可安全返回给管理员的运行失败摘要。
|
||||
/// 已脱敏且可安全返回给管理员的运行失败摘要
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct PluginInstanceRuntimeFailure {
|
||||
pub code: String,
|
||||
pub message: String,
|
||||
}
|
||||
|
||||
/// 当前发布视图与代次索引的只读诊断。
|
||||
/// 当前发布视图与代次索引的只读诊断
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct PluginInstanceRuntime {
|
||||
pub status: PluginInstanceRuntimeStatus,
|
||||
@@ -120,18 +122,18 @@ pub struct PluginInstanceView {
|
||||
#[derive(Deserialize)]
|
||||
#[serde(rename_all = "camelCase", deny_unknown_fields)]
|
||||
pub struct ConfigurePluginInstance {
|
||||
/// 创建草稿固定一个 UUID,重试复用同一配置,不能作为编辑已有配置的入口。
|
||||
/// 创建草稿固定一个 UUID,重试复用同一配置,不能作为编辑已有配置的入口
|
||||
pub creation_id: Option<String>,
|
||||
/// 编辑时检查用户看到的配置版本,避免覆盖另一窗口已经保存的内容。
|
||||
/// 编辑时检查用户看到的配置版本,避免覆盖另一窗口已经保存的内容
|
||||
pub expected_revision: Option<u64>,
|
||||
/// 管理员确认停用的同插件配置,与当前配置在同一事务中切换。
|
||||
/// 管理员确认停用的同插件配置,与当前配置在同一事务中切换
|
||||
#[serde(default)]
|
||||
pub replace_instances: Vec<PluginInstanceReplacement>,
|
||||
pub name: String,
|
||||
pub artifact_sha256: String,
|
||||
pub enabled: bool,
|
||||
pub configuration: serde_json::Value,
|
||||
/// 缺省保留旧值;显式空对象清除全部 secret。
|
||||
/// 缺省保留旧值;显式空对象清除全部 secret
|
||||
pub secrets: Option<BTreeMap<String, SecretString>>,
|
||||
pub bindings: Vec<PluginCapabilityBinding>,
|
||||
}
|
||||
@@ -143,7 +145,7 @@ pub struct PluginInstanceReplacement {
|
||||
pub expected_revision: u64,
|
||||
}
|
||||
|
||||
/// 恢复已安装旧版本的配置快照,并重新通过目标版本校验。
|
||||
/// 恢复已安装旧版本的配置快照,并重新通过目标版本校验
|
||||
#[derive(Debug, Deserialize)]
|
||||
#[serde(rename_all = "camelCase", deny_unknown_fields)]
|
||||
pub struct RollbackPluginInstance {
|
||||
@@ -159,7 +161,7 @@ pub struct PluginRollbackTarget {
|
||||
pub platforms: Vec<String>,
|
||||
}
|
||||
|
||||
/// 静态候选不启动插件;实际回滚仍须复验配置、平台和私有状态兼容性。
|
||||
/// 静态候选不启动插件;实际回滚仍须复验配置、平台和私有状态兼容性
|
||||
#[derive(Debug, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct PluginRollbackPlan {
|
||||
@@ -168,7 +170,7 @@ pub struct PluginRollbackPlan {
|
||||
pub targets: Vec<PluginRollbackTarget>,
|
||||
}
|
||||
|
||||
/// 版本配置快照只用于服务端恢复,敏感值不实现 Debug 或 Serialize。
|
||||
/// 版本配置快照只用于服务端恢复,敏感值不实现 Debug 或 Serialize
|
||||
#[derive(Clone)]
|
||||
pub struct PluginVersionConfiguration {
|
||||
pub configuration: serde_json::Value,
|
||||
@@ -176,7 +178,7 @@ pub struct PluginVersionConfiguration {
|
||||
pub bindings: Vec<PluginCapabilityBinding>,
|
||||
}
|
||||
|
||||
/// 切换版本的只读草稿,不包含密钥值,也不会启动插件或写入配置。
|
||||
/// 切换版本的只读草稿,不包含密钥值,也不会启动插件或写入配置
|
||||
#[derive(Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct PluginVersionPlan {
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
//! 插件管理页面、静态资源、请求与授权回调的数据合同
|
||||
|
||||
use std::sync::Arc;
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
/// 页面和业务调用绑定同一实例版本;制品摘要单独作为不可变资源身份。
|
||||
/// 页面和业务调用绑定同一实例版本;制品摘要单独作为不可变资源身份
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase", deny_unknown_fields)]
|
||||
pub struct PluginManagementTarget {
|
||||
@@ -37,7 +39,7 @@ pub struct StartPluginManagementCallback {
|
||||
pub ttl_seconds: u32,
|
||||
}
|
||||
|
||||
/// state 是短期一次性秘密,只交给发起登录的管理员,不进入 Debug 或日志。
|
||||
/// state 是短期一次性秘密,只交给发起登录的管理员,不进入 Debug 或日志
|
||||
#[derive(Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct PluginManagementCallbackTicket {
|
||||
@@ -72,7 +74,7 @@ pub struct PluginManagementResource {
|
||||
pub public: bool,
|
||||
}
|
||||
|
||||
/// 原始正文可能含业务数据,不能派生内容型 Debug 或写入诊断日志。
|
||||
/// 原始正文可能含业务数据,不能派生内容型 Debug 或写入诊断日志
|
||||
pub struct PluginManagementRequest {
|
||||
pub method: String,
|
||||
pub path: String,
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
//! 插件制品元数据、宿主兼容性与管理领域模型的公共入口
|
||||
|
||||
use std::{
|
||||
collections::{BTreeMap, BTreeSet},
|
||||
sync::Arc,
|
||||
@@ -13,7 +15,7 @@ pub mod management;
|
||||
pub mod official;
|
||||
pub mod state;
|
||||
|
||||
/// 一个宿主发行版明确承诺支持的插件能力版本。
|
||||
/// 一个宿主发行版明确承诺支持的插件能力版本
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct PluginHostCapability {
|
||||
@@ -21,7 +23,7 @@ pub struct PluginHostCapability {
|
||||
pub versions: Vec<u32>,
|
||||
}
|
||||
|
||||
/// 随宿主发行物封口的插件合同;Runtime 与发布清单共用这一份声明。
|
||||
/// 随宿主发行物封口的插件合同;Runtime 与发布清单共用这一份声明
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct PluginHostCompatibility {
|
||||
@@ -32,7 +34,7 @@ pub struct PluginHostCompatibility {
|
||||
}
|
||||
|
||||
impl PluginHostCompatibility {
|
||||
/// 拒绝空集合、重复项和不受控标识,避免发行清单把“未知”解释为通配。
|
||||
/// 拒绝空集合、重复项和不受控标识,避免发行清单把“未知”解释为通配
|
||||
#[must_use]
|
||||
pub fn is_valid(&self) -> bool {
|
||||
if self.schema_version != 2
|
||||
@@ -101,7 +103,7 @@ fn valid_plugin_segment(value: &str) -> bool {
|
||||
.all(|byte| byte.is_ascii_lowercase() || byte.is_ascii_digit() || byte == b'-')
|
||||
}
|
||||
|
||||
/// 已安装包对目标宿主的静态要求;不包含包体、配置或 secret。
|
||||
/// 已安装包对目标宿主的静态要求;不包含包体、配置或 secret
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct PluginCompatibilityRequirements {
|
||||
pub host_version: String,
|
||||
@@ -110,7 +112,7 @@ pub struct PluginCompatibilityRequirements {
|
||||
pub capabilities: Vec<(String, u32)>,
|
||||
}
|
||||
|
||||
/// 一次下载使用的非敏感出站代理身份;地址与认证由受管代理记录持有。
|
||||
/// 一次下载使用的非敏感出站代理身份;地址与认证由受管代理记录持有
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase", deny_unknown_fields)]
|
||||
pub struct PluginSourceEgress {
|
||||
@@ -118,7 +120,7 @@ pub struct PluginSourceEgress {
|
||||
pub revision: u64,
|
||||
}
|
||||
|
||||
/// 安装来源只保存公开定位信息,下载凭据和出站代理通过独立记录引用。
|
||||
/// 安装来源只保存公开定位信息,下载凭据和出站代理通过独立记录引用
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
#[serde(tag = "kind", rename_all = "snake_case", deny_unknown_fields)]
|
||||
pub enum PluginSource {
|
||||
@@ -164,7 +166,7 @@ impl PluginSource {
|
||||
}
|
||||
}
|
||||
|
||||
/// 管理端保留的单项贡献声明;map key 是能力标识,`id` 是实例绑定引用的扩展项身份。
|
||||
/// 管理端保留的单项贡献声明;map key 是能力标识,`id` 是实例绑定引用的扩展项身份
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct PluginContribution {
|
||||
@@ -178,7 +180,7 @@ pub struct PluginContribution {
|
||||
pub output_formats: Vec<String>,
|
||||
}
|
||||
|
||||
/// 制品清单中的展示图标;路径只用于选择已校验的包资源。
|
||||
/// 制品清单中的展示图标;路径只用于选择已校验的包资源
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
#[serde(untagged)]
|
||||
pub enum PluginArtifactIcon {
|
||||
@@ -198,15 +200,15 @@ pub enum PluginIconTheme {
|
||||
Dark,
|
||||
}
|
||||
|
||||
/// 从不可变制品中读取的已校验图标字节。
|
||||
/// 从不可变制品中读取的已校验图标字节
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct PluginArtifactIconResource {
|
||||
pub content_type: String,
|
||||
pub body: Vec<u8>,
|
||||
}
|
||||
|
||||
/// 已校验制品的持久化投影;读取时忽略未知字段,可选字段缺省沿用清单语义。
|
||||
/// 身份与执行所需事实仍为必填,包清单及协议版本由 Runtime 独立校验。
|
||||
/// 已校验制品的持久化投影;读取时忽略未知字段,可选字段缺省沿用清单语义
|
||||
/// 身份与执行所需事实仍为必填,包清单及协议版本由 Runtime 独立校验
|
||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct PluginArtifactMetadata {
|
||||
@@ -235,7 +237,7 @@ fn empty_configuration_schema() -> serde_json::Value {
|
||||
serde_json::json!({})
|
||||
}
|
||||
|
||||
/// 由包检查端口产生的不可变制品;不派生 Debug,包内可能含敏感用户数据。
|
||||
/// 由包检查端口产生的不可变制品;不派生 Debug,包内可能含敏感用户数据
|
||||
#[derive(Clone)]
|
||||
pub struct InspectedPluginArtifact {
|
||||
pub metadata: PluginArtifactMetadata,
|
||||
|
||||
@@ -1,13 +1,15 @@
|
||||
//! 官方插件发行身份与导入命令
|
||||
|
||||
use super::super::Revision;
|
||||
|
||||
/// 编译进宿主二进制的发行身份;不能由运行时配置或插件清单覆盖。
|
||||
/// 编译进宿主二进制的发行身份;不能由运行时配置或插件清单覆盖
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct OfficialPluginReleaseIdentity {
|
||||
pub gateway_version: String,
|
||||
pub gateway_git_sha: String,
|
||||
}
|
||||
|
||||
/// 启动时导入官方制品的结果;导入只持久化制品,不创建或启用实例。
|
||||
/// 启动时导入官方制品的结果;导入只持久化制品,不创建或启用实例
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub struct OfficialPluginImport {
|
||||
pub artifacts: usize,
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
//! 插件私有状态的归属、代次隔离与迁移数据合同
|
||||
|
||||
use std::{collections::BTreeMap, fmt};
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::model::Revision;
|
||||
|
||||
/// 已由 Runtime 校验的命名空间 schema 与配额事实。
|
||||
/// 已由 Runtime 校验的命名空间 schema 与配额事实
|
||||
#[derive(Clone, PartialEq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct PluginStateSchema {
|
||||
@@ -42,7 +44,7 @@ pub struct PluginStateConfiguration {
|
||||
pub namespaces: Vec<PluginStateSchema>,
|
||||
}
|
||||
|
||||
/// 与实例保存同事务提交的状态配置;transition 只引用已完成的 staging 数据。
|
||||
/// 与实例保存同事务提交的状态配置;transition 只引用已完成的 staging 数据
|
||||
#[derive(Clone, Default)]
|
||||
pub struct PluginStateCommit {
|
||||
pub configuration: PluginStateConfiguration,
|
||||
@@ -57,7 +59,8 @@ pub struct PluginStateOwnerRequest {
|
||||
pub configuration: PluginStateConfiguration,
|
||||
}
|
||||
|
||||
/// Store 签发的实例私有状态 fence。插件协议中从不传输这些字段。
|
||||
/// Store 签发的实例私有状态 fence
|
||||
/// 插件协议中从不传输这些字段
|
||||
#[derive(Clone)]
|
||||
pub struct PluginStateOwner {
|
||||
instance_id: String,
|
||||
@@ -136,7 +139,7 @@ impl PluginStateNamespaceOwner {
|
||||
}
|
||||
}
|
||||
|
||||
/// 私有值可能含插件自己的敏感状态,故意不实现 `Debug`。
|
||||
/// 私有值可能含插件自己的敏感状态,故意不实现 `Debug`
|
||||
#[derive(Clone)]
|
||||
pub struct PluginStateRecord {
|
||||
pub key: String,
|
||||
@@ -149,7 +152,7 @@ pub struct PutPluginState {
|
||||
pub namespace: String,
|
||||
pub key: String,
|
||||
pub value: serde_json::Value,
|
||||
/// `None` 是 create-only;已有记录必须提交精确版本。
|
||||
/// `None` 是 create-only;已有记录必须提交精确版本
|
||||
pub expected_version: Option<u64>,
|
||||
}
|
||||
|
||||
@@ -188,7 +191,7 @@ pub struct ApplyPluginStateMigration {
|
||||
pub transition_id: String,
|
||||
pub namespace: String,
|
||||
pub cursor: Option<String>,
|
||||
/// Runtime 刚读取的有序源键;Store 会在写入前重新读取并逐项比对。
|
||||
/// Runtime 刚读取的有序源键;Store 会在写入前重新读取并逐项比对
|
||||
pub expected_keys: Vec<String>,
|
||||
pub changes: Vec<PluginStateMigrationChange>,
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
//! 全局模型定价的管理合同。
|
||||
//! 全局模型定价的管理合同
|
||||
|
||||
use gateway_core::metering::{ModelPriceOverride, PricingOverrides};
|
||||
use std::collections::{BTreeMap, BTreeSet};
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
//! Provider 管理能力交换的中立 Command 与 Result。
|
||||
//! Provider 管理能力交换的中立 Command 与 Result
|
||||
|
||||
use std::{fmt, pin::Pin};
|
||||
|
||||
@@ -22,7 +22,7 @@ use super::{
|
||||
},
|
||||
};
|
||||
|
||||
/// Provider-owned JSON;公共层只搬运且 Debug 不输出值。
|
||||
/// Provider-owned JSON;公共层只搬运且 Debug 不输出值
|
||||
#[derive(Clone, PartialEq)]
|
||||
pub struct ProviderDocument(OpaqueProviderData);
|
||||
|
||||
@@ -32,7 +32,7 @@ impl ProviderDocument {
|
||||
Self(data)
|
||||
}
|
||||
|
||||
/// 仅具体 Provider 可以解释内部字段。
|
||||
/// 仅具体 Provider 可以解释内部字段
|
||||
#[must_use]
|
||||
pub const fn expose_to_provider(&self) -> &OpaqueProviderData {
|
||||
&self.0
|
||||
@@ -50,14 +50,14 @@ impl fmt::Debug for ProviderDocument {
|
||||
}
|
||||
}
|
||||
|
||||
/// Provider credential 详情。
|
||||
/// Provider credential 详情
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct CredentialDetails {
|
||||
pub config_revision: Revision,
|
||||
pub credential: AccountRecord,
|
||||
}
|
||||
|
||||
/// Provider 正式文档批量导入命令。
|
||||
/// Provider 正式文档批量导入命令
|
||||
pub struct ImportCredentials {
|
||||
pub outbound_proxy_id: Option<String>,
|
||||
pub settings: Option<AccountImportSettings>,
|
||||
@@ -75,14 +75,14 @@ impl fmt::Debug for ImportCredentials {
|
||||
}
|
||||
}
|
||||
|
||||
/// 批量导入提交结果。
|
||||
/// 批量导入提交结果
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct CredentialImportResult {
|
||||
pub config_revision: Revision,
|
||||
pub credential_ids: Vec<ProviderAccountId>,
|
||||
}
|
||||
|
||||
/// Provider 解析导入文档时只接收不透明文档,不接触 revision 或审计上下文。
|
||||
/// Provider 解析导入文档时只接收不透明文档,不接触 revision 或审计上下文
|
||||
pub struct PrepareCredentialImport {
|
||||
pub default_outbound_proxy: Option<gateway_core::account::OutboundProxy>,
|
||||
pub document: ProviderDocument,
|
||||
@@ -97,7 +97,7 @@ impl fmt::Debug for PrepareCredentialImport {
|
||||
}
|
||||
}
|
||||
|
||||
/// Provider 已验证、可由 Store 原子创建的一份 credential。
|
||||
/// Provider 已验证、可由 Store 原子创建的一份 credential
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct PreparedCredentialCreate {
|
||||
pub model_access: Option<gateway_core::account::AccountModelAccess>,
|
||||
@@ -119,14 +119,14 @@ pub struct PreparedCredentialCreate {
|
||||
pub credential_observed_at: DateTime<Utc>,
|
||||
}
|
||||
|
||||
/// Provider 对一份导入文档的完整验证结果。
|
||||
/// Provider 对一份导入文档的完整验证结果
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct PreparedCredentialImport {
|
||||
pub provider_kind: ProviderKind,
|
||||
pub credentials: Vec<PreparedCredentialCreate>,
|
||||
}
|
||||
|
||||
/// Admin 交给 Store 的导入事务命令。
|
||||
/// Admin 交给 Store 的导入事务命令
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct CredentialImportCommit {
|
||||
pub outbound_proxy: Option<super::proxies::ImportProxyBinding>,
|
||||
@@ -134,7 +134,7 @@ pub struct CredentialImportCommit {
|
||||
pub prepared: PreparedCredentialImport,
|
||||
}
|
||||
|
||||
/// OAuth pending owner 的中立身份;不编码具体 Provider 的 Redis key 或 JSON。
|
||||
/// OAuth pending owner 的中立身份;不编码具体 Provider 的 Redis key 或 JSON
|
||||
#[derive(Clone, PartialEq, Eq)]
|
||||
pub enum AuthorizationOwner {
|
||||
AdminSession { admin_user_id: String },
|
||||
@@ -148,7 +148,7 @@ impl fmt::Debug for AuthorizationOwner {
|
||||
}
|
||||
}
|
||||
|
||||
/// Provider 写入 pending payload 与 Redis owner binding 所需的全部中立字段。
|
||||
/// Provider 写入 pending payload 与 Redis owner binding 所需的全部中立字段
|
||||
#[derive(Clone, PartialEq, Eq)]
|
||||
pub struct AuthorizationOwnerBinding {
|
||||
owner: AuthorizationOwner,
|
||||
@@ -193,17 +193,17 @@ impl fmt::Debug for AuthorizationOwnerBinding {
|
||||
}
|
||||
}
|
||||
|
||||
/// OAuth 完成时应创建新账号还是 CAS 更新既有 credential。
|
||||
/// OAuth 完成时应创建新账号还是 CAS 更新既有 credential
|
||||
///
|
||||
/// 重新授权只绑定稳定的账号身份:credential revision 由服务端在临近写入时读取,
|
||||
/// 长流程期间的后台刷新不得让恢复操作失效。
|
||||
/// 长流程期间的后台刷新不得让恢复操作失效
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub enum AuthorizationMutationTarget {
|
||||
Create { name: String },
|
||||
Reauthorize { account_id: ProviderAccountId },
|
||||
}
|
||||
|
||||
/// 必须完整进入 Provider opaque pending payload、并在 complete 后原样恢复的事务信封。
|
||||
/// 必须完整进入 Provider opaque pending payload、并在 complete 后原样恢复的事务信封
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct PendingAuthorizationMutation {
|
||||
provider_kind: ProviderKind,
|
||||
@@ -269,7 +269,7 @@ impl PendingAuthorizationMutation {
|
||||
}
|
||||
|
||||
impl PendingAuthorizationMutation {
|
||||
/// 编码 v1 中立事务字段;版本号的位置和 Provider 私有外层文档由 adapter 保持。
|
||||
/// 编码 v1 中立事务字段;版本号的位置和 Provider 私有外层文档由 adapter 保持
|
||||
#[must_use]
|
||||
pub fn to_storage_v1(&self) -> Map<String, Value> {
|
||||
let target = match &self.target {
|
||||
@@ -311,11 +311,11 @@ impl PendingAuthorizationMutation {
|
||||
document
|
||||
}
|
||||
|
||||
/// 恢复 adapter 已确认版本为 v1 的中立事务字段。
|
||||
/// 恢复 adapter 已确认版本为 v1 的中立事务字段
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// 文档字段、Provider kind 或账号身份非法时返回错误,不携带原始 pending 内容。
|
||||
/// 文档字段、Provider kind 或账号身份非法时返回错误,不携带原始 pending 内容
|
||||
pub fn from_storage_v1(value: Value) -> Result<Self, AdminError> {
|
||||
let invalid = || AdminError::invalid("invalid pending authorization mutation");
|
||||
let document: StoredAuthorizationMutationV1 =
|
||||
@@ -385,7 +385,7 @@ enum StoredAuthorizationOwnerV1 {
|
||||
System,
|
||||
}
|
||||
|
||||
/// Provider 授权请求。
|
||||
/// Provider 授权请求
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct StartAuthorization {
|
||||
pub context: MutationContext,
|
||||
@@ -394,7 +394,7 @@ pub struct StartAuthorization {
|
||||
pub outbound_proxy: Option<super::proxies::AccountProxySelection>,
|
||||
}
|
||||
|
||||
/// OAuth 流程启动结果。
|
||||
/// OAuth 流程启动结果
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct AuthorizationStarted {
|
||||
pub flow_id: String,
|
||||
@@ -402,35 +402,37 @@ pub struct AuthorizationStarted {
|
||||
pub expires_at: DateTime<Utc>,
|
||||
}
|
||||
|
||||
/// refresh/rotate/reauthorize 的 lease 或 completion 生命周期。
|
||||
/// refresh/rotate/reauthorize 的 lease 或 completion 生命周期
|
||||
///
|
||||
/// 该 guard 不可 Clone。Admin 只能在 Store CAS 与审计事务成功后调用 `finish`;失败路径直接
|
||||
/// drop,使 Provider 可以释放 lease 或执行补偿。
|
||||
/// 该 guard 不可 Clone
|
||||
/// Admin 只能在 Store CAS 与审计事务成功后调用 `finish`;失败路径直接
|
||||
/// drop,使 Provider 可以释放 lease 或执行补偿
|
||||
pub trait CredentialCommitGuard: Send + 'static {
|
||||
fn finish(self: Box<Self>);
|
||||
}
|
||||
|
||||
/// OAuth pending claim 在 Store 事务结果确定后执行的显式结算动作。
|
||||
/// OAuth pending claim 在 Store 事务结果确定后执行的显式结算动作
|
||||
///
|
||||
/// OAuth provider 必须在 credential 准备阶段持有 claim:Store 提交成功后消费 flow,提交或
|
||||
/// 校验失败后释放 flow。异步结算不能依赖 `Drop`,否则请求提前返回会让同一授权流无谓地
|
||||
/// 进入短暂的不可重试状态。
|
||||
/// 校验失败后释放 flow
|
||||
/// 异步结算不能依赖 `Drop`,否则请求提前返回会让同一授权流无谓地
|
||||
/// 进入短暂的不可重试状态
|
||||
#[async_trait]
|
||||
pub trait AuthorizationCommitGuard: Send + 'static {
|
||||
/// Store 已提交 OAuth credential 后消费对应的一次性 flow。
|
||||
/// Store 已提交 OAuth credential 后消费对应的一次性 flow
|
||||
async fn commit(self: Box<Self>) -> Result<(), AdminError>;
|
||||
|
||||
/// Store 未提交 OAuth credential 时释放 claim,允许同一 flow 重试。
|
||||
/// Store 未提交 OAuth credential 时释放 claim,允许同一 flow 重试
|
||||
async fn abort(self: Box<Self>) -> Result<(), AdminError>;
|
||||
}
|
||||
|
||||
/// OAuth complete 后由 Provider 返回的准备结果;Store 仍是唯一提交者。
|
||||
/// OAuth complete 后由 Provider 返回的准备结果;Store 仍是唯一提交者
|
||||
pub enum PreparedAuthorizationCredential {
|
||||
Create(Box<PreparedCredentialCreate>),
|
||||
Reauthorize(Box<PreparedCredentialRotation>),
|
||||
}
|
||||
|
||||
/// Provider 从 opaque pending payload 恢复的信封与已验证 credential 必须一起返回。
|
||||
/// Provider 从 opaque pending payload 恢复的信封与已验证 credential 必须一起返回
|
||||
pub struct PreparedAuthorizationCommit {
|
||||
pub pending: PendingAuthorizationMutation,
|
||||
pub credential: PreparedAuthorizationCredential,
|
||||
@@ -447,14 +449,14 @@ impl fmt::Debug for PreparedAuthorizationCommit {
|
||||
}
|
||||
}
|
||||
|
||||
/// Store 可持久化的 OAuth credential facts,不携带 Provider guard。
|
||||
/// Store 可持久化的 OAuth credential facts,不携带 Provider guard
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub enum AuthorizationCredentialCommit {
|
||||
Create(Box<PreparedCredentialCreate>),
|
||||
Reauthorize(Box<PreparedCredentialRotationFacts>),
|
||||
}
|
||||
|
||||
/// Admin 交给 Store 的 OAuth 原子事务命令。
|
||||
/// Admin 交给 Store 的 OAuth 原子事务命令
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct AuthorizationCommit {
|
||||
pub key: AuthorizationReceiptKey,
|
||||
@@ -463,7 +465,7 @@ pub struct AuthorizationCommit {
|
||||
pub credential: AuthorizationCredentialCommit,
|
||||
}
|
||||
|
||||
/// 回执以 Provider、授权流和管理员身份绑定;不持久化原始 flow 或回调材料。
|
||||
/// 回执以 Provider、授权流和管理员身份绑定;不持久化原始 flow 或回调材料
|
||||
#[derive(Clone, PartialEq, Eq, PartialOrd, Ord)]
|
||||
pub struct AuthorizationReceiptKey {
|
||||
provider_kind: ProviderKind,
|
||||
@@ -531,10 +533,10 @@ pub struct AuthorizationCommitResult {
|
||||
pub newly_committed: bool,
|
||||
}
|
||||
|
||||
/// OAuth 准备结果拆解后的 Store 命令与两个结算 guard。
|
||||
/// OAuth 准备结果拆解后的 Store 命令与两个结算 guard
|
||||
///
|
||||
/// Store 成功时先结束 credential guard,再消费 OAuth claim;失败时丢弃 credential guard 并
|
||||
/// 释放 OAuth claim,使同一授权流可以重试。
|
||||
/// 释放 OAuth claim,使同一授权流可以重试
|
||||
pub(crate) struct AuthorizationCommitSettlement {
|
||||
pub(crate) command: AuthorizationCommit,
|
||||
pub(crate) credential_guard: Option<Box<dyn CredentialCommitGuard>>,
|
||||
@@ -554,7 +556,7 @@ impl PreparedAuthorizationCommit {
|
||||
}
|
||||
}
|
||||
|
||||
/// 让 OAuth claim 覆盖准备完成到 Store 事务结算之间的全部窗口。
|
||||
/// 让 OAuth claim 覆盖准备完成到 Store 事务结算之间的全部窗口
|
||||
#[must_use]
|
||||
pub fn with_authorization_guard(mut self, guard: Box<dyn AuthorizationCommitGuard>) -> Self {
|
||||
self.authorization_guard = Some(guard);
|
||||
@@ -599,7 +601,7 @@ impl PreparedAuthorizationCommit {
|
||||
}
|
||||
}
|
||||
|
||||
/// 完成 Provider OAuth 流程。
|
||||
/// 完成 Provider OAuth 流程
|
||||
#[derive(Clone, PartialEq, Eq)]
|
||||
pub struct CompleteAuthorization {
|
||||
pub settings: Option<AccountImportSettings>,
|
||||
@@ -619,14 +621,14 @@ impl fmt::Debug for CompleteAuthorization {
|
||||
}
|
||||
}
|
||||
|
||||
/// Credential 生命周期写操作。
|
||||
/// Credential 生命周期写操作
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct CredentialMutation {
|
||||
pub context: MutationContext,
|
||||
pub account_id: ProviderAccountId,
|
||||
}
|
||||
|
||||
/// Credential 写入提交结果。
|
||||
/// Credential 写入提交结果
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct CredentialMutationResult {
|
||||
pub config_revision: Revision,
|
||||
@@ -634,28 +636,28 @@ pub struct CredentialMutationResult {
|
||||
pub credential_revision: Option<Revision>,
|
||||
}
|
||||
|
||||
/// 同一 Provider 管理范围内的 credential 批量删除。
|
||||
/// 同一 Provider 管理范围内的 credential 批量删除
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct CredentialDeletion {
|
||||
pub context: MutationContext,
|
||||
pub account_ids: Vec<ProviderAccountId>,
|
||||
}
|
||||
|
||||
/// Credential 批量删除提交结果。
|
||||
/// Credential 批量删除提交结果
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct CredentialDeletionResult {
|
||||
pub config_revision: Revision,
|
||||
pub account_ids: Vec<ProviderAccountId>,
|
||||
}
|
||||
|
||||
/// Provider-owned token 轮换命令。
|
||||
/// Provider-owned token 轮换命令
|
||||
pub struct RotateCredential {
|
||||
pub mutation: CredentialMutation,
|
||||
pub provider_material: ProviderDocument,
|
||||
pub settings: Option<super::accounts::UpdateAccount>,
|
||||
}
|
||||
|
||||
/// Provider 校验手工轮换材料时所需的非事务输入。
|
||||
/// Provider 校验手工轮换材料时所需的非事务输入
|
||||
pub struct PrepareCredentialRotation {
|
||||
pub account: AccountRecord,
|
||||
pub provider_material: ProviderDocument,
|
||||
@@ -671,7 +673,7 @@ impl fmt::Debug for PrepareCredentialRotation {
|
||||
}
|
||||
}
|
||||
|
||||
/// Provider 已验证、可由 Store 以 credential revision CAS 原子提交的轮换 facts。
|
||||
/// Provider 已验证、可由 Store 以 credential revision CAS 原子提交的轮换 facts
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct PreparedCredentialRotationFacts {
|
||||
pub account_id: ProviderAccountId,
|
||||
@@ -681,9 +683,9 @@ pub struct PreparedCredentialRotationFacts {
|
||||
pub name: String,
|
||||
pub email: Option<String>,
|
||||
pub plan_type: Option<String>,
|
||||
/// Token 刷新保留提交时的资料,不以准备阶段的副本覆盖新套餐。
|
||||
/// Token 刷新保留提交时的资料,不以准备阶段的副本覆盖新套餐
|
||||
pub preserve_profile: bool,
|
||||
/// 连接配置变更保留当前凭据健康状态与错误事实。
|
||||
/// 连接配置变更保留当前凭据健康状态与错误事实
|
||||
pub preserve_credential_state: bool,
|
||||
pub provider_material: ProviderDocument,
|
||||
pub has_refresh_token: bool,
|
||||
@@ -691,7 +693,7 @@ pub struct PreparedCredentialRotationFacts {
|
||||
pub next_refresh_at: Option<DateTime<Utc>>,
|
||||
}
|
||||
|
||||
/// Provider 返回的轮换准备结果;guard 必须覆盖后续 Store CAS 与审计事务。
|
||||
/// Provider 返回的轮换准备结果;guard 必须覆盖后续 Store CAS 与审计事务
|
||||
pub struct PreparedCredentialRotation {
|
||||
facts: PreparedCredentialRotationFacts,
|
||||
guard: Box<dyn CredentialCommitGuard>,
|
||||
@@ -706,7 +708,7 @@ impl PreparedCredentialRotation {
|
||||
Self { facts, guard }
|
||||
}
|
||||
|
||||
/// 仅修改连接配置时,不把配置更新视为凭据恢复成功。
|
||||
/// 仅修改连接配置时,不把配置更新视为凭据恢复成功
|
||||
#[must_use]
|
||||
pub fn preserving_credential_state(mut self) -> Self {
|
||||
self.facts.preserve_credential_state = true;
|
||||
@@ -739,7 +741,7 @@ impl fmt::Debug for PreparedCredentialRotation {
|
||||
}
|
||||
}
|
||||
|
||||
/// Admin 交给 Store 的轮换或 refresh 事务命令。
|
||||
/// Admin 交给 Store 的轮换或 refresh 事务命令
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct CredentialRotationCommit {
|
||||
pub prepared: PreparedCredentialRotationFacts,
|
||||
@@ -757,16 +759,16 @@ impl fmt::Debug for RotateCredential {
|
||||
}
|
||||
}
|
||||
|
||||
/// 通用账号用量能否可靠归属到一个 Provider quota 窗口。
|
||||
/// 通用账号用量能否可靠归属到一个 Provider quota 窗口
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum QuotaLocalUsageAttribution {
|
||||
/// 窗口覆盖账号的全部请求,可按账号与时间范围聚合。
|
||||
/// 窗口覆盖账号的全部请求,可按账号与时间范围聚合
|
||||
AccountWide,
|
||||
/// 窗口需要 Provider / 模型级归属,通用账号聚合不可用。
|
||||
/// 窗口需要 Provider / 模型级归属,通用账号聚合不可用
|
||||
Unavailable,
|
||||
}
|
||||
|
||||
/// Provider quota bucket 中窗口的官方位置语义。
|
||||
/// Provider quota bucket 中窗口的官方位置语义
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum ProviderQuotaWindowRole {
|
||||
Primary,
|
||||
@@ -785,7 +787,7 @@ impl ProviderQuotaWindowRole {
|
||||
}
|
||||
}
|
||||
|
||||
/// 一个 Provider quota 窗口的公共投影。
|
||||
/// 一个 Provider quota 窗口的公共投影
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct ProviderQuotaWindow {
|
||||
pub key: String,
|
||||
@@ -803,14 +805,14 @@ pub struct ProviderQuotaWindow {
|
||||
pub provider_data: Option<ProviderDocument>,
|
||||
}
|
||||
|
||||
/// 账号用量统计周期,按周优先、月次之选择;短期限流窗口不参与统计面板。
|
||||
/// 账号用量统计周期,按周优先、月次之选择;短期限流窗口不参与统计面板
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
|
||||
pub enum AccountUsagePeriod {
|
||||
Weekly,
|
||||
Monthly,
|
||||
}
|
||||
|
||||
/// Provider 解释 quota 所需的公共请求事实。
|
||||
/// Provider 解释 quota 所需的公共请求事实
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct ProviderQuotaRequest {
|
||||
pub account_id: ProviderAccountId,
|
||||
@@ -818,21 +820,21 @@ pub struct ProviderQuotaRequest {
|
||||
pub rolling_usage: Option<AccountUsage>,
|
||||
}
|
||||
|
||||
/// Provider 已解析的 quota 结果及其不透明差异字段。
|
||||
/// Provider 已解析的 quota 结果及其不透明差异字段
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct ProviderQuota {
|
||||
/// 上游额度响应明确提供的套餐,可用于补全账号展示。
|
||||
/// 上游额度响应明确提供的套餐,可用于补全账号展示
|
||||
pub plan_type: Option<String>,
|
||||
pub observed_at: Option<DateTime<Utc>>,
|
||||
pub refresh_token_expires_at: Option<DateTime<Utc>>,
|
||||
pub windows: Vec<ProviderQuotaWindow>,
|
||||
pub credits: Option<ProviderQuotaCredits>,
|
||||
/// 展示用快照级触顶事实(顶层或任一窗口触顶);不参与账号五态派生。
|
||||
/// 展示用快照级触顶事实(顶层或任一窗口触顶);不参与账号五态派生
|
||||
pub limit_reached: bool,
|
||||
pub provider_data: Option<ProviderDocument>,
|
||||
}
|
||||
|
||||
/// 上游点数余额的展示事实,保留原始十进制文本以避免精度损失。
|
||||
/// 上游点数余额的展示事实,保留原始十进制文本以避免精度损失
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct ProviderQuotaCredits {
|
||||
pub has_credits: bool,
|
||||
@@ -840,7 +842,7 @@ pub struct ProviderQuotaCredits {
|
||||
pub balance: Option<String>,
|
||||
}
|
||||
|
||||
/// 空值和 `unknown` 代表未提供套餐;新的套餐标识仍按明确值保留。
|
||||
/// 空值和 `unknown` 代表未提供套餐;新的套餐标识仍按明确值保留
|
||||
pub(crate) fn explicit_plan_type(value: Option<&str>) -> Option<&str> {
|
||||
value.filter(|value| {
|
||||
let value = value.trim();
|
||||
@@ -848,7 +850,7 @@ pub(crate) fn explicit_plan_type(value: Option<&str>) -> Option<&str> {
|
||||
})
|
||||
}
|
||||
|
||||
/// 按需查询的订阅周期,不参与额度或账号可用性判断。
|
||||
/// 按需查询的订阅周期,不参与额度或账号可用性判断
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct ProviderSubscription {
|
||||
pub starts_at: Option<DateTime<Utc>>,
|
||||
@@ -859,14 +861,14 @@ pub struct ProviderSubscription {
|
||||
pub observed_at: DateTime<Utc>,
|
||||
}
|
||||
|
||||
/// 按需汇聚的个人信息;资料查询失败不丢弃可用的订阅结果。
|
||||
/// 按需汇聚的个人信息;资料查询失败不丢弃可用的订阅结果
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct AccountPersonalInfo {
|
||||
pub profile: Result<ProviderProfileStatistics, AdminError>,
|
||||
pub subscription: Option<ProviderSubscription>,
|
||||
}
|
||||
|
||||
/// Provider 官方个人资料中的累计摘要。
|
||||
/// Provider 官方个人资料中的累计摘要
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct ProviderProfileStatisticsSummary {
|
||||
pub total_text_tokens: Option<u64>,
|
||||
@@ -876,14 +878,14 @@ pub struct ProviderProfileStatisticsSummary {
|
||||
pub longest_streak_days: Option<u64>,
|
||||
}
|
||||
|
||||
/// Provider 官方个人资料中的单日 Token bucket。
|
||||
/// Provider 官方个人资料中的单日 Token bucket
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct ProviderProfileDailyUsage {
|
||||
pub date: NaiveDate,
|
||||
pub tokens: u64,
|
||||
}
|
||||
|
||||
/// Provider 官方个人资料中的插件或 Skill 调用排行。
|
||||
/// Provider 官方个人资料中的插件或 Skill 调用排行
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct ProviderProfileInvocation {
|
||||
pub invocation_type: String,
|
||||
@@ -894,7 +896,7 @@ pub struct ProviderProfileInvocation {
|
||||
pub usage_count: Option<u64>,
|
||||
}
|
||||
|
||||
/// Provider 官方个人资料中的活动洞察。
|
||||
/// Provider 官方个人资料中的活动洞察
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct ProviderProfileActivityInsights {
|
||||
pub fast_mode_percent: Option<f64>,
|
||||
@@ -906,7 +908,7 @@ pub struct ProviderProfileActivityInsights {
|
||||
pub invocations: Option<Vec<ProviderProfileInvocation>>,
|
||||
}
|
||||
|
||||
/// Provider 已解释的官方个人资料统计。
|
||||
/// Provider 已解释的官方个人资料统计
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct ProviderProfileStatistics {
|
||||
pub display_name: Option<String>,
|
||||
@@ -918,16 +920,16 @@ pub struct ProviderProfileStatistics {
|
||||
pub activity_insights: ProviderProfileActivityInsights,
|
||||
}
|
||||
|
||||
/// Provider 头像正文的中立字节流;公共层不解释 MIME,也不限制总字节数。
|
||||
/// Provider 头像正文的中立字节流;公共层不解释 MIME,也不限制总字节数
|
||||
pub type ProviderProfileAvatarStream =
|
||||
Pin<Box<dyn Stream<Item = Result<Bytes, ProviderProfileAvatarStreamError>> + Send + 'static>>;
|
||||
|
||||
/// Provider 头像流在响应开始后的通用失败,不携带上游 URL 或正文。
|
||||
/// Provider 头像流在响应开始后的通用失败,不携带上游 URL 或正文
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
#[error("provider profile avatar stream failed")]
|
||||
pub struct ProviderProfileAvatarStreamError;
|
||||
|
||||
/// Provider 已校验并打开的官方头像响应。
|
||||
/// Provider 已校验并打开的官方头像响应
|
||||
pub struct ProviderProfileAvatar {
|
||||
pub content_type: Option<String>,
|
||||
pub content_length: Option<u64>,
|
||||
@@ -947,7 +949,7 @@ impl fmt::Debug for ProviderProfileAvatar {
|
||||
}
|
||||
}
|
||||
|
||||
/// Provider 返回的一张安全主动额度重置卡。
|
||||
/// Provider 返回的一张安全主动额度重置卡
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct ProviderResetCredit {
|
||||
pub id: String,
|
||||
@@ -957,14 +959,14 @@ pub struct ProviderResetCredit {
|
||||
pub reset_type: Option<String>,
|
||||
}
|
||||
|
||||
/// Provider 主动额度重置卡列表。
|
||||
/// Provider 主动额度重置卡列表
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct ProviderResetCredits {
|
||||
pub available_count: u64,
|
||||
pub credits: Vec<ProviderResetCredit>,
|
||||
}
|
||||
|
||||
/// 一次主动额度重置卡消费命令。
|
||||
/// 一次主动额度重置卡消费命令
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct ConsumeProviderResetCredit {
|
||||
pub account_id: ProviderAccountId,
|
||||
@@ -972,7 +974,7 @@ pub struct ConsumeProviderResetCredit {
|
||||
pub redeem_request_id: Uuid,
|
||||
}
|
||||
|
||||
/// Provider 返回的主动额度重置卡消费结果。
|
||||
/// Provider 返回的主动额度重置卡消费结果
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct ProviderResetCreditResult {
|
||||
pub code: String,
|
||||
@@ -980,17 +982,18 @@ pub struct ProviderResetCreditResult {
|
||||
}
|
||||
|
||||
impl ProviderQuota {
|
||||
/// 保留账号已有的套餐子类型,仅在缺失时使用上游额度快照补全。
|
||||
/// 保留账号已有的套餐子类型,仅在缺失时使用上游额度快照补全
|
||||
pub(crate) fn fill_missing_plan_type(&self, account_plan_type: &mut Option<String>) {
|
||||
if explicit_plan_type(account_plan_type.as_deref()).is_none() {
|
||||
*account_plan_type = explicit_plan_type(self.plan_type.as_deref()).map(str::to_owned);
|
||||
}
|
||||
}
|
||||
|
||||
/// 选择账号用量统计的周/月窗口,同时返回展示所需的周期事实。
|
||||
/// 选择账号用量统计的周/月窗口,同时返回展示所需的周期事实
|
||||
///
|
||||
/// 只使用边界完整、可归属到整个账号的窗口;周优先于月,同周期保持
|
||||
/// Provider 投影顺序。没有符合条件的窗口时不回退到短期或历史累计。
|
||||
/// Provider 投影顺序
|
||||
/// 没有符合条件的窗口时不回退到短期或历史累计
|
||||
#[must_use]
|
||||
pub fn usage_window(&self) -> Option<(&ProviderQuotaWindow, AccountUsagePeriod)> {
|
||||
self.usage_windows()
|
||||
@@ -998,7 +1001,7 @@ impl ProviderQuota {
|
||||
.min_by_key(|(_, period)| *period)
|
||||
}
|
||||
|
||||
/// 统计与预测共用窗口归属规则,避免把模型专属桶或短期限流当作账号容量。
|
||||
/// 统计与预测共用窗口归属规则,避免把模型专属桶或短期限流当作账号容量
|
||||
pub(crate) fn usage_windows(
|
||||
&self,
|
||||
) -> impl Iterator<Item = (&ProviderQuotaWindow, AccountUsagePeriod)> {
|
||||
@@ -1020,11 +1023,12 @@ impl ProviderQuota {
|
||||
})
|
||||
}
|
||||
|
||||
/// 返回 Dashboard 使用的代表性额度比例。
|
||||
/// 返回 Dashboard 使用的代表性额度比例
|
||||
///
|
||||
/// 优先使用覆盖账号全部请求的窗口;同一归属范围内再依次使用短周期、周、月
|
||||
/// 和其它窗口,同一优先级取较高的已用比例。这里只解释跨 Provider 共享的窗口
|
||||
/// 语义,绝不读取 Provider 私有 JSON。
|
||||
/// 和其它窗口,同一优先级取较高的已用比例
|
||||
/// 这里只解释跨 Provider 共享的窗口
|
||||
/// 语义,绝不读取 Provider 私有 JSON
|
||||
#[must_use]
|
||||
pub fn representative_used_percent(&self) -> Option<f64> {
|
||||
if self.limit_reached {
|
||||
@@ -1034,7 +1038,7 @@ impl ProviderQuota {
|
||||
.map(|(_, used_percent)| used_percent)
|
||||
}
|
||||
|
||||
/// 选择 Dashboard 展示的真实窗口;没有百分比时仍保留 Provider 的滚动窗口语义。
|
||||
/// 选择 Dashboard 展示的真实窗口;没有百分比时仍保留 Provider 的滚动窗口语义
|
||||
#[must_use]
|
||||
pub fn representative_window(&self) -> Option<&ProviderQuotaWindow> {
|
||||
self.representative_used_window()
|
||||
@@ -1048,10 +1052,10 @@ impl ProviderQuota {
|
||||
})
|
||||
}
|
||||
|
||||
/// 将已确认的账号级额度耗尽事实投影到展示窗口。
|
||||
/// 将已确认的账号级额度耗尽事实投影到展示窗口
|
||||
///
|
||||
/// Provider 已指出具体触顶窗口时只归一化这些窗口;否则归一化 Dashboard
|
||||
/// 同样会选择的代表窗口,避免把多个独立额度窗口全部伪造成已用尽。
|
||||
/// 同样会选择的代表窗口,避免把多个独立额度窗口全部伪造成已用尽
|
||||
pub fn apply_limit_reached_display(&mut self) {
|
||||
if !self.limit_reached {
|
||||
return;
|
||||
@@ -1131,21 +1135,21 @@ fn is_week_window(seconds: u64) -> bool {
|
||||
seconds > 0 && seconds.abs_diff(WEEK_SECONDS) <= WEEK_SECONDS / 20
|
||||
}
|
||||
|
||||
/// Provider 实时模型目录的一项。
|
||||
/// Provider 实时模型目录的一项
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct ProviderModel {
|
||||
pub id: UpstreamModelId,
|
||||
pub name: String,
|
||||
}
|
||||
|
||||
/// Provider 实时模型目录。
|
||||
/// Provider 实时模型目录
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct ProviderModels {
|
||||
pub models: Vec<ProviderModel>,
|
||||
pub observed_at: Option<DateTime<Utc>>,
|
||||
}
|
||||
|
||||
/// Provider 原生模型目录正文;wire 语义由对应 Provider 拥有,公共层只搬运不解释字段。
|
||||
/// Provider 原生模型目录正文;wire 语义由对应 Provider 拥有,公共层只搬运不解释字段
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct ProviderModelCatalogDocument {
|
||||
pub document: RawJsonPayload,
|
||||
@@ -1153,7 +1157,7 @@ pub struct ProviderModelCatalogDocument {
|
||||
pub observed_at: DateTime<Utc>,
|
||||
}
|
||||
|
||||
/// 插件账号回调的服务端过滤条件;Provider 缺省时跨全部已注册类型分页。
|
||||
/// 插件账号回调的服务端过滤条件;Provider 缺省时跨全部已注册类型分页
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct PluginAccountListQuery {
|
||||
pub provider_kind: Option<ProviderKind>,
|
||||
@@ -1167,7 +1171,7 @@ pub struct PluginAccountPage {
|
||||
pub next_cursor: Option<ProviderAccountId>,
|
||||
}
|
||||
|
||||
/// 原始凭据与当前账号 revision 的同一读取结果。
|
||||
/// 原始凭据与当前账号 revision 的同一读取结果
|
||||
#[derive(Clone, PartialEq)]
|
||||
pub struct PluginAccountCredential {
|
||||
pub account: AccountRecord,
|
||||
@@ -1184,7 +1188,7 @@ impl fmt::Debug for PluginAccountCredential {
|
||||
}
|
||||
}
|
||||
|
||||
/// Runtime 已完成 wire 校验、Admin 仍需绑定当前账号和 revision 的 prepared facts。
|
||||
/// Runtime 已完成 wire 校验、Admin 仍需绑定当前账号和 revision 的 prepared facts
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub enum PreparedPluginAccountSave {
|
||||
Create(PreparedCredentialCreate),
|
||||
@@ -1201,20 +1205,20 @@ pub struct PluginAccountSaveResult {
|
||||
pub credential_revision: Revision,
|
||||
}
|
||||
|
||||
/// Provider 执行 refresh 时所需的当前公共账号事实。
|
||||
/// Provider 执行 refresh 时所需的当前公共账号事实
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct PrepareCredentialRefresh {
|
||||
pub account: AccountRecord,
|
||||
}
|
||||
|
||||
/// Provider 敏感导出结果。
|
||||
/// Provider 敏感导出结果
|
||||
pub struct ProviderExport {
|
||||
pub provider_kind: ProviderKind,
|
||||
pub account_ids: Vec<ProviderAccountId>,
|
||||
pub document: ProviderDocument,
|
||||
}
|
||||
|
||||
/// Store 为 Provider 导出序列化准备的最小输入;material 对公共层保持不透明。
|
||||
/// Store 为 Provider 导出序列化准备的最小输入;material 对公共层保持不透明
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct ProviderExportCredentialInput {
|
||||
pub account: AccountRecord,
|
||||
@@ -1232,20 +1236,20 @@ impl fmt::Debug for ProviderExport {
|
||||
}
|
||||
}
|
||||
|
||||
/// 统一账号目录的一行完整结果。
|
||||
/// 统一账号目录的一行完整结果
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct AccountDirectoryItem {
|
||||
pub account: AccountRecord,
|
||||
pub capacity: super::accounts::AccountCapacity,
|
||||
pub capabilities: super::accounts::ProviderAccountCapabilities,
|
||||
/// Provider 提供的套餐展示名称;未识别到套餐时为空。
|
||||
/// Provider 提供的套餐展示名称;未识别到套餐时为空
|
||||
pub plan_type_display: Option<String>,
|
||||
pub projection: gateway_core::account::AccountStatusProjection,
|
||||
pub usage: Option<super::accounts::AccountUsage>,
|
||||
pub quota: ProviderQuota,
|
||||
}
|
||||
|
||||
/// 统一账号目录页。
|
||||
/// 统一账号目录页
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct AccountDirectoryPage {
|
||||
pub config_revision: Revision,
|
||||
@@ -1254,14 +1258,14 @@ pub struct AccountDirectoryPage {
|
||||
pub summary: AccountSummary,
|
||||
}
|
||||
|
||||
/// 凭据刷新提交后的完整账号结果。
|
||||
/// 凭据刷新提交后的完整账号结果
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct AccountRefreshResult {
|
||||
pub config_revision: Revision,
|
||||
pub account: AccountDirectoryItem,
|
||||
}
|
||||
|
||||
/// 多 Provider 导出文档集合。
|
||||
/// 多 Provider 导出文档集合
|
||||
pub struct AccountExportBundle {
|
||||
pub exported_at: DateTime<Utc>,
|
||||
pub documents: Vec<ProviderExport>,
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
//! 可复用的账号出口配置,以及脱敏后的连通性测试结果。
|
||||
//! 可复用的账号出口配置,以及脱敏后的连通性测试结果
|
||||
|
||||
use chrono::{DateTime, Utc};
|
||||
use gateway_core::account::{OutboundProxy, RequestLocation};
|
||||
@@ -39,7 +39,7 @@ pub struct ProxyAccountRef {
|
||||
pub enabled: bool,
|
||||
}
|
||||
|
||||
/// 按代理查询关联账号,分页与搜索均在存储层执行。
|
||||
/// 按代理查询关联账号,分页与搜索均在存储层执行
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct ProxyAccountListQuery {
|
||||
pub proxy_id: String,
|
||||
@@ -94,7 +94,7 @@ pub struct ProxyPage {
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct NewProxy {
|
||||
pub auto_location: bool,
|
||||
/// 由管理用例探测,随配置原子保存,不接受 HTTP 调用方提供结果。
|
||||
/// 由管理用例探测,随配置原子保存,不接受 HTTP 调用方提供结果
|
||||
pub test: Option<ProxyTestResult>,
|
||||
pub location: Option<gateway_core::account::RequestLocation>,
|
||||
pub name: String,
|
||||
@@ -105,7 +105,7 @@ pub struct NewProxy {
|
||||
pub struct UpdateProxy {
|
||||
pub auto_location: Option<bool>,
|
||||
pub test: Option<ProxyTestResult>,
|
||||
/// 外层为空保留配置,内层为空恢复全局继承。
|
||||
/// 外层为空保留配置,内层为空恢复全局继承
|
||||
pub location: Option<Option<gateway_core::account::RequestLocation>>,
|
||||
pub id: String,
|
||||
pub revision: Revision,
|
||||
@@ -119,7 +119,7 @@ pub struct ProxyMutation {
|
||||
pub record: ProxyRecord,
|
||||
}
|
||||
|
||||
/// 检测失败与连通性分开,双栈冲突不得继续应用旧位置。
|
||||
/// 检测失败与连通性分开,双栈冲突不得继续应用旧位置
|
||||
#[derive(Debug, Clone, Default, PartialEq, Eq, Deserialize, Serialize)]
|
||||
#[serde(tag = "status", rename_all = "camelCase")]
|
||||
pub enum ProxyLocationDetection {
|
||||
@@ -155,7 +155,7 @@ impl ProxyRecord {
|
||||
}
|
||||
}
|
||||
|
||||
/// 未知出口可暂用上次成功结果,已知出口变化或时区冲突必须撤销旧覆盖。
|
||||
/// 未知出口可暂用上次成功结果,已知出口变化或时区冲突必须撤销旧覆盖
|
||||
#[must_use]
|
||||
pub fn detected_location_after_test(
|
||||
&self,
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
//! 账号额度的只读容量估算,不参与金额结算或账号调度。
|
||||
//! 账号额度的只读容量估算,不参与金额结算或账号调度
|
||||
|
||||
use chrono::{DateTime, Duration, Utc};
|
||||
|
||||
@@ -40,9 +40,9 @@ pub struct QuotaForecastSource {
|
||||
pub usd: Option<f64>,
|
||||
}
|
||||
|
||||
/// 为目标周期选择唯一的预测源窗口;缺少同周期窗口时复用最短的可统计窗口。
|
||||
/// 为目标周期选择唯一的预测源窗口;缺少同周期窗口时复用最短的可统计窗口
|
||||
///
|
||||
/// Admin 的历史查询与最终投影必须共用这里的顺序,避免查询不会进入响应的窗口。
|
||||
/// Admin 的历史查询与最终投影必须共用这里的顺序,避免查询不会进入响应的窗口
|
||||
pub(crate) fn quota_forecast_source_window(
|
||||
quota: &ProviderQuota,
|
||||
period: AccountUsagePeriod,
|
||||
@@ -53,8 +53,8 @@ pub(crate) fn quota_forecast_source_window(
|
||||
.or_else(|| quota.usage_windows().min_by_key(|(_, period)| *period))
|
||||
}
|
||||
|
||||
/// 优先预测真实的对应窗口;缺少对应周期时只给出明确标识的 7/30 天容量折算。
|
||||
/// 本地日志不能证明站外消耗或完整留存,因此即使样本充足也不声称官方额度。
|
||||
/// 优先预测真实的对应窗口;缺少对应周期时只给出明确标识的 7/30 天容量折算
|
||||
/// 本地日志不能证明站外消耗或完整留存,因此即使样本充足也不声称官方额度
|
||||
#[must_use]
|
||||
pub fn account_quota_forecasts(
|
||||
quota: &ProviderQuota,
|
||||
@@ -178,9 +178,9 @@ impl AccountQuotaForecast {
|
||||
};
|
||||
self.low_sample = sample.sampled_percent < LOW_SAMPLE_PERCENT
|
||||
|| (method == QuotaForecastMethod::Incremental && sample.block_count < 2);
|
||||
// 漏记和个别缺失只影响精度,仍按已记录数值估算,不按请求数补齐未知消耗。
|
||||
// 预测是近似展示值;不复用为账单金额,也不把月折算当成自然月或额外余额。
|
||||
// 已发生的用量保持本周期累计,只把近期消耗比例用于尚未使用的额度。
|
||||
// 漏记和个别缺失只影响精度,仍按已记录数值估算,不按请求数补齐未知消耗
|
||||
// 预测是近似展示值;不复用为账单金额,也不把月折算当成自然月或额外余额
|
||||
// 已发生的用量保持本周期累计,只把近期消耗比例用于尚未使用的额度
|
||||
let factor = self.target_seconds as f64 / seconds as f64;
|
||||
let remaining_factor = (100.0 - percent) / sample.sampled_percent;
|
||||
let remaining_tokens = Some(sample.usage.tokens)
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
//! 预测专用的配对采样;不改变完整交付用量或费用账本的统计口径。
|
||||
//! 预测专用的配对采样;不改变完整交付用量或费用账本的统计口径
|
||||
|
||||
use chrono::{DateTime, Utc};
|
||||
|
||||
use super::provider_credentials::ProviderDocument;
|
||||
|
||||
/// 限制带 Provider 文档的历史点数量;累计用量仍覆盖整个查询区间。
|
||||
/// 限制带 Provider 文档的历史点数量;累计用量仍覆盖整个查询区间
|
||||
pub const MAX_FORECAST_HISTORY_POINTS: usize = 128;
|
||||
const MIN_BLOCK_PERCENT: f64 = 5.0;
|
||||
const RECENT_BLOCKS: usize = 3;
|
||||
@@ -24,7 +24,7 @@ pub struct QuotaForecastUsage {
|
||||
}
|
||||
|
||||
impl QuotaForecastUsage {
|
||||
/// 累计计数倒退表示不能配对,不用饱和减法掩盖数据断点。
|
||||
/// 累计计数倒退表示不能配对,不用饱和减法掩盖数据断点
|
||||
fn difference(&self, baseline: &Self) -> Option<Self> {
|
||||
let usd = self.usd - baseline.usd;
|
||||
if !usd.is_finite() || usd < -1e-9 {
|
||||
@@ -68,7 +68,7 @@ pub struct QuotaForecastHistory {
|
||||
pub pending_request_count: u64,
|
||||
}
|
||||
|
||||
/// 具体 Provider 从自己的历史文档解释出的额度事实。
|
||||
/// 具体 Provider 从自己的历史文档解释出的额度事实
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct QuotaForecastObservation {
|
||||
pub used_percent: f64,
|
||||
@@ -101,13 +101,13 @@ pub struct QuotaForecastSample {
|
||||
pub observation_count: usize,
|
||||
pub pending_request_count: u64,
|
||||
pub discontinuous: bool,
|
||||
/// 本周期已记录用量;近期采样仅用于预测剩余量,不能覆盖这份累计值。
|
||||
/// 本周期已记录用量;近期采样仅用于预测剩余量,不能覆盖这份累计值
|
||||
pub cycle_usage: QuotaForecastUsage,
|
||||
pub usage: QuotaForecastUsage,
|
||||
}
|
||||
|
||||
/// 对同一额度段的累计点形成至少 5 个百分点的连续块,使用最近三个完整块及尾部。
|
||||
/// 不平均逐请求比值;重复百分比不增加块数,5/10 个百分点仅是量化噪声门槛。
|
||||
/// 对同一额度段的累计点形成至少 5 个百分点的连续块,使用最近三个完整块及尾部
|
||||
/// 不平均逐请求比值;重复百分比不增加块数,5/10 个百分点仅是量化噪声门槛
|
||||
#[must_use]
|
||||
pub fn select_forecast_sample(
|
||||
key: String,
|
||||
@@ -139,7 +139,7 @@ pub fn select_forecast_sample(
|
||||
usage: current.usage.clone(),
|
||||
};
|
||||
history.push(current);
|
||||
// 重置边界不在查询起点时,从首个新观测重新计数,避免把旧周期累计带入总量。
|
||||
// 重置边界不在查询起点时,从首个新观测重新计数,避免把旧周期累计带入总量
|
||||
let mut cycle_baseline = interrupted.then(|| &history[0]);
|
||||
let mut anchors: Vec<&QuotaForecastPoint> = Vec::new();
|
||||
let mut high_water: Option<&QuotaForecastPoint> = None;
|
||||
@@ -149,7 +149,7 @@ pub fn select_forecast_sample(
|
||||
continue;
|
||||
}
|
||||
if point.used_percent < previous.used_percent {
|
||||
// 一个百分点内的回落可能是量化/并发乱序;更大的回落不能跨越。
|
||||
// 一个百分点内的回落可能是量化/并发乱序;更大的回落不能跨越
|
||||
if previous.used_percent - point.used_percent <= 1.0 {
|
||||
continue;
|
||||
}
|
||||
@@ -198,7 +198,7 @@ pub fn select_forecast_sample(
|
||||
sample.sampled_percent = progress;
|
||||
sample.block_count = anchors.len() - baseline_index - 1;
|
||||
sample.usage = usage;
|
||||
// 断点后的新基线已有足够进度时,只消费新段;旧段从不参与本次估算。
|
||||
// 断点后的新基线已有足够进度时,只消费新段;旧段从不参与本次估算
|
||||
sample.discontinuous = false;
|
||||
sample
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
//! 历史数据保留规则;存储实现只接受已校验的窗口。
|
||||
//! 历史数据保留规则;存储实现只接受已校验的窗口
|
||||
|
||||
use super::AdminError;
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
//! Runtime settings 与明文管理员 API Key 的语义模型。
|
||||
//! Runtime settings 与明文管理员 API Key 的语义模型
|
||||
|
||||
use std::{collections::BTreeMap, fmt};
|
||||
|
||||
@@ -8,21 +8,21 @@ use gateway_core::routing::{ProviderKind, PublicModelId, UpstreamModelId};
|
||||
|
||||
use super::Revision;
|
||||
|
||||
/// 客户端模型到上游模型的全局精确映射。
|
||||
/// 客户端模型到上游模型的全局精确映射
|
||||
pub type ModelMappings = BTreeMap<PublicModelId, UpstreamModelId>;
|
||||
|
||||
/// Provider-owned 请求画像选择;Provider ID 是唯一命名空间。
|
||||
/// Provider-owned 请求画像选择;Provider ID 是唯一命名空间
|
||||
pub type ProviderRequestProfiles =
|
||||
BTreeMap<ProviderKind, gateway_core::account::OpaqueProviderData>;
|
||||
|
||||
/// Provider-owned 请求画像的部分更新;`None` 只表示显式删除该 Provider 的历史选择。
|
||||
/// Provider-owned 请求画像的部分更新;`None` 只表示显式删除该 Provider 的历史选择
|
||||
pub type ProviderRequestProfileUpdates =
|
||||
BTreeMap<ProviderKind, Option<gateway_core::account::OpaqueProviderData>>;
|
||||
|
||||
/// 账号调度策略;由 Core 拥有稳定值与 wire 映射。
|
||||
/// 账号调度策略;由 Core 拥有稳定值与 wire 映射
|
||||
pub use gateway_core::account::RotationStrategy;
|
||||
|
||||
/// 完整运行设置事实。
|
||||
/// 完整运行设置事实
|
||||
#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct RuntimeSettings {
|
||||
@@ -60,13 +60,13 @@ pub struct RuntimeSettings {
|
||||
pub updated_at: DateTime<Utc>,
|
||||
}
|
||||
|
||||
/// 原子替换运行设置的命令。
|
||||
/// 原子替换运行设置的命令
|
||||
#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct ReplaceRuntimeSettings {
|
||||
/// 读取设置时的版本;与写入在同一事务内比较,防止覆盖并发更新。
|
||||
/// 读取设置时的版本;与写入在同一事务内比较,防止覆盖并发更新
|
||||
pub expected_revision: Revision,
|
||||
/// 只覆盖提交的 Provider;未提交项保留当前持久值。
|
||||
/// 只覆盖提交的 Provider;未提交项保留当前持久值
|
||||
pub request_profile_updates: ProviderRequestProfileUpdates,
|
||||
pub request_location_enabled: bool,
|
||||
pub request_location: gateway_core::account::RequestLocation,
|
||||
@@ -99,7 +99,7 @@ pub struct ReplaceRuntimeSettings {
|
||||
pub account_warmup_model: Option<String>,
|
||||
}
|
||||
|
||||
/// 明文管理员 API Key;按产品约束明文落库,但禁止 Debug 泄漏。
|
||||
/// 明文管理员 API Key;按产品约束明文落库,但禁止 Debug 泄漏
|
||||
#[derive(Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
|
||||
#[serde(transparent)]
|
||||
pub struct AdminApiKey(String);
|
||||
@@ -115,7 +115,7 @@ impl AdminApiKey {
|
||||
&self.0
|
||||
}
|
||||
|
||||
/// 仅供显式 regenerate 响应读取一次。
|
||||
/// 仅供显式 regenerate 响应读取一次
|
||||
#[must_use]
|
||||
pub fn expose_for_response(&self) -> &str {
|
||||
&self.0
|
||||
@@ -128,7 +128,7 @@ impl fmt::Debug for AdminApiKey {
|
||||
}
|
||||
}
|
||||
|
||||
/// 管理员 API Key 更新结果。
|
||||
/// 管理员 API Key 更新结果
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct AdminApiKeyMutation {
|
||||
@@ -136,7 +136,7 @@ pub struct AdminApiKeyMutation {
|
||||
pub exists: bool,
|
||||
}
|
||||
|
||||
/// 新管理员 API Key 的一次性返回结果。
|
||||
/// 新管理员 API Key 的一次性返回结果
|
||||
#[derive(serde::Serialize, serde::Deserialize)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct RegeneratedAdminApiKey {
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
//! 版本、自更新、回滚与进程重启的 UTC 语义模型。
|
||||
//! 版本、自更新、回滚与进程重启的 UTC 语义模型
|
||||
|
||||
use chrono::{DateTime, Utc};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
/// 本次检查或安装选择的更新稳定性范围,具体版本准入由 Host 判定。
|
||||
/// 本次检查或安装选择的更新稳定性范围,具体版本准入由 Host 判定
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Deserialize, Serialize)]
|
||||
#[serde(rename_all = "lowercase")]
|
||||
pub enum SystemUpdateChannel {
|
||||
@@ -28,7 +28,7 @@ impl SystemUpdateChannel {
|
||||
}
|
||||
}
|
||||
|
||||
/// Host 返回的本次检查通道及当前构建允许选择的范围。
|
||||
/// Host 返回的本次检查通道及当前构建允许选择的范围
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct SystemUpdatePolicy {
|
||||
@@ -36,9 +36,9 @@ pub struct SystemUpdatePolicy {
|
||||
pub available_channels: Vec<SystemUpdateChannel>,
|
||||
}
|
||||
|
||||
/// 当前运行版本及更新检查摘要。
|
||||
/// 当前运行版本及更新检查摘要
|
||||
///
|
||||
/// `deployment_mode_label` 之类的本地化字符串由 API 根据原始枚举值生成。
|
||||
/// `deployment_mode_label` 之类的本地化字符串由 API 根据原始枚举值生成
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct SystemVersion {
|
||||
pub version: String,
|
||||
@@ -52,9 +52,9 @@ pub struct SystemVersion {
|
||||
pub update_warning: Option<String>,
|
||||
}
|
||||
|
||||
/// 发布源提供的完整更新详情。
|
||||
/// 发布源提供的完整更新详情
|
||||
///
|
||||
/// 部署模式与构建类型的展示标签归 API;其余字段均为 Host 已确认的原始事实。
|
||||
/// 部署模式与构建类型的展示标签归 API;其余字段均为 Host 已确认的原始事实
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct SystemUpdateDetail {
|
||||
pub policy: SystemUpdatePolicy,
|
||||
@@ -71,7 +71,7 @@ pub struct SystemUpdateDetail {
|
||||
pub warning: Option<String>,
|
||||
}
|
||||
|
||||
/// 系统操作类型。
|
||||
/// 系统操作类型
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum SystemOperationKind {
|
||||
Update,
|
||||
@@ -79,7 +79,7 @@ pub enum SystemOperationKind {
|
||||
Restart,
|
||||
}
|
||||
|
||||
/// 系统操作状态。
|
||||
/// 系统操作状态
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum SystemOperationStatus {
|
||||
Idle,
|
||||
@@ -88,7 +88,7 @@ pub enum SystemOperationStatus {
|
||||
Failed,
|
||||
}
|
||||
|
||||
/// 持久化系统操作的完整状态。
|
||||
/// 持久化系统操作的完整状态
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct SystemOperationState {
|
||||
pub operation_id: Option<String>,
|
||||
@@ -101,7 +101,7 @@ pub struct SystemOperationState {
|
||||
pub finished_at: Option<DateTime<Utc>>,
|
||||
}
|
||||
|
||||
/// 最近一次系统操作及版本交换状态。
|
||||
/// 最近一次系统操作及版本交换状态
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct SystemUpdateStatus {
|
||||
pub previous_version: Option<String>,
|
||||
@@ -110,7 +110,7 @@ pub struct SystemUpdateStatus {
|
||||
pub operation: SystemOperationState,
|
||||
}
|
||||
|
||||
/// 自更新日志级别。
|
||||
/// 自更新日志级别
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum SystemUpdateEventLevel {
|
||||
Info,
|
||||
@@ -119,7 +119,7 @@ pub enum SystemUpdateEventLevel {
|
||||
Error,
|
||||
}
|
||||
|
||||
/// 一条已脱敏的系统更新事件。
|
||||
/// 一条已脱敏的系统更新事件
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct SystemUpdateEvent {
|
||||
pub id: String,
|
||||
@@ -132,7 +132,7 @@ pub struct SystemUpdateEvent {
|
||||
pub occurred_at: DateTime<Utc>,
|
||||
}
|
||||
|
||||
/// 已受理的异步系统操作;不同 wire shape 由枚举保持为合法状态。
|
||||
/// 已受理的异步系统操作;不同 wire shape 由枚举保持为合法状态
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub enum SystemOperationAccepted {
|
||||
Update {
|
||||
@@ -163,7 +163,7 @@ impl SystemOperationAccepted {
|
||||
}
|
||||
}
|
||||
|
||||
/// 重启前的只读确认快照;目标发行与全局配置版本一起绑定用户确认。
|
||||
/// 重启前的只读确认快照;目标发行与全局配置版本一起绑定用户确认
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase", deny_unknown_fields)]
|
||||
pub struct SystemRestartPlan {
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
//! 备份控制面所需的持久化与基础设施能力。
|
||||
//! 备份控制面所需的持久化与基础设施能力
|
||||
//!
|
||||
//! 端口方法只接受/返回领域类型与既有错误体系:`BackupRepository` 复用
|
||||
//! `AdminStoreError`;`DatabaseDumpPort` 与 `BackupObjectStorePort` 返回只携带稳定
|
||||
//! 错误码的 [`BackupError`]。`gateway-store` 实现全部端口;`gateway-admin` 拥有
|
||||
//! 状态机、Cron、保留策略与 BackupTask 算法。
|
||||
//! 错误码的 [`BackupError`]
|
||||
//! `gateway-store` 实现全部端口;`gateway-admin` 拥有
|
||||
//! 状态机、Cron、保留策略与 BackupTask 算法
|
||||
|
||||
use std::path::PathBuf;
|
||||
use std::sync::Arc;
|
||||
@@ -23,7 +24,7 @@ use crate::model::{
|
||||
};
|
||||
use crate::ports::store::AdminStoreResult;
|
||||
|
||||
/// Worker 状态迁移携带的字段更新;`None` 字段不写入。
|
||||
/// Worker 状态迁移携带的字段更新;`None` 字段不写入
|
||||
#[derive(Debug, Clone, Default, PartialEq, Eq)]
|
||||
pub struct StatusTransitionUpdate {
|
||||
pub size_bytes: Option<u64>,
|
||||
@@ -33,23 +34,23 @@ pub struct StatusTransitionUpdate {
|
||||
pub completed_at: Option<DateTime<Utc>>,
|
||||
}
|
||||
|
||||
/// 备份配置、任务与计划游标的 PostgreSQL 仓储端口。
|
||||
/// 备份配置、任务与计划游标的 PostgreSQL 仓储端口
|
||||
#[async_trait]
|
||||
pub trait BackupRepository: Send + Sync {
|
||||
/// 读取完整设置(含 Secret)。
|
||||
/// 读取完整设置(含 Secret)
|
||||
async fn load_settings(&self) -> AdminStoreResult<BackupSettings>;
|
||||
|
||||
/// 同事务更新 S3 存储配置、递增 revision、清空验证状态并写审计事件。
|
||||
/// 同事务更新 S3 存储配置、递增 revision、清空验证状态并写审计事件
|
||||
///
|
||||
/// 已有记录时禁止切换 endpoint/region/bucket/path-style(存储身份锁定)。
|
||||
/// 返回更新后的设置与新的全局 `config_revision`。
|
||||
/// 已有记录时禁止切换 endpoint/region/bucket/path-style(存储身份锁定)
|
||||
/// 返回更新后的设置与新的全局 `config_revision`
|
||||
async fn update_storage_settings(
|
||||
&self,
|
||||
command: UpdateBackupStorageCommand,
|
||||
context: &MutationContext,
|
||||
) -> AdminStoreResult<(BackupSettings, Revision)>;
|
||||
|
||||
/// 同事务更新调度配置、推进计划游标并写审计事件。
|
||||
/// 同事务更新调度配置、推进计划游标并写审计事件
|
||||
async fn update_schedule_settings(
|
||||
&self,
|
||||
command: UpdateBackupScheduleCommand,
|
||||
@@ -58,18 +59,18 @@ pub trait BackupRepository: Send + Sync {
|
||||
timezone: gateway_core::time::DeploymentTimeZone,
|
||||
) -> AdminStoreResult<BackupSettings>;
|
||||
|
||||
/// CAS 记录探针成功;`storage_revision` 与当前配置不一致时丢弃结果(返回 `false`)。
|
||||
/// CAS 记录探针成功;`storage_revision` 与当前配置不一致时丢弃结果(返回 `false`)
|
||||
async fn record_verification(
|
||||
&self,
|
||||
storage_revision: u64,
|
||||
at: DateTime<Utc>,
|
||||
) -> AdminStoreResult<bool>;
|
||||
|
||||
/// 插入 queued 任务;存在活跃任务时返回 `backup.active_task_conflict`。
|
||||
/// 插入 queued 任务;存在活跃任务时返回 `backup.active_task_conflict`
|
||||
async fn insert_backup_record(&self, seed: BackupRecordSeed) -> AdminStoreResult<BackupRecord>;
|
||||
|
||||
/// 核对启停、Cron、时区及旧游标后,同事务推进游标并插入 scheduled 任务。
|
||||
/// 配置已变化时不写入;同一时间点或活跃冲突时只推进游标,返回 `false`。
|
||||
/// 核对启停、Cron、时区及旧游标后,同事务推进游标并插入 scheduled 任务
|
||||
/// 配置已变化时不写入;同一时间点或活跃冲突时只推进游标,返回 `false`
|
||||
async fn insert_scheduled_record(
|
||||
&self,
|
||||
seed: BackupRecordSeed,
|
||||
@@ -79,29 +80,29 @@ pub trait BackupRepository: Send + Sync {
|
||||
expected_next_run_at: Option<DateTime<Utc>>,
|
||||
) -> AdminStoreResult<bool>;
|
||||
|
||||
/// 分页查询记录。
|
||||
/// 分页查询记录
|
||||
async fn list_backup_records(
|
||||
&self,
|
||||
query: BackupRecordListQuery,
|
||||
) -> AdminStoreResult<BackupRecordPage>;
|
||||
|
||||
/// 读取单条记录。
|
||||
/// 读取单条记录
|
||||
async fn load_backup_record(&self, id: &str) -> AdminStoreResult<Option<BackupRecord>>;
|
||||
|
||||
/// 读取中间状态记录(dumping/uploading),供启动恢复。
|
||||
/// 读取中间状态记录(dumping/uploading),供启动恢复
|
||||
async fn list_intermediate_records(&self) -> AdminStoreResult<Vec<BackupRecord>>;
|
||||
|
||||
/// 读取等待完成墓碑的记录(status = deleting)。
|
||||
/// 读取等待完成墓碑的记录(status = deleting)
|
||||
async fn list_pending_deletions(&self, limit: u32) -> AdminStoreResult<Vec<BackupRecord>>;
|
||||
|
||||
/// 读取 `expires_at` 已到期且非活跃的记录(completed/failed),供保留清理。
|
||||
/// 读取 `expires_at` 已到期且非活跃的记录(completed/failed),供保留清理
|
||||
async fn list_expired_records(&self, limit: u32) -> AdminStoreResult<Vec<BackupRecord>>;
|
||||
|
||||
/// 原子领取最旧的 queued 任务并迁移到 dumping;无任务返回 `None`。
|
||||
/// 原子领取最旧的 queued 任务并迁移到 dumping;无任务返回 `None`
|
||||
async fn claim_next_queued(&self, now: DateTime<Utc>)
|
||||
-> AdminStoreResult<Option<BackupRecord>>;
|
||||
|
||||
/// 条件迁移状态:要求当前状态一致;不匹配返回 `None`。
|
||||
/// 条件迁移状态:要求当前状态一致;不匹配返回 `None`
|
||||
async fn transition_status(
|
||||
&self,
|
||||
id: &str,
|
||||
@@ -110,17 +111,17 @@ pub trait BackupRepository: Send + Sync {
|
||||
now: DateTime<Utc>,
|
||||
) -> AdminStoreResult<Option<BackupRecord>>;
|
||||
|
||||
/// 迁移到删除流程(completed/failed → deleting);供管理员与保留策略调用。
|
||||
/// 迁移到删除流程(completed/failed → deleting);供管理员与保留策略调用
|
||||
async fn transition_to_deleting(
|
||||
&self,
|
||||
id: &str,
|
||||
now: DateTime<Utc>,
|
||||
) -> AdminStoreResult<Option<BackupRecord>>;
|
||||
|
||||
/// 远端删除成功后硬删除记录行。
|
||||
/// 远端删除成功后硬删除记录行
|
||||
async fn delete_record(&self, id: &str) -> AdminStoreResult<()>;
|
||||
|
||||
/// 条件推进计划游标;仅当启用状态、计划表达式、时区及旧游标仍匹配时才写入,返回是否推进。
|
||||
/// 条件推进计划游标;仅当启用状态、计划表达式、时区及旧游标仍匹配时才写入,返回是否推进
|
||||
async fn advance_schedule_cursor(
|
||||
&self,
|
||||
next_run_at: DateTime<Utc>,
|
||||
@@ -130,35 +131,36 @@ pub trait BackupRepository: Send + Sync {
|
||||
timezone: &str,
|
||||
) -> AdminStoreResult<bool>;
|
||||
|
||||
/// 按完成时间倒序读取 completed 计划备份,供保留策略扫描(最多 `limit` 条)。
|
||||
/// 按完成时间倒序读取 completed 计划备份,供保留策略扫描(最多 `limit` 条)
|
||||
async fn list_scheduled_completed_desc(
|
||||
&self,
|
||||
limit: u32,
|
||||
) -> AdminStoreResult<Vec<BackupRecord>>;
|
||||
}
|
||||
|
||||
/// `pg_dump` 导出与本地暂存端口。
|
||||
/// `pg_dump` 导出与本地暂存端口
|
||||
#[async_trait]
|
||||
pub trait DatabaseDumpPort: Send + Sync {
|
||||
/// 创建归档;完成后原子落盘。取消时终止子进程并清理部分文件。
|
||||
/// 创建归档;完成后原子落盘
|
||||
/// 取消时终止子进程并清理部分文件
|
||||
async fn dump(&self, request: DumpRequest) -> Result<DumpArtifact, BackupError>;
|
||||
|
||||
/// 检查暂存目录是否存在完整归档。
|
||||
/// 检查暂存目录是否存在完整归档
|
||||
async fn inspect_staging(&self, backup_id: &str)
|
||||
-> Result<Option<StagedArtifact>, BackupError>;
|
||||
|
||||
/// 清理暂存目录中该备份的全部文件。
|
||||
/// 清理暂存目录中该备份的全部文件
|
||||
async fn cleanup_staging(&self, backup_id: &str) -> Result<(), BackupError>;
|
||||
}
|
||||
|
||||
/// 一次 `pg_dump` 请求。
|
||||
/// 一次 `pg_dump` 请求
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct DumpRequest {
|
||||
pub backup_id: String,
|
||||
pub cancellation: gateway_core::lifecycle::CancellationToken,
|
||||
}
|
||||
|
||||
/// 已完成并落盘的归档事实。
|
||||
/// 已完成并落盘的归档事实
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct DumpArtifact {
|
||||
pub path: PathBuf,
|
||||
@@ -166,7 +168,7 @@ pub struct DumpArtifact {
|
||||
pub sha256: String,
|
||||
}
|
||||
|
||||
/// 暂存目录中已存在的完整归档。
|
||||
/// 暂存目录中已存在的完整归档
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct StagedArtifact {
|
||||
pub path: PathBuf,
|
||||
@@ -174,39 +176,39 @@ pub struct StagedArtifact {
|
||||
pub sha256: String,
|
||||
}
|
||||
|
||||
/// S3 兼容对象存储适配器端口。
|
||||
/// S3 兼容对象存储适配器端口
|
||||
///
|
||||
/// 方法接受已经校验的存储配置;SDK client 的构造与缓存属于 adapter 实现细节。
|
||||
/// 方法接受已经校验的存储配置;SDK client 的构造与缓存属于 adapter 实现细节
|
||||
#[async_trait]
|
||||
pub trait BackupObjectStorePort: Send + Sync {
|
||||
/// 对专用探针对象执行 Put/Head/Get/Delete。
|
||||
/// 对专用探针对象执行 Put/Head/Get/Delete
|
||||
async fn test_connection(
|
||||
&self,
|
||||
config: &BackupStorageConfig,
|
||||
) -> Result<ConnectionTestResult, BackupError>;
|
||||
|
||||
/// 分片流式上传本地归档;取消时中止 multipart 并返回 `backup.cancelled`。
|
||||
/// 分片流式上传本地归档;取消时中止 multipart 并返回 `backup.cancelled`
|
||||
async fn upload_file(
|
||||
&self,
|
||||
config: &BackupStorageConfig,
|
||||
request: UploadObjectRequest,
|
||||
) -> Result<(), BackupError>;
|
||||
|
||||
/// 读取对象大小与 metadata;对象不存在返回 `Ok(None)`。
|
||||
/// 读取对象大小与 metadata;对象不存在返回 `Ok(None)`
|
||||
async fn head_object(
|
||||
&self,
|
||||
config: &BackupStorageConfig,
|
||||
object_key: &str,
|
||||
) -> Result<Option<BackupObjectMetadata>, BackupError>;
|
||||
|
||||
/// 删除对象;对象不存在视为成功。
|
||||
/// 删除对象;对象不存在视为成功
|
||||
async fn delete_object(
|
||||
&self,
|
||||
config: &BackupStorageConfig,
|
||||
object_key: &str,
|
||||
) -> Result<(), BackupError>;
|
||||
|
||||
/// 创建短时预签名下载 URL。
|
||||
/// 创建短时预签名下载 URL
|
||||
async fn presigned_download(
|
||||
&self,
|
||||
config: &BackupStorageConfig,
|
||||
@@ -216,7 +218,7 @@ pub trait BackupObjectStorePort: Send + Sync {
|
||||
) -> Result<String, BackupError>;
|
||||
}
|
||||
|
||||
/// 一次对象上传请求。
|
||||
/// 一次对象上传请求
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct UploadObjectRequest {
|
||||
pub object_key: String,
|
||||
@@ -225,7 +227,7 @@ pub struct UploadObjectRequest {
|
||||
pub cancellation: gateway_core::lifecycle::CancellationToken,
|
||||
}
|
||||
|
||||
/// 备份控制面所需的全部 Store 能力集合。
|
||||
/// 备份控制面所需的全部 Store 能力集合
|
||||
#[derive(Clone)]
|
||||
pub struct BackupStorePorts {
|
||||
repository: Arc<dyn BackupRepository>,
|
||||
@@ -234,7 +236,7 @@ pub struct BackupStorePorts {
|
||||
}
|
||||
|
||||
impl BackupStorePorts {
|
||||
/// 组合备份端口。
|
||||
/// 组合备份端口
|
||||
#[must_use]
|
||||
pub fn new(
|
||||
repository: Arc<dyn BackupRepository>,
|
||||
@@ -248,25 +250,25 @@ impl BackupStorePorts {
|
||||
}
|
||||
}
|
||||
|
||||
/// 备份任务/配置仓储。
|
||||
/// 备份任务/配置仓储
|
||||
#[must_use]
|
||||
pub fn repository(&self) -> Arc<dyn BackupRepository> {
|
||||
self.repository.clone()
|
||||
}
|
||||
|
||||
/// `pg_dump` 导出与暂存。
|
||||
/// `pg_dump` 导出与暂存
|
||||
#[must_use]
|
||||
pub fn dump(&self) -> Arc<dyn DatabaseDumpPort> {
|
||||
self.dump.clone()
|
||||
}
|
||||
|
||||
/// S3/R2 对象存储适配器。
|
||||
/// S3/R2 对象存储适配器
|
||||
#[must_use]
|
||||
pub fn object_store(&self) -> Arc<dyn BackupObjectStorePort> {
|
||||
self.object_store.clone()
|
||||
}
|
||||
|
||||
/// 测试与不可用路径使用的空实现;任何调用都返回不可用错误。
|
||||
/// 测试与不可用路径使用的空实现;任何调用都返回不可用错误
|
||||
#[doc(hidden)]
|
||||
#[must_use]
|
||||
pub fn disabled() -> Self {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
//! 客户端安装包解析的外部能力端口。
|
||||
//! 客户端安装包解析的外部能力端口
|
||||
|
||||
use async_trait::async_trait;
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
//! 管理用例依赖的外部能力端口。
|
||||
//! 管理用例依赖的外部能力端口
|
||||
|
||||
pub mod backup;
|
||||
pub mod client_distribution;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
//! 插件宿主访问账号权威所需的窄端口。
|
||||
//! 插件宿主访问账号权威所需的窄端口
|
||||
|
||||
use async_trait::async_trait;
|
||||
use gateway_core::account::ProviderAccountId;
|
||||
@@ -11,7 +11,7 @@ use crate::model::{
|
||||
},
|
||||
};
|
||||
|
||||
/// Runtime 只取得账号读写用例,不接触 Store、SQL 或完整管理服务集合。
|
||||
/// Runtime 只取得账号读写用例,不接触 Store、SQL 或完整管理服务集合
|
||||
#[async_trait]
|
||||
pub trait PluginAccountAccess: Send + Sync {
|
||||
async fn list(&self, query: PluginAccountListQuery) -> Result<PluginAccountPage, AdminError>;
|
||||
@@ -26,7 +26,7 @@ pub trait PluginAccountAccess: Send + Sync {
|
||||
account_id: &ProviderAccountId,
|
||||
) -> Result<PluginAccountCredential, AdminError>;
|
||||
|
||||
/// 只读 Provider 已有额度观测;实现不得主动刷新上游或附加用量预测。
|
||||
/// 只读 Provider 已有额度观测;实现不得主动刷新上游或附加用量预测
|
||||
async fn get_quota(
|
||||
&self,
|
||||
_account_id: &ProviderAccountId,
|
||||
@@ -34,7 +34,7 @@ pub trait PluginAccountAccess: Send + Sync {
|
||||
Err(AdminError::unavailable("插件额度事实查询暂不可用"))
|
||||
}
|
||||
|
||||
/// 通过 Provider 原生管理路径刷新额度;不向插件暴露凭据或执行额度重置。
|
||||
/// 通过 Provider 原生管理路径刷新额度;不向插件暴露凭据或执行额度重置
|
||||
async fn refresh_quota(
|
||||
&self,
|
||||
_account_id: &ProviderAccountId,
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
//! 插件 Client Key 非秘密目录与受控预算管理端口。
|
||||
//! 插件 Client Key 非秘密目录与受控预算管理端口
|
||||
|
||||
use async_trait::async_trait;
|
||||
use gateway_core::{engine::budget::ClientBudgetStatus, policy::ClientApiKeyId};
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
//! 插件管理页面、资源访问与授权回调的运行时端口
|
||||
|
||||
use async_trait::async_trait;
|
||||
use gateway_core::runtime::extensions::ExtensionSetReference;
|
||||
|
||||
@@ -9,10 +11,10 @@ use crate::model::{
|
||||
},
|
||||
};
|
||||
|
||||
/// 仅按 Admin 固定的发布引用分派;Runtime 不自行选择当前代次,也不拥有管理员身份。
|
||||
/// 仅按 Admin 固定的发布引用分派;Runtime 不自行选择当前代次,也不拥有管理员身份
|
||||
#[async_trait]
|
||||
pub trait PluginManagement: Send + Sync {
|
||||
/// 复核固定发布代次中的可用目标;调用方已校验持久实例仍为当前版本。
|
||||
/// 复核固定发布代次中的可用目标;调用方已校验持久实例仍为当前版本
|
||||
async fn validate_target(
|
||||
&self,
|
||||
published: &ExtensionSetReference,
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
//! 官方插件发行清单与制品文件的读取端口
|
||||
|
||||
use std::sync::Arc;
|
||||
|
||||
use async_trait::async_trait;
|
||||
@@ -9,7 +11,7 @@ pub enum OfficialPluginReleaseReadErrorKind {
|
||||
Unavailable,
|
||||
}
|
||||
|
||||
/// 文件系统错误不携带部署路径,避免把宿主布局传播到管理域和日志之外。
|
||||
/// 文件系统错误不携带部署路径,避免把宿主布局传播到管理域和日志之外
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, thiserror::Error)]
|
||||
#[error("official plugin release files are unavailable")]
|
||||
pub struct OfficialPluginReleaseReadError {
|
||||
@@ -28,7 +30,7 @@ impl OfficialPluginReleaseReadError {
|
||||
}
|
||||
}
|
||||
|
||||
/// Host 只暴露与宿主发行物同信任边界的只读文件,不解释官方身份或插件包。
|
||||
/// Host 只暴露与宿主发行物同信任边界的只读文件,不解释官方身份或插件包
|
||||
#[async_trait]
|
||||
pub trait OfficialPluginReleaseFiles: Send + Sync {
|
||||
async fn manifest(&self) -> Result<Option<Arc<[u8]>>, OfficialPluginReleaseReadError>;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
//! 插件自有资源的管理入口与原子持久化端口。
|
||||
//! 插件自有资源的管理入口与原子持久化端口
|
||||
|
||||
use super::store::AdminStoreResult;
|
||||
use crate::model::{
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
//! 插件准备、分发、持久化与私有状态生命周期的外部能力端口
|
||||
|
||||
use std::{collections::BTreeMap, sync::Arc};
|
||||
|
||||
use async_trait::async_trait;
|
||||
@@ -26,10 +28,10 @@ use crate::model::{
|
||||
};
|
||||
use gateway_core::runtime::extensions::ExtensionSetReference;
|
||||
|
||||
/// Admin 准备完整候选并保活到提交后的发布结束;进程状态不能代替持久启用状态。
|
||||
/// Admin 准备完整候选并保活到提交后的发布结束;进程状态不能代替持久启用状态
|
||||
#[async_trait]
|
||||
pub trait PluginPreparation: PluginRuntimeDiagnostics + PluginStateLifecycle {
|
||||
/// 静态判断业务配置是否完整,不启动进程;不完整的安装保留为待配置。
|
||||
/// 静态判断业务配置是否完整,不启动进程;不完整的安装保留为待配置
|
||||
async fn configuration_ready(
|
||||
&self,
|
||||
instance: PluginInstance,
|
||||
@@ -39,14 +41,14 @@ pub trait PluginPreparation: PluginRuntimeDiagnostics + PluginStateLifecycle {
|
||||
&self,
|
||||
instance: PluginInstance,
|
||||
) -> Result<PluginStateConfiguration, AdminError>;
|
||||
/// 本次修改的实例 revision 必须等于候选 config_revision,准备失败时拒绝提交;历史故障可隔离。
|
||||
/// 本次修改的实例 revision 必须等于候选 config_revision,准备失败时拒绝提交;历史故障可隔离
|
||||
async fn prepare(
|
||||
&self,
|
||||
snapshot: PluginInstanceSnapshot,
|
||||
) -> Result<ExtensionSetReference, AdminError>;
|
||||
}
|
||||
|
||||
/// 运行诊断独立于发布准备;没有进程事实时显式返回 None。
|
||||
/// 运行诊断独立于发布准备;没有进程事实时显式返回 None
|
||||
#[async_trait]
|
||||
pub trait PluginRuntimeDiagnostics: Send + Sync {
|
||||
async fn runtime_diagnostics(
|
||||
@@ -57,7 +59,7 @@ pub trait PluginRuntimeDiagnostics: Send + Sync {
|
||||
) -> Option<BTreeMap<String, PluginInstanceRuntime>>;
|
||||
}
|
||||
|
||||
/// 发布实现必须显式承担状态激活、排空和迁移,不能以缺省成功跳过生命周期。
|
||||
/// 发布实现必须显式承担状态激活、排空和迁移,不能以缺省成功跳过生命周期
|
||||
#[async_trait]
|
||||
pub trait PluginStateLifecycle: Send + Sync {
|
||||
async fn activate_state(
|
||||
@@ -103,7 +105,7 @@ impl PluginStateStoreError {
|
||||
|
||||
pub type PluginStateStoreResult<T> = Result<T, PluginStateStoreError>;
|
||||
|
||||
/// 私有状态的窄端口;不暴露 SQL、连接或跨命名空间事务。
|
||||
/// 私有状态的窄端口;不暴露 SQL、连接或跨命名空间事务
|
||||
#[async_trait]
|
||||
pub trait PluginStateStore: Send + Sync {
|
||||
async fn load_owner(
|
||||
@@ -151,7 +153,7 @@ pub trait PluginStateStore: Send + Sync {
|
||||
async fn abort_transition(&self, transition_id: &str) -> PluginStateStoreResult<()>;
|
||||
}
|
||||
|
||||
/// Host 拥有联网、重定向、凭据作用域、限流与有界下载,不解释插件包。
|
||||
/// Host 拥有联网、重定向、凭据作用域、限流与有界下载,不解释插件包
|
||||
#[async_trait]
|
||||
pub trait PluginDistribution: Send + Sync {
|
||||
fn validate_source(&self, source: &PluginUpdateSource) -> Result<(), AdminError>;
|
||||
@@ -170,7 +172,7 @@ pub trait PluginDistribution: Send + Sync {
|
||||
) -> Result<DownloadedPlugin, AdminError>;
|
||||
}
|
||||
|
||||
/// Runtime 只解释插件包格式;安装事务与来源选择归 Admin。
|
||||
/// Runtime 只解释插件包格式;安装事务与来源选择归 Admin
|
||||
#[async_trait]
|
||||
pub trait PluginPackageInspector: Send + Sync {
|
||||
async fn inspect(
|
||||
@@ -179,7 +181,7 @@ pub trait PluginPackageInspector: Send + Sync {
|
||||
expected_sha256: Option<String>,
|
||||
) -> Result<InspectedPluginArtifact, AdminError>;
|
||||
|
||||
/// 从已安装且摘要匹配的制品中读取可展示的静态图标,不启动插件实例。
|
||||
/// 从已安装且摘要匹配的制品中读取可展示的静态图标,不启动插件实例
|
||||
async fn icon(
|
||||
&self,
|
||||
_archive: Arc<[u8]>,
|
||||
@@ -189,7 +191,7 @@ pub trait PluginPackageInspector: Send + Sync {
|
||||
Err(AdminError::unavailable("插件图标读取不可用"))
|
||||
}
|
||||
|
||||
/// 只读取已校验包的静态宿主要求;不得准备或启动插件进程。
|
||||
/// 只读取已校验包的静态宿主要求;不得准备或启动插件进程
|
||||
async fn compatibility(
|
||||
&self,
|
||||
_archive: Arc<[u8]>,
|
||||
@@ -201,13 +203,13 @@ pub trait PluginPackageInspector: Send + Sync {
|
||||
|
||||
#[async_trait]
|
||||
pub trait PluginStore: Send + Sync {
|
||||
/// 只复核精确管理目标的启用、接受事实和版本,不读取实例配置或密钥。
|
||||
/// 只复核精确管理目标的启用、接受事实和版本,不读取实例配置或密钥
|
||||
async fn management_target_is_current(
|
||||
&self,
|
||||
target: &crate::model::plugins::management::PluginManagementTarget,
|
||||
) -> AdminStoreResult<bool>;
|
||||
async fn load_instances(&self) -> AdminStoreResult<PluginInstanceSnapshot>;
|
||||
/// 读取对应制品最近一次启用时提交的配置,密钥始终留在服务端。
|
||||
/// 读取对应制品最近一次启用时提交的配置,密钥始终留在服务端
|
||||
async fn load_version_configuration(
|
||||
&self,
|
||||
_id: &str,
|
||||
@@ -215,11 +217,11 @@ pub trait PluginStore: Send + Sync {
|
||||
) -> AdminStoreResult<Option<PluginVersionConfiguration>> {
|
||||
Ok(None)
|
||||
}
|
||||
/// 只返回有可恢复配置的制品摘要,不读取敏感值。
|
||||
/// 只返回有可恢复配置的制品摘要,不读取敏感值
|
||||
async fn configuration_versions(&self, _id: &str) -> AdminStoreResult<Vec<String>> {
|
||||
Ok(Vec::new())
|
||||
}
|
||||
/// 在单个事务中停用确认快照中的实例,保留配置、密钥及私有状态。
|
||||
/// 在单个事务中停用确认快照中的实例,保留配置、密钥及私有状态
|
||||
async fn disable_instances(
|
||||
&self,
|
||||
_ids: &[String],
|
||||
@@ -255,7 +257,7 @@ pub trait PluginStore: Send + Sync {
|
||||
self.save_instance(instance, expected_revision, context)
|
||||
.await
|
||||
}
|
||||
/// 保存目标并停用明确确认的配置,必须共享事务和配置版本检查。
|
||||
/// 保存目标并停用明确确认的配置,必须共享事务和配置版本检查
|
||||
async fn save_instance_replacing(
|
||||
&self,
|
||||
instance: PluginInstance,
|
||||
@@ -307,13 +309,13 @@ pub trait PluginStore: Send + Sync {
|
||||
source: PluginSource,
|
||||
context: &MutationContext,
|
||||
) -> AdminStoreResult<PluginArtifactMutation>;
|
||||
/// 确认信任并安装精确摘要的制品;安装事实不随实例设置变化。
|
||||
/// 确认信任并安装精确摘要的制品;安装事实不随实例设置变化
|
||||
async fn accept_artifact(
|
||||
&self,
|
||||
digest: &str,
|
||||
context: &MutationContext,
|
||||
) -> AdminStoreResult<PluginArtifactMutation>;
|
||||
/// 删除制品及其无引用下载凭据,最后一个版本同时删除来源规则,清理与审计必须原子提交。
|
||||
/// 删除制品及其无引用下载凭据,最后一个版本同时删除来源规则,清理与审计必须原子提交
|
||||
async fn delete_artifact(
|
||||
&self,
|
||||
digest: &str,
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
//! 手动同步的外部价目来源;网络和外部文档解析由 Host 实现。
|
||||
//! 手动同步的外部价目来源;网络和外部文档解析由 Host 实现
|
||||
|
||||
use crate::model::{AdminError, pricing::PricingSyncPreview};
|
||||
use async_trait::async_trait;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
//! 原生 Provider 管理能力与启动时注册表。
|
||||
//! 原生 Provider 管理能力与启动时注册表
|
||||
|
||||
use std::{collections::BTreeMap, sync::Arc};
|
||||
|
||||
@@ -26,14 +26,14 @@ use crate::model::{
|
||||
quota_forecast_sampling::QuotaForecastObservation,
|
||||
};
|
||||
|
||||
/// Provider 管理失败的稳定分类。
|
||||
/// Provider 管理失败的稳定分类
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum ProviderAdminErrorKind {
|
||||
Invalid,
|
||||
Unsupported,
|
||||
NotFound,
|
||||
Conflict,
|
||||
/// Provider 已发起不可逆操作,但无法确认最终执行结果。
|
||||
/// Provider 已发起不可逆操作,但无法确认最终执行结果
|
||||
Ambiguous,
|
||||
Unavailable,
|
||||
CredentialRefreshRequired,
|
||||
@@ -41,11 +41,11 @@ pub enum ProviderAdminErrorKind {
|
||||
Internal,
|
||||
}
|
||||
|
||||
/// 不携带 OAuth 请求材料的管理错误。
|
||||
/// 不携带 OAuth 请求材料的管理错误
|
||||
///
|
||||
/// `message` 是 Provider 局部诊断,可能包含原始上游正文;通用管理用例不得自动把它作为公开文案。
|
||||
/// 只有明确拥有原始诊断合同的调用方才能读取,`Debug` 始终只记录是否存在。
|
||||
/// `public_message` 则是明确标记为可公开的静态提示,不允许携带动态上游材料。
|
||||
/// `message` 是 Provider 局部诊断,可能包含原始上游正文;通用管理用例不得自动把它作为公开文案
|
||||
/// 只有明确拥有原始诊断合同的调用方才能读取,`Debug` 始终只记录是否存在
|
||||
/// `public_message` 则是明确标记为可公开的静态提示,不允许携带动态上游材料
|
||||
#[derive(Clone, PartialEq, Eq, thiserror::Error)]
|
||||
#[error("provider admin operation failed: {kind:?}")]
|
||||
pub struct ProviderAdminError {
|
||||
@@ -81,7 +81,7 @@ impl ProviderAdminError {
|
||||
self
|
||||
}
|
||||
|
||||
/// Provider 明确允许公开的静态提示;不得承载上游响应或凭据材料。
|
||||
/// Provider 明确允许公开的静态提示;不得承载上游响应或凭据材料
|
||||
#[must_use]
|
||||
pub const fn with_public_message(mut self, message: &'static str) -> Self {
|
||||
self.public_message = Some(message);
|
||||
@@ -104,20 +104,21 @@ impl ProviderAdminError {
|
||||
}
|
||||
}
|
||||
|
||||
/// 一个具体 Provider 对管理控制面提供的解析、验证、上游交互与运行时资源回收能力。
|
||||
/// 一个具体 Provider 对管理控制面提供的解析、验证、上游交互与运行时资源回收能力
|
||||
///
|
||||
/// 数据变更由 Provider 返回 prepared facts;config revision、审计与 PostgreSQL 事务
|
||||
/// 全部由 [`crate::ports::store::AccountStore`] 提交。运行时资源通知只在事务成功后发生。
|
||||
/// 全部由 [`crate::ports::store::AccountStore`] 提交
|
||||
/// 运行时资源通知只在事务成功后发生
|
||||
#[async_trait]
|
||||
pub trait ProviderAdmin: Send + Sync {
|
||||
/// 只读内置价目;没有本地计价能力的 Provider 返回空目录。
|
||||
/// 只读内置价目;没有本地计价能力的 Provider 返回空目录
|
||||
fn pricing_catalog(&self) -> crate::model::pricing::ProviderPricingCatalog {
|
||||
Default::default()
|
||||
}
|
||||
|
||||
fn provider_kind(&self) -> &ProviderKind;
|
||||
|
||||
/// 只读取原生 Provider 能力及账号类型,不执行网络或数据库查询。
|
||||
/// 只读取原生 Provider 能力及账号类型,不执行网络或数据库查询
|
||||
fn account_capabilities(
|
||||
&self,
|
||||
_account_id: &ProviderAccountId,
|
||||
@@ -126,19 +127,19 @@ pub trait ProviderAdmin: Send + Sync {
|
||||
Default::default()
|
||||
}
|
||||
|
||||
/// 提供该 Provider 的可选客户端身份;通用管理层不解释内部字段。
|
||||
/// 提供该 Provider 的可选客户端身份;通用管理层不解释内部字段
|
||||
fn client_profile_options(
|
||||
&self,
|
||||
) -> Result<gateway_core::account::OpaqueProviderData, ProviderAdminError> {
|
||||
Err(ProviderAdminError::new(ProviderAdminErrorKind::Unsupported))
|
||||
}
|
||||
|
||||
/// 没有持久选择时使用的 Provider 默认画像;只返回已准备的本地事实。
|
||||
/// 没有持久选择时使用的 Provider 默认画像;只返回已准备的本地事实
|
||||
fn default_client_profile(&self) -> Option<gateway_core::account::OpaqueProviderData> {
|
||||
None
|
||||
}
|
||||
|
||||
/// 校验并投影客户端身份,结果不含认证或账号材料。
|
||||
/// 校验并投影客户端身份,结果不含认证或账号材料
|
||||
fn preview_client_profile(
|
||||
&self,
|
||||
_configuration: &gateway_core::account::OpaqueProviderData,
|
||||
@@ -146,31 +147,33 @@ pub trait ProviderAdmin: Send + Sync {
|
||||
Err(ProviderAdminError::new(ProviderAdminErrorKind::Unsupported))
|
||||
}
|
||||
|
||||
/// 将原始套餐值投影为展示名称;默认保留未知 Provider 的原始名称。
|
||||
/// 将原始套餐值投影为展示名称;默认保留未知 Provider 的原始名称
|
||||
fn plan_type_display(&self, plan_type: &str) -> String {
|
||||
plan_type.to_owned()
|
||||
}
|
||||
|
||||
/// 账号已经由控制面提交为不可调度状态,释放 Provider 持有的账号级运行时资源。
|
||||
/// 账号已经由控制面提交为不可调度状态,释放 Provider 持有的账号级运行时资源
|
||||
///
|
||||
/// 无账号级运行时资源的 Provider 不需要执行额外操作。该通知发生在 Store 事务
|
||||
/// 成功之后,不参与事务成败,也不得恢复或改写已经提交的账号状态。
|
||||
/// 无账号级运行时资源的 Provider 不需要执行额外操作
|
||||
/// 该通知发生在 Store 事务
|
||||
/// 成功之后,不参与事务成败,也不得恢复或改写已经提交的账号状态
|
||||
async fn account_unavailable(&self, account_id: &ProviderAccountId);
|
||||
|
||||
/// 账号资格事实已经由控制面提交,失效 Provider 持有的可重建派生状态。
|
||||
/// 账号资格事实已经由控制面提交,失效 Provider 持有的可重建派生状态
|
||||
///
|
||||
/// 该通知发生在 Store 事务成功之后、下一份 RuntimeSnapshot 编译之前;通知
|
||||
/// 不参与已提交事务成败。没有账号派生状态的 Provider 可使用默认空实现。
|
||||
/// 不参与已提交事务成败
|
||||
/// 没有账号派生状态的 Provider 可使用默认空实现
|
||||
async fn account_facts_changed(&self, _account_ids: &[ProviderAccountId]) {}
|
||||
|
||||
/// 生成一次连接测试所需的 Provider-owned operation;Core 负责实际执行与落账。
|
||||
/// 生成一次连接测试所需的 Provider-owned operation;Core 负责实际执行与落账
|
||||
async fn connection_test_operation(
|
||||
&self,
|
||||
upstream_model: &UpstreamModelId,
|
||||
input_text: &str,
|
||||
) -> Result<Operation, ProviderAdminError>;
|
||||
|
||||
/// 返回该 Provider 实际持有的 Dashboard 上游身份画像。
|
||||
/// 返回该 Provider 实际持有的 Dashboard 上游身份画像
|
||||
fn dashboard_wire_profile(&self) -> Option<DashboardWireProfile>;
|
||||
|
||||
fn configured_wire_profile(
|
||||
@@ -180,8 +183,8 @@ pub trait ProviderAdmin: Send + Sync {
|
||||
self.dashboard_wire_profile()
|
||||
}
|
||||
|
||||
/// 使用 Provider-owned 价格规则恢复持久请求的逐项费用。
|
||||
/// 未保存的长上下文计费标记不得从当前价格反推。
|
||||
/// 使用 Provider-owned 价格规则恢复持久请求的逐项费用
|
||||
/// 未保存的长上下文计费标记不得从当前价格反推
|
||||
fn calculated_billing(
|
||||
&self,
|
||||
input: &ProviderBillingInput,
|
||||
@@ -212,7 +215,7 @@ pub trait ProviderAdmin: Send + Sync {
|
||||
command: PrepareCredentialRefresh,
|
||||
) -> Result<PreparedCredentialRotation, ProviderAdminError>;
|
||||
|
||||
/// 可公开的账号连接设置;实现只能显式投影非敏感字段,不能返回凭据原文。
|
||||
/// 可公开的账号连接设置;实现只能显式投影非敏感字段,不能返回凭据原文
|
||||
async fn account_configuration(
|
||||
&self,
|
||||
_account_id: &ProviderAccountId,
|
||||
@@ -225,7 +228,7 @@ pub trait ProviderAdmin: Send + Sync {
|
||||
request: ProviderQuotaRequest,
|
||||
) -> Result<ProviderQuota, ProviderAdminError>;
|
||||
|
||||
/// 历史观测的协议字段仅由具体 Provider 解释;不支持时保留累计估算。
|
||||
/// 历史观测的协议字段仅由具体 Provider 解释;不支持时保留累计估算
|
||||
fn quota_forecast_observation(
|
||||
&self,
|
||||
_document: &ProviderDocument,
|
||||
@@ -234,8 +237,8 @@ pub trait ProviderAdmin: Send + Sync {
|
||||
None
|
||||
}
|
||||
|
||||
/// 查询 Provider 官方个人资料统计;不支持该能力的 Provider 使用默认拒绝。
|
||||
/// 只由个人信息显式查询,不在额度刷新或后台任务中预取。
|
||||
/// 查询 Provider 官方个人资料统计;不支持该能力的 Provider 使用默认拒绝
|
||||
/// 只由个人信息显式查询,不在额度刷新或后台任务中预取
|
||||
async fn subscription(
|
||||
&self,
|
||||
_account_id: &ProviderAccountId,
|
||||
@@ -250,7 +253,7 @@ pub trait ProviderAdmin: Send + Sync {
|
||||
Err(ProviderAdminError::new(ProviderAdminErrorKind::Unsupported))
|
||||
}
|
||||
|
||||
/// 打开 Provider 官方头像字节流;不支持该能力的 Provider 使用默认拒绝。
|
||||
/// 打开 Provider 官方头像字节流;不支持该能力的 Provider 使用默认拒绝
|
||||
async fn profile_avatar(
|
||||
&self,
|
||||
_account_id: &ProviderAccountId,
|
||||
@@ -258,7 +261,7 @@ pub trait ProviderAdmin: Send + Sync {
|
||||
Err(ProviderAdminError::new(ProviderAdminErrorKind::Unsupported))
|
||||
}
|
||||
|
||||
/// 查询 Provider 主动额度重置卡;不支持该能力的 Provider 使用默认拒绝。
|
||||
/// 查询 Provider 主动额度重置卡;不支持该能力的 Provider 使用默认拒绝
|
||||
async fn reset_credits(
|
||||
&self,
|
||||
_account_id: &ProviderAccountId,
|
||||
@@ -266,7 +269,7 @@ pub trait ProviderAdmin: Send + Sync {
|
||||
Err(ProviderAdminError::new(ProviderAdminErrorKind::Unsupported))
|
||||
}
|
||||
|
||||
/// 消费 Provider 主动额度重置卡;不支持该能力的 Provider 使用默认拒绝。
|
||||
/// 消费 Provider 主动额度重置卡;不支持该能力的 Provider 使用默认拒绝
|
||||
async fn consume_reset_credit(
|
||||
&self,
|
||||
_command: ConsumeProviderResetCredit,
|
||||
@@ -280,7 +283,7 @@ pub trait ProviderAdmin: Send + Sync {
|
||||
refresh: bool,
|
||||
) -> Result<ProviderModels, ProviderAdminError>;
|
||||
|
||||
/// 导出该账号的 Provider 原生模型目录正文;不提供原生目录的 Provider 使用默认拒绝。
|
||||
/// 导出该账号的 Provider 原生模型目录正文;不提供原生目录的 Provider 使用默认拒绝
|
||||
async fn model_catalog_document(
|
||||
&self,
|
||||
_account_id: &ProviderAccountId,
|
||||
@@ -294,7 +297,7 @@ pub trait ProviderAdmin: Send + Sync {
|
||||
) -> Result<ProviderExport, ProviderAdminError>;
|
||||
}
|
||||
|
||||
/// 启动时注册原生 Provider,按 ProviderKind 查找管理能力。
|
||||
/// 启动时注册原生 Provider,按 ProviderKind 查找管理能力
|
||||
#[derive(Clone)]
|
||||
pub struct ProviderAdminRegistry {
|
||||
providers: Arc<BTreeMap<ProviderKind, Arc<dyn ProviderAdmin>>>,
|
||||
@@ -308,11 +311,11 @@ impl ProviderAdminRegistry {
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// 创建无重复 ProviderKind 的注册表。
|
||||
/// 创建无重复 ProviderKind 的注册表
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// 重复注册同一 ProviderKind 时返回 Conflict。
|
||||
/// 重复注册同一 ProviderKind 时返回 Conflict
|
||||
pub fn new(
|
||||
providers: impl IntoIterator<Item = Arc<dyn ProviderAdmin>>,
|
||||
) -> Result<Self, ProviderAdminError> {
|
||||
@@ -338,7 +341,7 @@ impl ProviderAdminRegistry {
|
||||
.ok_or_else(|| ProviderAdminError::new(ProviderAdminErrorKind::Unsupported))
|
||||
}
|
||||
|
||||
/// 账号目录与关联列表共用套餐补全和展示规则,已知账号套餐优先于额度快照。
|
||||
/// 账号目录与关联列表共用套餐补全和展示规则,已知账号套餐优先于额度快照
|
||||
pub(crate) fn resolve_account_plan(
|
||||
&self,
|
||||
provider_kind: &str,
|
||||
@@ -351,7 +354,7 @@ impl ProviderAdminRegistry {
|
||||
self.plan_type_display(provider_kind, plan_type.as_deref())
|
||||
}
|
||||
|
||||
/// 账号页和 Dashboard 共用的大驼峰套餐展示名称,不修改原始套餐值。
|
||||
/// 账号页和 Dashboard 共用的大驼峰套餐展示名称,不修改原始套餐值
|
||||
pub(crate) fn plan_type_display(
|
||||
&self,
|
||||
provider_kind: &str,
|
||||
@@ -365,11 +368,11 @@ impl ProviderAdminRegistry {
|
||||
|| plan_type.to_owned(),
|
||||
|provider| provider.plan_type_display(plan_type),
|
||||
);
|
||||
// 所有 Provider 及未知套餐统一排版,保留 Plus 后缀的含义。
|
||||
// 所有 Provider 及未知套餐统一排版,保留 Plus 后缀的含义
|
||||
Some(display.replace('+', " Plus ").to_upper_camel_case())
|
||||
}
|
||||
|
||||
/// 返回所有已注册 Provider 的 Dashboard 上游身份画像。
|
||||
/// 返回所有已注册 Provider 的 Dashboard 上游身份画像
|
||||
pub fn dashboard_wire_profiles(
|
||||
&self,
|
||||
configurations: &std::collections::BTreeMap<
|
||||
@@ -386,7 +389,7 @@ impl ProviderAdminRegistry {
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// 按平台计算请求费用。
|
||||
/// 按平台计算请求费用
|
||||
pub fn calculated_billing(
|
||||
&self,
|
||||
provider_kind: &ProviderKind,
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
//! 账号出站代理的持久化、导入占用与连通性探测端口
|
||||
|
||||
use async_trait::async_trait;
|
||||
use gateway_core::account::{OutboundProxy, ProviderAccountId};
|
||||
|
||||
@@ -12,7 +14,7 @@ use crate::model::{
|
||||
|
||||
#[async_trait]
|
||||
pub trait ProxyStore: Send + Sync {
|
||||
/// 在凭据交换到提交期间保护选定代理的连接配置和测试结果。
|
||||
/// 在凭据交换到提交期间保护选定代理的连接配置和测试结果
|
||||
async fn reserve_import(&self, id: &str) -> AdminStoreResult<ProxyImportReservation>;
|
||||
async fn list(&self, query: ProxyListQuery) -> AdminStoreResult<ProxyPage>;
|
||||
async fn list_accounts(
|
||||
@@ -20,7 +22,7 @@ pub trait ProxyStore: Send + Sync {
|
||||
query: ProxyAccountListQuery,
|
||||
) -> AdminStoreResult<ProxyAccountPage>;
|
||||
async fn get(&self, id: &str) -> AdminStoreResult<ProxyRecord>;
|
||||
/// 仅在账号仍绑定指定代理时解除关联,并清除账号保存的连接地址。
|
||||
/// 仅在账号仍绑定指定代理时解除关联,并清除账号保存的连接地址
|
||||
async fn remove_account(
|
||||
&self,
|
||||
proxy_id: &str,
|
||||
@@ -52,7 +54,7 @@ pub trait ProxyStore: Send + Sync {
|
||||
) -> AdminStoreResult<ProxyMutation>;
|
||||
}
|
||||
|
||||
/// 离开作用域时释放保护,错误返回和请求取消也遵循相同规则。
|
||||
/// 离开作用域时释放保护,错误返回和请求取消也遵循相同规则
|
||||
pub trait ProxyImportGuard: Send + Sync {}
|
||||
|
||||
pub struct ProxyImportReservation {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
//! 有界保留期删除,不承载周期、暂停或重试策略。
|
||||
//! 有界保留期删除,不承载周期、暂停或重试策略
|
||||
|
||||
use std::num::NonZeroU32;
|
||||
|
||||
@@ -12,7 +12,7 @@ use crate::model::retention::{RetentionPolicy, RetentionTarget};
|
||||
pub trait RetentionStore: Send + Sync {
|
||||
async fn load_policy(&self) -> AdminStoreResult<RetentionPolicy>;
|
||||
|
||||
/// 每次最多删除 limit 行;请求关联事件随请求事务级联删除。
|
||||
/// 每次最多删除 limit 行;请求关联事件随请求事务级联删除
|
||||
async fn purge_batch(
|
||||
&self,
|
||||
target: RetentionTarget,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
//! 管理控制面所需的持久化能力。
|
||||
//! 管理控制面所需的持久化能力
|
||||
//!
|
||||
//! 端口按业务资源拆分,方法使用领域模型,不暴露连接池、事务或 Redis client。
|
||||
//! 端口按业务资源拆分,方法使用领域模型,不暴露连接池、事务或 Redis client
|
||||
|
||||
use std::{collections::BTreeMap, net::IpAddr, sync::Arc, time::Duration};
|
||||
|
||||
@@ -40,7 +40,7 @@ use crate::model::{
|
||||
settings::{AdminApiKey, AdminApiKeyMutation, ReplaceRuntimeSettings, RuntimeSettings},
|
||||
};
|
||||
|
||||
/// 管理端可判定的持久化失败类型。
|
||||
/// 管理端可判定的持久化失败类型
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum AdminStoreErrorKind {
|
||||
Invalid,
|
||||
@@ -51,7 +51,7 @@ pub enum AdminStoreErrorKind {
|
||||
Unavailable,
|
||||
}
|
||||
|
||||
/// 隐藏数据库实现细节的持久化错误。
|
||||
/// 隐藏数据库实现细节的持久化错误
|
||||
#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
|
||||
#[error("{resource} store operation failed: {message}")]
|
||||
pub struct AdminStoreError {
|
||||
@@ -87,10 +87,10 @@ impl AdminStoreError {
|
||||
|
||||
pub type AdminStoreResult<T> = Result<T, AdminStoreError>;
|
||||
|
||||
/// 账号目录与公共账号写操作。
|
||||
/// 账号目录与公共账号写操作
|
||||
#[async_trait]
|
||||
pub trait AccountStore: Send + Sync {
|
||||
/// 插件回调按授权 Provider/账号在数据库过滤后做稳定 cursor 分页。
|
||||
/// 插件回调按授权 Provider/账号在数据库过滤后做稳定 cursor 分页
|
||||
async fn list_plugin_accounts(
|
||||
&self,
|
||||
query: PluginAccountListQuery,
|
||||
@@ -119,7 +119,7 @@ pub trait AccountStore: Send + Sync {
|
||||
windows: &[AccountUsageWindowQuery],
|
||||
) -> AdminStoreResult<Vec<AccountUsageWindowResult>>;
|
||||
|
||||
/// 从同一数据库语句取得截止快照的累计用量和有界历史观测。
|
||||
/// 从同一数据库语句取得截止快照的累计用量和有界历史观测
|
||||
async fn load_quota_forecast_history(
|
||||
&self,
|
||||
window: &AccountUsageWindowQuery,
|
||||
@@ -131,7 +131,7 @@ pub trait AccountStore: Send + Sync {
|
||||
account_id: &gateway_core::account::ProviderAccountId,
|
||||
) -> AdminStoreResult<Option<CredentialDetails>>;
|
||||
|
||||
/// 插件按全局账号 ID 读取时,由数据库记录提供权威 Provider 归属。
|
||||
/// 插件按全局账号 ID 读取时,由数据库记录提供权威 Provider 归属
|
||||
async fn credential_details_by_id(
|
||||
&self,
|
||||
account_id: &gateway_core::account::ProviderAccountId,
|
||||
@@ -143,7 +143,7 @@ pub trait AccountStore: Send + Sync {
|
||||
account_ids: &[gateway_core::account::ProviderAccountId],
|
||||
) -> AdminStoreResult<Vec<ProviderExportCredentialInput>>;
|
||||
|
||||
/// 插件凭据读取只接收账号 ID,不接受调用方另行声明 Provider。
|
||||
/// 插件凭据读取只接收账号 ID,不接受调用方另行声明 Provider
|
||||
async fn load_credential_for_plugin(
|
||||
&self,
|
||||
account_id: &gateway_core::account::ProviderAccountId,
|
||||
@@ -161,7 +161,7 @@ pub trait AccountStore: Send + Sync {
|
||||
context: &MutationContext,
|
||||
) -> AdminStoreResult<crate::model::provider_credentials::AuthorizationCommitResult>;
|
||||
|
||||
/// 已提交的授权结果独立于 Redis 临时状态;只有原管理员身份可读。
|
||||
/// 已提交的授权结果独立于 Redis 临时状态;只有原管理员身份可读
|
||||
async fn authorization_receipt(
|
||||
&self,
|
||||
key: &crate::model::provider_credentials::AuthorizationReceiptKey,
|
||||
@@ -185,7 +185,7 @@ pub trait AccountStore: Send + Sync {
|
||||
context: &MutationContext,
|
||||
) -> AdminStoreResult<AccountUpdateResult>;
|
||||
|
||||
/// 在事务内按最新启用状态、账号上限和全局默认值判断,只降低并发上限。
|
||||
/// 在事务内按最新启用状态、账号上限和全局默认值判断,只降低并发上限
|
||||
async fn lower_concurrency_limit(
|
||||
&self,
|
||||
account_id: &gateway_core::account::ProviderAccountId,
|
||||
@@ -218,7 +218,7 @@ pub trait AccountStore: Send + Sync {
|
||||
) -> AdminStoreResult<()>;
|
||||
}
|
||||
|
||||
/// 可丢失账号运行态的管理读端口;跨存储编排由 Admin application service 拥有。
|
||||
/// 可丢失账号运行态的管理读端口;跨存储编排由 Admin application service 拥有
|
||||
#[async_trait]
|
||||
pub trait AccountRuntimeStore: Send + Sync {
|
||||
async fn active_rate_limits(&self) -> AdminStoreResult<AccountRuntimeSnapshot>;
|
||||
@@ -228,18 +228,18 @@ pub trait AccountRuntimeStore: Send + Sync {
|
||||
account_ids: &[String],
|
||||
) -> AdminStoreResult<AccountRuntimeSnapshot>;
|
||||
|
||||
/// 容量熔断自动冻结中的账号与其冻结截止时间;429 临时限流不包含在内。
|
||||
/// 容量熔断自动冻结中的账号与其冻结截止时间;429 临时限流不包含在内
|
||||
async fn active_freezes(
|
||||
&self,
|
||||
) -> AdminStoreResult<BTreeMap<String, crate::model::accounts::AccountFreeze>>;
|
||||
|
||||
/// 读取容量失败窗口内观测到的在途并发峰值(自适应并发下调的证据)。
|
||||
/// 读取容量失败窗口内观测到的在途并发峰值(自适应并发下调的证据)
|
||||
async fn capacity_peaks(
|
||||
&self,
|
||||
account_ids: &[String],
|
||||
) -> AdminStoreResult<BTreeMap<String, u32>>;
|
||||
|
||||
/// 仅当冻结快照仍匹配时解除或顺延;旧探测不得覆盖手动恢复或新一轮冻结。
|
||||
/// 仅当冻结快照仍匹配时解除或顺延;旧探测不得覆盖手动恢复或新一轮冻结
|
||||
async fn finish_freeze(
|
||||
&self,
|
||||
account_id: &str,
|
||||
@@ -248,12 +248,12 @@ pub trait AccountRuntimeStore: Send + Sync {
|
||||
) -> AdminStoreResult<bool>;
|
||||
}
|
||||
|
||||
/// 控制面凭据、统一会话、登录限流与管理员安全审计。
|
||||
/// 控制面凭据、统一会话、登录限流与管理员安全审计
|
||||
#[async_trait]
|
||||
pub trait AuthStore: Send + Sync {
|
||||
async fn load_password_hash(&self, admin_user_id: &str) -> AdminStoreResult<Option<String>>;
|
||||
|
||||
/// 密码更新与审计必须在同一事务提交;旧哈希不匹配时不写入。
|
||||
/// 密码更新与审计必须在同一事务提交;旧哈希不匹配时不写入
|
||||
async fn change_password(
|
||||
&self,
|
||||
admin_user_id: &str,
|
||||
@@ -281,7 +281,7 @@ pub trait AuthStore: Send + Sync {
|
||||
id: &gateway_core::policy::ClientApiKeyId,
|
||||
) -> AdminStoreResult<bool>;
|
||||
|
||||
/// 原子消费来源桶与全局桶的一次登录尝试;被拒绝时返回建议重试间隔。
|
||||
/// 原子消费来源桶与全局桶的一次登录尝试;被拒绝时返回建议重试间隔
|
||||
async fn consume_login_attempt(
|
||||
&self,
|
||||
source_ip: IpAddr,
|
||||
@@ -293,10 +293,10 @@ pub trait AuthStore: Send + Sync {
|
||||
async fn append_audit_event(&self, event: AdminAuditEvent) -> AdminStoreResult<()>;
|
||||
}
|
||||
|
||||
/// Client API Key 资料读取与管理写入。
|
||||
/// Client API Key 资料读取与管理写入
|
||||
#[async_trait]
|
||||
pub trait ClientKeyStore: Send + Sync {
|
||||
/// 按已验证的 ID 读取资料,不读取完整明文 Key。
|
||||
/// 按已验证的 ID 读取资料,不读取完整明文 Key
|
||||
async fn get_client_key(
|
||||
&self,
|
||||
id: &gateway_core::policy::ClientApiKeyId,
|
||||
@@ -333,7 +333,7 @@ pub trait ClientKeyStore: Send + Sync {
|
||||
context: &MutationContext,
|
||||
) -> AdminStoreResult<Revision>;
|
||||
|
||||
/// 局部更新预算上限,保留其他策略和账本;无变化时不产生配置版本或审计。
|
||||
/// 局部更新预算上限,保留其他策略和账本;无变化时不产生配置版本或审计
|
||||
async fn update_client_key_budget_limits(
|
||||
&self,
|
||||
command: UpdateClientKeyBudgetLimits,
|
||||
@@ -341,7 +341,7 @@ pub trait ClientKeyStore: Send + Sync {
|
||||
context: &MutationContext,
|
||||
) -> AdminStoreResult<Option<Revision>>;
|
||||
|
||||
/// 仅修改运行时账本并原子记录审计,不推进配置版本。
|
||||
/// 仅修改运行时账本并原子记录审计,不推进配置版本
|
||||
async fn reset_client_key_budget(
|
||||
&self,
|
||||
command: ResetClientKeyBudget,
|
||||
@@ -350,7 +350,7 @@ pub trait ClientKeyStore: Send + Sync {
|
||||
) -> AdminStoreResult<()>;
|
||||
}
|
||||
|
||||
/// Provider-neutral account group management transactions.
|
||||
/// 跨 Provider 的账号分组管理事务
|
||||
#[async_trait]
|
||||
pub trait AccountGroupStore: Send + Sync {
|
||||
async fn list_account_groups(
|
||||
@@ -388,24 +388,24 @@ pub trait AccountGroupStore: Send + Sync {
|
||||
) -> AdminStoreResult<AccountGroupMutation>;
|
||||
}
|
||||
|
||||
/// 逐条读取的已计算费用事实;消费结束或丢弃时释放查询资源。
|
||||
/// 逐条读取的已计算费用事实;消费结束或丢弃时释放查询资源
|
||||
pub type UsageCalculatedBillingStream<'a> =
|
||||
BoxStream<'a, AdminStoreResult<UsageCalculatedBillingFact>>;
|
||||
|
||||
/// 用量、趋势、诊断与运维错误的只读能力。
|
||||
/// 用量、趋势、诊断与运维错误的只读能力
|
||||
#[async_trait]
|
||||
pub trait ObservabilityStore: Send + Sync {
|
||||
/// 返回历史统计区间和指定观测时刻下的实时账号状态。
|
||||
/// 返回历史统计区间和指定观测时刻下的实时账号状态
|
||||
async fn dashboard_summary(
|
||||
&self,
|
||||
range: TimeRange,
|
||||
observed_at: DateTime<Utc>,
|
||||
) -> AdminStoreResult<DashboardObservation>;
|
||||
|
||||
/// 返回 Dashboard 可选的实时槽位事实。
|
||||
/// 返回 Dashboard 可选的实时槽位事实
|
||||
///
|
||||
/// 该状态来自可丢失的运行时存储;无实现或运行时存储不可用时返回 `None`,不影响
|
||||
/// 持久观测数据的读取。
|
||||
/// 持久观测数据的读取
|
||||
async fn dashboard_runtime_slots(
|
||||
&self,
|
||||
_observed_at: DateTime<Utc>,
|
||||
@@ -421,8 +421,8 @@ pub trait ObservabilityStore: Send + Sync {
|
||||
filter: UsageFilter,
|
||||
) -> AdminStoreResult<Vec<RequestMetricPoint>>;
|
||||
|
||||
/// 流式返回可由 Provider 重新校验的已计算费用事实,不保证顺序。
|
||||
/// 查询及解码错误由流返回;调用方应逐条聚合,避免收集整个区间。
|
||||
/// 流式返回可由 Provider 重新校验的已计算费用事实,不保证顺序
|
||||
/// 查询及解码错误由流返回;调用方应逐条聚合,避免收集整个区间
|
||||
fn usage_calculated_billing_facts(
|
||||
&self,
|
||||
range: TimeRange,
|
||||
@@ -449,7 +449,7 @@ pub trait ObservabilityStore: Send + Sync {
|
||||
async fn list_ops_errors(&self, query: OpsErrorQuery) -> AdminStoreResult<OpsErrorPage>;
|
||||
}
|
||||
|
||||
/// Runtime settings 与管理员 API Key 写入。
|
||||
/// Runtime settings 与管理员 API Key 写入
|
||||
#[async_trait]
|
||||
pub trait SettingsStore: Send + Sync {
|
||||
async fn load_pricing(&self) -> AdminStoreResult<crate::model::pricing::StoredPricing>;
|
||||
@@ -486,7 +486,7 @@ pub trait SettingsStore: Send + Sync {
|
||||
) -> AdminStoreResult<AdminApiKeyMutation>;
|
||||
}
|
||||
|
||||
/// 账号目录、运行态与分组所需的 Store 能力集合。
|
||||
/// 账号目录、运行态与分组所需的 Store 能力集合
|
||||
#[derive(Clone)]
|
||||
pub struct AdminAccountStorePorts {
|
||||
accounts: Arc<dyn AccountStore>,
|
||||
@@ -512,9 +512,10 @@ impl AdminAccountStorePorts {
|
||||
}
|
||||
}
|
||||
|
||||
/// 管理用例所需能力的封闭集合。
|
||||
/// 管理用例所需能力的封闭集合
|
||||
///
|
||||
/// 字段保持私有,每个 getter 只交出一种明确能力。该类型不提供通用拆包入口。
|
||||
/// 字段保持私有,每个 getter 只交出一种明确能力
|
||||
/// 该类型不提供通用拆包入口
|
||||
#[derive(Clone)]
|
||||
pub struct AdminStorePorts {
|
||||
accounts: AdminAccountStorePorts,
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
//! Host 为系统管理用例提供的进程与操作系统能力。
|
||||
//! Host 为系统管理用例提供的进程与操作系统能力
|
||||
|
||||
use std::{pin::Pin, sync::Arc};
|
||||
|
||||
@@ -11,7 +11,7 @@ use crate::model::system::{
|
||||
SystemUpdateStatus, SystemVersion,
|
||||
};
|
||||
|
||||
/// Host 已校验并解包的同一更新候选;字节只含发行清单,不含运行配置或凭据。
|
||||
/// Host 已校验并解包的同一更新候选;字节只含发行清单,不含运行配置或凭据
|
||||
#[derive(Clone)]
|
||||
pub struct SystemUpdateCandidate {
|
||||
pub target_version: String,
|
||||
@@ -28,7 +28,7 @@ impl std::fmt::Debug for SystemUpdateCandidate {
|
||||
}
|
||||
}
|
||||
|
||||
/// Host 系统操作失败类型。
|
||||
/// Host 系统操作失败类型
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum SystemOperationErrorKind {
|
||||
Invalid,
|
||||
@@ -37,7 +37,7 @@ pub enum SystemOperationErrorKind {
|
||||
Internal,
|
||||
}
|
||||
|
||||
/// 不泄漏路径、命令行或发布凭据的系统操作错误。
|
||||
/// 不泄漏路径、命令行或发布凭据的系统操作错误
|
||||
#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
|
||||
#[error("system operation failed: {message}")]
|
||||
pub struct SystemOperationError {
|
||||
@@ -59,17 +59,17 @@ impl SystemOperationError {
|
||||
self.kind
|
||||
}
|
||||
|
||||
/// 返回 Host 已完成脱敏的客户端安全消息。
|
||||
/// 返回 Host 已完成脱敏的客户端安全消息
|
||||
#[must_use]
|
||||
pub fn message(&self) -> &str {
|
||||
&self.message
|
||||
}
|
||||
}
|
||||
|
||||
/// 每个订阅者独占的系统事件流。
|
||||
/// 每个订阅者独占的系统事件流
|
||||
pub type SystemUpdateEventStream = Pin<Box<dyn Stream<Item = SystemUpdateEvent> + Send + 'static>>;
|
||||
|
||||
/// Admin 对启用实例和制品作兼容决策;Host 只负责候选下载、校验与文件交换。
|
||||
/// Admin 对启用实例和制品作兼容决策;Host 只负责候选下载、校验与文件交换
|
||||
#[async_trait]
|
||||
pub trait SystemUpdatePreflight: Send + Sync {
|
||||
async fn validate(
|
||||
@@ -77,7 +77,7 @@ pub trait SystemUpdatePreflight: Send + Sync {
|
||||
candidate: SystemUpdateCandidate,
|
||||
) -> Result<Revision, SystemOperationError>;
|
||||
|
||||
/// 回滚保持严格预检,不能把旧目标当成具有当前重启确认能力的版本。
|
||||
/// 回滚保持严格预检,不能把旧目标当成具有当前重启确认能力的版本
|
||||
async fn validate_rollback(
|
||||
&self,
|
||||
candidate: SystemUpdateCandidate,
|
||||
@@ -85,11 +85,11 @@ pub trait SystemUpdatePreflight: Send + Sync {
|
||||
self.validate(candidate).await
|
||||
}
|
||||
|
||||
/// 在文件交换临界点复核全局配置 CAS,不能用较早快照掩盖实例变更。
|
||||
/// 在文件交换临界点复核全局配置 CAS,不能用较早快照掩盖实例变更
|
||||
async fn confirm_revision(&self, expected: Revision) -> Result<(), SystemOperationError>;
|
||||
}
|
||||
|
||||
/// 重启临界区内复查候选,确认后停用不兼容插件,再允许进程退出。
|
||||
/// 重启临界区内复查候选,确认后停用不兼容插件,再允许进程退出
|
||||
#[async_trait]
|
||||
pub trait SystemRestartPreflight: Send + Sync {
|
||||
async fn prepare(
|
||||
@@ -98,7 +98,7 @@ pub trait SystemRestartPreflight: Send + Sync {
|
||||
) -> Result<(), SystemOperationError>;
|
||||
}
|
||||
|
||||
/// 版本、自更新、回滚和重启能力;实现唯一归 gateway-host。
|
||||
/// 版本、自更新、回滚和重启能力;实现唯一归 gateway-host
|
||||
#[async_trait]
|
||||
pub trait SystemOperations: Send + Sync {
|
||||
async fn version(&self) -> Result<SystemVersion, SystemOperationError>;
|
||||
@@ -125,7 +125,7 @@ pub trait SystemOperations: Send + Sync {
|
||||
preflight: Arc<dyn SystemUpdatePreflight>,
|
||||
) -> Result<SystemOperationAccepted, SystemOperationError>;
|
||||
|
||||
/// 返回下一次启动实际使用的发行;源码运行没有封存发行清单。
|
||||
/// 返回下一次启动实际使用的发行;源码运行没有封存发行清单
|
||||
async fn restart_candidate(
|
||||
&self,
|
||||
) -> Result<Option<SystemUpdateCandidate>, SystemOperationError> {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
//! 主动服务调用的统一组合入口;普通业务方法由所属 HTTP 或执行边界包裹。
|
||||
//! 主动服务调用的统一组合入口;普通业务方法由所属 HTTP 或执行边界包裹
|
||||
|
||||
mod registry;
|
||||
mod settings;
|
||||
@@ -19,7 +19,7 @@ use crate::model::{AdminError, AdminErrorKind};
|
||||
|
||||
pub type PlanSource = Arc<dyn Fn() -> Option<FrozenMiddlewarePlan> + Send + Sync>;
|
||||
|
||||
/// 空计划也是快照,只有未解析的外部入口才读取当前发布集合。
|
||||
/// 空计划也是快照,只有未解析的外部入口才读取当前发布集合
|
||||
#[derive(Clone, Default)]
|
||||
pub enum Plan {
|
||||
#[default]
|
||||
@@ -27,7 +27,7 @@ pub enum Plan {
|
||||
Frozen(Option<FrozenMiddlewarePlan>),
|
||||
}
|
||||
|
||||
/// 非服务入口也有父调用身份和取消信号;发布计划在首次进入公开服务时解析。
|
||||
/// 非服务入口也有父调用身份和取消信号;发布计划在首次进入公开服务时解析
|
||||
#[derive(Clone)]
|
||||
pub struct Origin {
|
||||
pub request_id: String,
|
||||
@@ -51,7 +51,7 @@ impl From<Context> for Origin {
|
||||
|
||||
tokio::task_local! { static CURRENT: Origin; }
|
||||
|
||||
/// task local 随 future poll 进入/退出,不泄漏给并行任务。
|
||||
/// task local 随 future poll 进入/退出,不泄漏给并行任务
|
||||
pub async fn scope<T>(context: impl Into<Origin>, future: impl Future<Output = T>) -> T {
|
||||
CURRENT.scope(context.into(), future).await
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
//! 公开操作登记在所属服务;注册表只校验类型和分派,不实现业务。
|
||||
//! 公开操作登记在所属服务;注册表只校验类型和分派,不实现业务
|
||||
|
||||
use crate::model::AdminError;
|
||||
use futures::future::BoxFuture;
|
||||
@@ -21,7 +21,7 @@ impl Registry {
|
||||
}
|
||||
}
|
||||
|
||||
/// 网关与命令行复用同一组设置服务;注册不会执行读取或写入。
|
||||
/// 网关与命令行复用同一组设置服务;注册不会执行读取或写入
|
||||
pub fn register_settings(
|
||||
&mut self,
|
||||
service: &Arc<dyn crate::SettingsService>,
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
//! 设置服务的唯一公开操作声明;SDK 操作和数据合同由对应合同测试生成。
|
||||
//! 设置服务的唯一公开操作声明;SDK 操作和数据合同由对应合同测试生成
|
||||
|
||||
use crate::{
|
||||
SettingsService,
|
||||
@@ -14,7 +14,7 @@ pub(super) fn register(
|
||||
registry: &mut super::Registry,
|
||||
service: &Arc<dyn SettingsService>,
|
||||
) -> Result<(), AdminError> {
|
||||
// 声明保留 SDK 类型名和原生参数转换,分派名称由业务方法名直接派生。
|
||||
// 声明保留 SDK 类型名和原生参数转换,分派名称由业务方法名直接派生
|
||||
macro_rules! register {
|
||||
($sdk:ident, $method:ident, $input:ty, $pattern:pat $(, $argument:expr)* $(,)?) => {{
|
||||
let service = service.clone();
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
//! Account group management use cases.
|
||||
//! 账号分组的查询、配置与运行态容量聚合用例
|
||||
|
||||
use std::{
|
||||
collections::{BTreeMap, BTreeSet},
|
||||
@@ -29,7 +29,7 @@ use crate::{
|
||||
|
||||
use super::{map_store_error, publish_committed};
|
||||
|
||||
/// API-facing account group management service.
|
||||
/// 面向 API 的账号分组管理服务
|
||||
#[async_trait]
|
||||
pub trait AccountGroupService: Send + Sync {
|
||||
async fn list(&self, query: AccountGroupListQuery) -> Result<AccountGroupPage, AdminError>;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
//! 统一账号目录与原生 Provider 分派。
|
||||
//! 统一账号目录与原生 Provider 分派
|
||||
|
||||
use std::{
|
||||
collections::{BTreeMap, BTreeSet},
|
||||
@@ -49,7 +49,7 @@ use super::{
|
||||
|
||||
const CONNECTION_TEST_INPUT: &str = "Reply with exactly OK.";
|
||||
|
||||
/// 统一账号页消费的服务。
|
||||
/// 统一账号页消费的服务
|
||||
#[async_trait]
|
||||
pub trait AccountsService: Send + Sync {
|
||||
async fn list(&self, query: AccountListQuery) -> Result<AccountDirectoryPage, AdminError>;
|
||||
@@ -279,7 +279,7 @@ impl DefaultAccountsService {
|
||||
accounts: &[AccountPageItem],
|
||||
) -> Result<BTreeMap<String, AccountUsage>, AdminError> {
|
||||
let now = Utc::now();
|
||||
// API Key 没有套餐周期;本地累计直接查询账号创建后仍保留的请求记录。
|
||||
// API Key 没有套餐周期;本地累计直接查询账号创建后仍保留的请求记录
|
||||
let windows = accounts
|
||||
.iter()
|
||||
.filter(|item| item.account.authentication_kind == "api_key")
|
||||
@@ -333,7 +333,7 @@ impl DefaultAccountsService {
|
||||
.await
|
||||
{
|
||||
Ok(quota) => quota,
|
||||
// 凭据更新后的账号投影不要求 Provider 提供额度;显式刷新仍须支持该操作。
|
||||
// 凭据更新后的账号投影不要求 Provider 提供额度;显式刷新仍须支持该操作
|
||||
Err(error)
|
||||
if !refresh_quota
|
||||
&& error.kind()
|
||||
@@ -403,7 +403,7 @@ impl AccountsService for DefaultAccountsService {
|
||||
.iter()
|
||||
.map(|item| item.account.id.clone())
|
||||
.collect::<Vec<_>>();
|
||||
// 只读取当前页的租约占用;观测失败不能把账号误报为空闲或拖垮目录。
|
||||
// 只读取当前页的租约占用;观测失败不能把账号误报为空闲或拖垮目录
|
||||
let in_flight = if ids.is_empty() {
|
||||
None
|
||||
} else {
|
||||
@@ -429,7 +429,7 @@ impl AccountsService for DefaultAccountsService {
|
||||
let account_id = ProviderAccountId::new(account.id.clone())
|
||||
.map_err(|_| AdminError::invalid("Provider 账号 ID 不合法"))?;
|
||||
// 单个账号的 quota 投影失败(Provider 未注册或 quota 读取失败)不拖垮整页:
|
||||
// 该账号降级为空额度投影,其余账号与页面状态照常返回。
|
||||
// 该账号降级为空额度投影,其余账号与页面状态照常返回
|
||||
let provider = match providers.require(&account.provider_kind) {
|
||||
Ok(provider) => provider,
|
||||
Err(error) => {
|
||||
@@ -536,7 +536,7 @@ impl AccountsService for DefaultAccountsService {
|
||||
}) {
|
||||
return Err(AdminError::invalid("账号导出列表包含重复 ID"));
|
||||
}
|
||||
// 混选时先确认全部 Provider 已注册,拒绝路径不读取任何一组明文凭据。
|
||||
// 混选时先确认全部 Provider 已注册,拒绝路径不读取任何一组明文凭据
|
||||
let export_groups = grouped
|
||||
.into_iter()
|
||||
.map(|(provider_kind, ids)| {
|
||||
@@ -615,7 +615,7 @@ impl AccountsService for DefaultAccountsService {
|
||||
.map_err(|error| map_store_error(error, "provider account recovery"))?
|
||||
.config_revision
|
||||
} else {
|
||||
// 停用只表示不参与调度,重新启用不能抹除已观测的额度、凭据或冷却事实。
|
||||
// 停用只表示不参与调度,重新启用不能抹除已观测的额度、凭据或冷却事实
|
||||
self.accounts
|
||||
.batch_update_accounts(
|
||||
BatchUpdateAccounts {
|
||||
@@ -774,7 +774,7 @@ impl AccountsService for DefaultAccountsService {
|
||||
let Some((window, _)) = quota_forecast_source_window("a, period) else {
|
||||
continue;
|
||||
};
|
||||
// 缺少一个周期时两个结果会复用同一窗口,只查询一次历史快照。
|
||||
// 缺少一个周期时两个结果会复用同一窗口,只查询一次历史快照
|
||||
if !selected_keys.insert(window.key.as_str()) {
|
||||
continue;
|
||||
}
|
||||
@@ -814,8 +814,8 @@ impl AccountsService for DefaultAccountsService {
|
||||
.as_deref()
|
||||
.zip(fact.plan_type.as_deref())
|
||||
.is_some_and(|(current, previous)| current.eq_ignore_ascii_case(previous));
|
||||
// 仅容纳已观测到的秒级量化抖动,不用宽时间容差合并实际重置。
|
||||
// 不匹配的段截断基线;之后的有效观测可以重新积累。
|
||||
// 仅容纳已观测到的秒级量化抖动,不用宽时间容差合并实际重置
|
||||
// 不匹配的段截断基线;之后的有效观测可以重新积累
|
||||
if !same_plan || (fact.reset_at - reset_at).abs() > Duration::seconds(2) {
|
||||
points.clear();
|
||||
interrupted = true;
|
||||
@@ -853,7 +853,7 @@ impl AccountsService for DefaultAccountsService {
|
||||
account_id: &ProviderAccountId,
|
||||
) -> Result<AccountPersonalInfo, AdminError> {
|
||||
let (initial, provider) = self.provider_for_account(account_id).await?;
|
||||
// 两项读取相互独立;不因其中一项失败而取消另一项,也不触发凭据或额度刷新。
|
||||
// 两项读取相互独立;不因其中一项失败而取消另一项,也不触发凭据或额度刷新
|
||||
let (profile, subscription) = futures::join!(
|
||||
provider.profile_statistics(account_id),
|
||||
provider.subscription(account_id),
|
||||
@@ -865,7 +865,7 @@ impl AccountsService for DefaultAccountsService {
|
||||
.ok()
|
||||
.flatten();
|
||||
|
||||
// 汇聚等待期间发生重新授权、换绑或删除时,不返回混合身份的数据。
|
||||
// 汇聚等待期间发生重新授权、换绑或删除时,不返回混合身份的数据
|
||||
let current = self.load_account(account_id).await?;
|
||||
if current.account.provider_kind != initial.account.provider_kind
|
||||
|| current.account.credential_revision != initial.account.credential_revision
|
||||
@@ -921,7 +921,7 @@ impl AccountsService for DefaultAccountsService {
|
||||
) -> Result<ProviderResetCreditResult, AdminError> {
|
||||
let account_id = command.account_id.clone();
|
||||
// 覆盖 credential refresh + 同键重试的完整账号级临界区,避免 401 两次调用
|
||||
// 之间插入另一笔不可逆消费。
|
||||
// 之间插入另一笔不可逆消费
|
||||
let lock = self.reset_credit_lock(&account_id).await;
|
||||
let _guard = lock.lock().await;
|
||||
let (_, provider) = self.provider_for_account(&account_id).await?;
|
||||
@@ -1049,7 +1049,7 @@ fn map_reset_credits_error_after_refresh(
|
||||
map_provider_error(error, "provider reset credits")
|
||||
}
|
||||
|
||||
/// 账号目录中单个账号 quota 读取失败时使用的空额度投影。
|
||||
/// 账号目录中单个账号 quota 读取失败时使用的空额度投影
|
||||
fn empty_quota() -> ProviderQuota {
|
||||
ProviderQuota {
|
||||
credits: None,
|
||||
@@ -1073,7 +1073,7 @@ fn quota_usage_window(
|
||||
let seconds = i64::try_from(window.window_seconds?).ok()?;
|
||||
let start = reset_at.checked_sub_signed(Duration::try_seconds(seconds)?)?;
|
||||
let range = TimeRange::new(start, reset_at).ok()?;
|
||||
// 上游百分比以该 reset 边界定义;以当前时间回推会让本地 Token 属于另一窗口。
|
||||
// 上游百分比以该 reset 边界定义;以当前时间回推会让本地 Token 属于另一窗口
|
||||
Some(AccountUsageWindowQuery {
|
||||
account_id: account_id.to_owned(),
|
||||
key: window.key.clone(),
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
//! 控制面登录、会话恢复与身份校验的唯一 owner。
|
||||
//! 控制面登录、会话恢复与身份校验的唯一 owner
|
||||
|
||||
use std::{net::IpAddr, sync::Arc, time::Duration as StdDuration};
|
||||
|
||||
@@ -25,7 +25,7 @@ use crate::{
|
||||
|
||||
use super::map_store_error;
|
||||
|
||||
/// 所有控制面接口消费同一个会话服务,权限由服务端身份决定。
|
||||
/// 所有控制面接口消费同一个会话服务,权限由服务端身份决定
|
||||
#[async_trait]
|
||||
pub trait AuthService: Send + Sync {
|
||||
async fn change_password(
|
||||
@@ -208,7 +208,7 @@ impl AuthService for DefaultAuthService {
|
||||
{
|
||||
return Err(AdminError::conflict("密码已变更,请重新登录"));
|
||||
}
|
||||
// 密码事务提交后旧指纹不再匹配,会话撤销不依赖 Redis 删除成功。
|
||||
// 密码事务提交后旧指纹不再匹配,会话撤销不依赖 Redis 删除成功
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -344,7 +344,7 @@ impl AuthService for DefaultAuthService {
|
||||
let _ = self.store.delete_session(&session_id).await;
|
||||
return Err(LoginError::Unavailable);
|
||||
}
|
||||
// 新身份验证成功后才撤销旧会话;撤销失败时不向浏览器提交新会话。
|
||||
// 新身份验证成功后才撤销旧会话;撤销失败时不向浏览器提交新会话
|
||||
if let Some(previous) = previous_session_id.filter(|value| !value.is_empty())
|
||||
&& self.logout(previous).await.is_err()
|
||||
{
|
||||
@@ -425,7 +425,7 @@ fn valid_admin_api_key_shape(value: &str) -> bool {
|
||||
&& value[6..].bytes().all(|byte| byte.is_ascii_hexdigit())
|
||||
}
|
||||
|
||||
// 指纹只绑定已加盐的密码哈希,不把密码或原始哈希复制到 Redis 会话。
|
||||
// 指纹只绑定已加盐的密码哈希,不把密码或原始哈希复制到 Redis 会话
|
||||
fn password_fingerprint(password_hash: &str) -> String {
|
||||
URL_SAFE_NO_PAD.encode(Sha256::digest(password_hash.as_bytes()))
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
//! 备份配置、手动创建、下载与删除用例。
|
||||
//! 备份配置、手动创建、下载与删除用例
|
||||
|
||||
use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
@@ -26,10 +26,10 @@ use crate::ports::{
|
||||
|
||||
use super::map_store_error;
|
||||
|
||||
/// 默认下载地址有效期。
|
||||
/// 默认下载地址有效期
|
||||
const DOWNLOAD_TTL: Duration = Duration::from_secs(5 * 60);
|
||||
|
||||
/// API 消费的备份管理服务。
|
||||
/// API 消费的备份管理服务
|
||||
#[async_trait]
|
||||
pub trait BackupService: Send + Sync {
|
||||
async fn load_settings(&self) -> Result<BackupSettings, AdminError>;
|
||||
@@ -359,7 +359,7 @@ impl DefaultBackupService {
|
||||
}
|
||||
}
|
||||
|
||||
/// 校验存储配置;空串视为非法,Secret 缺省表示保留旧值。
|
||||
/// 校验存储配置;空串视为非法,Secret 缺省表示保留旧值
|
||||
fn validate_storage(command: &UpdateBackupStorageCommand) -> Result<(), AdminError> {
|
||||
validate_endpoint(&command.endpoint)?;
|
||||
validate_nonempty("region", &command.region)?;
|
||||
@@ -409,7 +409,7 @@ fn validate_length(field: &str, value: &str, max: usize) -> Result<(), AdminErro
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// 备份基础设施错误到既有 `AdminErrorKind` 的唯一边界映射。
|
||||
/// 备份基础设施错误到既有 `AdminErrorKind` 的唯一边界映射
|
||||
fn map_backup_error(error: BackupError) -> AdminError {
|
||||
match error.code() {
|
||||
code::INVALID_CONFIG | code::INVALID_CRON => {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
//! Codex 客户端下载信息用例。
|
||||
//! Codex 客户端下载信息用例
|
||||
|
||||
use std::sync::Arc;
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
//! Client API Key 管理用例。
|
||||
//! Client API Key 管理用例
|
||||
|
||||
use std::sync::Arc;
|
||||
|
||||
@@ -25,7 +25,7 @@ use crate::{
|
||||
|
||||
use super::{map_store_error, publish_committed};
|
||||
|
||||
/// API 消费的 Client Key 管理服务。
|
||||
/// API 消费的 Client Key 管理服务
|
||||
#[async_trait]
|
||||
pub trait ClientKeyService: Send + Sync {
|
||||
async fn get(&self, id: &ClientApiKeyId) -> Result<ClientKeyRecord, AdminError>;
|
||||
@@ -300,7 +300,7 @@ fn validate_cursor(query: &ClientKeyListQuery) -> Result<(), AdminError> {
|
||||
}
|
||||
}
|
||||
|
||||
// 原生与插件创建共用相同的密钥生成规则。
|
||||
// 原生与插件创建共用相同的密钥生成规则
|
||||
pub(super) fn generate_key() -> String {
|
||||
let mut bytes = [0_u8; 32];
|
||||
OsRng.fill_bytes(&mut bytes);
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
//! 已冻结 Provider 的凭据工作流;Admin 保持事务、审计与发布的唯一所有权。
|
||||
//! 已冻结 Provider 的凭据工作流;Admin 保持事务、审计与发布的唯一所有权
|
||||
|
||||
use std::sync::Arc;
|
||||
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
//! 按 Provider 分派凭据管理服务,组合授权与导入流程
|
||||
|
||||
mod flow;
|
||||
|
||||
use std::sync::Arc;
|
||||
@@ -11,7 +13,7 @@ use crate::{
|
||||
|
||||
pub use flow::ProviderCredentials;
|
||||
|
||||
/// 所有 Provider 共用凭据用例;每次操作从已发布目录冻结自己的管理实现。
|
||||
/// 所有 Provider 共用凭据用例;每次操作从已发布目录冻结自己的管理实现
|
||||
pub struct CredentialsService {
|
||||
providers: ProviderAdminRegistry,
|
||||
accounts: Arc<dyn AccountStore>,
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
//! 导入队列由 Host 管理生命周期,HTTP 只负责接受输入和读取快照。
|
||||
//! 导入队列由 Host 管理生命周期,HTTP 只负责接受输入和读取快照
|
||||
|
||||
use std::{
|
||||
collections::VecDeque,
|
||||
@@ -100,7 +100,7 @@ impl Entry {
|
||||
{
|
||||
self.finished_at = Some(Utc::now());
|
||||
self.finished_instant = Some(Instant::now());
|
||||
// 终态仅保留结果,连已清空的输入槽位分配也一并释放。
|
||||
// 终态仅保留结果,连已清空的输入槽位分配也一并释放
|
||||
self.inputs = Vec::new();
|
||||
}
|
||||
}
|
||||
@@ -163,7 +163,7 @@ impl DefaultImportTasksService {
|
||||
.tasks
|
||||
.iter()
|
||||
.position(|entry| entry.inputs.iter().any(Option::is_some))?;
|
||||
// 轮转任务,避免大批次独占后续全部执行机会。
|
||||
// 轮转任务,避免大批次独占后续全部执行机会
|
||||
let mut entry = registry.tasks.remove(task_index)?;
|
||||
let index = entry.inputs.iter().position(Option::is_some)?;
|
||||
let input = entry.inputs[index].take()?;
|
||||
@@ -192,7 +192,7 @@ impl DefaultImportTasksService {
|
||||
item.account_ids = result.credential_ids;
|
||||
}
|
||||
Ok(Err(error)) => {
|
||||
// 存储或发布异常也可能发生在提交之后;不自动重放可能轮换 RT 的操作。
|
||||
// 存储或发布异常也可能发生在提交之后;不自动重放可能轮换 RT 的操作
|
||||
item.status = if matches!(
|
||||
error.kind(),
|
||||
AdminErrorKind::UpstreamResultUnknown
|
||||
@@ -335,7 +335,7 @@ impl DaemonTask for ImportTaskWorker {
|
||||
fn run(&self, cancellation: CancellationToken) -> BoxFuture<'_, Result<(), WorkerTaskError>> {
|
||||
Box::pin(async move {
|
||||
let mut active = FuturesUnordered::new();
|
||||
// 固定一个取消 future,避免循环反复注册中立 CancellationToken 的 waiter。
|
||||
// 固定一个取消 future,避免循环反复注册中立 CancellationToken 的 waiter
|
||||
let cancelled = cancellation.cancelled();
|
||||
tokio::pin!(cancelled);
|
||||
let mut cleanup = tokio::time::interval(Duration::from_secs(60));
|
||||
@@ -361,7 +361,7 @@ impl DaemonTask for ImportTaskWorker {
|
||||
_ = cleanup.tick() => { drop(self.0.registry()); },
|
||||
}
|
||||
}
|
||||
// Host 提供总关闭预算;此处让已发出的交换与入库尽量完成。
|
||||
// Host 提供总关闭预算;此处让已发出的交换与入库尽量完成
|
||||
while active.next().await.is_some() {}
|
||||
Ok(())
|
||||
})
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
//! Key 自助查询从统一会话或只读 Key 校验取得范围,复用现有观测和额度账本。
|
||||
//! Key 自助查询从统一会话或只读 Key 校验取得范围,复用现有观测和额度账本
|
||||
|
||||
use std::sync::Arc;
|
||||
|
||||
@@ -31,10 +31,10 @@ use super::{map_store_error, observability::health_timeline_at};
|
||||
|
||||
#[async_trait]
|
||||
pub trait KeyUsageService: Send + Sync {
|
||||
/// 验证 Key 并只读查询当前额度,不记录 Key 使用或执行推理准入。
|
||||
/// 验证 Key 并只读查询当前额度,不记录 Key 使用或执行推理准入
|
||||
async fn budget(&self, plaintext: &str) -> Result<Option<ClientBudgetStatus>, AdminError>;
|
||||
|
||||
/// 使用 Core 已认证的宿主身份查询额度,不接收插件自报的 Key ID。
|
||||
/// 使用 Core 已认证的宿主身份查询额度,不接收插件自报的 Key ID
|
||||
async fn budget_for_client(
|
||||
&self,
|
||||
id: &ClientApiKeyId,
|
||||
@@ -155,7 +155,7 @@ impl KeyUsageService for DefaultKeyUsageService {
|
||||
let Some(id) = self.key_id(session_id).await? else {
|
||||
return Ok(None);
|
||||
};
|
||||
// 明文只按服务端会话绑定的 Key 读取,禁用或删除后不再提供配置。
|
||||
// 明文只按服务端会话绑定的 Key 读取,禁用或删除后不再提供配置
|
||||
self.keys
|
||||
.reveal_client_key(&id)
|
||||
.await
|
||||
@@ -182,7 +182,7 @@ impl KeyUsageService for DefaultKeyUsageService {
|
||||
};
|
||||
let filter = usage_filter(&id, query.model);
|
||||
let now = query.range.end;
|
||||
// 健康条始终展示部署时区今日,不随历史范围或模型筛选改变。
|
||||
// 健康条始终展示部署时区今日,不随历史范围或模型筛选改变
|
||||
let today = TimeRange {
|
||||
start: self
|
||||
.timezone
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
//! 管理控制面的用例实现。
|
||||
//! 管理控制面的用例实现
|
||||
|
||||
pub mod account_groups;
|
||||
pub mod accounts;
|
||||
@@ -113,7 +113,7 @@ async fn publish_credentials_and_observe_quota(
|
||||
provider.account_facts_changed(account_ids).await;
|
||||
publish_committed(snapshot, revision).await?;
|
||||
|
||||
// 凭据已经提交;额度只是可重建的观察,不能拖住管理请求或回滚提交结果。
|
||||
// 凭据已经提交;额度只是可重建的观察,不能拖住管理请求或回滚提交结果
|
||||
let provider = provider.clone();
|
||||
let account_ids = account_ids.to_vec();
|
||||
let request_id = request_id.to_owned();
|
||||
@@ -343,7 +343,7 @@ async fn commit_authorization(
|
||||
if let Some(guard) = authorization_guard
|
||||
&& let Err(error) = guard.commit().await
|
||||
{
|
||||
// 账号与回执已经原子提交,Redis 清理失败不能把确定的成功改写为失败。
|
||||
// 账号与回执已经原子提交,Redis 清理失败不能把确定的成功改写为失败
|
||||
tracing::warn!(resource, settlement_error = %error, "authorization committed but pending cleanup failed");
|
||||
}
|
||||
Ok(result)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
//! 观测查询、趋势、费用覆盖与健康阈值规则。
|
||||
//! 观测查询、趋势、费用覆盖与健康阈值规则
|
||||
|
||||
use std::{
|
||||
collections::{BTreeMap, HashMap, btree_map::Entry},
|
||||
@@ -93,7 +93,7 @@ impl DashboardQueryCache {
|
||||
}
|
||||
}
|
||||
|
||||
/// API 消费的观测控制面服务。
|
||||
/// API 消费的观测控制面服务
|
||||
#[async_trait]
|
||||
pub trait ObservabilityService: Send + Sync {
|
||||
async fn dashboard_summary(
|
||||
@@ -799,7 +799,7 @@ impl DefaultObservabilityService {
|
||||
}
|
||||
|
||||
/// 补充当前套餐展示名称,逐条尽力把可校验的总额升级为完整分解;单条脏数据(非法 Provider kind、
|
||||
/// 不支持的来源或费用规则失败)只保留该条已存的总额,不影响整页返回。
|
||||
/// 不支持的来源或费用规则失败)只保留该条已存的总额,不影响整页返回
|
||||
fn enrich_list_records(&self, records: &mut [crate::model::observability::UsageListRecord]) {
|
||||
for record in records {
|
||||
record.provider_account_plan_type_display =
|
||||
@@ -870,7 +870,7 @@ impl DefaultObservabilityService {
|
||||
}
|
||||
}
|
||||
|
||||
/// 按部署时区的自然日计算 15 分钟健康桶,桶数随夏令时变化。
|
||||
/// 按部署时区的自然日计算 15 分钟健康桶,桶数随夏令时变化
|
||||
pub(super) fn health_timeline_at(
|
||||
records: &[RequestMetricPoint],
|
||||
now: DateTime<Utc>,
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
//! 插件宿主账号回调;复用凭据事务,不建立第二套账号权威。
|
||||
//! 插件宿主账号回调;复用凭据事务,不建立第二套账号权威
|
||||
|
||||
use std::sync::Arc;
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
//! 插件 Client Key 管理;复用管理服务,只开放非秘密目录与预算操作。
|
||||
//! 插件 Client Key 管理;复用管理服务,只开放非秘密目录与预算操作
|
||||
|
||||
use std::sync::Arc;
|
||||
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
//! 插件自有分组、Key 与成员关系的写入及配置发布编排
|
||||
|
||||
use crate::{
|
||||
model::{
|
||||
AdminError, MutationContext,
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
//! 系统升级与回滚前的插件兼容性检查及确认版本校验
|
||||
|
||||
use std::{collections::BTreeMap, sync::Arc};
|
||||
|
||||
use super::publish_committed;
|
||||
@@ -24,7 +26,7 @@ use crate::{
|
||||
|
||||
use super::plugins::official::{update_compatibility, validate_update_release};
|
||||
|
||||
/// 只读取启用实例及其固定包体,检查目标宿主合同;不准备或执行插件。
|
||||
/// 只读取启用实例及其固定包体,检查目标宿主合同;不准备或执行插件
|
||||
pub(crate) struct PluginSystemUpdatePreflight {
|
||||
store: Arc<dyn PluginStore>,
|
||||
inspector: Arc<dyn PluginPackageInspector>,
|
||||
@@ -53,7 +55,7 @@ impl SystemUpdatePreflight for PluginSystemUpdatePreflight {
|
||||
) -> Result<Revision, SystemOperationError> {
|
||||
let target_version = candidate.target_version.trim_start_matches('v');
|
||||
semver::Version::parse(target_version).map_err(|_| invalid("目标网关版本不合法"))?;
|
||||
// 下载阶段只校验发行身份;插件兼容性在用户点击重启时检查并确认。
|
||||
// 下载阶段只校验发行身份;插件兼容性在用户点击重启时检查并确认
|
||||
validate_update_release(candidate.release_manifest.as_ref(), target_version)
|
||||
.map_err(|_| invalid("目标发行清单不合法或版本不匹配"))?;
|
||||
Ok(self
|
||||
@@ -148,7 +150,7 @@ impl PluginSystemUpdatePreflight {
|
||||
)
|
||||
.map_err(|_| invalid("目标发行清单不合法或版本不匹配"))?;
|
||||
}
|
||||
// 未知目标合同不能证明兼容,列入待确认停用;不伪造目标支持范围。
|
||||
// 未知目标合同不能证明兼容,列入待确认停用;不伪造目标支持范围
|
||||
let host = candidate.as_ref().and_then(|item| {
|
||||
update_compatibility(
|
||||
&item.release_manifest,
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
//! 插件制品的验证、接受、安装与删除,以及首次安装的默认实例配置
|
||||
|
||||
use std::{
|
||||
collections::{BTreeMap, BTreeSet},
|
||||
sync::Arc,
|
||||
@@ -30,7 +32,7 @@ use crate::{
|
||||
|
||||
use super::state::PluginStateService;
|
||||
|
||||
/// 远程插件来源访问所需的传输与受管出站代理目录。
|
||||
/// 远程插件来源访问所需的传输与受管出站代理目录
|
||||
pub struct PluginDistributionPorts {
|
||||
pub(super) transport: Arc<dyn PluginDistribution>,
|
||||
pub(super) proxies: Arc<dyn ProxyStore>,
|
||||
@@ -43,7 +45,7 @@ impl PluginDistributionPorts {
|
||||
}
|
||||
}
|
||||
|
||||
/// 所有安装入口在取得完整包后进入同一校验与事务流程。
|
||||
/// 所有安装入口在取得完整包后进入同一校验与事务流程
|
||||
pub struct PluginsService {
|
||||
pub(super) store: Arc<dyn PluginStore>,
|
||||
pub(super) inspector: Arc<dyn PluginPackageInspector>,
|
||||
@@ -118,7 +120,7 @@ impl PluginsService {
|
||||
})
|
||||
}
|
||||
|
||||
/// 管理上传固定为自定义来源;调用方不能借通用参数声明官方身份。
|
||||
/// 管理上传固定为自定义来源;调用方不能借通用参数声明官方身份
|
||||
pub async fn install_upload(
|
||||
&self,
|
||||
archive: Arc<[u8]>,
|
||||
@@ -135,7 +137,7 @@ impl PluginsService {
|
||||
self.complete_install(mutation, context).await
|
||||
}
|
||||
|
||||
/// 接受已导入但尚未安装的制品;官方发行导入也必须经过管理员的这一步。
|
||||
/// 接受已导入但尚未安装的制品;官方发行导入也必须经过管理员的这一步
|
||||
pub async fn accept_artifact(
|
||||
&self,
|
||||
digest: &str,
|
||||
@@ -185,7 +187,7 @@ impl PluginsService {
|
||||
.await?;
|
||||
|
||||
// 其它管理员写入或同插件的并发安装可能推进全局 revision;稳定 creation ID
|
||||
// 配合每轮重新读取,既不会复制默认实例,也不会覆盖先完成的另一个版本。
|
||||
// 配合每轮重新读取,既不会复制默认实例,也不会覆盖先完成的另一个版本
|
||||
for _ in 0..3 {
|
||||
let snapshot = self
|
||||
.store
|
||||
@@ -311,7 +313,7 @@ fn default_instance_id(plugin_id: &str) -> String {
|
||||
let digest = hash.finalize();
|
||||
let mut bytes = [0_u8; 16];
|
||||
bytes.copy_from_slice(&digest[..16]);
|
||||
// RFC 9562 UUIDv8 保留给应用自定义派生;variant 固定为 RFC 4122。
|
||||
// RFC 9562 UUIDv8 保留给应用自定义派生;variant 固定为 RFC 4122
|
||||
bytes[6] = (bytes[6] & 0x0f) | 0x80;
|
||||
bytes[8] = (bytes[8] & 0x3f) | 0x80;
|
||||
uuid::Uuid::from_bytes(bytes).to_string()
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
//! 插件更新来源与下载凭据管理,以及远程制品验证和安装编排
|
||||
|
||||
use std::collections::BTreeSet;
|
||||
|
||||
use super::PluginsService;
|
||||
@@ -93,7 +95,7 @@ impl PluginsService {
|
||||
binding.outbound_proxy_id.as_deref(),
|
||||
)
|
||||
.await?;
|
||||
// 查询期间来源或策略被修改时返回冲突,不把旧来源的候选交给下一步安装。
|
||||
// 查询期间来源或策略被修改时返回冲突,不把旧来源的候选交给下一步安装
|
||||
if !self.update_sources().await?.contains(&binding) {
|
||||
return Err(AdminError::conflict("插件更新来源已变更,请重新检查"));
|
||||
}
|
||||
@@ -259,7 +261,7 @@ impl PluginsService {
|
||||
{
|
||||
return Err(AdminError::invalid("插件清单身份与安装选择不符"));
|
||||
}
|
||||
// 首次解析前身份未知,按包内身份复核下载前的来源绑定。
|
||||
// 首次解析前身份未知,按包内身份复核下载前的来源绑定
|
||||
let binding = bindings
|
||||
.iter()
|
||||
.find(|binding| binding.plugin_id == artifact.metadata.plugin_id);
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
//! 插件实例的配置、停用、删除与私有状态迁移编排
|
||||
|
||||
use std::collections::{BTreeMap, BTreeSet};
|
||||
|
||||
use gateway_core::{policy::ClientApiKeyId, routing::AccountGroupId};
|
||||
@@ -18,10 +20,10 @@ use crate::{
|
||||
};
|
||||
|
||||
impl PluginsService {
|
||||
/// 只校验固定包体,不启动插件;停用的旧版本仍需向管理端说明不能启用的原因。
|
||||
/// 只校验固定包体,不启动插件;停用的旧版本仍需向管理端说明不能启用的原因
|
||||
async fn compatibility_warning(&self, digest: &str) -> Result<Option<String>, AdminError> {
|
||||
// 运行宿主和摘要固定,缓存纯静态结论,避免管理页轮询反复解包。
|
||||
// 只缓存有限数量的确定结果,暂时性错误仍可重试。
|
||||
// 运行宿主和摘要固定,缓存纯静态结论,避免管理页轮询反复解包
|
||||
// 只缓存有限数量的确定结果,暂时性错误仍可重试
|
||||
let mut cache = self.compatibility.lock().await;
|
||||
if let Some(warning) = cache.get(digest) {
|
||||
return Ok(warning.clone());
|
||||
@@ -362,7 +364,7 @@ impl PluginsService {
|
||||
self.preparation
|
||||
.activate_state(&prepared, &result.instance)
|
||||
.await?;
|
||||
// prepared 的强引用跨过提交与唯一发布入口,防止候选在被读取之前回收。
|
||||
// prepared 的强引用跨过提交与唯一发布入口,防止候选在被读取之前回收
|
||||
publish_committed(self.snapshots.as_ref(), result.config_revision).await?;
|
||||
drop(prepared);
|
||||
Ok(result)
|
||||
@@ -379,7 +381,7 @@ impl PluginsService {
|
||||
) -> Result<PluginInstanceMutation, AdminError> {
|
||||
let previous_state = self.preparation.validate(previous.clone()).await?;
|
||||
let original_revision = snapshot.config_revision;
|
||||
// 即使最终保持停用,迁移也必须由目标制品的受控候选执行。
|
||||
// 即使最终保持停用,迁移也必须由目标制品的受控候选执行
|
||||
let mut migration_instance = target.clone();
|
||||
migration_instance.enabled = true;
|
||||
snapshot.config_revision = migration_instance.revision;
|
||||
@@ -651,7 +653,7 @@ impl PluginsService {
|
||||
Ok(revision)
|
||||
}
|
||||
|
||||
/// 紧急管理修复不要求损坏插件成功准备;提交后沿用现有发布与暂停合同。
|
||||
/// 紧急管理修复不要求损坏插件成功准备;提交后沿用现有发布与暂停合同
|
||||
pub async fn disable_instance(
|
||||
&self,
|
||||
id: &str,
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
//! 插件管理请求的目标解析、身份授权与运行时分派
|
||||
|
||||
use std::sync::Arc;
|
||||
|
||||
use gateway_core::runtime::{RuntimeSnapshotHandle, extensions::ExtensionSetReference};
|
||||
@@ -16,7 +18,7 @@ use crate::{
|
||||
ports::{plugin_management::PluginManagement, plugins::PluginStore},
|
||||
};
|
||||
|
||||
/// 菜单来自发布视图;每次业务动作复核持久实例,旧页面不能继续使用已停用或换版的目标。
|
||||
/// 菜单来自发布视图;每次业务动作复核持久实例,旧页面不能继续使用已停用或换版的目标
|
||||
pub struct PluginManagementService {
|
||||
runtime: Arc<dyn PluginManagement>,
|
||||
store: Arc<dyn PluginStore>,
|
||||
@@ -24,7 +26,7 @@ pub struct PluginManagementService {
|
||||
}
|
||||
|
||||
impl PluginManagementService {
|
||||
/// 页面发起模型请求前复核持久目标与当前发布代次;长响应可重复调用以收敛撤销。
|
||||
/// 页面发起模型请求前复核持久目标与当前发布代次;长响应可重复调用以收敛撤销
|
||||
pub async fn validate_target(&self, target: &PluginManagementTarget) -> Result<(), AdminError> {
|
||||
let reference = self.authorize(target).await?;
|
||||
self.runtime.validate_target(&reference, target).await
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
//! 插件制品、实例、分发、管理页面与私有状态用例的公共入口
|
||||
|
||||
mod artifacts;
|
||||
mod distribution;
|
||||
mod instances;
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
//! 校验官方发行清单与平台制品,并编排插件发行导入
|
||||
|
||||
use std::{collections::BTreeSet, path::Path};
|
||||
|
||||
use serde::Deserialize;
|
||||
@@ -74,9 +76,9 @@ struct Platform {
|
||||
}
|
||||
|
||||
impl PluginsService {
|
||||
/// 从与宿主发行物同信任边界的封口清单导入官方包。
|
||||
/// 从与宿主发行物同信任边界的封口清单导入官方包
|
||||
///
|
||||
/// 所有清单项和包体会先完成静态校验,再进入与上传/远程安装相同的持久化用例。
|
||||
/// 所有清单项和包体会先完成静态校验,再进入与上传/远程安装相同的持久化用例
|
||||
pub async fn import_official_release(
|
||||
&self,
|
||||
files: &dyn OfficialPluginReleaseFiles,
|
||||
@@ -104,7 +106,7 @@ impl PluginsService {
|
||||
.ok_or_else(|| AdminError::invalid("当前平台不支持官方插件发行物"))?;
|
||||
let mut validated = Vec::with_capacity(plugins.len());
|
||||
|
||||
// 先完成整份清单的静态检查;持久化故障后的已提交前缀由下次启动幂等补齐。
|
||||
// 先完成整份清单的静态检查;持久化故障后的已提交前缀由下次启动幂等补齐
|
||||
for plugin in plugins {
|
||||
let artifact = select_artifact(&plugin, platform)?;
|
||||
let archive = files
|
||||
@@ -189,7 +191,7 @@ pub(crate) fn validate_update_release(
|
||||
fn parse_host_compatibility(
|
||||
mut value: serde_json::Value,
|
||||
) -> Result<PluginHostCompatibility, AdminError> {
|
||||
// 旧更新器要求 v1 与空权限字段,运行兼容性仍由清单、协议和能力版本表达。
|
||||
// 旧更新器要求 v1 与空权限字段,运行兼容性仍由清单、协议和能力版本表达
|
||||
if value.get("schema_version") == Some(&serde_json::json!(1))
|
||||
&& value.get("permissions") == Some(&serde_json::json!([]))
|
||||
{
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
//! 插件私有状态迁移的开始、撤销与存储错误映射
|
||||
|
||||
use std::sync::Arc;
|
||||
|
||||
use crate::{
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
//! 插件实例的版本切换与回滚计划,以及目标版本配置合并
|
||||
|
||||
use super::{
|
||||
PluginsService,
|
||||
artifacts::{configuration_defaults, default_bindings},
|
||||
@@ -69,7 +71,7 @@ impl PluginsService {
|
||||
secrets: Some(saved.secrets),
|
||||
bindings: saved.bindings,
|
||||
};
|
||||
// 沿用同一份快照的 CAS 和状态迁移流程,不能在检查目标后重新读取并覆盖并发配置。
|
||||
// 沿用同一份快照的 CAS 和状态迁移流程,不能在检查目标后重新读取并覆盖并发配置
|
||||
self.configure_from_snapshot(snapshot, Some(existing), configuration, context)
|
||||
.await
|
||||
}
|
||||
@@ -187,7 +189,7 @@ impl PluginsService {
|
||||
secrets: Some(configuration.secrets),
|
||||
bindings: configuration.bindings,
|
||||
};
|
||||
// 同一次读取的 CAS 覆盖草稿计算、准备与提交,不能覆盖并发设置。
|
||||
// 同一次读取的 CAS 覆盖草稿计算、准备与提交,不能覆盖并发设置
|
||||
self.configure_from_snapshot(snapshot, Some(existing), request, context)
|
||||
.await
|
||||
}
|
||||
@@ -223,7 +225,7 @@ impl PluginsService {
|
||||
return Ok((saved, true));
|
||||
}
|
||||
let mut configuration = instance.configuration.clone();
|
||||
// 只补充缺失的默认值,保留显式值及未知字段,让不兼容项由校验反馈给用户。
|
||||
// 只补充缺失的默认值,保留显式值及未知字段,让不兼容项由校验反馈给用户
|
||||
merge_missing_defaults(&mut configuration, configuration_defaults(&target.metadata));
|
||||
Ok((
|
||||
PluginVersionConfiguration {
|
||||
@@ -242,7 +244,7 @@ fn version_bindings(
|
||||
target: &PluginArtifactMetadata,
|
||||
) -> Vec<crate::model::plugins::instances::PluginCapabilityBinding> {
|
||||
let mut bindings = default_bindings(target);
|
||||
// 已关闭的既有能力继续关闭,新声明能力才采用包默认值。
|
||||
// 已关闭的既有能力继续关闭,新声明能力才采用包默认值
|
||||
bindings.retain(|binding| {
|
||||
let capability = target
|
||||
.contributes
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
//! 账号出站代理的管理、账号解绑与连通性测试用例
|
||||
|
||||
use std::sync::Arc;
|
||||
|
||||
use async_trait::async_trait;
|
||||
@@ -59,7 +61,7 @@ pub trait ProxiesService: Send + Sync {
|
||||
detect_location: bool,
|
||||
context: &MutationContext,
|
||||
) -> Result<ProxyRecord, AdminError>;
|
||||
/// 探测未保存的连接地址,不写入代理记录或修改账号绑定。
|
||||
/// 探测未保存的连接地址,不写入代理记录或修改账号绑定
|
||||
async fn probe(
|
||||
&self,
|
||||
proxy: &OutboundProxy,
|
||||
@@ -140,7 +142,7 @@ impl ProxiesService for DefaultProxiesService {
|
||||
.map_err(|error| map_store_error(error, "proxy"))?;
|
||||
page.items = stream::iter(page.items)
|
||||
.map(|mut account| async {
|
||||
// 与账号目录一致,缺失套餐时读取已有额度快照;限制并发且不触发上游刷新。
|
||||
// 与账号目录一致,缺失套餐时读取已有额度快照;限制并发且不触发上游刷新
|
||||
let mut cached_quota = None;
|
||||
if explicit_plan_type(account.plan_type.as_deref()).is_none()
|
||||
&& let Ok(kind) = ProviderKind::new(account.provider_kind.clone())
|
||||
@@ -216,7 +218,7 @@ impl ProxiesService for DefaultProxiesService {
|
||||
.map(|location| location.map(|value| value.normalized()).transpose())
|
||||
.transpose()
|
||||
.map_err(|_| AdminError::invalid("代理位置不合法"))?;
|
||||
// 只在开启自动模式或连接地址变化时检测,普通编辑不刷新已识别位置。
|
||||
// 只在开启自动模式或连接地址变化时检测,普通编辑不刷新已识别位置
|
||||
command.test = None;
|
||||
if command.auto_location.is_some() || command.proxy.is_some() {
|
||||
let current = self
|
||||
@@ -287,7 +289,7 @@ impl ProxiesService for DefaultProxiesService {
|
||||
if record.revision != revision {
|
||||
return Err(AdminError::conflict("代理已被修改,请刷新后重新测试"));
|
||||
}
|
||||
// 手动解析只为本次测试请求位置,不改变自动跟随的持久配置。
|
||||
// 手动解析只为本次测试请求位置,不改变自动跟随的持久配置
|
||||
let result = self
|
||||
.probe
|
||||
.test(&record.proxy, record.auto_location || detect_location)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
//! Runtime settings 与管理员 API Key 用例。
|
||||
//! Runtime settings 与管理员 API Key 用例
|
||||
|
||||
use std::sync::Arc;
|
||||
|
||||
@@ -20,7 +20,7 @@ use crate::{
|
||||
|
||||
use super::{map_store_error, publish_committed};
|
||||
|
||||
/// API 消费的 Runtime settings 管理服务。
|
||||
/// API 消费的 Runtime settings 管理服务
|
||||
#[async_trait]
|
||||
pub trait SettingsService: Send + Sync {
|
||||
async fn preview_pricing_sync(
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
//! 系统管理用例。
|
||||
//! 系统管理用例
|
||||
|
||||
use std::sync::Arc;
|
||||
|
||||
@@ -19,7 +19,7 @@ use crate::{
|
||||
},
|
||||
};
|
||||
|
||||
/// API 消费的系统管理服务。
|
||||
/// API 消费的系统管理服务
|
||||
#[async_trait]
|
||||
pub trait SystemService: Send + Sync {
|
||||
async fn version(&self) -> Result<SystemVersion, AdminError>;
|
||||
@@ -44,7 +44,7 @@ pub trait SystemService: Send + Sync {
|
||||
) -> Result<SystemOperationAccepted, AdminError>;
|
||||
}
|
||||
|
||||
/// 保持 Host 能力窄边界的默认系统用例。
|
||||
/// 保持 Host 能力窄边界的默认系统用例
|
||||
pub(crate) struct DefaultSystemService {
|
||||
operations: Arc<dyn SystemOperations>,
|
||||
preflight: Arc<PluginSystemUpdatePreflight>,
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
//! 备份模块测试:镜像 src/backup/ 的 policy 与 task。
|
||||
//! 备份模块测试:镜像 src/backup/ 的 policy 与 task
|
||||
|
||||
mod policy;
|
||||
pub mod support;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
//! 备份调度与保留策略(backup/policy.rs)的纯逻辑测试。
|
||||
//! 备份调度与保留策略(backup/policy.rs)的纯逻辑测试
|
||||
|
||||
use chrono::{Duration, TimeZone as _, Utc};
|
||||
|
||||
@@ -55,7 +55,7 @@ fn next_after_is_strictly_later() {
|
||||
#[test]
|
||||
fn last_firing_is_at_or_before() {
|
||||
let schedule = BackupSchedule::parse("0 2 * * *", "Asia/Shanghai".parse().unwrap()).unwrap();
|
||||
// 北京时间 02:00 = 前一日 18:00 UTC。
|
||||
// 北京时间 02:00 = 前一日 18:00 UTC
|
||||
let before = Utc.with_ymd_and_hms(2026, 8, 1, 17, 59, 0).unwrap();
|
||||
assert_eq!(
|
||||
schedule.last_firing_at_or_before(before).unwrap(),
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
//! 备份测试共享 fixture:内存 fake 端口与辅助函数。
|
||||
//! 备份测试共享 fixture:内存 fake 端口与辅助函数
|
||||
//!
|
||||
//! 用内存 fake 端口驱动编排层,不依赖 MinIO/真实 S3。
|
||||
//! s3.rs / pg_dump.rs 是 SDK 薄适配器,协议正确性交给 aws-sdk / pg_dump 保证。
|
||||
//! 供 backup::task 与 use_case::backup 的测试复用。
|
||||
//! 用内存 fake 端口驱动编排层,不依赖 MinIO/真实 S3
|
||||
//! s3.rs / pg_dump.rs 是 SDK 薄适配器,协议正确性交给 aws-sdk / pg_dump 保证
|
||||
//! 供 backup::task 与 use_case::backup 的测试复用
|
||||
|
||||
use std::sync::Mutex;
|
||||
|
||||
@@ -31,20 +31,20 @@ use secrecy::SecretString;
|
||||
use sha2::{Digest as _, Sha256};
|
||||
use tempfile::TempDir;
|
||||
|
||||
/// 假归档内容;dump 与上传校验共用同一字节与校验值。
|
||||
/// 假归档内容;dump 与上传校验共用同一字节与校验值
|
||||
pub(crate) const DUMP_CONTENT: &[u8] = b"custom-format-archive-bytes-0123456789";
|
||||
|
||||
/// 计算一段内容的 SHA-256 十六进制。
|
||||
/// 计算一段内容的 SHA-256 十六进制
|
||||
pub(crate) fn sha256_hex(content: &[u8]) -> String {
|
||||
hex::encode(Sha256::digest(content))
|
||||
}
|
||||
|
||||
/// 返回 32 位十六进制的合法备份记录 id。
|
||||
/// 返回 32 位十六进制的合法备份记录 id
|
||||
pub(crate) fn backup_id(suffix: &str) -> String {
|
||||
format!("backup_{}", sha256_hex(suffix.as_bytes()))
|
||||
}
|
||||
|
||||
/// 已配置且已通过连接测试的设置快照。
|
||||
/// 已配置且已通过连接测试的设置快照
|
||||
pub(crate) fn configured_settings() -> BackupSettings {
|
||||
let now = Utc::now();
|
||||
BackupSettings {
|
||||
@@ -67,7 +67,7 @@ pub(crate) fn configured_settings() -> BackupSettings {
|
||||
}
|
||||
}
|
||||
|
||||
/// 从 seed 构造 queued 记录。
|
||||
/// 从 seed 构造 queued 记录
|
||||
pub(crate) fn queued_record(seed: &BackupRecordSeed) -> BackupRecord {
|
||||
let now = Utc::now();
|
||||
BackupRecord {
|
||||
@@ -89,7 +89,7 @@ pub(crate) fn queued_record(seed: &BackupRecordSeed) -> BackupRecord {
|
||||
}
|
||||
}
|
||||
|
||||
/// 内存版备份仓储。
|
||||
/// 内存版备份仓储
|
||||
pub(crate) struct FakeBackupRepository {
|
||||
settings: Mutex<BackupSettings>,
|
||||
records: Mutex<Vec<BackupRecord>>,
|
||||
@@ -103,12 +103,12 @@ impl FakeBackupRepository {
|
||||
}
|
||||
}
|
||||
|
||||
/// 测试辅助:读取当前全部记录。
|
||||
/// 测试辅助:读取当前全部记录
|
||||
pub(crate) fn all_records(&self) -> Vec<BackupRecord> {
|
||||
self.records.lock().expect("records").clone()
|
||||
}
|
||||
|
||||
/// 测试辅助:强制把一条记录标记为指定时间完成的 completed。
|
||||
/// 测试辅助:强制把一条记录标记为指定时间完成的 completed
|
||||
pub(crate) fn set_completed(&self, id: &str, completed_at: DateTime<Utc>) {
|
||||
let mut records = self.records.lock().expect("records");
|
||||
if let Some(record) = records.iter_mut().find(|record| record.id == id) {
|
||||
@@ -118,7 +118,7 @@ impl FakeBackupRepository {
|
||||
}
|
||||
}
|
||||
|
||||
/// 测试辅助:强制把一条记录迁移到指定状态。
|
||||
/// 测试辅助:强制把一条记录迁移到指定状态
|
||||
pub(crate) fn force_status(&self, id: &str, status: BackupStatus) {
|
||||
let mut records = self.records.lock().expect("records");
|
||||
if let Some(record) = records.iter_mut().find(|record| record.id == id) {
|
||||
@@ -450,7 +450,7 @@ impl BackupRepository for FakeBackupRepository {
|
||||
}
|
||||
}
|
||||
|
||||
/// 假 `pg_dump` 端口:把固定内容写入临时文件作为归档;可注入失败。
|
||||
/// 假 `pg_dump` 端口:把固定内容写入临时文件作为归档;可注入失败
|
||||
pub(crate) struct FakeDumpPort {
|
||||
dir: TempDir,
|
||||
fail_dump: bool,
|
||||
@@ -464,7 +464,7 @@ impl FakeDumpPort {
|
||||
}
|
||||
}
|
||||
|
||||
/// 让下一次 `dump` 返回 `backup.pg_dump_failed`。
|
||||
/// 让下一次 `dump` 返回 `backup.pg_dump_failed`
|
||||
pub(crate) fn fail_next_dump(mut self) -> Self {
|
||||
self.fail_dump = true;
|
||||
self
|
||||
@@ -525,7 +525,7 @@ impl DatabaseDumpPort for FakeDumpPort {
|
||||
}
|
||||
}
|
||||
|
||||
/// 内存版对象存储:把上传内容按 key 保存并支持 Head/Delete/预签名。
|
||||
/// 内存版对象存储:把上传内容按 key 保存并支持 Head/Delete/预签名
|
||||
pub(crate) struct FakeObjectStore {
|
||||
pub(crate) objects: Mutex<std::collections::HashMap<String, Vec<u8>>>,
|
||||
upload_error: Option<BackupError>,
|
||||
@@ -539,7 +539,7 @@ impl FakeObjectStore {
|
||||
}
|
||||
}
|
||||
|
||||
/// 让上传返回指定的脱敏 S3 错误。
|
||||
/// 让上传返回指定的脱敏 S3 错误
|
||||
pub(crate) fn fail_upload(mut self, code: &'static str, message: impl Into<String>) -> Self {
|
||||
self.upload_error = Some(BackupError::new(code, message.into()));
|
||||
self
|
||||
@@ -621,7 +621,7 @@ impl BackupObjectStorePort for FakeObjectStore {
|
||||
}
|
||||
}
|
||||
|
||||
/// 记录审计事件的假 AuthStore。
|
||||
/// 记录审计事件的假 AuthStore
|
||||
pub(crate) struct FakeAuthStore {
|
||||
audit: Mutex<Vec<AdminAuditEvent>>,
|
||||
}
|
||||
@@ -712,7 +712,7 @@ impl AuthStore for FakeAuthStore {
|
||||
}
|
||||
}
|
||||
|
||||
/// 系统发起者上下文。
|
||||
/// 系统发起者上下文
|
||||
pub(crate) fn system_context() -> MutationContext {
|
||||
MutationContext {
|
||||
actor: MutationActor::System,
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
//! BackupTask 执行链测试:驱动内存 fake 端口跑完整 dump/upload/verify/retention 流程。
|
||||
//! BackupTask 执行链测试:驱动内存 fake 端口跑完整 dump/upload/verify/retention 流程
|
||||
|
||||
use std::sync::Arc;
|
||||
|
||||
@@ -50,7 +50,7 @@ async fn backup_task_runs_full_dump_upload_verify_pipeline() {
|
||||
Some(sha256_hex(DUMP_CONTENT).as_str())
|
||||
);
|
||||
|
||||
// 对象已上传且内容与本地归档一致。
|
||||
// 对象已上传且内容与本地归档一致
|
||||
let stored = object_store
|
||||
.object(&seed.object_key)
|
||||
.expect("uploaded object exists");
|
||||
@@ -85,7 +85,7 @@ async fn backup_task_marks_failed_when_dump_port_fails() {
|
||||
assert_eq!(failed.status, BackupStatus::Failed);
|
||||
assert_eq!(failed.error_code.as_deref(), Some("backup.pg_dump_failed"));
|
||||
assert!(failed.completed_at.is_some());
|
||||
// 归档未上传,对象不存在。
|
||||
// 归档未上传,对象不存在
|
||||
assert!(object_store.object(&seed.object_key).is_none());
|
||||
}
|
||||
|
||||
@@ -167,7 +167,7 @@ async fn retention_cleans_expired_scheduled_backups() {
|
||||
let dump = Arc::new(FakeDumpPort::new());
|
||||
let object_store = Arc::new(FakeObjectStore::new());
|
||||
|
||||
// 造一条旧 completed 计划备份(超过 retention_days)与一条新备份。
|
||||
// 造一条旧 completed 计划备份(超过 retention_days)与一条新备份
|
||||
let old_seed = BackupRecordSeed {
|
||||
id: backup_id("old"),
|
||||
trigger_kind: BackupTriggerKind::Scheduled,
|
||||
@@ -186,16 +186,16 @@ async fn retention_cleans_expired_scheduled_backups() {
|
||||
.insert_backup_record(old_seed.clone())
|
||||
.await
|
||||
.expect("insert old");
|
||||
// 旧备份 40 天前完成(释放活跃名额),再插入新备份。
|
||||
// 旧备份 40 天前完成(释放活跃名额),再插入新备份
|
||||
let now = Utc::now();
|
||||
repository.set_completed(&backup_id("old"), now - Duration::days(40));
|
||||
repository
|
||||
.insert_backup_record(new_seed.clone())
|
||||
.await
|
||||
.expect("insert new");
|
||||
// 新备份 1 天前完成。
|
||||
// 新备份 1 天前完成
|
||||
repository.set_completed(&backup_id("new"), now - Duration::days(1));
|
||||
// 两个对象都已上传。
|
||||
// 两个对象都已上传
|
||||
object_store
|
||||
.objects
|
||||
.lock()
|
||||
@@ -207,7 +207,7 @@ async fn retention_cleans_expired_scheduled_backups() {
|
||||
.expect("objects")
|
||||
.insert(new_seed.object_key.clone(), DUMP_CONTENT.to_vec());
|
||||
|
||||
// 设置 retention_days = 30。
|
||||
// 设置 retention_days = 30
|
||||
repository
|
||||
.update_schedule_settings(
|
||||
UpdateBackupScheduleCommand {
|
||||
@@ -229,7 +229,7 @@ async fn retention_cleans_expired_scheduled_backups() {
|
||||
.await
|
||||
.expect("run cycle");
|
||||
|
||||
// 旧备份被硬删除,对象被删除;新备份保留。
|
||||
// 旧备份被硬删除,对象被删除;新备份保留
|
||||
let records = repository.all_records();
|
||||
let ids: Vec<_> = records.iter().map(|record| record.id.as_str()).collect();
|
||||
assert_eq!(ids, vec![backup_id("new").as_str()]);
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
//! 管理控制面测试入口,以及通用分页、版本与查询值对象测试
|
||||
|
||||
use std::str::FromStr as _;
|
||||
|
||||
use chrono::{TimeDelta, Utc};
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user