fix: audit findings (fallback dead code, silent fallback, registry re-parse, shadow sort, gateway capability)
- RoutingPolicy.fallback_models: move the cleaning loop into __init__ (it was unreachable after tier_mode's return, so the attribute never existed); still parsed but not applied. - Capability fallback: RoutingDecision gains capability_fallback; the worker audits worker_capability_fallback when it fires, so a tier-model rejection followed by a worker-default fallback is no longer invisible (source stays worker_default, no model_routing event, so shadow samples are unaffected). - Registry: resolve_registry_agent checks skills directly against the loaded definition instead of re-parsing a hand-built dict, so future fields cannot be silently dropped the way tool_approvals was in v9. - Shadow: evaluate_shadow sorts rows earliest-first by created_at (id tiebreak) when timestamps are present, so dedup no longer depends on caller row order; the server path (which strips created_at) is unchanged. - Gateway: when sub2api fills the default model, the worker now checks it against the tier's capability requirements with the same mode semantics as v8 -- shadow only audits, active fails fast with worker_capability_gate_rejected and never calls the runner. No worker-default fallback here: the gateway model is the task's gateway model, falling back to a worker default would run against the wrong gateway.
This commit is contained in:
1 parent
a51919f24b
commit
f9a77577c5
5 files changed
+392
-34
No files matched your search
+14
-19
@@ -460,25 +460,20 @@ def resolve_registry_agent(name, version=None, agents_dir=None,
|
||||
"registry file %s says version %r, not requested %r" %
|
||||
(filename, definition.version, version))
|
||||
if known_skills is not None:
|
||||
# Re-parse for skill existence only (load already validated
|
||||
# format); the re-parsed definition is the one returned, so
|
||||
# callers never see a definition that failed skill checks.
|
||||
definition = parse_agent_definition(
|
||||
{
|
||||
"name": definition.name,
|
||||
"version": definition.version,
|
||||
"description": definition.description,
|
||||
"instructions": definition.instructions,
|
||||
"skills": definition.skills,
|
||||
"tools": definition.tools,
|
||||
# v9: the re-parse must carry tool_approvals, or
|
||||
# a registry definition's declaration would be
|
||||
# silently dropped on the server path (inline
|
||||
# definitions never re-parse, so they were fine).
|
||||
"tool_approvals": definition.tool_approvals,
|
||||
"defaults": definition.defaults,
|
||||
},
|
||||
known_skills=known_skills)
|
||||
# Audit fix: skill existence is now checked directly
|
||||
# against the loaded definition, instead of re-parsing
|
||||
# a hand-built dict. The old re-parse silently dropped
|
||||
# any field not copied into that dict (tool_approvals
|
||||
# was lost exactly this way in v9, and the next new
|
||||
# field would have been lost the same way). The
|
||||
# original definition is returned unchanged, so future
|
||||
# fields survive automatically. The error message is
|
||||
# kept identical to parse_agent_definition's.
|
||||
known = set(known_skills)
|
||||
missing = [s for s in definition.skills if s not in known]
|
||||
if missing:
|
||||
raise ValueError("agent.skills unknown skill(s): %s"
|
||||
% ", ".join(missing))
|
||||
return definition
|
||||
|
||||
|
||||
|
||||
Reference in new issue
Block a user