fix: second-audit gate findings (B1, I1-I8, M1-M10)
B1: gateway capability check now triggers on walking the sub2api gateway, not on the gateway having filled a model; task-level explicit models are audited (worker_capability_gateway_explicit) but never blocked, preserving explicit priority; gateway-default models still fail-fast under active. I1: dry-run adds a gateway-model section, sourced only from CLOUD_SUB2API_DEFAULT_MODEL (never queries the relay); an active tier whose requires the gateway model fails or cannot verify blocks, shadow only warns. I2: shadow review gate now counts completed samples (completed >= 20), same basis for sample_shortfall; the endpoint attaches per-tier capability status (read-only policy load) and flags capability-failing tiers in the recommendation. I3: configured-but-broken routing policy logs a warning in load_routing_policy (unconfigured stays silent); the worker sets routing_policy_load_failed and audits worker_routing_policy_load_failed per task. I4: resume preamble wraps prior output/error as data with an explicit do-not-execute constraint; position verified in run_one (tree -> preamble -> project -> original) and the docs corrected from the false 'closest to the original prompt' claim. I5: decided model is written back with a CAS UPDATE (model IS NULL OR model=''), so later attempts reuse it; explicit models are never overwritten. I6: agent_* columns added to worker._ensure_result_columns and db.init_postgres_schema backfill; migrations backfills them for tasks before insert (sender_task_id pattern); column-level tests for agent_tool_approvals and task_attempts. I7: coordinator requeue adds AND status=<snapshot> (rowcount 0 skips requeue/audit) and filters paused_by_user / user_cancel / user_pause children, which are never revived or cleared. I8: explicit-model tasks (including coordinator children that inherited a parent model) audit a source=explicit worker_routing row; evaluate_shadow counts it as per-tier explicit_n only, never routed_n, never a dedup slot. M1/M2/M3/M5/M6/M8/M10: dedup slot moved after tier validation, shadow sort doc fixed, truncated recommendation notes truncation, seed cleanup on gateway/quota/goal-cap early returns, tools-only coordinator definitions rejected, preamble goal branch marked effectively unreachable, DESIGN.md drift corrected (outside repo). M4/M7/M9: documentation/new-field only (pass_rate_fully_measured, whitespace-model shadow-drop note, bound-note race note); old field semantics unchanged.
This commit is contained in:
1 parent
f9a77577c5
commit
621c08d10f
13 files changed
+907
-44
No files matched your search
+38
-4
@@ -17,8 +17,11 @@ worker-default chain instead of failing the task.
|
||||
"""
|
||||
|
||||
import json
|
||||
import logging
|
||||
import os
|
||||
|
||||
_logger = logging.getLogger("cloud.models")
|
||||
|
||||
FALLBACKABLE = {404, 429, 500, 502, 503, 504, "timeout"}
|
||||
|
||||
|
||||
@@ -277,6 +280,12 @@ def classify_task(task) -> str:
|
||||
|
||||
|
||||
def _clean_model_name(value):
|
||||
# Audit fix 3(M7,文档级):只 strip、不校验字符集。
|
||||
# 含空白的模型名仍会原样进审计 detail,而影子审计
|
||||
# 格式是空白分隔,整行解析失败即无声丢样本(不报错、
|
||||
# 不计数)。这里不加字符集校验——那会改变模型名接受
|
||||
# 语义、可能拦住合法模型名;风险以文档记录,修复
|
||||
# 需另起一轨(审计格式改分隔或模型名白名单)。
|
||||
if isinstance(value, str) and value.strip():
|
||||
return value.strip()
|
||||
return None
|
||||
@@ -432,23 +441,38 @@ def load_routing_policy(env=None):
|
||||
``CLOUD_ROUTING_POLICY_FILE`` is read first, then
|
||||
``CLOUD_ROUTING_POLICY_JSON`` overrides its fields per tier;
|
||||
``CLOUD_ROUTING_MODE`` overrides the mode when valid. Any source
|
||||
that fails to parse is ignored, never fatal. Returns None when no
|
||||
usable policy was found, so the caller keeps the old behaviour.
|
||||
that fails to parse is ignored, never fatal, but the failure is
|
||||
logged as a warning (Audit fix 3, I3): an unconfigured policy
|
||||
is silent, a configured-but-broken one is not. Returns None
|
||||
when no usable policy was found, so the caller keeps the old
|
||||
behaviour.
|
||||
"""
|
||||
env = env if env is not None else os.environ
|
||||
merged = None
|
||||
# Audit fix 3(I3):区分“未配置策略”(无 env,正常,
|
||||
# 不记警告)与“配置了但解析失败”(异常,必须可见)。
|
||||
# 旧版任一来源失败都静默吞掉,运营方无法区分
|
||||
# “策略生效”与“策略已死”。行为不变:失败仍返回
|
||||
# None、派发回落旧链,只是失败现在记警告日志;
|
||||
# worker 侧另有 routing_policy_load_failed 标志与
|
||||
# 审计(见 worker.py),让失败在运行期也可查。
|
||||
configured = bool(env.get("CLOUD_ROUTING_POLICY_FILE")
|
||||
or env.get("CLOUD_ROUTING_POLICY_JSON"))
|
||||
failures = []
|
||||
path = env.get("CLOUD_ROUTING_POLICY_FILE") or ""
|
||||
if path:
|
||||
try:
|
||||
with open(path, encoding="utf-8") as f:
|
||||
merged = json.load(f)
|
||||
except (OSError, ValueError):
|
||||
except (OSError, ValueError) as exc:
|
||||
failures.append("policy file %r: %s" % (path, exc))
|
||||
merged = None
|
||||
raw = env.get("CLOUD_ROUTING_POLICY_JSON") or ""
|
||||
if raw:
|
||||
try:
|
||||
override = json.loads(raw)
|
||||
except ValueError:
|
||||
except ValueError as exc:
|
||||
failures.append("CLOUD_ROUTING_POLICY_JSON: %s" % exc)
|
||||
override = None
|
||||
if isinstance(override, dict):
|
||||
if not isinstance(merged, dict):
|
||||
@@ -463,7 +487,17 @@ def load_routing_policy(env=None):
|
||||
merged["tiers"] = tiers
|
||||
policy = parse_routing_policy(merged)
|
||||
if policy is None:
|
||||
if configured:
|
||||
failures.append("merged policy document is unusable")
|
||||
if failures:
|
||||
_logger.warning(
|
||||
"routing policy load failed (%s); falling back to"
|
||||
" worker default", "; ".join(failures))
|
||||
return None
|
||||
if failures:
|
||||
_logger.warning(
|
||||
"routing policy partially failed (%s); continuing with"
|
||||
" the usable source", "; ".join(failures))
|
||||
mode_override = env.get("CLOUD_ROUTING_MODE") or ""
|
||||
if mode_override in ROUTING_MODES:
|
||||
policy.mode = mode_override
|
||||
|
||||
Reference in new issue
Block a user