- 新增标准库叶子层 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 项。
125 lines
5.5 KiB
Python
125 lines
5.5 KiB
Python
"""超时默认值的单一配置源。
|
||
|
||
背景:主线(origin/main)上同一个“3600 秒”以字面量散落在
|
||
多处——审批队列(cloud/approvals.py)、协调员
|
||
(cloud/coordinator.py)、接线层任务超时
|
||
(cloud/harness2_tasks.py)、标记轮询上限
|
||
(cloud/marker_tools.py)、服务端审批入口(cloud/server.py),
|
||
另有单轮护栏 600 秒(host.py 的 turn_timeout 默认值,见下)
|
||
与陈旧回收 300 秒(server.sweep_stale_tasks /
|
||
worker._reap_kill_requests)。改一处漏一处是真实风险:
|
||
本次收敛前,审批入口与队列模块的上下界就各写了一套。
|
||
|
||
本模块只依赖标准库,是叶子模块:任何 cloud 模块都可
|
||
import 它而不引入循环。每个用途各有名常量,值即使相同
|
||
也不共用一个名字——改审批默认绝不能顺带改掉轮询上限。
|
||
|
||
各常量与现状(改值前必须先读本段):
|
||
- APPROVAL_DEFAULT_SECONDS = 3600:新审批项的默认等待
|
||
秒数。覆盖层:环境变量 CLOUD_APPROVAL_TIMEOUT_SECONDS,
|
||
仅当解析为 60..86400 的整数时生效,否则回落默认值。
|
||
- APPROVAL_MIN_SECONDS / APPROVAL_MAX_SECONDS = 60 / 86400:
|
||
服务端审批入口的校验区间(保持与旧行为一致)。
|
||
- LONG_TIMEOUT_SECONDS = 3600:协调员、接线层任务超时、
|
||
标记轮询上限三处共用的数值,各模块仍保留自己的常量名
|
||
作为别名(调用点与测试 import 的名字不变)。
|
||
- TURN_SECONDS = 600:host.py 单轮护栏的默认值。host.py
|
||
不在 cloud/ 内,本轨文件边界不改它;这里只登记数值与
|
||
含义,杜绝“审批 600 秒”的误读——主线上不存在 600 秒的
|
||
审批超时,600 秒是单轮护栏,不是审批等待。
|
||
- STALE_SWEEP_SECONDS = 300:陈旧回收与失联收殓共用的
|
||
阈值默认值(server 与 worker 各保留自己的参数名)。
|
||
- WORKER_ALIVE_SECONDS = 120:worker 注册表存活阈值。
|
||
|
||
已建审批项的迁移语义(冻结,不可悄悄改):
|
||
- 本模块没有任何回填、改写 expires_at 的函数。默认值或
|
||
环境变量变化只影响之后新建的审批项;已建项的 expires_at
|
||
在创建时已落库,之后一律不动。过期扫荡仍按行上的
|
||
expires_at 判定,绝不按当前默认值重算。
|
||
- 服务端入口的校验与队列模块的创建语义各有分工,不可
|
||
混用:服务端入口只接受 60..86400 的整数(见
|
||
validate_server_timeout,显式 null、布尔值、浮点数、
|
||
越界一律拒绝,与旧行为一致);队列模块级创建允许
|
||
timeout_seconds=None(用默认值)与 0(不过期,见
|
||
resolve_module_timeout),服务端入口永远传不出 0。
|
||
"""
|
||
|
||
import os
|
||
|
||
APPROVAL_DEFAULT_SECONDS = 3600
|
||
APPROVAL_MIN_SECONDS = 60
|
||
APPROVAL_MAX_SECONDS = 86400
|
||
LONG_TIMEOUT_SECONDS = 3600
|
||
TURN_SECONDS = 600
|
||
STALE_SWEEP_SECONDS = 300
|
||
WORKER_ALIVE_SECONDS = 120
|
||
|
||
|
||
def approval_default_seconds(override=None):
|
||
"""新审批项的默认等待秒数:显式参数 > 环境变量 > 默认值。
|
||
|
||
环境变量 CLOUD_APPROVAL_TIMEOUT_SECONDS 只有解析为
|
||
60..86400 的整数时才生效;非法、越界、布尔语义一律
|
||
回落默认值,绝不抛异常(默认值解析失败不能炸掉建项)。
|
||
"""
|
||
if override is not None:
|
||
try:
|
||
value = int(override)
|
||
except (TypeError, ValueError):
|
||
return APPROVAL_DEFAULT_SECONDS
|
||
if isinstance(override, bool):
|
||
return APPROVAL_DEFAULT_SECONDS
|
||
if APPROVAL_MIN_SECONDS <= value <= APPROVAL_MAX_SECONDS:
|
||
return value
|
||
return APPROVAL_DEFAULT_SECONDS
|
||
raw = os.environ.get("CLOUD_APPROVAL_TIMEOUT_SECONDS") or ""
|
||
try:
|
||
value = int(raw)
|
||
except (TypeError, ValueError):
|
||
return APPROVAL_DEFAULT_SECONDS
|
||
if APPROVAL_MIN_SECONDS <= value <= APPROVAL_MAX_SECONDS:
|
||
return value
|
||
return APPROVAL_DEFAULT_SECONDS
|
||
|
||
|
||
def validate_server_timeout(value):
|
||
"""服务端审批入口的超时校验:返回合法整数,否则 ValueError。
|
||
|
||
与旧入口行为逐字一致:缺省由调用方先填默认值;显式
|
||
null、布尔值、浮点数、非整数、越界一律拒绝,错误文案
|
||
保持 "timeout_seconds must be 60..86400" 不变。
|
||
"""
|
||
if isinstance(value, bool) or not isinstance(value, int):
|
||
raise ValueError("timeout_seconds must be 60..86400")
|
||
if value < APPROVAL_MIN_SECONDS or value > APPROVAL_MAX_SECONDS:
|
||
raise ValueError("timeout_seconds must be 60..86400")
|
||
return value
|
||
|
||
|
||
def resolve_module_timeout(value, default=None):
|
||
"""队列模块级创建的超时解析:返回秒数或 None(不过期)。
|
||
|
||
None 用默认值;0 表示不过期(只允许模块级调用,服务
|
||
端入口另有限制,永远传不出 0);负数、布尔值、非数字
|
||
一律 ValueError。浮点数沿用旧行为:接受并按原值参与
|
||
expires_at 计算(服务端入口不走这条路,不受影响)。
|
||
"""
|
||
if default is None:
|
||
default = approval_default_seconds()
|
||
if value is None:
|
||
return default
|
||
if isinstance(value, bool) or not isinstance(value, (int, float)):
|
||
raise ValueError("timeout_seconds invalid")
|
||
if value < 0:
|
||
raise ValueError("timeout_seconds invalid")
|
||
if not value:
|
||
return None
|
||
return value
|
||
|
||
|
||
def expires_at_for(now, timeout_seconds):
|
||
"""由创建时刻与解析后的超时算 expires_at:不过期回 None。"""
|
||
if not timeout_seconds:
|
||
return None
|
||
return now + timeout_seconds
|