fix: 9.5-code reachable items (parse unification, goal branch, fail-open logs, M7 test, fingerprint)

Parse: agent_tools/tool_approvals column parsing unified into agent_def shared helpers (parse None = undeclared; server keeps None, worker maps to []/{} at _task_from_row, dispatch behaviour unchanged). Goal: _apply_resume_preamble goal branch proven reachable via _goal_convert_queued on a queued auto-resumed task, kept with a test and corrected docs (not dead code). Fail-open: audit write failure and preamble lookup failure now log warnings (best-effort unchanged); partial policy failure and shadow parse drops documented as known gaps. M7: shadow endpoint explicit_capability_failures_truncated test. Fingerprint: RoutingPolicy.content_fingerprint (canonical JSON sha256, version excluded) surfaced only in dry-run output, never in audit rows. M1 wording: legacy gap stated as fixed stock persisting per-task until completion, not converging.
This commit is contained in:
host committed 2026-11-04 18:02:21 +00:00
1 parent d95d678379
commit 1b8b6e525d
7 files changed
+498 -57

No files matched your search

+68 -34
View File
@@ -137,6 +137,8 @@ from types import SimpleNamespace
from cloud.agent_def import AgentDefinition as _AgentDefinition
from cloud.agent_def import format_instructions_header as _agent_header
from cloud.agent_def import missing_required_tools as _missing_tools
from cloud.agent_def import parse_agent_tool_approvals_column as _parse_tool_approvals_col
from cloud.agent_def import parse_agent_tools_column as _parse_tools_col
from cloud.attempts import build_resume_preamble as _resume_preamble
from cloud.attempts import ensure_attempts_table as _ensure_attempts
from cloud.attempts import latest_attempt as _latest_attempt
@@ -362,30 +364,22 @@ def _resolve_model(model=None, config_path=None):
def _agent_tools_from_col(raw):
"""agent_tools 列的容错解析:JSON 数组,非数组/脏数据为 []。"""
if raw is None:
return []
if isinstance(raw, list):
return list(raw)
try:
parsed = json.loads(raw)
except (TypeError, ValueError):
return []
return list(parsed) if isinstance(parsed, list) else []
"""agent_tools 列的容错解析:唯一实现在
agent_def.parse_agent_tools_column(9.5-code 统一);本
函数只把解析层的 None(= 未声明)映射为派发默认值
[]——worker 每个使用点本来就读 ``or []``,所以映射后
的派发行为与统一前逐字一致,缺列/脏数据不炸认领。"""
parsed = _parse_tools_col(raw)
return parsed if parsed is not None else []
def _agent_tool_approvals_from_col(raw):
"""agent_tool_approvals 列的容错解析:JSON 对象,非对象/
脏数据为 {}(= 未声明),不炸认领。"""
if raw is None:
return {}
if isinstance(raw, dict):
return dict(raw)
try:
parsed = json.loads(raw)
except (TypeError, ValueError):
return {}
return dict(parsed) if isinstance(parsed, dict) else {}
"""agent_tool_approvals 列的容错解析:唯一实现在
agent_def.parse_agent_tool_approvals_column;解析层的
None(= 未声明)映射为派发默认值 {},理由同上(每个
使用点本来就读 ``or {}``),派发行为与统一前一致。"""
parsed = _parse_tool_approvals_col(raw)
return parsed if parsed is not None else {}
def _task_from_row(row):
@@ -1772,7 +1766,14 @@ class Worker:
conn.close()
def _audit(self, project_id, action, detail=""):
"""best-effort 审计:失败不影响任务状态。"""
"""best-effort 审计:失败不影响任务状态(9.5-code
fail-open 清单第 1 项)。
此处是 fail-open:审计写失败时派发照常进行,运营
方只能从这条警告日志看到审计丢了——审计失败绝不
炸派发(语义不变),但失败本身不再静默。日志调用
本身也加 guard,日志失败时仍不炸派发。
"""
try:
store = AuthStore(self.db_path)
try:
@@ -1782,8 +1783,14 @@ class Worker:
store.conn.close()
except Exception:
pass
except Exception:
pass
except Exception as exc:
try:
log_event(self.logger, "worker_audit_failed",
project_id=project_id, status="warning",
message="audit write failed (%s): %s"
% (action, exc))
except Exception:
pass
def _notify_task_finished(self, task_id, project_id, event_type,
error=None):
@@ -2013,8 +2020,8 @@ class Worker:
是模型最先看到的一段之一;位置是刻意的前置(重
排说明应当先被看到),不是紧贴原 prompt。goal
任务若意外走到这里也同步贴 goal(执行器取 goal
优先于 prompt);该分支正常流程不可达(见下),
只是防御性保留。查账本失败时原样返回,不炸认领。
优先于 prompt);goal 分支在正常流程可达(见下),
不是死代码,绝不能删。查账本失败时原样返回,不炸认领。
"""
if task.get("coordinator_parts"):
return task
@@ -2028,7 +2035,20 @@ class Worker:
attempt = _latest_attempt(conn, task["id"])
finally:
conn.close()
except Exception:
except Exception as exc:
# 9.5-code fail-open 清单:账本查不到时不贴前言、
# 不炸认领(语义不变),但失败记一条警告日志,
# 否则“前言丢了”在运行期不可见。
try:
log_event(self.logger,
"worker_resume_preamble_lookup_failed",
task_id=task["id"],
project_id=task.get("project_id"),
status="warning",
message="resume preamble lookup failed: %s"
% exc)
except Exception:
pass
return task
preamble = _resume_preamble(attempt)
if not preamble:
@@ -2037,13 +2057,19 @@ class Worker:
new_task["prompt"] = preamble + "\n\n" + (
task.get("prompt") or "")
if task.get("goal_mode") or task.get("verify_command"):
# Audit fix 3(M8):此分支正常流程不可达——
# build_resume_preamble 只对 platform_fresh 出
# 前言,而 auto_resume 把 goal 任务一律归为
# goal_breakpoint;且 goal 断点任务在函数入口
# 已被 _goal_resume_breakpoint 挡回。保留不删:
# 万一未来有 platform_fresh 的 goal 尝试,goal
# 字段不同步贴前言会静默丢说明。
# 9.5-code 已证实可达(非 M8 所说的防御性保留):
# platform_fresh 账本只由 auto_resume 给非 goal
# 任务记(_record_attempt 唯一调用点),但同一
# 任务随后可在 queued 状态下被
# server._goal_convert_queued 原地转成 goal
# (goal_mode=1,CAS 只改 queued 行,不动
# auto_resume_count 与账本);认领时它既无
# _resume(auto_resume 已清 resume_message),
# 也无 _goal_resume_breakpoint,于是走到这里。
# 删掉本分支会让这类转 goal 任务静默丢前言,
# 故保留;复活路径(_goal_revive_terminal)因
# resume_message 置 _resume 而在入口被挡,不
# 走本分支,二者不要混淆。
new_task["goal"] = preamble + "\n\n" + (
task.get("goal") or task.get("prompt") or "")
new_task["_resume_preamble_injected"] = True
@@ -5835,6 +5861,14 @@ class Worker:
也就是写回的来源。
查找失败(DB 异常)时返回 False 并记警告日志:复核查
不到依据就跳过,而不是拦任务。
残留缺口措辞(9.5-code,M1):fixes3 时期由 shadow /
worker_default 决策写回的旧模型既无 persisted 行又
无 policy 行,启发式认不出来——这部分存量是固定
的、逐任务持续直至完成,不是“会收敛”(收敛一词
会误读为随时间自动消失;实际上每个存量任务在完
成前每次续跑都不经复核)。新任务不再产生这类写
回,所以缺口不扩大,但存量本身不会被重新识别。
"""
try:
conn = _db_connect(self.db_path)