Files
codex-app-server/cloud/tests/test_arch_converge.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

345 lines
16 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.
"""架构收敛 A 线单测:超时 / 门禁 / 成本配额 / 沙箱清理。
只覆盖本轨新增的四个叶子模块与调用点的委托关系,不跑
模型、不联网、不碰生产:SQLite 内存库 + 临时目录。
钉住的冻结语义(改动这些断言即改语义,必须先改设计):
- 审批默认 3600、服务端入口 60..86400、已建项 expires_at
永不因默认值变化而重算;600 秒是单轮护栏,不是审批超时。
- 目录门无 confirm / override / bypass 参数;合并状态门
仅 completed 放行;控制门与旧 marker 行为逐字一致。
- 配额判定唯一来源是 quota.decide:USD 门先于无价门,
命中均为 >=;无价只计 source='actual',估算行不触发上限;
无配额行按默认值,显式 NULL 行才是不限额。
- 失联清理只在 interrupted + 心跳停滞严格超阈时放行;
容器清理只认 codex-<32 位 hex>,绝不按前缀删。
"""
import inspect
import os
import sqlite3
import sys
import tempfile
import unittest
from pathlib import Path
from unittest import mock
try:
from cloud import gates, quota, sandbox_policy, timeouts
from cloud.approvals import DEFAULT_TIMEOUT_SECONDS, create_approval
from cloud.automations import (
_automation_quota_exceeded,
_unpriced_token_cap as _auto_cap,
)
from cloud.coordinator import DEFAULT_TIMEOUT_SECONDS as _COORD_TIMEOUT
from cloud.cost import unpriced_token_cap as _cost_cap
from cloud.harness2_tasks import (
DEFAULT_TASK_TIMEOUT_SECONDS as _HARNESS_TIMEOUT,
)
from cloud.marker_tools import (
MAX_POLL_TIMEOUT_SECONDS as _MARKER_POLL_MAX,
)
from cloud.marker_tools import check_control_gate, safe_worktree_path
from cloud.sandbox import DockerExecutor
from cloud.server import _safe_worktree_path as _server_safe_path
from cloud.tree import resolve_tree_path
from cloud.workers import DEFAULT_ALIVE_SECONDS as _WORKER_ALIVE
except ImportError: # discover -s cloud/tests 时把项目根目录补进 sys.path
sys.path.insert(0, str(Path(__file__).resolve().parents[2]))
from cloud import gates, quota, sandbox_policy, timeouts
from cloud.approvals import DEFAULT_TIMEOUT_SECONDS, create_approval
from cloud.automations import (
_automation_quota_exceeded,
_unpriced_token_cap as _auto_cap,
)
from cloud.coordinator import DEFAULT_TIMEOUT_SECONDS as _COORD_TIMEOUT
from cloud.cost import unpriced_token_cap as _cost_cap
from cloud.harness2_tasks import (
DEFAULT_TASK_TIMEOUT_SECONDS as _HARNESS_TIMEOUT,
)
from cloud.marker_tools import (
MAX_POLL_TIMEOUT_SECONDS as _MARKER_POLL_MAX,
)
from cloud.marker_tools import check_control_gate, safe_worktree_path
from cloud.sandbox import DockerExecutor
from cloud.server import _safe_worktree_path as _server_safe_path
from cloud.tree import resolve_tree_path
from cloud.workers import DEFAULT_ALIVE_SECONDS as _WORKER_ALIVE
def _quota_conn():
"""配额测试用的最小库:tasks + cost_records + user_quotas。"""
conn = sqlite3.connect(":memory:")
conn.row_factory = sqlite3.Row
conn.execute("CREATE TABLE tasks(id TEXT PRIMARY KEY,"
" created_by TEXT)")
conn.execute("CREATE TABLE cost_records(task_id TEXT,"
" usd_micros INTEGER, input_tokens INTEGER,"
" output_tokens INTEGER, day TEXT, source TEXT)")
conn.execute("CREATE TABLE user_quotas(user_id TEXT PRIMARY KEY,"
" quota_usd_micros BIGINT, updated_at DOUBLE PRECISION)")
conn.execute("INSERT INTO tasks(id, created_by) VALUES('t1', 'u1')")
conn.commit()
return conn
def _cost_row(conn, task_id, usd, inp, out, source, day="2026-10-31"):
conn.execute(
"INSERT INTO cost_records(task_id, usd_micros, input_tokens,"
" output_tokens, day, source) VALUES(?, ?, ?, ?, ?, ?)",
(task_id, usd, inp, out, day, source))
conn.commit()
class TimeoutConvergeTestCase(unittest.TestCase):
def test_constants_match_old_literals(self):
self.assertEqual(timeouts.APPROVAL_DEFAULT_SECONDS, 3600)
self.assertEqual(timeouts.APPROVAL_MIN_SECONDS, 60)
self.assertEqual(timeouts.APPROVAL_MAX_SECONDS, 86400)
self.assertEqual(timeouts.TURN_SECONDS, 600)
self.assertEqual(timeouts.STALE_SWEEP_SECONDS, 300)
self.assertEqual(timeouts.WORKER_ALIVE_SECONDS, 120)
# 各模块别名与单一来源同值(调用点 import 的名字不变)。
self.assertEqual(DEFAULT_TIMEOUT_SECONDS, 3600)
self.assertEqual(_COORD_TIMEOUT, timeouts.LONG_TIMEOUT_SECONDS)
self.assertEqual(_HARNESS_TIMEOUT, timeouts.LONG_TIMEOUT_SECONDS)
self.assertEqual(_MARKER_POLL_MAX, timeouts.LONG_TIMEOUT_SECONDS)
self.assertEqual(_WORKER_ALIVE, timeouts.WORKER_ALIVE_SECONDS)
def test_server_validation_keeps_old_bounds_and_message(self):
for good in (60, 3600, 86400):
self.assertEqual(timeouts.validate_server_timeout(good), good)
for bad in (None, True, 59, 86401, 3.5, "3600"):
with self.assertRaises(ValueError) as ctx:
timeouts.validate_server_timeout(bad)
self.assertEqual(str(ctx.exception),
"timeout_seconds must be 60..86400")
def test_module_timeout_semantics(self):
self.assertEqual(timeouts.resolve_module_timeout(None), 3600)
self.assertIsNone(timeouts.resolve_module_timeout(0))
self.assertEqual(timeouts.resolve_module_timeout(60), 60)
for bad in (-1, True, "60"):
with self.assertRaises(ValueError):
timeouts.resolve_module_timeout(bad)
self.assertIsNone(timeouts.expires_at_for(1000, None))
self.assertEqual(timeouts.expires_at_for(1000, 3600), 4600)
def test_env_override_and_invalid_fallback(self):
with mock.patch.dict(os.environ,
{"CLOUD_APPROVAL_TIMEOUT_SECONDS": "1800"}):
self.assertEqual(timeouts.approval_default_seconds(), 1800)
with mock.patch.dict(os.environ,
{"CLOUD_APPROVAL_TIMEOUT_SECONDS": "30"}):
self.assertEqual(timeouts.approval_default_seconds(), 3600)
with mock.patch.dict(os.environ,
{"CLOUD_APPROVAL_TIMEOUT_SECONDS": "abc"}):
self.assertEqual(timeouts.approval_default_seconds(), 3600)
def test_existing_approval_expires_at_never_recomputed(self):
conn = sqlite3.connect(":memory:")
conn.row_factory = sqlite3.Row
try:
with mock.patch.dict(os.environ, {}, clear=False):
os.environ.pop("CLOUD_APPROVAL_TIMEOUT_SECONDS", None)
old = create_approval(conn, "t1", "p1", summary="s",
now=1000.0)
self.assertEqual(old["expires_at"], 4600.0)
with mock.patch.dict(os.environ,
{"CLOUD_APPROVAL_TIMEOUT_SECONDS": "60"}):
new = create_approval(conn, "t1", "p1", summary="s",
now=2000.0)
self.assertEqual(new["expires_at"], 2060.0)
# 旧行不受默认值变化影响:不回填、不重算。
from cloud.approvals import get_approval
self.assertEqual(get_approval(conn, old["id"])["expires_at"],
4600.0)
finally:
conn.close()
class GatesConvergeTestCase(unittest.TestCase):
def test_path_gate_cases_across_all_wrappers(self):
with tempfile.TemporaryDirectory() as tmp:
base = os.path.join(tmp, "w")
os.makedirs(os.path.join(base, "sub"))
# 目录内指回内部的符号链接放行,指出去的拦住。
os.symlink(os.path.join(base, "sub"),
os.path.join(base, "inner-link"))
os.symlink("/etc", os.path.join(base, "escape-link"))
for fn in (gates.resolve_inside, resolve_tree_path,
safe_worktree_path, _server_safe_path):
self.assertEqual(fn(base, ""), base)
self.assertEqual(fn(base, "sub"),
os.path.realpath(os.path.join(base, "sub")))
self.assertIsNone(fn(base, "../etc/passwd"))
self.assertIsNone(fn(base, "/etc/passwd"))
self.assertIsNotNone(fn(base, "inner-link"))
self.assertIsNone(fn(base, "escape-link"))
def test_path_and_control_gates_have_no_bypass_parameter(self):
for fn in (gates.resolve_inside, gates.decide_control,
gates.merge_state_decision):
params = set(inspect.signature(fn).parameters)
self.assertFalse(params & {"confirm", "override", "bypass",
"force", "confirmed"})
def test_merge_state_gate(self):
self.assertEqual(gates.merge_state_decision("completed"),
(True, ""))
for status in ("queued", "running", "failed", "interrupted",
"merged", "merge-failed"):
ok, reason = gates.merge_state_decision(status)
self.assertFalse(ok)
self.assertEqual(reason,
"only completed tasks can be marked merged")
def test_control_gate_matches_old_marker_behavior(self):
sender = {"id": "s1", "project_id": "p1"}
target = {"id": "t1", "project_id": "p1", "status": "running"}
self.assertEqual(check_control_gate(target, sender, "descendant",
"cancel"), (True, ""))
self.assertEqual(
check_control_gate(target, sender, "descendant", "retry")[0],
False)
target2 = dict(target, status="failed")
self.assertEqual(check_control_gate(target2, sender, "same_creator",
"retry"), (True, ""))
self.assertEqual(
check_control_gate(target2, sender, "other", "retry")[0], False)
self.assertEqual(
check_control_gate(target2, {"id": "t1", "project_id": "p1"},
"descendant", "cancel")[0], False)
goal = dict(target2, goal_mode=1)
self.assertEqual(
check_control_gate(goal, sender, "descendant", "retry")[0],
False)
cross = dict(target2, project_id="p2")
self.assertEqual(
check_control_gate(cross, sender, "descendant", "retry")[0],
False)
class QuotaConvergeTestCase(unittest.TestCase):
def test_decide_precedence_and_equality(self):
self.assertIsNone(quota.decide(None, 999999, 999999, 100, "2026-10"))
self.assertIsNone(quota.decide(1000, 999, 99, 100, "2026-10"))
both = quota.decide(1000, 1000, 1000, 100, "2026-10")
self.assertEqual(both["kind"], "usd")
only_unpriced = quota.decide(1000, 10, 100, 100, "2026-10")
self.assertEqual(only_unpriced["kind"], "unpriced")
self.assertIn("quota_exceeded", only_unpriced["message"])
def test_quota_for_owner_rows(self):
conn = _quota_conn()
try:
with mock.patch.dict(os.environ, {}, clear=False):
os.environ.pop("CLOUD_DEFAULT_QUOTA_USD_MICROS", None)
self.assertEqual(quota.quota_for_owner(conn, "u1"),
10000000)
conn.execute("INSERT INTO user_quotas VALUES('u1', NULL, 0)")
conn.commit()
self.assertIsNone(quota.quota_for_owner(conn, "u1"))
conn.execute("UPDATE user_quotas SET quota_usd_micros = 5000"
" WHERE user_id = 'u1'")
conn.commit()
self.assertEqual(quota.quota_for_owner(conn, "u1"), 5000)
finally:
conn.close()
def test_monthly_usage_actual_only(self):
conn = _quota_conn()
try:
_cost_row(conn, "t1", 300, 10, 5, "actual")
_cost_row(conn, "t1", None, 100, 50, "actual")
_cost_row(conn, "t1", None, 999900, 99, "estimated")
used, unpriced = quota.monthly_usage(conn, "u1", "2026-10")
self.assertEqual(used, 300)
# 估算行 999999 token 不计入无价上限口径。
self.assertEqual(unpriced, 150)
finally:
conn.close()
def test_automation_gate_uses_actual_only(self):
conn = _quota_conn()
try:
conn.execute("INSERT INTO user_quotas VALUES('u1', 100000000, 0)")
conn.commit()
_cost_row(conn, "t1", None, 999900, 99, "estimated")
with mock.patch.dict(
os.environ,
{"CLOUD_QUOTA_UNPRICED_TOKEN_CAP": "1000"}):
# 估算行再大也不拦。
self.assertIsNone(_automation_quota_exceeded(conn, "u1"))
_cost_row(conn, "t1", None, 600, 500, "actual")
blocked = _automation_quota_exceeded(conn, "u1")
self.assertIsNotNone(blocked)
self.assertIn("quota_exceeded", blocked)
finally:
conn.close()
def test_unpriced_cap_env_alias_matches_cost(self):
with mock.patch.dict(
os.environ, {"CLOUD_QUOTA_UNPRICED_TOKEN_CAP": "12345"}):
self.assertEqual(_auto_cap(), 12345)
self.assertEqual(_cost_cap(), 12345)
with mock.patch.dict(
os.environ, {"CLOUD_QUOTA_UNPRICED_TOKEN_CAP": "junk"}):
self.assertEqual(_auto_cap(), _cost_cap())
class SandboxPolicyTestCase(unittest.TestCase):
def test_tmpfs_matrix(self):
self.assertTrue(sandbox_policy.tmpfs_isolated("docker", "/tmp")[0])
self.assertTrue(
sandbox_policy.tmpfs_isolated("docker", "/var/tmp")[0])
self.assertFalse(
sandbox_policy.tmpfs_isolated("host-bwrap", "/tmp")[0])
self.assertFalse(sandbox_policy.tmpfs_isolated("local", "/tmp")[0])
self.assertFalse(
sandbox_policy.tmpfs_isolated("docker", "/work")[0])
def test_task_container_regex_shared_with_sandbox(self):
self.assertIs(sandbox_policy.TASK_CONTAINER_RE,
DockerExecutor._TASK_CONTAINER_RE)
good = "codex-" + "a" * 32
self.assertTrue(sandbox_policy.TASK_CONTAINER_RE.match(good))
for bad in ("codex-postgres", "codex-xyz", "codex-" + "a" * 31,
"codex-" + "A" * 32):
self.assertIsNone(sandbox_policy.TASK_CONTAINER_RE.match(bad))
def test_cleanup_decision(self):
now = 10000.0
self.assertEqual(
sandbox_policy.cleanup_decision("running", 1.0, now, 300)[0],
False)
self.assertEqual(
sandbox_policy.cleanup_decision("interrupted", now - 301,
now, 300)[0], True)
# 恰等于阈值不杀(旧 _reap:hb >= now - threshold 即跳过)。
self.assertEqual(
sandbox_policy.cleanup_decision("interrupted", now - 300,
now, 300)[0], False)
self.assertEqual(
sandbox_policy.cleanup_decision("interrupted", now - 10,
now, 300)[0], False)
self.assertEqual(
sandbox_policy.cleanup_decision("interrupted", None,
now, 300)[0], True)
self.assertEqual(
sandbox_policy.cleanup_decision("interrupted", "junk",
now, 300)[0], False)
def test_worker_address(self):
self.assertEqual(sandbox_policy.worker_address("my-host-42"),
("my-host", 42))
self.assertEqual(sandbox_policy.worker_address("plain"),
("plain", None))
self.assertEqual(sandbox_policy.worker_address("host-abc"),
("host-abc", None))
self.assertEqual(sandbox_policy.worker_address(""), ("", None))
if __name__ == "__main__":
unittest.main()