|
@@ -61,44 +61,20 @@ flowchart LR
|
|
|
|
|
|
|
|
## 2. 本次审查新增发现(两份既有报告均漏检的「查漏补缺」)
|
|
## 2. 本次审查新增发现(两份既有报告均漏检的「查漏补缺」)
|
|
|
|
|
|
|
|
-以下 **8 个问题**在初审报告(`ARCHITECTURE_REVIEW.md`)和复核报告(`ARCHITECTURE_REVIEW_RECHECK.md`)中**均未被发现**,其中包含基于 `runtime-state` 线上真实运行数据证实的 **P0 级选优方向反转 Bug**。
|
|
|
|
|
|
|
+本节保留 **8 项审查记录**。其中 §2.1 已于 **2026-10-18** 更正为上游字段说明与自定义评分策略记录;其余条目保留既有审查结论。
|
|
|
|
|
|
|
|
---
|
|
---
|
|
|
|
|
|
|
|
-### 2.1 【P0 严重业务反转】【✅ 已修复】`config.server.json` 中 `yd_score` 极性反转,导致算法优先挑选劣化节点
|
|
|
|
|
|
|
+### 2.1 【文档已统一】上游 `yd_score` 字段与本项目自定义评分策略
|
|
|
|
|
|
|
|
- **位置**:
|
|
- **位置**:
|
|
|
- - `config.server.json:68-83`
|
|
|
|
|
- - `scripts/domain_updater.py:482-490`(`validate_config`)
|
|
|
|
|
- - `scripts/domain_updater.py:708-735`(`compute_weighted_average`)
|
|
|
|
|
- - `README.md:221-225`
|
|
|
|
|
-- **现象与证据**:
|
|
|
|
|
- `config.server.json` 的选优配置如下:
|
|
|
|
|
- ```json
|
|
|
|
|
- "selection": {
|
|
|
|
|
- "strategy": "weighted_average",
|
|
|
|
|
- "prefer_lower": true,
|
|
|
|
|
- "weights": {
|
|
|
|
|
- "yd_latency": 1.0,
|
|
|
|
|
- "yd_pkg_lost_rate": 10.0,
|
|
|
|
|
- "yd_score": 5.0
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
- ```
|
|
|
|
|
- 1. 在 `vps789.com` 接口中,`yd_latency`(移动延迟,越小越好)和 `yd_pkg_lost_rate`(移动丢包率,越小越好)是**越小越优(负向指标)**;而 `yd_score`(对应接口 `ydScore`)是**综合健康度得分,越大越优(正向指标,通常在 75~110 之间)**。
|
|
|
|
|
- 2. 核查远端 `vmess-domain-rotator/runtime-state` 分支的真实生产提交记录可铁证这一点:
|
|
|
|
|
- - 在提交 `4fd8a65`(`2026-06-05T11:32:56Z`)中:
|
|
|
|
|
- - `icook.tw`:`yd_latency = 49`, `yd_pkg_lost_rate = 1.9`, `yd_score = 92` -> 加权分 `(49*1 + 1.9*10 + 92*5)/16 = 33.0`(第 1名)
|
|
|
|
|
- - `www.fastsoso.cn`:`yd_latency = 36`, `yd_pkg_lost_rate = 0`, `yd_score = 109` -> 加权分 `(36*1 + 0*10 + 109*5)/16 = 36.3125`(因 `109` 分更高,在 `prefer_lower: true` 下反而排到第 3 名!)
|
|
|
|
|
- - 30 分钟后,在提交 `1a38eea`(`2026-06-05T12:08:25Z`)中:
|
|
|
|
|
- - `www.fastsoso.cn` 的移动延迟从 `36ms` **恶化翻倍**到 `74ms`,上游接口将其健康分 `yd_score` 从 `109` **下调惩罚**至 `78`。
|
|
|
|
|
- - 然而因为 `prefer_lower: true`,其加权分变为 `(74*1 + 0*10 + 78*5)/16 = 29.0`,**节点延迟暴涨、健康分暴跌后,反而被本脚本逆向提拔为全网第 1 名(最优域名)!**
|
|
|
|
|
- 3. 更严重的是,由于 `yd_score` 数值量级在 `80~109`,乘以权重 `5.0` 后高达 `400~545`,而 `yd_latency * 1.0` 仅 `35~75`,`yd_score` 实际上主导了 **85%~90% 的总评分权重**。
|
|
|
|
|
- 4. 同时,`domain_updater.py:486` 中硬编码了 `if not is_int_or_float(weight) or float(weight) <= 0: raise ValueError(...)`,**从代码层面禁止了负数权重**,且不支持为每个字段单独指定方向(`asc`/`desc`)。此外 `README.md:221-225` 的文档示例中只写了 `yd_latency` 和 `yd_pkg_lost_rate`,与实际 `config.server.json` 脱节。
|
|
|
|
|
-- **修复方案与落地状态**:
|
|
|
|
|
- - **✅ 已落地修复**:在 `config.server.json` 中移出 `weighted_fields`,转移至 `tie_breakers`;在 `scripts/domain_updater.py` 中支持字段级 `order: desc/asc` 与迟滞防抖 `switch_margin_pct: 5.0`。
|
|
|
|
|
- - **【2026-10 实测终局纠偏注记】**:经对线上真实多节点全量 Payload 数学逆向,确证上游公式严格为 $\text{Score} = \text{LatencyAvg} + \text{PkgLostRateAvg} \times 50$(综合成本惩罚分,越小越优)。原审查误将其推断为百分制健康分(误配 desc),现已在 `config.server.json`、`CLAUDE.md` 与测试中永久纠偏为 `order: "asc"`。
|
|
|
|
|
- - **测试验证**:`tests/test_domain_updater.py` 中用例 `test_p0_yd_score_polarity_fix_and_healthy_ranking` 100% 验证通过。
|
|
|
|
|
|
|
+ - [`configs/config.server.json`](../../configs/config.server.json):字段映射、加权字段与平局决胜配置
|
|
|
|
|
+ - [`scripts/rotator/scoring.py`](../../scripts/rotator/scoring.py):按配置计算 `score_value` 并排序
|
|
|
|
|
+- **字段含义**:`yd_score` 是上游 API 字段 `ydScore` 的映射;`dx_score`、`lt_score`、`avg_score` 同理。其含义、单位与计算公式由上游定义,本项目不重新计算这些字段,也不将其固定解释为健康分或成本分。不能仅凭字段名、少量样本或单个延迟指标的变化推断上游评分的方向。
|
|
|
|
|
+- **自定义策略**:本项目的 `score_value` 由 `scoring.weighted_fields`、权重及字段级 `prefer_lower` / `order` 计算;平局决胜由 `scoring.tie_breakers` 决定。是否使用某个上游评分字段以及如何排序,均属于配置选择。
|
|
|
|
|
+- **当前配置**:仅以 `yd_latency`(权重 1.0)和 `yd_pkg_lost_rate`(权重 10.0)加权,均配置为较小值优先。`yd_score` 不参与加权,仅在平局决胜中配置为 `order: "asc"`(数值较小者优先)。这描述的是当前策略,不是对所有数据源的固定规则。
|
|
|
|
|
+- **审查结论更正(2026-10-18)**:撤回此前根据样本推断上游评分公式、将旧加权配置定性为 P0 业务错误,以及据此推荐 `yd_score` 降序的结论。将该字段移出加权、改为升序平局决胜属于评分策略调整;更换数据源或调整策略时,应核对上游定义并同步更新配置、测试和文档。
|
|
|
|
|
+- **测试边界**:`test_p0_yd_score_polarity_fix_and_healthy_ranking` 与 `test_empirical_score_metric_asc_tie_breaking_and_consistency` 是现有测试名称,验证当前配置下的排序行为,不能据此证明上游实际计算公式。
|
|
|
|
|
|
|
|
---
|
|
---
|
|
|
|
|
|
|
@@ -210,7 +186,7 @@ flowchart LR
|
|
|
| 编号 | 分歧点 | `ARCHITECTURE_REVIEW.md`(初审) | `ARCHITECTURE_REVIEW_RECHECK.md`(复核) | 本次终审裁定与实证依据 |
|
|
| 编号 | 分歧点 | `ARCHITECTURE_REVIEW.md`(初审) | `ARCHITECTURE_REVIEW_RECHECK.md`(复核) | 本次终审裁定与实证依据 |
|
|
|
| :--- | :--- | :--- | :--- | :--- |
|
|
| :--- | :--- | :--- | :--- | :--- |
|
|
|
| **D-01** | **线上 `runtime-state` 是否已在 2026-07-27 停摆?** | 认为 `2026-07-27` 后无自动 commit,推断线上服务已停摆或数据源卡死(§2.2) | 指出远端 `vmess-domain-rotator/runtime-state` 在 `2026-10-06` 仍有提交,初审误看了本地过期分支(§2.1) | **复核正确,且深层原因已查明**:<br>远端 `vmess-domain-rotator/runtime-state` 最新提交为 `2026-10-11T00:34:14Z`(`ff09327`)。为什么 7 月底到 10 月初 commit 稀少?因为:① `run_update_and_commit.sh` 仅在文件内容变化时才 commit;② 7 月底以来 `baka.fun` 一直稳居第 1;③ 10 月初的几次 commit(`7672a03`、`56404a9`、`bfc3933`、`a7c6fdc`、`ff09327`)全都是因为上游偶发报错/空数据导致 `status` 在 `ok` 与 `error`/`fallback_last_good` 之间翻转触发的! |
|
|
| **D-01** | **线上 `runtime-state` 是否已在 2026-07-27 停摆?** | 认为 `2026-07-27` 后无自动 commit,推断线上服务已停摆或数据源卡死(§2.2) | 指出远端 `vmess-domain-rotator/runtime-state` 在 `2026-10-06` 仍有提交,初审误看了本地过期分支(§2.1) | **复核正确,且深层原因已查明**:<br>远端 `vmess-domain-rotator/runtime-state` 最新提交为 `2026-10-11T00:34:14Z`(`ff09327`)。为什么 7 月底到 10 月初 commit 稀少?因为:① `run_update_and_commit.sh` 仅在文件内容变化时才 commit;② 7 月底以来 `baka.fun` 一直稳居第 1;③ 10 月初的几次 commit(`7672a03`、`56404a9`、`bfc3933`、`a7c6fdc`、`ff09327`)全都是因为上游偶发报错/空数据导致 `status` 在 `ok` 与 `error`/`fallback_last_good` 之间翻转触发的! |
|
|
|
-| **D-02** | **581 次历史提交是否说明域名频繁抖动、急需 `switch_margin_pct` 防抖?** | 认为 581 次提交证明轮换过频,建议作为高优项引入 10% 切换阈值(§2.1) | 指出 commit 数不等于域名切换数(状态恢复也会产生 commit),防抖属锦上添花(§2.2) | **复核更准确**:<br>当前造成无效 commit 的首要元凶是 **`yd_score` 极性反转(2.1 节)** 与 **`ok <-> fallback` 状态翻转重复提交(2.3 节)**。应在修复这两项后,将 `switch_margin_pct` 作为可选策略增强项,而非最高优止血项。 |
|
|
|
|
|
|
|
+| **D-02** | **581 次历史提交是否说明域名频繁抖动、急需 `switch_margin_pct` 防抖?** | 认为 581 次提交证明轮换过频,建议作为高优项引入 10% 切换阈值(§2.1) | 指出 commit 数不等于域名切换数(状态恢复也会产生 commit),防抖属锦上添花(§2.2) | **复核更准确**:<br>`ok <-> fallback` 状态翻转会造成重复提交(§2.3);不能将提交数量直接等同于域名切换次数,也不能据此推断某个上游评分字段的方向。`switch_margin_pct` 是可选策略增强项,评分策略按 §2.1 的字段与配置约定维护。 |
|
|
|
| **D-03** | **`run_notify()` 子进程失败是否会抛异常触发 `last_good` 回退?** | 认为通知命令返回非 0 会抛异常中断主流程(§3.1.4) | 指出 `subprocess.run(..., check=False)` 不会因非 0 退出码抛异常,真正问题是污染 `stdout`、无 `timeout` 及未捕获 `OSError`(§2.3) | **复核完全正确**:<br>`domain_updater.py:999` 未传 `check=True`,退出码非 0 不会抛异常。真正的风险是:① 命令注入(`shell=True`);② 子进程输出直接打到 `stdout` 冲掉最后一行 JSON;③ 无 `timeout` 可永久挂起定时任务。 |
|
|
| **D-03** | **`run_notify()` 子进程失败是否会抛异常触发 `last_good` 回退?** | 认为通知命令返回非 0 会抛异常中断主流程(§3.1.4) | 指出 `subprocess.run(..., check=False)` 不会因非 0 退出码抛异常,真正问题是污染 `stdout`、无 `timeout` 及未捕获 `OSError`(§2.3) | **复核完全正确**:<br>`domain_updater.py:999` 未传 `check=True`,退出码非 0 不会抛异常。真正的风险是:① 命令注入(`shell=True`);② 子进程输出直接打到 `stdout` 冲掉最后一行 JSON;③ 无 `timeout` 可永久挂起定时任务。 |
|
|
|
| **D-04** | **`install_debian.sh` 写入 `.updater.env` 是否存在引号闭合注入?** | 认为用双引号包裹变量写入 `.updater.env` 会因特殊字符闭合引号导致语法错误(§3.3.2) | 指出 `write_env_file` 使用的是单引号包裹 + `sed "s/'/'\\\\''/g"` 转义,单引号路径安全,真正隐患在 `systemd` `EnvironmentFile=` 解析规则及换行符(§2.4) | **复核完全正确**:<br>`install_debian.sh:349-358` 已对单引号做了 POSIX 标准转义,Shell `source` 是安全的;但若变量含换行符或特殊控制字符,或结合 `run_update_and_commit.sh:97` 的进程参数暴露(2.4 节),仍需加固。 |
|
|
| **D-04** | **`install_debian.sh` 写入 `.updater.env` 是否存在引号闭合注入?** | 认为用双引号包裹变量写入 `.updater.env` 会因特殊字符闭合引号导致语法错误(§3.3.2) | 指出 `write_env_file` 使用的是单引号包裹 + `sed "s/'/'\\\\''/g"` 转义,单引号路径安全,真正隐患在 `systemd` `EnvironmentFile=` 解析规则及换行符(§2.4) | **复核完全正确**:<br>`install_debian.sh:349-358` 已对单引号做了 POSIX 标准转义,Shell `source` 是安全的;但若变量含换行符或特殊控制字符,或结合 `run_update_and_commit.sh:97` 的进程参数暴露(2.4 节),仍需加固。 |
|
|
|
| **D-05** | **是否应将 `cfst_local` 与 `api` 选优流水线强行统一为同一套打分模型?** | 建议把 `cfst_local` 也并入统一的 `filter -> score -> sort` 评分管线(§4.2) | 反对强行用 `api` 权重评分覆盖 `cfst` 原生测速排序,认为会破坏 `cfst` 自身语义(§2.5) | **折中裁定(各取所长)**:<br>不应强行用 `weighted_average` 重排 `cfst` 结果,但**必须统一两者前后的「准入校验层」与「产物契约层」**:即 `cfst_local` 同样需要经过合法 IP/域名校验、开启下载测速时过滤 `download_speed <= 0`(见 2.7 节)、去重,并输出与 `api` 模式结构一致的 `state.json`。 |
|
|
| **D-05** | **是否应将 `cfst_local` 与 `api` 选优流水线强行统一为同一套打分模型?** | 建议把 `cfst_local` 也并入统一的 `filter -> score -> sort` 评分管线(§4.2) | 反对强行用 `api` 权重评分覆盖 `cfst` 原生测速排序,认为会破坏 `cfst` 自身语义(§2.5) | **折中裁定(各取所长)**:<br>不应强行用 `weighted_average` 重排 `cfst` 结果,但**必须统一两者前后的「准入校验层」与「产物契约层」**:即 `cfst_local` 同样需要经过合法 IP/域名校验、开启下载测速时过滤 `download_speed <= 0`(见 2.7 节)、去重,并输出与 `api` 模式结构一致的 `state.json`。 |
|
|
@@ -294,7 +270,7 @@ flowchart TD
|
|
|
subgraph 统一核心管线
|
|
subgraph 统一核心管线
|
|
|
S1 --> V["1. 候选准入校验层<br/>• 合法域名/IP 格式校验<br/>• 过滤规则 & 黑名单 (含 last_good 复检)<br/>• CFST 零下载速度过滤 (download_speed > 0)"]
|
|
S1 --> V["1. 候选准入校验层<br/>• 合法域名/IP 格式校验<br/>• 过滤规则 & 黑名单 (含 last_good 复检)<br/>• CFST 零下载速度过滤 (download_speed > 0)"]
|
|
|
S2 --> V
|
|
S2 --> V
|
|
|
- V --> M["2. 极性感知评分排序层<br/>• 支持字段级 prefer_lower (修复 yd_score 反转)<br/>• 支持可选切换迟滞阈值 (switch_margin_pct)"]
|
|
|
|
|
|
|
+ V --> M["2. 配置驱动评分排序层<br/>• 按上游字段定义配置 prefer_lower / order<br/>• 支持可选切换迟滞阈值 (switch_margin_pct)"]
|
|
|
M --> F["3. 统一状态机与回退层<br/>• 状态枚举: ok | fallback_empty | fallback_error<br/>• 回退时保留上次有效 top_candidates"]
|
|
M --> F["3. 统一状态机与回退层<br/>• 状态枚举: ok | fallback_empty | fallback_error<br/>• 回退时保留上次有效 top_candidates"]
|
|
|
end
|
|
end
|
|
|
|
|
|
|
@@ -323,7 +299,7 @@ flowchart TD
|
|
|
|
|
|
|
|
| 阶段 | 优先级 | 进度状态 | 改动目标 | 涉及文件 | 验收与测试验证 |
|
|
| 阶段 | 优先级 | 进度状态 | 改动目标 | 涉及文件 | 验收与测试验证 |
|
|
|
| :--- | :--- | :--- | :--- | :--- | :--- |
|
|
| :--- | :--- | :--- | :--- | :--- | :--- |
|
|
|
-| **Phase 1:止血与高危安全修复** | **P0** | **✅ 已完成 (100%)** | 1. 修复 `config.server.json` 中 `yd_score` 极性反转问题,在 `domain_updater.py` 支持字段级正负向与平局决胜<br>2. 修复 `uninstall_debian.sh` 空 `SERVICE_NAME` 导致 `rm -rf /var/lib/` 漏洞<br>3. 修复 `build_router_package.sh` `trap` 提前注册导致误删 `TMP_DIR` 风险<br>4. 修复 `domain_updater.py` `run_notify()` 的 `shell=True` 命令注入、无超时及 `stdout` 污染问题 | `config.server.json`<br>`scripts/domain_updater.py`<br>`scripts/uninstall_debian.sh`<br>`build_router_package.sh` | 测试通过:`test_p0_yd_score_polarity_fix_and_healthy_ranking`、`test_p0_notify_stdout_isolation_and_timeout`、`test_p0_uninstall_debian_empty_service_name_deletes_var_lib`、`test_p0_build_router_package_trap_prematurely_deletes_tmp_dir` |
|
|
|
|
|
|
|
+| **Phase 1:止血与高危安全修复** | **P0** | **✅ 已完成 (100%)** | 1. 调整 `config.server.json` 自定义评分策略,并在 `domain_updater.py` 支持字段级方向与平局决胜(§2.1 已更正文档,该策略调整本身不列为 P0 缺陷)<br>2. 修复 `uninstall_debian.sh` 空 `SERVICE_NAME` 导致 `rm -rf /var/lib/` 漏洞<br>3. 修复 `build_router_package.sh` `trap` 提前注册导致误删 `TMP_DIR` 风险<br>4. 修复 `domain_updater.py` `run_notify()` 的 `shell=True` 命令注入、无超时及 `stdout` 污染问题 | `config.server.json`<br>`scripts/domain_updater.py`<br>`scripts/uninstall_debian.sh`<br>`build_router_package.sh` | 测试通过:`test_p0_yd_score_polarity_fix_and_healthy_ranking`、`test_p0_notify_stdout_isolation_and_timeout`、`test_p0_uninstall_debian_empty_service_name_deletes_var_lib`、`test_p0_build_router_package_trap_prematurely_deletes_tmp_dir` |
|
|
|
| **Phase 2:发布链路与消费端可靠性加固** | **P1** | **✅ 已完成 (100%)** | 1. 统一 `fallback_last_good` 与 `error_use_last_good` 处理,回退且值未变时执行回退静默,不向远端推送噪音 commit<br>2. 对 `last_good` 强制执行当前 `domain_filter` / 格式复检;彻底失败时补发 `run_notify`<br>3. `run_update_and_commit.sh` 区分 `ls-remote` 退出码 `2` 与 `128`,改用 `GIT_CONFIG_*` 环境变量安全传递 Git Token<br>4. `domain_updater.py` 与 `router_local_update.sh` 全面改为同目录临时文件 + `mv` 原子写入,并过滤 `download_speed == 0` 的测速失败节点<br>5. `substore/operator_template.js` 修复非字符串强转 `"[object Object]"` Bug,增加域名/IP 格式校验与本地数据源过期降级(`local_max_age_sec: 86400`) | `scripts/domain_updater.py`<br>`scripts/run_update_and_commit.sh`<br>`scripts/router_local_update.sh`<br>`substore/operator_template.js` | 测试通过:`test_p1_run_update_and_commit_passes_token_in_env_not_argv`、`test_p1_run_update_and_commit_ls_remote_distinguishes_error_code`、`test_p1_run_update_and_commit_fallback_silence`、`test_p1_extract_value_object_stringification_hazard_prevented`、`test_p1_extract_value_host_validation_enforced`、`test_p1_router_local_update_atomic_file_write`、`test_p1_last_good_bypasses_blacklist_prevented`、`test_p1_error_fallback_stdout_contains_domain_key` |
|
|
| **Phase 2:发布链路与消费端可靠性加固** | **P1** | **✅ 已完成 (100%)** | 1. 统一 `fallback_last_good` 与 `error_use_last_good` 处理,回退且值未变时执行回退静默,不向远端推送噪音 commit<br>2. 对 `last_good` 强制执行当前 `domain_filter` / 格式复检;彻底失败时补发 `run_notify`<br>3. `run_update_and_commit.sh` 区分 `ls-remote` 退出码 `2` 与 `128`,改用 `GIT_CONFIG_*` 环境变量安全传递 Git Token<br>4. `domain_updater.py` 与 `router_local_update.sh` 全面改为同目录临时文件 + `mv` 原子写入,并过滤 `download_speed == 0` 的测速失败节点<br>5. `substore/operator_template.js` 修复非字符串强转 `"[object Object]"` Bug,增加域名/IP 格式校验与本地数据源过期降级(`local_max_age_sec: 86400`) | `scripts/domain_updater.py`<br>`scripts/run_update_and_commit.sh`<br>`scripts/router_local_update.sh`<br>`substore/operator_template.js` | 测试通过:`test_p1_run_update_and_commit_passes_token_in_env_not_argv`、`test_p1_run_update_and_commit_ls_remote_distinguishes_error_code`、`test_p1_run_update_and_commit_fallback_silence`、`test_p1_extract_value_object_stringification_hazard_prevented`、`test_p1_extract_value_host_validation_enforced`、`test_p1_router_local_update_atomic_file_write`、`test_p1_last_good_bypasses_blacklist_prevented`、`test_p1_error_fallback_stdout_contains_domain_key` |
|
|
|
| **Phase 3:工程化、跨平台打包与自动化测试** | **P2** | **✅ 已完成 (100%)** | 1. 补齐 `validate_config()` 对 `api` / `parser` / `selection` 的完整结构校验<br>2. 修复 `router_local_http.sh` PID 复用误杀与 `router_local_update.sh` JSON 转义及零速 IP 过滤(确认 `-dt/-dn/-dd` 已就绪)<br>3. 修复 `build_router_package.sh` 在 macOS 下的 `.DS_Store` / AppleDouble 打包污染与跨架构 `httpd` 静态检测<br>4. 建立 `tests/` 自动化测试套件(覆盖评分极性、过滤、回退、Shell 安全与 Sub-Store 解析)并修正 `README.md`、`workflow.md`、`CLAUDE.md` 文档偏差 | `scripts/router_local_http.sh`<br>`scripts/router_local_update.sh`<br>`build_router_package.sh`<br>`README.md`<br>`workflow.md`<br>`CLAUDE.md`<br>`tests/` | 测试通过:`test_p2_validate_config_enforces_api_structure`、`test_p2_cfst_filters_zero_download_speed`、`test_p2_result_json_file_decouples_ipc_from_stdout`、`test_p2_router_local_update_filters_zero_speed`、`test_recheck_correction_router_cfst_params_already_supported`、`test_p2_operator_template_checks_staleness`;18 项测试 0.38s 全绿 |
|
|
| **Phase 3:工程化、跨平台打包与自动化测试** | **P2** | **✅ 已完成 (100%)** | 1. 补齐 `validate_config()` 对 `api` / `parser` / `selection` 的完整结构校验<br>2. 修复 `router_local_http.sh` PID 复用误杀与 `router_local_update.sh` JSON 转义及零速 IP 过滤(确认 `-dt/-dn/-dd` 已就绪)<br>3. 修复 `build_router_package.sh` 在 macOS 下的 `.DS_Store` / AppleDouble 打包污染与跨架构 `httpd` 静态检测<br>4. 建立 `tests/` 自动化测试套件(覆盖评分极性、过滤、回退、Shell 安全与 Sub-Store 解析)并修正 `README.md`、`workflow.md`、`CLAUDE.md` 文档偏差 | `scripts/router_local_http.sh`<br>`scripts/router_local_update.sh`<br>`build_router_package.sh`<br>`README.md`<br>`workflow.md`<br>`CLAUDE.md`<br>`tests/` | 测试通过:`test_p2_validate_config_enforces_api_structure`、`test_p2_cfst_filters_zero_download_speed`、`test_p2_result_json_file_decouples_ipc_from_stdout`、`test_p2_router_local_update_filters_zero_speed`、`test_recheck_correction_router_cfst_params_already_supported`、`test_p2_operator_template_checks_staleness`;18 项测试 0.38s 全绿 |
|
|
|
|
|
|
|
@@ -337,7 +313,7 @@ flowchart TD
|
|
|
|
|
|
|
|
| 编号 | 问题摘要 | 代码行号 | 初审报告 | 复核报告 | 本次终审实证与验证结论 | 严重度 | 修复状态 |
|
|
| 编号 | 问题摘要 | 代码行号 | 初审报告 | 复核报告 | 本次终审实证与验证结论 | 严重度 | 修复状态 |
|
|
|
| :--- | :--- | :--- | :--- | :--- | :--- | :--- | :--- |
|
|
| :--- | :--- | :--- | :--- | :--- | :--- | :--- | :--- |
|
|
|
-| **PY-01** | **`yd_score` 评分极性反转**:`config.server.json` 将正向健康分 `yd_score`(权重 5.0)与负向延迟/丢包混用且 `prefer_lower: true`;`validate_config` 禁止负权重,导致延迟翻倍、健康分暴跌的劣化节点反而排第 1 | `config.server.json:68-83`<br>`domain_updater.py:486, 708-735` | ❌ 漏检 | ❌ 漏检 | ✅ **线上实证确认**:`runtime-state` 提交 `4fd8a65 -> 1a38eea` 证明 `www.fastsoso.cn` 延迟从 36ms 恶化到 74ms、`yd_score` 从 109 跌至 78 后,加权分从 36.31 降至 29.0 逆向夺冠 | **P0** | **✅ 已修复**<br>移出加权改入平局决胜降序;测试 `test_p0_yd_score_polarity_fix_and_healthy_ranking` 通过 |
|
|
|
|
|
|
|
+| **PY-01** | **上游评分字段与自定义策略说明**:`yd_score` 映射上游 `ydScore`;是否参与加权及排序方向由配置决定 | `configs/config.server.json`<br>`scripts/rotator/scoring.py` | §6.5 提醒核对上游字段定义 | — | **文档更正(2026-10-18)**:撤回原 P0 定性和上游公式推断;当前配置仅以延迟、丢包加权,`yd_score` 用于升序平局决胜 | **配置策略 / 文档** | **✅ 已统一**<br>现有评分测试验证当前配置行为,不证明上游公式 |
|
|
|
| **PY-02** | **通知命令 Shell 注入(RCE)**:`run_notify()` 用 `.format()` 将外部 API 的 `{domain}` / `{error}` 拼入命令并传给 `subprocess.run(..., shell=True)` | `domain_updater.py:988-1000` | ✅ §3.1.4 | ✅ §3.1 | ✅ **源码确认**:API 返回恶意字符串或特殊字符即可执行任意命令或破坏 Shell 语法 | **P0** | **✅ 已修复**<br>改用环境变量传递变量;测试 `test_p0_notify_stdout_isolation_and_timeout` 通过 |
|
|
| **PY-02** | **通知命令 Shell 注入(RCE)**:`run_notify()` 用 `.format()` 将外部 API 的 `{domain}` / `{error}` 拼入命令并传给 `subprocess.run(..., shell=True)` | `domain_updater.py:988-1000` | ✅ §3.1.4 | ✅ §3.1 | ✅ **源码确认**:API 返回恶意字符串或特殊字符即可执行任意命令或破坏 Shell 语法 | **P0** | **✅ 已修复**<br>改用环境变量传递变量;测试 `test_p0_notify_stdout_isolation_and_timeout` 通过 |
|
|
|
| **PY-03** | **通知子进程无超时、污染 `stdout`、未捕获 `OSError`**:未设 `timeout`,未将 `stdout` 重定向到 `stderr` | `domain_updater.py:999-1002` | ⚠️ 误判非0退出码会抛异常 | ✅ §2.3 修正 | ✅ **源码确认**:`check=False` 不会因非 0 退出码抛异常,但通知输出到 `stdout` 会冲掉最后一行 JSON,导致外层脚本 `tail -n 1` 解析崩溃 | **P1** | **✅ 已修复**<br>`stdout=sys.stderr` 并设 `timeout=15`;测试 `test_p0_notify_stdout_isolation_and_timeout` 通过 |
|
|
| **PY-03** | **通知子进程无超时、污染 `stdout`、未捕获 `OSError`**:未设 `timeout`,未将 `stdout` 重定向到 `stderr` | `domain_updater.py:999-1002` | ⚠️ 误判非0退出码会抛异常 | ✅ §2.3 修正 | ✅ **源码确认**:`check=False` 不会因非 0 退出码抛异常,但通知输出到 `stdout` 会冲掉最后一行 JSON,导致外层脚本 `tail -n 1` 解析崩溃 | **P1** | **✅ 已修复**<br>`stdout=sys.stderr` 并设 `timeout=15`;测试 `test_p0_notify_stdout_isolation_and_timeout` 通过 |
|
|
|
| **PY-04** | **彻底失败(无 `last_good`)时漏调 `run_notify()`**:当抛异常且无 `last_good` 可回退时,直接 `return 1` 未发告警 | `domain_updater.py:1125-1149` | ❌ 漏检 | ❌ 漏检 | ✅ **源码确认**:最需要告警的「完全无可用输出」场景反而静默退出 | **P1** | **✅ 已修复**<br>异常且无 `last_good` 时补调 `run_notify(..., "", "error")` |
|
|
| **PY-04** | **彻底失败(无 `last_good`)时漏调 `run_notify()`**:当抛异常且无 `last_good` 可回退时,直接 `return 1` 未发告警 | `domain_updater.py:1125-1149` | ❌ 漏检 | ❌ 漏检 | ✅ **源码确认**:最需要告警的「完全无可用输出」场景反而静默退出 | **P1** | **✅ 已修复**<br>异常且无 `last_good` 时补调 `run_notify(..., "", "error")` |
|
|
@@ -348,7 +324,7 @@ flowchart TD
|
|
|
| **PY-09** | **文件落盘非原子写入**:`write_selected_outputs()` 直接 `write_text()` 覆写 `.txt`、`.json`、`state.json` | `domain_updater.py:1023-1069` | ✅ §3.1.5 | ✅ §3.1 | ✅ **源码确认**:写入中途被中断或并发读取会产生半截损坏 JSON | **P1** | **✅ 已修复**<br>`write_text_file` / `write_json_file` 采用 `.tmp.$$` + `os.replace` 原子写入 |
|
|
| **PY-09** | **文件落盘非原子写入**:`write_selected_outputs()` 直接 `write_text()` 覆写 `.txt`、`.json`、`state.json` | `domain_updater.py:1023-1069` | ✅ §3.1.5 | ✅ §3.1 | ✅ **源码确认**:写入中途被中断或并发读取会产生半截损坏 JSON | **P1** | **✅ 已修复**<br>`write_text_file` / `write_json_file` 采用 `.tmp.$$` + `os.replace` 原子写入 |
|
|
|
| **PY-10** | **`vps789` API 的 `createdTime` 实为建档时间而非测速时间** | `config.server.json:45, 63`<br>`domain_updater.py:669-678` | ⚠️ §2.2 误认为测速时间并建议开 `within_hours: 24` | ⚠️ §3.2 沿用初审假设 | ✅ **线上实证确认**:`baka.fun` 在 7 月和 10 月的 `created_raw` 恒为 `"2026-05-28 20:14:47"`,开启 `within_hours: 24` 会误杀所有老牌稳定域名 | **P1** | **✅ 已修复**<br>修正文档说明,默认禁用 `within_hours` 防误杀老牌稳定域名 |
|
|
| **PY-10** | **`vps789` API 的 `createdTime` 实为建档时间而非测速时间** | `config.server.json:45, 63`<br>`domain_updater.py:669-678` | ⚠️ §2.2 误认为测速时间并建议开 `within_hours: 24` | ⚠️ §3.2 沿用初审假设 | ✅ **线上实证确认**:`baka.fun` 在 7 月和 10 月的 `created_raw` 恒为 `"2026-05-28 20:14:47"`,开启 `within_hours: 24` 会误杀所有老牌稳定域名 | **P1** | **✅ 已修复**<br>修正文档说明,默认禁用 `within_hours` 防误杀老牌稳定域名 |
|
|
|
| **PY-11** | **`validate_config()` 漏检 `api`、`parser`、`domain_filter`、`selection` 顶层字典存在性** | `domain_updater.py:375-525` | ❌ 漏检 | ❌ 漏检 | ✅ **源码确认**:`source.type == "api"` 时未检查 `cfg.get("api")`,`--print-output-settings` 可通过但运行时抛 `KeyError` | **P2** | **✅ 已修复**<br>严格校验顶层字典与 `api.url`;测试 `test_p2_validate_config_enforces_api_structure` 通过 |
|
|
| **PY-11** | **`validate_config()` 漏检 `api`、`parser`、`domain_filter`、`selection` 顶层字典存在性** | `domain_updater.py:375-525` | ❌ 漏检 | ❌ 漏检 | ✅ **源码确认**:`source.type == "api"` 时未检查 `cfg.get("api")`,`--print-output-settings` 可通过但运行时抛 `KeyError` | **P2** | **✅ 已修复**<br>严格校验顶层字典与 `api.url`;测试 `test_p2_validate_config_enforces_api_structure` 通过 |
|
|
|
-| **PY-12** | **缺少切换迟滞(Hysteresis / `switch_margin_pct`)机制**:新旧第 1 名分差极小时也会触发轮换 | `domain_updater.py:905-923` | ✅ §2.1 | ✅ §2.2(定性为增强项) | ✅ **源码确认**:在修复 `yd_score` 极性后,增加可选 `switch_margin_pct`(如 5%~10%)可进一步抑制边界抖动 | **P2** | **✅ 已修复**<br>`choose_domain()` 实现迟滞防抖,配置 `switch_margin_pct: 5.0` |
|
|
|
|
|
|
|
+| **PY-12** | **缺少切换迟滞(Hysteresis / `switch_margin_pct`)机制**:新旧第 1 名分差极小时也会触发轮换 | `domain_updater.py:905-923` | ✅ §2.1 | ✅ §2.2(定性为增强项) | ✅ **源码确认**:在既定自定义评分策略下,增加可选 `switch_margin_pct`(如 5%~10%)可进一步抑制边界抖动 | **P2** | **✅ 已修复**<br>`choose_domain()` 实现迟滞防抖,配置 `switch_margin_pct: 5.0` |
|
|
|
|
|
|
|
|
---
|
|
---
|
|
|
|
|
|
|
@@ -399,105 +375,40 @@ flowchart TD
|
|
|
|
|
|
|
|
本节针对上述 P0/P1/P2 问题提供可直接落地的代码级修改参考。
|
|
本节针对上述 P0/P1/P2 问题提供可直接落地的代码级修改参考。
|
|
|
|
|
|
|
|
-### 8.1 修复 `config.server.json` 评分极性反转与 `domain_updater.py` 评分/迟滞模型(对应 PY-01, PY-12)
|
|
|
|
|
|
|
+### 8.1 配置驱动的评分与迟滞策略(对应 PY-01, PY-12)
|
|
|
|
|
+
|
|
|
|
|
+#### 1) 当前 `configs/config.server.json` 配置示例
|
|
|
|
|
+
|
|
|
|
|
+评分设置位于 `scoring`,`selection` 仅设置候选数量。下例与当前配置一致;使用其他数据源时,应先核对上游字段定义,再决定是否使用 `yd_score` 及其排序方向。
|
|
|
|
|
|
|
|
-#### 1) `config.server.json` 修正建议
|
|
|
|
|
-在 `selection` 中支持显式的指标极性定义(或者立即从负向加权中移除正向指标 `yd_score`,或通过 `field_directions` 指定 `yd_score` 为越大越优):
|
|
|
|
|
```json
|
|
```json
|
|
|
- "selection": {
|
|
|
|
|
|
|
+{
|
|
|
|
|
+ "scoring": {
|
|
|
|
|
+ "enabled": true,
|
|
|
"strategy": "weighted_average",
|
|
"strategy": "weighted_average",
|
|
|
|
|
+ "weighted_fields": [
|
|
|
|
|
+ {"field": "yd_latency", "weight": 1.0, "prefer_lower": true},
|
|
|
|
|
+ {"field": "yd_pkg_lost_rate", "weight": 10.0, "prefer_lower": true}
|
|
|
|
|
+ ],
|
|
|
"prefer_lower": true,
|
|
"prefer_lower": true,
|
|
|
|
|
+ "within_hours": 0,
|
|
|
"switch_margin_pct": 5.0,
|
|
"switch_margin_pct": 5.0,
|
|
|
- "weights": {
|
|
|
|
|
- "yd_latency": 1.0,
|
|
|
|
|
- "yd_pkg_lost_rate": 10.0
|
|
|
|
|
- },
|
|
|
|
|
- "field_prefer_lower": {
|
|
|
|
|
- "yd_latency": true,
|
|
|
|
|
- "yd_pkg_lost_rate": true,
|
|
|
|
|
- "yd_score": false
|
|
|
|
|
- },
|
|
|
|
|
"tie_breakers": [
|
|
"tie_breakers": [
|
|
|
- {
|
|
|
|
|
- "field": "yd_score",
|
|
|
|
|
- "order": "desc"
|
|
|
|
|
- },
|
|
|
|
|
- {
|
|
|
|
|
- "field": "yd_pkg_lost_rate",
|
|
|
|
|
- "order": "asc"
|
|
|
|
|
- },
|
|
|
|
|
- {
|
|
|
|
|
- "field": "yd_latency",
|
|
|
|
|
- "order": "asc"
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ {"field": "yd_score", "order": "asc"},
|
|
|
|
|
+ {"field": "avg_latency", "order": "asc"},
|
|
|
|
|
+ {"field": "created_at", "order": "desc"},
|
|
|
|
|
+ {"field": "domain", "order": "asc"}
|
|
|
]
|
|
]
|
|
|
- }
|
|
|
|
|
|
|
+ },
|
|
|
|
|
+ "selection": {"top_n": 3}
|
|
|
|
|
+}
|
|
|
```
|
|
```
|
|
|
|
|
|
|
|
-#### 2) `scripts/domain_updater.py` 极性感知评分与防抖迟滞(Hysteresis)实现
|
|
|
|
|
-```python
|
|
|
|
|
-def compute_weighted_average(
|
|
|
|
|
- record: Dict[str, Any],
|
|
|
|
|
- weights: Dict[str, Any],
|
|
|
|
|
- prefer_lower: bool = True,
|
|
|
|
|
- field_prefer_lower: Optional[Dict[str, bool]] = None,
|
|
|
|
|
-) -> Optional[float]:
|
|
|
|
|
- if not weights:
|
|
|
|
|
- return None
|
|
|
|
|
- field_dirs = field_prefer_lower or {}
|
|
|
|
|
- total_weight = 0.0
|
|
|
|
|
- weighted_sum = 0.0
|
|
|
|
|
- for field, raw_weight in weights.items():
|
|
|
|
|
- try:
|
|
|
|
|
- weight = float(raw_weight)
|
|
|
|
|
- except Exception:
|
|
|
|
|
- continue
|
|
|
|
|
- if weight <= 0:
|
|
|
|
|
- continue
|
|
|
|
|
- value = to_float(record.get(field))
|
|
|
|
|
- if value is None:
|
|
|
|
|
- return None
|
|
|
|
|
- # 若单字段极性与全局 prefer_lower 相反,则取相反数参与加权
|
|
|
|
|
- is_field_lower_better = bool(field_dirs.get(field, prefer_lower))
|
|
|
|
|
- effective_value = value if (is_field_lower_better == prefer_lower) else -value
|
|
|
|
|
- weighted_sum += effective_value * weight
|
|
|
|
|
- total_weight += weight
|
|
|
|
|
- if total_weight <= 0:
|
|
|
|
|
- return None
|
|
|
|
|
- return weighted_sum / total_weight
|
|
|
|
|
-
|
|
|
|
|
-
|
|
|
|
|
-def apply_hysteresis_if_needed(
|
|
|
|
|
- ranked: List[Dict[str, Any]],
|
|
|
|
|
- last_good: str,
|
|
|
|
|
- selection_cfg: Dict[str, Any],
|
|
|
|
|
-) -> Dict[str, Any]:
|
|
|
|
|
- """当新第 1 名相比仍在候选池中的 last_good 领先幅度不足 switch_margin_pct 时,保持 last_good。"""
|
|
|
|
|
- best = ranked[0]
|
|
|
|
|
- margin_pct = to_float(selection_cfg.get("switch_margin_pct")) or 0.0
|
|
|
|
|
- if margin_pct <= 0 or not last_good or best.get("domain") == last_good:
|
|
|
|
|
- return best
|
|
|
|
|
-
|
|
|
|
|
- prefer_lower = bool(selection_cfg.get("prefer_lower", True))
|
|
|
|
|
- best_score = to_float(best.get("score"))
|
|
|
|
|
- if best_score is None:
|
|
|
|
|
- return best
|
|
|
|
|
-
|
|
|
|
|
- for cand in ranked[1:]:
|
|
|
|
|
- if cand.get("domain") == last_good:
|
|
|
|
|
- last_score = to_float(cand.get("score"))
|
|
|
|
|
- if last_score is None or last_score == 0:
|
|
|
|
|
- break
|
|
|
|
|
- improvement_pct = (
|
|
|
|
|
- ((last_score - best_score) / abs(last_score)) * 100.0
|
|
|
|
|
- if prefer_lower
|
|
|
|
|
- else ((best_score - last_score) / abs(last_score)) * 100.0
|
|
|
|
|
- )
|
|
|
|
|
- if improvement_pct < margin_pct:
|
|
|
|
|
- return cand
|
|
|
|
|
- break
|
|
|
|
|
- return best
|
|
|
|
|
-```
|
|
|
|
|
|
|
+#### 2) 当前实现入口与约定
|
|
|
|
|
+
|
|
|
|
|
+- [`parse_scored_records()` / `rank_scored_records()`](../../scripts/rotator/scoring.py) 按配置计算本项目的 `score_value`,再按全局方向与 `tie_breakers` 排序。字段级方向与全局方向不一致时,先调整该字段值的符号再加权;上游 `yd_score` 的值按映射读取。
|
|
|
|
|
+- [`choose_domain()`](../../scripts/rotator/scoring.py) 使用传入的 `switch_margin_pct` 执行可选迟滞策略;[`scripts/rotator/main.py`](../../scripts/rotator/main.py) 从 `scoring` 读取该设置并传入。
|
|
|
|
|
+- 加权字段的取舍、权重、方向与迟滞阈值共同构成自定义策略。当前 `yd_score` 升序平局决胜不表示本项目定义了上游的评分公式,相关测试也仅验证配置行为。
|
|
|
|
|
|
|
|
---
|
|
---
|
|
|
|
|
|
|
@@ -757,7 +668,7 @@ function extractValue(rawText, format, jsonKeys, maxAgeSec) {
|
|
|
|
|
|
|
|
| 测试文件 | 覆盖范围与断言目标 | 用例数 | 执行耗时 | 测试状态 |
|
|
| 测试文件 | 覆盖范围与断言目标 | 用例数 | 执行耗时 | 测试状态 |
|
|
|
| :--- | :--- | :--- | :--- | :--- |
|
|
| :--- | :--- | :--- | :--- | :--- |
|
|
|
-| [`tests/test_domain_updater.py`](./tests/test_domain_updater.py) | • P0 极性反转真实数据排序回归与平局决胜<br>• run_notify 标准输出隔离与超时保护<br>• 异常回退包含 domain/ip 键<br>• 黑名单 last_good 禁止回退复活<br>• 本地 cfst 过滤 0.00 下载速度<br>• `--result-json-file` IPC 解耦<br>• validate_config 严格顶层结构校验<br>• **[方案B] rotator 子包解耦与 Facade 契约完整性** | 8 项 | ~0.15s | **✅ 全部通过 (PASS)** |
|
|
|
|
|
|
|
+| [`tests/test_domain_updater.py`](./tests/test_domain_updater.py) | • 当前自定义加权策略与 `yd_score` 升序平局决胜(不验证上游公式)<br>• run_notify 标准输出隔离与超时保护<br>• 异常回退包含 domain/ip 键<br>• 黑名单 last_good 禁止回退复活<br>• 本地 cfst 过滤 0.00 下载速度<br>• `--result-json-file` IPC 解耦<br>• validate_config 严格顶层结构校验<br>• **[方案B] rotator 子包解耦与 Facade 契约完整性** | 8 项 | ~0.15s | **✅ 全部通过 (PASS)** |
|
|
|
| [`tests/test_shell_lifecycle.py`](./tests/test_shell_lifecycle.py) | • `uninstall_debian.sh` 空服务名安全校验(严禁 `rm -rf /var/lib/`)<br>• `build_router_package.sh` 延迟注册 trap 防止误删工作目录<br>• `run_update_and_commit.sh` 采用 `GIT_CONFIG_*` 环境变量传递 Token(argv 零暴露)<br>• 严格区分 `git ls-remote` 退出码 `2` vs `128`<br>• 回退静默跳过噪音 Git commit<br>• **[方案B] 模式A Git 历史滚动修剪(50 commit 滚动保留)端到端验证**<br>• **[结构优化] scripts 分层物理结构与 configs/ 目录结构集中收敛完整性断言** | 7 项 | ~0.75s | **✅ 全部通过 (PASS)** |
|
|
| [`tests/test_shell_lifecycle.py`](./tests/test_shell_lifecycle.py) | • `uninstall_debian.sh` 空服务名安全校验(严禁 `rm -rf /var/lib/`)<br>• `build_router_package.sh` 延迟注册 trap 防止误删工作目录<br>• `run_update_and_commit.sh` 采用 `GIT_CONFIG_*` 环境变量传递 Token(argv 零暴露)<br>• 严格区分 `git ls-remote` 退出码 `2` vs `128`<br>• 回退静默跳过噪音 Git commit<br>• **[方案B] 模式A Git 历史滚动修剪(50 commit 滚动保留)端到端验证**<br>• **[结构优化] scripts 分层物理结构与 configs/ 目录结构集中收敛完整性断言** | 7 项 | ~0.75s | **✅ 全部通过 (PASS)** |
|
|
|
| [`tests/test_router_scripts.py`](./tests/test_router_scripts.py) | • `router_local_update.sh` `.tmp.$$` + `mv -f` 原子替换写入<br>• 测速结果优先过滤 `download_speed > 0`<br>• 纠偏初审报告关于 `-dt/-dn/-dd` 参数支持情况<br>• **[方案B] `build_router_package.sh --sync-from-config` SSOT 单源参数自动投影验证** | 4 项 | ~0.50s | **✅ 全部通过 (PASS)** |
|
|
| [`tests/test_router_scripts.py`](./tests/test_router_scripts.py) | • `router_local_update.sh` `.tmp.$$` + `mv -f` 原子替换写入<br>• 测速结果优先过滤 `download_speed > 0`<br>• 纠偏初审报告关于 `-dt/-dn/-dd` 参数支持情况<br>• **[方案B] `build_router_package.sh --sync-from-config` SSOT 单源参数自动投影验证** | 4 项 | ~0.50s | **✅ 全部通过 (PASS)** |
|
|
|
| [`tests/test_substore_operator.py`](./tests/test_substore_operator.py) | • `extractValue()` 严格类型守卫(拒绝非字符串对象强转为 `[object Object]`)<br>• 域名 (RFC 1123) 与 IPv4/IPv6 正则校验(拒绝 HTML 报错与非法字符)<br>• `LOCAL_MAX_AGE_SEC` (24h) 本地路由器源超时自动降级 | 3 项 | ~0.06s | **✅ 全部通过 (PASS)** |
|
|
| [`tests/test_substore_operator.py`](./tests/test_substore_operator.py) | • `extractValue()` 严格类型守卫(拒绝非字符串对象强转为 `[object Object]`)<br>• 域名 (RFC 1123) 与 IPv4/IPv6 正则校验(拒绝 HTML 报错与非法字符)<br>• `LOCAL_MAX_AGE_SEC` (24h) 本地路由器源超时自动降级 | 3 项 | ~0.06s | **✅ 全部通过 (PASS)** |
|
|
@@ -797,7 +708,7 @@ python3 -m unittest discover tests -v
|
|
|
- `config.py`:配置严格校验、路径解析与输出映射
|
|
- `config.py`:配置严格校验、路径解析与输出映射
|
|
|
- `filter.py`:域名与 IP 验证、正则清洗与黑名单排除
|
|
- `filter.py`:域名与 IP 验证、正则清洗与黑名单排除
|
|
|
- `sources.py`:远程 API 抓取与本地 CFST 结果解析
|
|
- `sources.py`:远程 API 抓取与本地 CFST 结果解析
|
|
|
- - `scoring.py`:健康评分、字典序排序、迟滞防抖与候选列表构造
|
|
|
|
|
|
|
+ - `scoring.py`:自定义加权评分、字典序排序、迟滞防抖与候选列表构造
|
|
|
- `notify.py`:通知命令执行与超时防护
|
|
- `notify.py`:通知命令执行与超时防护
|
|
|
- `main.py`:主轮换工作流编排与 CLI 参数解析
|
|
- `main.py`:主轮换工作流编排与 CLI 参数解析
|
|
|
- [`scripts/domain_updater.py`](./scripts/domain_updater.py) 重构为精简的外观门面(Facade),全面重新导出所有符号,保持 100% 向后兼容,现存 systemd、cron 及自动化测试零修改直接运行。
|
|
- [`scripts/domain_updater.py`](./scripts/domain_updater.py) 重构为精简的外观门面(Facade),全面重新导出所有符号,保持 100% 向后兼容,现存 systemd、cron 及自动化测试零修改直接运行。
|