feat: cloud 门禁/超时/成本/沙箱收敛为单一判定源

- 新增标准库叶子层 timeouts/gates/quota/sandbox_policy:
  审批 3600 与单轮 600 各有名常量与环境覆盖层,已建项
  expires_at 永不重算;目录/控制/合并状态门禁统一判定,
  目录违规无任何覆盖参数;配额取数与比较唯一来源
  (USD 先于无价、命中均为 >=、无价只计 actual 行);
  /tmp 隔离矩阵与失联清理判定纯函数化。
- 调用点委托:server/worker/automations/auto_memory 的
  配额 SQL 与比较、server/tree/marker 的目录解析、
  marker 的控制门、sandbox 的任务容器名正则、两处 300 秒
  陈旧阈值默认值均收敛,别名与错误文案不变。
- 统一两个 P0 口径分歧:服务端无价计数由全部 usd IS NULL
  行改为 actual-only;显式不限额用户在服务端准入亦不受
  无价上限约束(与 worker 运行时一致)。
- 不新增门禁种类与 record 模式(主线无此实现);不改
  skill_seed/skills/SKILL.md;不改 v1.1 契约与分支保护。
- 新增 test_arch_converge.py 18 项。
This commit is contained in:
host committed 2026-11-04 20:54:50 +00:00
1 parent 17fa493da7
commit facc2a249a
16 files changed
+1070 -301

No files matched your search

+24 -51
View File
@@ -38,9 +38,10 @@
id 记在 automation_runs.task_id 里,派发与任务绑定,不用
为自动化另起一套通知系统)。
本模块只依赖标准库,不 import cloud 其它模块,避免循环
import;表迁移写法仿 peer_messages(CREATE TABLE IF NOT
EXISTS + PRAGMA 查列补齐,SQLite 与 Postgres 适配层同款)。
本模块只依赖标准库与同为标准库叶子的 cloud.quota,不
import 其他 cloud 模块,避免循环 import;表迁移写法仿
peer_messages(CREATE TABLE IF NOT EXISTS + PRAGMA 查列补齐,
SQLite 与 Postgres 适配层同款)。
"""
import os
@@ -48,6 +49,11 @@ import time
import uuid
from datetime import datetime, timezone
from cloud.quota import decide as _quota_decide
from cloud.quota import default_quota_usd_micros as _quota_default
from cloud.quota import monthly_usage as _quota_monthly_usage
from cloud.quota import quota_for_owner as _quota_for_owner
MIN_INTERVAL_SECONDS = 60
MAX_INTERVAL_SECONDS = 30 * 86400
MAX_NAME_LEN = 100
@@ -520,13 +526,9 @@ def record_automation_run(conn, automation_id, project_id, task_id=None,
def _default_quota_usd_micros():
"""默认月配额(微美元):与服务端 _default_quota_usd_micros
同值同环境变量(CLOUD_DEFAULT_QUOTA_USD_MICROS,默认 $10)。"""
try:
return max(0, int(os.environ.get(
"CLOUD_DEFAULT_QUOTA_USD_MICROS", "10000000")))
except (TypeError, ValueError):
return 10000000
"""默认月配额(微美元):解析收敛到 cloud/quota.py
(同值同环境变量 CLOUD_DEFAULT_QUOTA_USD_MICROS,默认 $10)。"""
return _quota_default()
def _unpriced_token_cap():
@@ -549,29 +551,17 @@ def _automation_quota_exceeded(conn, owner):
过处理,绝不建任务)。cost_records 表不存在时视为零花费
(CostStore 首次记账时才建表,不存在即从未记过账),其
它查库异常才 fail-closed——全新库上不能因此误拒全部运行。
本模块只依赖标准库,不 import cloud 其它模块,故此处就
地实现 SQL 版口径,注释里标明三处同口径点,改口径时三
处要一起改(server._quota_status、worker._quota_exceeded、
本函数)。
取数与比较判定收敛到 cloud/quota.py(本函数只保留
表存在探针与 PG 事务回滚处理,因其与自动化执行器的
事务形态绑定,不宜塞进通用取数层)。
"""
if not owner:
return ("quota_exceeded: 自动化缺少创建人,无法查配额,"
"拒绝建任务")
try:
conn.execute(
"CREATE TABLE IF NOT EXISTS user_quotas("
"user_id TEXT PRIMARY KEY, quota_usd_micros BIGINT, "
" updated_at DOUBLE PRECISION)")
row = conn.execute(
"SELECT quota_usd_micros FROM user_quotas"
" WHERE user_id = ?",
(owner,)).fetchone()
if row is None:
quota = _default_quota_usd_micros()
else:
quota = row_get(row, "quota_usd_micros")
if quota is None:
return None
quota = _quota_for_owner(conn, owner)
if quota is None:
return None
month = datetime.now(timezone.utc).strftime("%Y-%m")
try:
conn.execute("SELECT COUNT(*) FROM cost_records").fetchone()
@@ -585,22 +575,8 @@ def _automation_quota_exceeded(conn, owner):
pass
table_exists = False
if table_exists:
used_row = conn.execute(
"SELECT COALESCE(SUM(c.usd_micros), 0) AS total"
" FROM cost_records c JOIN tasks t"
" ON c.task_id = t.id"
" WHERE t.created_by = ? AND c.day LIKE ?",
(owner, month + "%")).fetchone()
used = row_get(used_row, "total") or 0
unpriced_row = conn.execute(
"SELECT COALESCE(SUM(c.input_tokens + c.output_tokens), 0)"
" AS total"
" FROM cost_records c JOIN tasks t"
" ON c.task_id = t.id"
" WHERE t.created_by = ? AND c.day LIKE ?"
" AND c.usd_micros IS NULL AND c.source = 'actual'",
(owner, month + "%")).fetchone()
unpriced_used = row_get(unpriced_row, "total") or 0
used, unpriced_used = _quota_monthly_usage(
conn, owner, month)
else:
used = 0
unpriced_used = 0
@@ -612,13 +588,10 @@ def _automation_quota_exceeded(conn, owner):
except Exception:
pass
return "quota_check_failed: %s" % exc
if used >= quota:
return ("quota_exceeded: 本月配额已用完(已用 %d / "
"配额 %d 微美元,%s)" % (used, quota, month))
if unpriced_used >= _unpriced_token_cap():
return ("quota_exceeded: 本月无价模型 token 已用完"
"(已用 %d / 上限 %d,%s)"
% (unpriced_used, _unpriced_token_cap(), month))
decision = _quota_decide(quota, used, unpriced_used,
_unpriced_token_cap(), month)
if decision is not None:
return decision["message"]
return None