Audit batch 4: I-6 whitespace audit format, I-7 explicit non-sub2api capability gate, I-8 coord gate attribution, I-10 checked gates, I-11 gate-without-finding escalate, I-13 structured-first classification

This commit is contained in:
host committed 2026-11-04 18:02:23 +00:00
1 parent 06e4e16bbb
commit cdac8039b3
5 files changed
+959 -47

No files matched your search

+54 -11
View File
@@ -155,7 +155,11 @@ def parse_model_capabilities(raw):
return out
for model, entry in raw.items():
model = _clean_model_name(model)
if not model or not isinstance(entry, dict):
# 审计批 4(I-6):能力目录键含空白时整条丢弃——
# 含空白的键在空白分隔的审计 detail 里无法被消费点
# 稳健识别,保留它只会让能力检查静默走 unknown。
if not model or _model_name_has_whitespace(model) \
or not isinstance(entry, dict):
continue
cleaned = {}
valid = True
@@ -432,17 +436,28 @@ def classify_task(task) -> str:
def _clean_model_name(value):
# Audit fix 3(M7,文档级):只 strip、不校验字符集。
# 含空白的模型名仍会原样进审计 detail,而影子审计
# 格式是空白分隔,整行解析失败即无声丢样本(不报错、
# 不计数)。这里不加字符集校验——那会改变模型名接受
# 语义、可能拦住合法模型名;风险以文档记录,修复
# 需另起一轨(审计格式改分隔或模型名白名单)。
"""把模型名规范成可比较的字符串:仅 strip。
注意:这里刻意不做空白白名单。decide_routing 里清洗后
的 None 会被当成“无显式模型”而改走策略/worker 默认路由,
全局白名单会把“显式模型含空白(目前原样派发、provider
侧 loud fail)”悄悄改成“静默替换用户选型”,比现状更差。
空白模型名的处理因此分两层(审计批 4,I-6):
(a) 策略写入路径白名单——能力目录键、档模型、
fallback_models 含空白的一律丢弃;(b) 运行时消费点
稳健解析——影子 chosen 贪到行尾、_is_persisted_model
按键提取 model 值,不再依赖空白分隔的位置。
"""
if isinstance(value, str) and value.strip():
return value.strip()
return None
def _model_name_has_whitespace(name):
"""清洗后的模型名是否含空白(审计批 4,I-6 白名单判定)。"""
return isinstance(name, str) and any(ch.isspace() for ch in name)
def _clean_tiers(raw):
"""Return {tier: model} keeping only valid non-empty entries."""
tiers = {}
@@ -450,13 +465,17 @@ def _clean_tiers(raw):
return tiers
for tier in ROUTING_TIERS:
model = _clean_model_name(raw.get(tier))
if isinstance(model, str):
# 审计批 4(I-6):档模型含空白时整档丢弃(parse 层
# 另行记入 dropped_tiers + 警告),不让含空白的模型
# 名进空白分隔的审计 detail。
if isinstance(model, str) \
and not _model_name_has_whitespace(model):
# A tier entry may itself be a dict {"model": ...}; accept
# that shape too so policy authors are not punished for it.
tiers[tier] = model
elif isinstance(raw.get(tier), dict):
model = _clean_model_name(raw[tier].get("model"))
if model:
if model and not _model_name_has_whitespace(model):
tiers[tier] = model
return tiers
@@ -501,7 +520,12 @@ class RoutingPolicy:
self.version = version if isinstance(version, str) \
and version.strip() else None
self.mode = mode if mode in ROUTING_MODES else "off"
self.tiers = dict(tiers or {})
# 审计批 4(I-6):直接构造的策略同样过白名单——含
# 空白的档模型进不了空白分隔的审计 detail,整档丢弃
# (与 parse 层口径一致;parse 层另行记 dropped_tiers)。
self.tiers = {tier: model for tier, model in (tiers or {}).items()
if not (isinstance(model, str)
and _model_name_has_whitespace(model))}
self.tier_modes = {}
for tier, tier_mode in (tier_modes or {}).items():
if tier in self.tiers and tier_mode in ROUTING_MODES:
@@ -517,7 +541,9 @@ class RoutingPolicy:
self.fallback_models = []
for item in fallback_models or []:
model = _clean_model_name(item)
if model and model not in self.fallback_models:
# 审计批 4(I-6):fallback 模型名同款白名单。
if model and not _model_name_has_whitespace(model) \
and model not in self.fallback_models:
self.fallback_models.append(model)
self.model_capabilities = parse_model_capabilities(
model_capabilities)
@@ -598,6 +624,23 @@ def parse_routing_policy(data):
# for an empty model) is not a second drop and is not logged:
# the warning must name a tier this pop really removed.
dropped_tiers = []
# 审计批 4(I-6):_clean_tiers 已把含空白模型名的档
# 丢掉,这里只记名——与无效模式/无效 requires 的 pop
# 同款可见性。只认 ROUTING_TIERS 内的档(未知档名本
# 来就被 _clean_tiers 忽略,不是“被丢弃”);档已被
# _clean_tiers 滤掉时,后面的 mode/requires pop 因
# `tier in tiers` 为假不会重复记,不会双计。
for tier, entry in raw_tiers.items():
if tier not in ROUTING_TIERS or tier in tiers:
continue
raw_model = entry if isinstance(entry, str) else \
entry.get("model") if isinstance(entry, dict) else None
cleaned = _clean_model_name(raw_model)
if cleaned and _model_name_has_whitespace(cleaned):
dropped_tiers.append(tier)
_logger.warning(
"routing policy tier %r dropped: model name contains"
" whitespace %r", tier, cleaned)
tier_modes = {}
for tier, entry in raw_tiers.items():
if not isinstance(entry, dict) or "mode" not in entry: