这里为您重新生成了一份单次完整输出、无任何截断的 Markdown 审查报告。您可以直接点击代码块右上角的“复制”按钮,一键保存为本地 .md 文件:
ARCHITECTURE_REVIEW_UNIFIED.md)与当前实际代码的一致性,排查未闭环的逻辑漏洞、边界风险与测试泄漏。vmess-domain-rotator 是一个完成度极高、工程边界清晰的 Cloudflare 优选域名/IP 自动化选优与分发系统。其核心架构分为三层:
configs/config.server.json + scripts/rotator/):拉取第三方监控 API,经字段映射、黑名单过滤、加权评分、平局决胜及切换迟滞(Hysteresis)防抖后选出最优域名。configs/config.local.json + scripts/rotator/):调用本地 cfst 二进制测速,解析 CSV,自动剔除零速死节点(除非显式配置 -dd)。configs/config_router.conf + scripts/router/):纯 POSIX sh + awk 实现,采用 RAM (/tmp) 与 Flash (/jffs) 动静分离设计,保护路由器闪存寿命,并通过 busybox httpd 暴露给局域网。scripts/server/run_update_and_commit.sh:双频自适应时间窗口(高峰/非高峰容差)、独立 Git Worktree 检出 runtime-state 孤儿分支、回退静默抑制(防状态翻转刷屏)、以及 Mode A 历史滚动修剪(Squashed Rebase 保留最近 50 次提交)。substore/operator_template.js:Sub-Store 节点替换算子,支持手动参数最高优先级覆盖、多源降级(default / server_only / router_only)、RFC 1123 域名/IP 严格校验及本地数据 24 小时过期降级。domain_updater.py 拆分为 scripts/rotator/ 下 7 个高内聚子模块(config / filter / sources / scoring / storage / notify / main),同时顶层保留完整 Facade 导出,兼顾了代码可维护性与外部调用兼容。.claude/hooks/check_syntax.py 实现了零副作用的语法门禁(Python AST compile、Bash/sh -n、Node --check),配合 verify-rotator Skill 的隔离预检规范,工程化程度极高。tests/ 下 7 个测试文件完全基于标准库 unittest 与 Node 内置 vm 构建,执行速度快且可移植性强。尽管 docs/reviews/ARCHITECTURE_REVIEW_UNIFIED.md 标记“所有 P0/P1/P2 问题 100% 修复完毕”,但通过逐行比对当前实际代码与终审报告台账,共发现 8 处关键的实际代码与报告声明不符、未闭环的逻辑缺陷及安全隐患:
default 模式依然会被路由器的故障回退值劫持substore/operator_template.js:184-239 (selectValueRouterFirst)router_local_update.sh:188-196 会写出 "status": "error_use_last_good",且该 JSON 不包含 top_candidates 字段,同时将 updated_at 刷新为当前报错时间。
此时在 operator_template.js 中:
isRouterDataFresh(routerObj) 检查通过(因为 updated_at 刚刚被错误回退刷新);parseRouterSpeedMBps(routerObj) 因为没有 top_candidates,返回 status: "missing";if (telemetry.router.speedParse === "ok" && telemetry.router.speedMBps < ROUTER_MIN_SPEED_MB_PER_S) —— 由于 speedParse 是 "missing",直接绕过了低速门禁;server 完全健康(status: "ok")的情况下,依然优先返回了路由器处于故障回退状态的旧 IP。selectValueRouterFirst() 中,当 telemetry.router.status !== "ok" 且 telemetry.server.ok && telemetry.server.status === "ok" 时,优先降级选用健康的 server;或者在 router_local_update.sh 回退时不要刷新用于新鲜度判定的成功观测时间戳。scripts/server/run_update_and_commit.sh:277-318P1-3 和复审报告 §3.2 均指出了此问题,但当前代码中依然未修复。
脚本在第 277 行读取 before="$(git -C "$work_dir" show "HEAD:${selected_rel}")"(读取的是本地 runtime-state 分支的 HEAD),并在第 314 行判断:如果 after == before 且 status_unchanged == 1,则直接 log "... skip git commit and push"; exit 0。
故障推演:第 1 轮选出新域名 a.com,本地 git commit 成功,但因网络抖动 git push 失败退出。第 2 轮(30 分钟后)运行,API 依然选出最优域名 a.com —— 此时本地 HEAD 已经是 a.com,触发 after == before 直接 exit 0。本地与远端永久分叉,远端永远收不到这次更新,直到未来某天域名再次变化或人工执行 --force-commit。git commit”与“是否需要 git push”解耦。在 exit 0 跳过前,检查当 push_enabled == "1" 时,本地 HEAD 是否领先于远端跟踪分支(例如比对 git rev-parse HEAD 与 git rev-parse @{u}),若存在未推送的 commit,则直接跳转至推送阶段重试。configs/config.server.json:6"url": "https://vps789.com/openApi/listMonitors?token=UR6SYEEATJGVOPIST6QHCPFNAJ8233S8" 依然明文硬编码在配置文件中,且 scripts/rotator/config.py / sources.py 并未实现环境变量(如 AUTODOMAIN_API_TOKEN)或 token_file 的注入支持。vps789.com 后台轮换重置该 Token;sources.py:fetch_api_json() 中支持读取环境变量(如 os.environ.get("AUTODOMAIN_API_TOKEN"))或 api.token_file 注入查询参数,将 config.server.json 中的明文移除。last_good 回退与状态读取的三个漏洞scripts/rotator/main.py:59, 125-130, 179-181 & configs/config.local.json:37state.json 非字典类型崩溃风险 (P-07):若 state.json 内容被外部意外写成合法 JSON 数组 []、null 或 "string",read_json_file 原样返回非 dict 对象,随后 main.py:59 执行 state.get(...) 会直接在主 try 块外抛出 AttributeError 崩溃退出,导致正常的 API 采集也无法启动。record_filter 黑名单穿透:如 CLAUDE.md 所述,当前回退仅复检了 domain_filter(正则),未复检 record_filter(例如在 config.server.json 的 record_filter 中拉黑了 domain equals openai.com,当 API 返回空列表触发 fallback_last_good 时,若上次 last_good 是 openai.com,它依然会绕过 record_filter 复活)。runtime/state.json:两份配置默认都指向 ./runtime/state.json。在同一台机器交替运行 Server 和 Local 模式时,后运行者的 write_json_file(state_file, new_state) 会全量覆盖 state.json,直接抹掉另一模式的 last_good_domain / last_good_ip。build_router_package.sh SSOT 同步将“下载速度下限”误映射为“丢包率上限”build_router_package.sh:172-173 vs scripts/router/router_local_update.sh:258-263build_router_package.sh:172-173 中:
``python
"-sl": "CFST_LOSS_LIMIT",
"-speedlimit": "CFST_SPEED_LIMIT",
而在 router_local_update.sh:258-263(以及 cfst 官方参数定义)中,丢包率上限是 -tlr
(CFST_LOSS_LIMIT),下载速度下限才是 -sl (CFST_SPEED_LIMIT)。 这意味着如果用户在 config.local.json
里配置了 -sl 5(速度下限 5MB/s),执行 --sync-from-config 打包时会将其错误同步为路由器的
CFST_LOSS_LIMIT="5"(丢包率上限),且漏掉了对 -tlr 的映射。
- 修复建议: 将 build_router_package.sh 中的映射修正为 "-tlr": "CFST_LOSS_LIMIT" 和 "-sl":
"CFST_SPEED_LIMIT"。
### 🟡 6. 【P2 契约与文档漂移】终审报告宣称已修复,但实际代码未落地的 2 处细节
1. 路由器 JSON 稀疏语义未统一(scripts/router/router_local_update.sh:123):
- README.md §1.2 与终审报告宣称“仅在字段非空时输出真实键值,彻底消除空字符串/空数组/null 占位污染”,但
router_local_update.sh:123 的 awk
依然硬编码输出:"location_country":"","location_city":"","host_provider":"","score_value":null,"scores":[],"created_raw":""。
2. 终审文档对 router_local_http.sh (RT-03) 的修复描述与代码脱节:
- ARCHITECTURE_REVIEW_UNIFIED.md §7.4 RT-03 和 §8.5 声称已增加 is_running() 检查
/proc/$pid/cmdline 包含 httpd。核查 scripts/router/router_local_http.sh
全文,实际采用的是前台运行 httpd -f + wait "$httpd_pid" 模式,并无 PID_FILE 或
/proc/$pid/cmdline 逻辑。虽然前台 wait 模式本身规避了 PID 文件复用误杀,但终审台账的描述与代码事实不符。
### 🟡 7. 【P2 测试隔离缺陷】test_server_mode_adaptive_interval_window 存在真实网络请求泄漏
- 位置:tests/test_server_mode_lifecycle.py:165-199
- 现象与根因: 正如 .claude/skills/verify-rotator/SKILL.md 中记录的那样,该测试在“场景 A(第 184
行)”首次调用 run_update_and_commit.sh 时,尚未将 config.server.json 中的 api.url 替换为本地
file:// 模拟路径(替换操作写在了第 205 行的场景 B 里)。 如果运行测试时恰好处于所设定的时间窗口或整点边界(例如 curr_min
== 0 或 curr_hour == 2),场景 A 不会被跳过,而是会直接向真实的 https://vps789.com/...
发起外部网络请求,破坏测试的离线隔离性。
- 修复建议: 在场景 A 执行前先完成本地模拟 file:// API 配置写入,并通过在测试环境 PATH 中注入 mock date
命令固定测试时钟,从而将此用例安全纳入常规回归测试。
## 四、 问题汇总矩阵 (Summary Matrix)
| 编号 | 严重度 | 模块 | 问题简述 | 当前状态 |
| :------- | :---------- | :-------------------------------------------- | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :------- |
| **F-01** | **P0 / S0** |configs/config.server.json| 真实 API Token 明文硬编码入库,缺乏环境变量/文件注入机制 | ❌ 未修复 |
| **F-02** | **P1** |substore/operator_template.js|default模式下路由器error_use_last_good因无top_candidates绕过测速门禁,压制健康 Server | ❌ 未修复 |
| **F-03** | **P1** |scripts/server/run_update_and_commit.sh| 本地git commit成功但push失败后,若下一轮优选值未变,将触发跳过判断导致永久不重试推送 | ❌ 未修复 |
| **F-04** | **P1** |scripts/rotator/main.py/storage.py| 1\.state.json若为非字典 JSON(如[]/null)将直接抛出AttributeError崩溃<br>2\.last_good回退未复检record_filter静态域名黑名单<br>3\. Server 与 Local 配置默认共用runtime/state.json互相覆盖 | ❌ 未修复 |
| **F-05** | **P1** |build_router_package.sh|--sync-from-config将-sl(下载速度下限)误映射为CFST_LOSS_LIMIT(丢包上限),且漏映射-tlr| ❌ 未修复 |
| **F-06** | **P2** |scripts/router/router_local_update.sh| 路由器生成的top_candidates仍包含硬编码空字符串/null/[],未对齐稀疏字段契约 | ⚠️ 契约漂移 |
| **F-07** | **P2** |docs/reviews/ARCHITECTURE_REVIEW_UNIFIED.md| 终审报告RT-03声称router_local_http.sh检查/proc/$pid/cmdline,与实际前台wait代码不符 | ⚠️ 文档失真 |
| **F-08** | **P2** |tests/test_server_mode_lifecycle.py|test_server_mode_adaptive_interval_window在配置本地模拟 API 前先执行了一次脚本,存在真实网络请求泄漏风险 | ⚠️ 测试未隔离 |
## 五、 核心修复方案与代码级建议 (Actionable Fixes)
### 5.1 修复 build_router_package.sh 参数映射错位(对应 F-05)
修改位置:build_router_package.sh:160-175
arg_map = {
"-p": "CFST_DISPLAY_COUNT",
"-f": "CFST_IP_FILE",
"-n": "CFST_THREADS",
"-t": "CFST_TEST_COUNT",
"-dn": "CFST_DOWNLOAD_COUNT",
"-dt": "CFST_DOWNLOAD_TIME",
"-tp": "CFST_PORT",
"-url": "CFST_URL",
"-cfcolo": "CFST_CFCOLO",
"-tl": "CFST_LATENCY_LIMIT",
"-tll": "CFST_LATENCY_LOWER",
"-tlr": "CFST_LOSS_LIMIT", # 修正:-tlr 对应丢包率上限
"-sl": "CFST_SPEED_LIMIT", # 修正:-sl 对应下载速度下限
}
### 5.2 修复 substore/operator_template.js 故障回退劫持(对应 F-02)
修改位置:substore/operator_template.js:223-239 (selectValueRouterFirst)
if (!telemetry.router.ok && !telemetry.server.ok) {
throw new Error(router failed: ${telemetry.router.error}; server failed: ${telemetry.server.error}`);
}
if (!telemetry.router.ok && telemetry.server.ok) {
return { value: telemetry.server.value, chosenSource: "server", reason: "router_fail", telemetry };
}
if (telemetry.router.ok && !telemetry.server.ok) {
return { value: telemetry.router.value, chosenSource: "router", reason: "server_fail", telemetry };
}
// 新增:当路由器处于故障回退状态(非 ok)而 Server 健康时,优先降级至健康的 Server
if (telemetry.router.status !== "ok" && telemetry.server.status === "ok") {
return { value: telemetry.server.value, chosenSource: "server", reason: "router_degraded_fallback", telemetry };
}
if (telemetry.router.speedParse === "ok" && telemetry.router.speedMBps < ROUTER_MIN_SPEED_MB_PER_S) {
return { value: telemetry.server.value, chosenSource: "server", reason: "router_low_speed", telemetry };
}
return { value: telemetry.router.value, chosenSource: "router", reason: "router_preferred", telemetry };
### 5.3 修复 run_update_and_commit.sh 未推送提交永久跳过问题(对应 F-03)
修改位置:scripts/server/run_update_and_commit.sh:313-318
在判定“值未变化”准备跳过前,检查本地分支是否还有未推送到远端的 commit:
has_unpushed="0"
if [[ "$push_enabled" == "1" ]] && [[ -n "$push_remote" ]]; then
if git -C "$work_dir" rev-parse --abbrev-ref --symbolic-full-name "@{u}" >/dev/null 2>&1; then
local_head="$(git -C "$work_dir" rev-parse HEAD 2>/dev/null || echo "")"
remote_head="$(git -C "$work_dir" rev-parse "@{u}" 2>/dev/null || echo "")"
if [[ -n "$local_head" ]] && [[ "$local_head" != "$remote_head" ]]; then
has_unpushed="1"
fi
else
has_unpushed="1"
fi
fi
if [[ "$force_commit" != "1" ]] && [[ -n "$before" ]] && [[ "$after" == "$before" ]]; then
if [[ "$status_unchanged" == "1" || "$is_fallback" == "1" ]]; then
if [[ "$has_unpushed" == "1" ]]; then
log "[vmess-domain-rotator] value unchanged (${after}), but local branch has unpushed commits; retrying push"
git_auth "$work_dir" push -u "$push_remote" "$runtime_branch:$runtime_branch" || {
log "[vmess-domain-rotator] retry git push failed"
[[ "$push_required" == "1" ]] && exit 1
}
else
log "[vmess-domain-rotator] selected value unchanged (${after}) [status=${updater_status:-unknown}], skip git commit and push"
fi
exit 0
fi
fi
### 5.4 加固 state.json 类型安全与回退黑名单检查(对应 F-04)
修改位置:scripts/rotator/main.py:59
state = read_json_file(state_file, default={})
if not isinstance(state, dict):
state = {}
last_good = str(state.get(state_last_good_key, "") or "").strip()
同时提取一个通用的 is_domain_blocked(domain, cfg) 辅助函数,在 last_good 回退时不仅调用
apply_filter(),还对 record_filter 中针对 field == "domain" 的规则执行复检,防止被拉黑的域名通过
last_good 复活。
### 5.5 修复 test_server_mode_adaptive_interval_window 离线隔离(对应 F-08)
修改位置:tests/test_server_mode_lifecycle.py:165-215
将 payload_file = self._write_mock_api_payload(...) 与改写 cfg["api"]["url"] =
f"file://{payload_file}" 的代码上移至场景 A 执行之前,并通过在 env 的 PATH 最前面注入一个临时 mock date
脚本来固定测试时钟(例如场景 A 固定返回 04 时 15 分),使该测试 100% 确定性通过且绝不触发外网请求。
### 六、 建议推进路线 (Next Steps)
1. 第一优先级(立竿见影的正确性与测试闭环):
- 修复 build_router_package.sh 的 -sl / -tlr 参数映射错位并补充对应单元测试断言;
- 修复 tests/test_server_mode_lifecycle.py 的模拟 API 注入顺序,解除
.claude/skills/verify-rotator/SKILL.md 中对该测试的默认排除限制;
- 修复 scripts/rotator/main.py 对非字典 state.json 的防御性容错。
2. 第二优先级(端到端容灾闭环):
- 修复 substore/operator_template.js 对 error_use_last_good 状态的降级判定;
- 修复 scripts/server/run_update_and_commit.sh 的未推送 commit 自动重试机制;
- 清理 router_local_update.sh 中 top_candidates 的冗余空字段,对齐稀疏 JSON 契约。
3. 第三优先级(凭据安全与文档对齐):
- 支持通过环境变量或外部文件注入 API Token,轮换并移除 configs/config.server.json 中的明文 Token;
- 修正 ARCHITECTURE_REVIEW_UNIFIED.md 中的少量失真描述,保持文档与代码 100% 同步。