P1 security batch: quota execution checks, cost accounting, goal/coordinator semantics, PR channel, orphan signature, control-plane and console hardening

- Docker quota watchdog + fail-closed quota checks, system tasks need real owner
- max_cost_usd actually accumulated, cancel records cost, estimated rows excluded from unpriced cap, cache_write declared non-billed
- goal-resume keeps original verify on breakpoint resume, goal retry falls back to resume, goal_max_rounds capped at 100 at create and execution
- coordinator children inherit parent model/effort, orphans marked interrupted with audit
- PR jobs on docker workers return explicit needs_local_worker instead of silent empty
- worker code signature covers 6 files and is checked before claim, fail-closed
- sub2api lazy provisioning with rotation, backup whitelist + 0600, push SSRF block, repos/workers desensitized, key/project audits and limits, console buttons/quota/status wording, coordinator/automation quota reserve
This commit is contained in:
host committed 2026-10-27 16:27:50 +00:00
1 parent d254240d84
commit 1402dc67fd
20 files changed
+2616 -179

No files matched your search

+83 -36
View File
@@ -423,6 +423,15 @@ class LocalExecutor(Executor):
rounds = task.get("goal_max_rounds")
if rounds is None:
rounds = 3
# P1 轮次上限(与 worker 的 GOAL_MAX_ROUNDS_CAP 同值 100):
# 有限轮次超限直接 loud fail,不静默截断;goal_mode 的 0
#(不限轮次)放行,上界是配额;旧验收形态的 0 同样拒绝
#(0 在旧形态里表示不限轮次,P1 已禁用无上限验收循环)。
if rounds is not None and rounds > 100:
raise ValueError("goal_max_rounds 超过上限 100 轮")
if rounds == 0 and verify and not task.get("goal_mode"):
raise ValueError(
"旧验收形态 goal_max_rounds=0 表示不限轮次,已禁用")
cmd = self._base_command(model=task.get("model"),
effort=task.get("effort"),
approval=task.get("approval"),
@@ -440,26 +449,35 @@ class LocalExecutor(Executor):
return self._goal_result_from_runner(
task["id"], self._run_local(cmd, extra_env))
def goal_resume_task(self, task, goal_text, extra_env=None,
model_provider=None) -> dict:
"""调 host.py goal-resume --goal-text:在旧线程上开新目标
循环(旧任务接续 goal 模式,云端 /continue 带 goal_mode=1
时走这里)。命令:host.py goal-resume --task <host 名>
--goal-text <新目标> --max-rounds 0(新语义不限轮次)。
def goal_resume_task(self, task, goal_text=None, extra_env=None,
model_provider=None, max_rounds=None) -> dict:
"""调 host.py goal-resume,分两种调用(P1,与 worker、
host 三处一致,绝不能混用):
- 断点续跑:goal_text=None(worker 的 _resume_kind=
"breakpoint" 时传 None)。命令不带 --goal-text、默认
也不带 --max-rounds:host 沿用原 goal + 原 verify_command
+ 原 goal_max_rounds,从断点轮次续跑。传了 --goal-text
就会走 host 的开新目标分支、把原 verify 覆盖成 None,
所以断点调用绝不能顺手把续跑消息塞进 goal_text。
- 开新目标:goal_text 为新目标文本(云端 /continue 带
goal_mode=1 时走这里)。命令带 --goal-text 与
--max-rounds(默认 0,新语义不限轮次,上界是配额)。
不传 --repo:goal-resume 操作已有 host 任务,不新建
worktree。结论解析与 goal_task 同款。
"""
if not isinstance(task, dict) or task.get("id") is None:
raise ValueError("任务缺少 id")
if not goal_text:
raise ValueError("goal-resume 缺少目标文本")
cmd = self._base_command(model=task.get("model"),
effort=task.get("effort"),
approval=task.get("approval"),
model_provider=model_provider)
cmd += ["goal-resume", "--task", self._host_name(task),
"--goal-text", goal_text,
"--max-rounds", "0"]
cmd += ["goal-resume", "--task", self._host_name(task)]
if goal_text:
cmd += ["--goal-text", goal_text,
"--max-rounds",
str(max_rounds if max_rounds is not None else 0)]
elif max_rounds is not None:
cmd += ["--max-rounds", str(max_rounds)]
return self._goal_result_from_runner(
task["id"], self._run_local(cmd, extra_env))
@@ -469,7 +487,9 @@ class LocalExecutor(Executor):
解析用完整 stdout(结论在末尾),存库的 stdout 才截尾;
returncode 非 0 一律 failed;为 0 时无结论也判 failed,
绝不把“没打印结论”当成达标。host 任务已存在时(同任务
/retry 撞名,goal 没有 run 续跑语义)错误写明请新建任务。
/retry 撞名,goal 没有 run 续跑语义)错误写明“goal 失败
请用继续而非重试”(P1:重试在已存在 host-state 上必失败,
断点续跑才是正确路径,见 worker 的回落逻辑)。
"""
returncode = getattr(result, "returncode", 1)
stdout_full = getattr(result, "stdout", "") or ""
@@ -478,8 +498,10 @@ class LocalExecutor(Executor):
combined = stdout_full + stderr
if "已存在" in combined:
met, goal_error = None, (
"host 任务已存在:goal 任务不支持同任务重试,"
"请新建任务")
"host 任务已存在:goal 失败请用继续而非重试——"
"同任务重试会撞已存在 host-state,worker 会先"
"尝试断点续跑,断点续跑仍失败时请用继续(不要"
"重排同一任务)")
status = "failed"
elif returncode != 0:
status = "failed"
@@ -516,9 +538,11 @@ class LocalExecutor(Executor):
killpg 才能连同子孙一起杀掉。注意:只有默认 runner(真实
subprocess)时才用本方法;单测注入的假 runner 走同步路径,
不走这里。kind=goal 时建 goal 命令、kind=goal-resume 时建
goal-resume 命令(旧任务接续 goal 模式,目标文本取本轮
prompt;结论解析由 worker 的 _execute_via_process 按 kind
分发到 _goal_result_from_runner)。goal 语义与同步 goal_task
goal-resume 命令(结论解析由 worker 的 _execute_via_process
按 kind 分发到 _goal_result_from_runner)。goal-resume 分流
(P1):_resume_kind="breakpoint" 时不传 --goal-text(断点
续跑,沿用原 goal+verify),其它情况才把本轮 prompt 当
--goal-text(开新目标)。goal 语义与同步 goal_task
一致:新形态(goal_mode)无验收命令时 --verify 不传、
--max-rounds 传 0(0 不能用 or 兜底成 3);旧形态仍传验收
命令与轮次。seed_dir 只对 dispatch/goal 追加(run 续跑与
@@ -545,6 +569,11 @@ class LocalExecutor(Executor):
rounds = task.get("goal_max_rounds")
if rounds is None:
rounds = 3
if rounds is not None and rounds > 100:
raise ValueError("goal_max_rounds 超过上限 100 轮")
if rounds == 0 and verify and not task.get("goal_mode"):
raise ValueError(
"旧验收形态 goal_max_rounds=0 表示不限轮次,已禁用")
cmd += ["goal", "--name", self._host_name(task),
"--task", goal_text]
if verify:
@@ -556,12 +585,15 @@ class LocalExecutor(Executor):
if seed_dir:
cmd += ["--seed-dir", seed_dir]
elif kind == "goal-resume":
# 旧任务接续 goal:host 侧用 --goal-text 在旧线程上开
# 新目标循环(新语义,不限轮次);task["prompt"] 在续跑
# 时已被 worker 换成续跑消息。
cmd += ["goal-resume", "--task", self._host_name(task),
"--goal-text", task["prompt"],
"--max-rounds", "0"]
# P1 分流(与 goal_resume_task 同款契约):
# _resume_kind="breakpoint" 时不传 --goal-text(host
# 沿用原 goal+verify 断点续跑,绝不能用续跑消息覆盖
# 原 verify);其它情况(开新目标)才用 task["prompt"]
#(worker 已换成续跑消息)当 --goal-text,--max-rounds 0。
cmd += ["goal-resume", "--task", self._host_name(task)]
if task.get("_resume_kind") != "breakpoint":
cmd += ["--goal-text", task["prompt"],
"--max-rounds", "0"]
else:
cmd += ["dispatch", "--name", self._host_name(task),
"--task", task["prompt"]]
@@ -1101,6 +1133,12 @@ class DockerExecutor(Executor):
rounds = task.get("goal_max_rounds")
if rounds is None:
rounds = 3
# P1 轮次上限(与 Local goal_task 同款,不静默截断)。
if rounds is not None and rounds > 100:
raise ValueError("goal_max_rounds 超过上限 100 轮")
if rounds == 0 and verify and not task.get("goal_mode"):
raise ValueError(
"旧验收形态 goal_max_rounds=0 表示不限轮次,已禁用")
inner_args = ["goal", "--name", self._host_name(task),
"--task", goal_text]
if verify:
@@ -1150,8 +1188,10 @@ class DockerExecutor(Executor):
combined = stdout_full + stderr
if "已存在" in combined:
met, goal_error = None, (
"host 任务已存在:goal 任务不支持同任务重试,"
"请新建任务")
"host 任务已存在:goal 失败请用继续而非重试——"
"同任务重试会撞已存在 host-state,worker 会先"
"尝试断点续跑,断点续跑仍失败时请用继续(不要"
"重排同一任务)")
status = "failed"
elif returncode != 0:
status = "failed"
@@ -1181,22 +1221,29 @@ class DockerExecutor(Executor):
"usage": _parse_goal_usage(stdout_full),
}
def goal_resume_task(self, task, goal_text, timeout=None,
def goal_resume_task(self, task, goal_text=None, timeout=None,
extra_mounts=None, network=None,
extra_env=None, model_provider=None) -> dict:
"""容器内调 host.py goal-resume --goal-text:在旧线程上开
新目标循环(旧任务接续 goal 模式)。解析与 goal_task 同款
(完整 stdout 结论、存库截尾),但没有“已存在”分支:
goal-resume 本来就是操作已有 host 任务,不存在 dispatch
撞名问题;returncode 非 0 或无结论一律 failed。
extra_env=None, model_provider=None,
max_rounds=None) -> dict:
"""容器内调 host.py goal-resume,分两种调用(P1,与 Local
goal_resume_task 同款契约):断点续跑传 goal_text=None
(不带 --goal-text,host 沿用原 goal+verify+轮次),开新
目标才传 goal_text(带 --goal-text 与 --max-rounds,默认 0)。
解析与 goal_task 同款(完整 stdout 结论、存库截尾),但
没有“已存在”分支:goal-resume 本来就是操作已有 host 任务,
不存在 dispatch 撞名问题;returncode 非 0 或无结论一律 failed。
"""
if not isinstance(task, dict) or task.get("id") is None:
raise ValueError("任务缺少 id")
if not goal_text:
raise ValueError("goal-resume 缺少目标文本")
_resume_args = ["goal-resume", "--task", self._host_name(task)]
if goal_text:
_resume_args += ["--goal-text", goal_text, "--max-rounds",
str(max_rounds
if max_rounds is not None else 0)]
elif max_rounds is not None:
_resume_args += ["--max-rounds", str(max_rounds)]
inner = self._inner_host_command(
"goal-resume", "--task", self._host_name(task),
"--goal-text", goal_text, "--max-rounds", "0",
*_resume_args,
model=task.get("model"), effort=task.get("effort"),
approval=task.get("approval"),
model_provider=model_provider)