- 新增标准库叶子层 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 项。
89 lines
4.3 KiB
Python
89 lines
4.3 KiB
Python
"""沙箱与清理的单一口径(纯函数层,只依赖标准库)。
|
||
|
||
收敛前分层口径散在各处注释里,本模块把它钉成可单测
|
||
的函数,调用点只传事实、不再各写一套:
|
||
|
||
1. /tmp 隔离矩阵(Docker 外层 vs 宿主 bwrap 内层):
|
||
- Docker 执行器:容器 /tmp 与 /var/tmp 固定为 tmpfs
|
||
(sandbox.build_run_command 在额外挂载之后追加
|
||
--tmpfs,且 /tmp 目标的额外挂载在解析时即被拒绝),
|
||
容器内 /tmp 绝不落在宿主 /tmp。
|
||
- 宿主 bwrap / 本地执行器:/tmp 不隔离。宿主级 bwrap
|
||
的 /tmp 会落到宿主 /tmp 上,内层 codex 的 bwrap
|
||
--proc 挂载亦不受本仓库控制——本模块只如实标注,
|
||
绝不宣称“内层沙箱已修”。
|
||
- 任务容器名正则(codex-<32 位 hex 任务 id>)此前只
|
||
活在 sandbox.cleanup_stale_containers 的注释与正则里;
|
||
现收为 TASK_CONTAINER_RE,sandbox 直接复用它。
|
||
清理绝不按 "codex-" 前缀删:基础设施容器
|
||
(如 codex-postgres)名字不匹配正则,永远不被删。
|
||
2. 失联清理判定(cleanup_decision):worker
|
||
_reap_kill_requests 的状态与心跳两道门——任务必须
|
||
已是 interrupted,且心跳停滞超过阈值(心跳取
|
||
COALESCE(last_heartbeat, updated_at),与
|
||
server.sweep_stale_tasks 同口径;无心跳时间戳时按
|
||
已停滞处理,与 _reap 旧代码的 fall-through 一致;
|
||
恰等于阈值时不杀,旧代码是 hb >= now - threshold 即
|
||
跳过)。请求 pending、执行器有 kill_container、容器
|
||
名由任务 id 派生这三道门仍在 worker 侧,不进本层:
|
||
本层只判“该不该杀”,不执行杀。
|
||
3. Worker 寻址(worker_address):workers 注册表只有
|
||
worker_id 一列身份,默认形为“主机名-进程号”
|
||
(worker.py,未给 --worker-id 时)。本函数从该串
|
||
派生 (host, pid):取最后一个连字符后全为数字的一段
|
||
作 pid,否则 pid 为 None。注册表本身不加 host/pid
|
||
列——加列需 PG 迁移与 test_p2_migrations 登记,超出
|
||
本轨收敛范围;寻址先用派生函数,不动表结构。
|
||
单一状态来源仍是:workers 表(注册与心跳)+
|
||
tasks.last_heartbeat(陈旧判定)+ kill_requests
|
||
(清理意图),三者缺一不可,不得另起一套状态表。
|
||
"""
|
||
|
||
import re
|
||
|
||
TMPFS_PATHS = ("/tmp", "/var/tmp")
|
||
TASK_CONTAINER_RE = re.compile(r"^codex-[0-9a-f]{32}$")
|
||
|
||
|
||
def tmpfs_isolated(executor_kind, path):
|
||
"""某执行器下某路径是否走 tmpfs 隔离:(isolated, reason)。"""
|
||
if executor_kind == "docker" and path in TMPFS_PATHS:
|
||
return True, "docker --tmpfs 隔离,容器内路径不落宿主"
|
||
if executor_kind == "docker":
|
||
return False, "docker 下仅 /tmp 与 /var/tmp 走 tmpfs"
|
||
if executor_kind in ("host-bwrap", "local"):
|
||
return False, "宿主 bwrap / 本地执行器 /tmp 不隔离,会落宿主"
|
||
return False, "未知执行器,按不隔离处理(fail-closed 口径)"
|
||
|
||
|
||
def cleanup_decision(status, heartbeat_ts, now, threshold_seconds):
|
||
"""失联清理判定:放行回 (True, reason),否则 (False, reason)。
|
||
|
||
status 必须为 'interrupted';心跳停滞须严格超过阈值
|
||
(恰等于阈值不杀);heartbeat_ts 为 None 时按已停滞
|
||
处理(与 _reap 旧代码一致:无时间戳不阻拦收殓)。
|
||
"""
|
||
if status != "interrupted":
|
||
return False, "任务不在 interrupted,不杀(防复活任务被旧请求误杀)"
|
||
if heartbeat_ts is None:
|
||
return True, "无心跳时间戳,按已停滞处理"
|
||
try:
|
||
age = now - float(heartbeat_ts)
|
||
except (TypeError, ValueError):
|
||
return False, "心跳时间戳不可解析,不杀"
|
||
if age > threshold_seconds:
|
||
return True, "心跳停滞 %.0f 秒,超过阈值 %s 秒" % (
|
||
age, threshold_seconds)
|
||
return False, "owner 可能还活着(心跳 %d 秒前),留待下轮" % age
|
||
|
||
|
||
def worker_address(worker_id):
|
||
"""从 worker_id 派生 (host, pid):pid 取末段纯数字,否则 None。"""
|
||
if not worker_id or not isinstance(worker_id, str):
|
||
return "", None
|
||
if "-" in worker_id:
|
||
host, _, tail = worker_id.rpartition("-")
|
||
if host and tail.isdigit():
|
||
return host, int(tail)
|
||
return worker_id, None
|