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

+47
View File
@@ -200,6 +200,53 @@ class AgentDefinition:
}
def parse_agent_tools_column(raw):
"""Parse one tasks.agent_tools column value (the single shared
parser for server and worker; 9.5-code unification).
Returns a list copy, or None when the value is missing, is not
a JSON list, or is otherwise dirty. None is the parse-layer
default and means "undeclared", not "no required tools": the
server keeps None in task serialization (an old row with a
dirty value serializes exactly as before), while the worker
maps None to [] at the _task_from_row boundary, because
dispatch treats "no declaration" and "no required tools"
identically (both skip the v3 gate, and every worker use site
already reads ``task.get("agent_tools") or []``). Unifying the
parse logic therefore changes no observable behaviour on
either side; the old server/worker defaults (None vs []) were
only different spellings of the same dispatch outcome.
"""
if raw is None:
return None
if isinstance(raw, list):
return list(raw)
try:
parsed = json.loads(raw)
except (TypeError, ValueError):
return None
return list(parsed) if isinstance(parsed, list) else None
def parse_agent_tool_approvals_column(raw):
"""Parse one tasks.agent_tool_approvals column value (the
single shared parser for server and worker; same None
convention as parse_agent_tools_column: None means
"undeclared", the server keeps it, the worker maps it to {}
at the _task_from_row boundary because every worker use site
already reads ``task.get("agent_tool_approvals") or {}``).
"""
if raw is None:
return None
if isinstance(raw, dict):
return dict(raw)
try:
parsed = json.loads(raw)
except (TypeError, ValueError):
return None
return dict(parsed) if isinstance(parsed, dict) else None
def effective_tool_approvals(tool_approvals, host_approval):
"""What host.py will actually answer per declared tool, today.