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
+24
-7
@@ -336,6 +336,19 @@ class RoutingPolicy:
|
||||
for tier, tier_mode in (tier_modes or {}).items():
|
||||
if tier in self.tiers and tier_mode in ROUTING_MODES:
|
||||
self.tier_modes[tier] = tier_mode
|
||||
# Audit fix: fallback_models used to live after the
|
||||
# return in tier_mode (unreachable dead code), so a
|
||||
# RoutingPolicy never actually had the attribute and
|
||||
# any future reader would get AttributeError. It is
|
||||
# stored here, cleaned and deduplicated, and still
|
||||
# deliberately NOT applied by the router (see class
|
||||
# docstring): only worker CLI --fallback-models enables
|
||||
# a real fallback retry.
|
||||
self.fallback_models = []
|
||||
for item in fallback_models or []:
|
||||
model = _clean_model_name(item)
|
||||
if model and model not in self.fallback_models:
|
||||
self.fallback_models.append(model)
|
||||
self.model_capabilities = parse_model_capabilities(
|
||||
model_capabilities)
|
||||
self.tier_requirements = {}
|
||||
@@ -348,11 +361,6 @@ class RoutingPolicy:
|
||||
def tier_mode(self, tier):
|
||||
"""Effective mode for one tier: explicit tier mode, else global."""
|
||||
return self.tier_modes.get(tier, self.mode)
|
||||
self.fallback_models = []
|
||||
for item in fallback_models or []:
|
||||
model = _clean_model_name(item)
|
||||
if model and model not in self.fallback_models:
|
||||
self.fallback_models.append(model)
|
||||
|
||||
|
||||
def parse_routing_policy(data):
|
||||
@@ -466,7 +474,8 @@ class RoutingDecision:
|
||||
"""One pre-dispatch model decision, kept auditable by construction."""
|
||||
|
||||
def __init__(self, task_id, tier, chosen_model, suggested_model,
|
||||
source, policy_version, applied, reason, signals):
|
||||
source, policy_version, applied, reason, signals,
|
||||
capability_fallback=False):
|
||||
self.task_id = task_id
|
||||
self.tier = tier
|
||||
self.chosen_model = chosen_model
|
||||
@@ -476,6 +485,13 @@ class RoutingDecision:
|
||||
self.applied = applied
|
||||
self.reason = reason
|
||||
self.signals = dict(signals or {})
|
||||
# Audit fix: True only when an active tier model failed
|
||||
# its capability check and the worker default was used
|
||||
# instead. The source stays "worker_default" (existing
|
||||
# callers rely on it), so the worker must check this
|
||||
# flag to audit the fallback -- otherwise the fallback
|
||||
# is invisible in the audit trail.
|
||||
self.capability_fallback = bool(capability_fallback)
|
||||
|
||||
def to_event(self) -> dict:
|
||||
return {
|
||||
@@ -488,6 +504,7 @@ class RoutingDecision:
|
||||
"policy_version": self.policy_version,
|
||||
"applied": self.applied,
|
||||
"reason": self.reason,
|
||||
"capability_fallback": self.capability_fallback,
|
||||
}
|
||||
|
||||
|
||||
@@ -614,7 +631,7 @@ def decide_routing(task, worker_model, policy=None,
|
||||
policy_version=policy.version, applied=True,
|
||||
reason="%s tier model %s%s, worker default satisfies"
|
||||
" requirements, falling back" % (tier, tier_model, cap_note),
|
||||
signals=signals)
|
||||
signals=signals, capability_fallback=True)
|
||||
return RoutingDecision(
|
||||
task_id=task.get("id"), tier=tier, chosen_model=None,
|
||||
suggested_model=tier_model, source="capability_blocked",
|
||||
|
||||
Reference in new issue
Block a user