refactor(plugin): keep external quota cycles out of host ledger (#332)

Restore the pre-window host implementation and document plugin-owned policy and presentation. Keep native reset RPC coverage and run plugin integration tests with the standard isolated test services.

Co-authored-by: Codex <noreply@openai.com>
This commit is contained in:
Zyy
2026-09-30 21:47:50 +08:00
co-authored by Codex
parent 9d651856be
commit caa6c8ffc8
5 changed files with 31 additions and 10 deletions
@@ -106,7 +106,7 @@ async fn plugin_process_manages_native_budget_through_client_key_service() {
"client_key_id":"key_budget", "daily_limit_usd":"10", "weekly_limit_usd":"20",
"daily_used_usd":"3", "weekly_used_usd":"0",
"daily_resets_at_ms":before.budget.daily_resets_at.map(|time| chrono::DateTime::<chrono::Utc>::from(time).timestamp_millis()),
"weekly_resets_at_ms":before.budget.weekly_resets_at.map(|time| chrono::DateTime::<chrono::Utc>::from(time).timestamp_millis()),
"weekly_resets_at_ms":null,
});
assert_eq!(results[6], expected);
assert_eq!(results[7], json!({"client_key_id":"key_budget"}));
@@ -126,10 +126,7 @@ async fn plugin_process_manages_native_budget_through_client_key_service() {
1
);
assert_eq!(after.budget.daily_resets_at, before.budget.daily_resets_at);
assert_eq!(
after.budget.weekly_resets_at,
before.budget.weekly_resets_at
);
assert_eq!(after.budget.weekly_resets_at, None);
assert_eq!(audits.len(), 1);
assert!(audits[0].starts_with(&format!("plugin:{}:scope:", view.target.instance_id)));
}
@@ -644,8 +644,24 @@ impl Environment {
}
async fn create_with_store_mode(command_line: bool) -> Option<Self> {
let database = std::env::var("CPR_PLUGIN_TEST_DATABASE_URL").ok()?;
let redis = std::env::var("CPR_PLUGIN_TEST_REDIS_URL").expect("isolated plugin Redis URL");
// 插件专用服务优先;CI 的标准测试服务同样通过随机 schema 隔离数据库。
let (database, redis) = if let Ok(database) = std::env::var("CPR_PLUGIN_TEST_DATABASE_URL")
{
(
database,
std::env::var("CPR_PLUGIN_TEST_REDIS_URL").expect("isolated plugin Redis URL"),
)
} else {
let database = std::env::var("CPR_TEST_DATABASE_URL").ok();
assert!(
database.is_some() || std::env::var_os("CI").is_none(),
"CI requires plugin or standard test services"
);
(
database?,
std::env::var("CPR_TEST_REDIS_URL").expect("isolated test Redis URL"),
)
};
let schema = format!("cpr_plugin_{}", uuid::Uuid::new_v4().simple());
let admin = PgPoolOptions::new()
.max_connections(1)
@@ -186,6 +186,12 @@ SDK 不自动重试。超时或断连不能证明写入未提交;重试上限
不存在的 Key 返回 `rejected`;写入事务复验发现实例停用或版本变化时返回 `conflict`,
非法输入返回 `invalid_input`。账号关联、预算分配和重置触发由插件决定,宿主不自动串联上述接口
插件的外部周期、截止时间和同步进度保存在自身 `host.state.*` 中,并通过自己的管理页面展示;
这些数据不会改写宿主日/周窗口及其显示。插件可在 `request` 中间件中按自己的策略提前拒绝请求,
在确认新周期后调用预算重置或上限修改接口;调用仍遵循上述原生语义,宿主窗口继续按自身日期规则滚动。
私有状态写入与预算操作不是一个事务,不能把私有状态标记当作预算操作已提交的证明;结果未知时须按上述合同处理。
停用插件会停止其后续拦截与同步,已执行的额度修改或重置不会自动撤销
### Key、模型与模型调用
模型调用方法:
@@ -53,7 +53,7 @@ impl HostClient {
serde_json::from_value(reply.result).map_err(|_| invalid())
}
/// 清零指定周期,保留限额和到期时间;结果未知时不能盲目重试。
/// 清零并关闭指定周期,保留限额,下次使用时重新开启;结果未知时不能盲目重试。
///
/// # Errors
/// 实例过期、Key 不存在、宿主写入失败时返回错误。
+4 -2
View File
@@ -561,7 +561,8 @@ Admin 的手动重置复用同一账本与 Key 行锁,在一个事务中清零
插件预算回调通过 `PluginClientKeyAccess` 进入同一 `ClientKeyService` 和 `ClientKeyStore`,存储事务先复核插件实例版本,
再执行原生预算操作;不建立另一份预算状态。查询复用账本投影,不触发准入。局部更新金额上限不改用量或窗口,
仅在值变化时提交配置 revision、审计并发布;重置仍不推进配置版本。重复调用及结果未知的合同见
[SDK 预算接口](../backend/crates/gateway-plugin/sdk/docs/capabilities.md#client-key-预算)
[SDK 预算接口](../backend/crates/gateway-plugin/sdk/docs/capabilities.md#client-key-预算)。插件的外部周期和截止时间属于插件私有状态,展示在插件页面;
宿主预算接口和管理页面只表达原生窗口事实,不为插件周期修改滚动规则或重置语义
- 日窗口按北京时间零点划分;七天窗口从首次准入当天零点开始,到期后由下一次使用重新开启,不固定为周一
- 金额优先使用 Provider 上报的 USD,否则按现有模型价格估算;订阅账号的估算费用不代表上游订阅账单。
@@ -849,7 +850,8 @@ RUST_MIN_STACK=16777216 cargo +1.97.0 test --manifest-path backend/Cargo.toml --
其他检查与界面验证按 [贡献与审查](../CONTRIBUTING.md#验证) 执行
插件 Runtime 的真实子进程与持久化测试使用 `CPR_PLUGIN_TEST_DATABASE_URL` 和
`CPR_PLUGIN_TEST_REDIS_URL` 指向专用实例,密码及隔离要求与上述 Store 测试一致。
`CPR_PLUGIN_TEST_REDIS_URL` 指向专用实例;未提供插件专用变量时复用 `CPR_TEST_DATABASE_URL` 与 `CPR_TEST_REDIS_URL`。
密码及隔离要求与上述 Store 测试一致,CI 缺少服务配置时直接失败。
设置 `CPR_PLUGIN_TEST_LIVE_HTTP=1` 会额外请求 GitHub 公共 HTTPS API,验证受管出站链路;这不代表模型推理验收。
若测试环境使用 Fake-IP 或私网 DNS,需通过 `CPR_PLUGIN_TEST_LIVE_NETWORK_RANGES` 显式提供逗号分隔的
CIDR 授权。该选项只用于真实网络测试,默认为空,不改变生产网络策略或其他测试的授权