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:
1 parent
d95d678379
commit
1b8b6e525d
7 files changed
+498
-57
No files matched your search
+68
-34
@@ -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)
|
||||
|
||||
Reference in new issue
Block a user