Files
codex-app-server/cloud/sandbox_policy.py
T
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

89 lines
4.3 KiB
Python
Raw 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.
"""沙箱与清理的单一口径(纯函数层,只依赖标准库)。
收敛前分层口径散在各处注释里,本模块把它钉成可单测
的函数,调用点只传事实、不再各写一套:
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