Docker sandbox real test + cloud merge status endpoint

- DockerExecutor: extra mounts (host.py work/state, repo, codex-home
  at same paths), worker --docker-extra-mounts, container user
  configurable; Dockerfile copies codex-resources beside the codex
  binary and puts bwrap on PATH (previous /usr/local/lib layout
  made codex report bubblewrap unavailable inside the container).
- Real Docker task on external host: triple added in ~18s, merged
  inside a container, main repo now add+mul2+triple, --rm cleanup.
  Honest boundary: this host/kernel forbids unprivileged user
  namespaces, so bwrap fails on host and in containers; production
  mode here is Docker isolation + inner danger-full-access, not
  double sandboxing.
- Cloud merge status: POST /tasks/{id}/merge syncs status only
  (merge itself stays with host.py merge); completed -> merged,
  maintainer+ (approve_merge), other states and repeat calls 409,
  audit merge_task. Tests: cloud 223, host 27, all passing.
This commit is contained in:
host committed 2026-10-26 02:46:10 +08:00
1 parent 631580beab
commit 07a8eaaa73
9 files changed
+1082 -37

No files matched your search

+51 -2
View File
@@ -14,8 +14,8 @@
cost 的 CostStore、webhooks 的 NotificationService)本版尚未接成
HTTP 端点,留给 worker 接线时调用;本 server 只接入其中两处:
AuthStore(认证/权限/审计)与 webhooks 的验签/解析函数。
- DockerExecutor/CloudExecutor 目前仍是占位(见 sandbox.py),
本 server 不假装已接通容器或云端执行。
- DockerExecutor 已接通(见 sandbox.py),CloudExecutor 目前仍是占位
(见 sandbox.py),本 server 不假装已接通云端执行。
导入规则:只用绝对包导入(from cloud.auth import ...)。绝不要把
cloud/ 目录加进 sys.path:标准库已有 queue 模块,会和 cloud/queue.py
@@ -367,6 +367,8 @@ class CloudControlPlaneHandler(BaseHTTPRequestHandler):
self._task_cost(user_id, rest[: -len("/cost")])
elif rest.endswith("/cancel") and method == "POST":
self._cancel_task(user_id, rest[: -len("/cancel")])
elif rest.endswith("/merge") and method == "POST":
self._merge_task(user_id, rest[: -len("/merge")])
elif rest.endswith("/heartbeat") and method == "POST":
self._heartbeat_task(user_id, rest[: -len("/heartbeat")])
else:
@@ -984,6 +986,53 @@ class CloudControlPlaneHandler(BaseHTTPRequestHandler):
store.audit(user_id, task["project_id"], "cancel_task", task_id)
self._send_json(200, task)
def _merge_task(self, user_id, task_id):
"""把云端任务标记为已合并:只补状态,不执行合并本身。
合并本身仍由 host.py merge(本地或 Docker 容器内)执行,
成功后再调本接口同步云端状态——两者分离是故意的:合并
是要人看 diff 才点的操作,云端不该在任务 completed 时
自动把主分支改了。规则:任务必须存在(404)、调用方
必须是项目 maintainer 以上(approve_merge,403)、任务
必须是 completed(其它状态一律 409,含已 merged 的重复
调用,避免二次合并混淆)。成功后写审计 merge_task。
"""
row = self._fetch_task(task_id)
if row is None:
self._send_json(404, {"error": "task not found"})
return
task = _task_to_dict(row)
with _auth_store(self.server.db_path) as store:
if not store.check_permission(
user_id, task["project_id"], "approve_merge"):
self._send_json(403, {"error": "forbidden"})
return
if task["status"] != "completed":
self._send_json(409, {
"error": "only completed tasks can be marked merged",
"status": task["status"],
})
return
now = time.time()
conn = self._db()
try:
conn.execute(
"UPDATE tasks SET status = 'merged', updated_at = ?"
" WHERE id = ?",
(now, task_id),
)
conn.commit()
task = _task_to_dict(
conn.execute(
"SELECT * FROM tasks WHERE id = ?", (task_id,)
).fetchone()
)
finally:
conn.close()
with _auth_store(self.server.db_path) as store:
store.audit(user_id, task["project_id"], "merge_task", task_id)
self._send_json(200, task)
def _heartbeat_task(self, user_id, task_id):
row = self._fetch_task(task_id)
if row is None: