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 项。
This commit is contained in:
host committed 2026-11-04 20:54:50 +00:00
1 parent 17fa493da7
commit facc2a249a
16 files changed
+1070 -301

No files matched your search

+344
View File
@@ -0,0 +1,344 @@
"""架构收敛 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()