Files
codex-app-server/cloud/timeouts.py
host 41e8c98512 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 项。
2026-11-03 02:49:25 +00:00

125 lines
5.5 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""超时默认值的单一配置源。
背景:主线(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