Blink:把规则仓库做成产品的工程化实践
Blink:把规则仓库做成产品的工程化实践
这篇讲实现:同一思想(规则类型即 DNS 语义、domain-first / IP-last 不变式)如何落地。Blink (github.com/Bluetrae/Blink) 是那套思想的完整落地:29 个 App、7 个客户端、 203 个生成产物、每日自动构建、9 道机器门禁。本文按架构层级拆解,重点看 "可验证性"和"变化可控"这两个词是怎么被写进代码的。
0. 一句话概括
Blink 做了一件事:把"个人维护的规则集"从手工劳作变成一条自动流水线, 同时让"正确性"不再是常识,而是机器断言。
它分两层:
- 规则层(自动维护):审计上游 → canonical 规则模型 → 渲染七种客户端格式,每日更新;
- 配置层(人工维护):一份意图 + 模板 → 生成七端完整配置文件,人工确认后提交。
生成目录只由构建器写入,绝不手工修改;仓库不含订阅 URL、token、密码或证书。
1. 核心抽象:canonical 规则模型
规则层的第一性原理是:客户端之间共享的是语义,不是文本。
# engine/scripts/build.py
# Canonical rule kinds. The names happen to match Surge vocabulary, but the
# model is client-neutral: renderers own every client-specific serialization.
ALLOWED_RULE_TYPES = (
"DOMAIN",
"DOMAIN-SUFFIX",
"DOMAIN-KEYWORD",
"USER-AGENT",
"PROCESS-NAME",
"IP-CIDR",
"IP-CIDR6",
)七个白名单类型是唯一事实源。渲染层负责所有客户端差异:
| 渲染器 | 用途 |
|---|---|
render_classical |
Surge / Loon / Shadowrocket / Stash 四端,逐字节相同 |
render_classical_clash |
同 classical,去掉 USER-AGENT |
render_egern_yaml |
Egern 自有 YAML schema |
render_quantumultx |
Quantumult X filter 行 |
这套"数据与序列化分离"的价值在于:任何客户端格式变化, 只改一个 renderer,而不是 29 个 App × 7 个客户端的手工副本。
2. 语义视图:规则类型即 DNS 语义
每个 App 默认输出三类视图,这是上一篇的 domain-first / IP-last 不变式的实现:
<App>-domainset.conf— 纯域名段(DOMAIN/DOMAIN-SUFFIX),不触发本地 DNS;<App>-nonip.conf— 非 IP 段(含 keyword / UA / process),不触发 DNS;<App>-ip.conf— IP 段(IP-CIDR/IP-CIDR6),触发 DNS,必须置后。
视图的一致性有专门门禁守护:
python engine/scripts/validate_views.py --root .
# 断言:IP 不进 nonip、domain 不进 ip、纯域名 App 不产生空 ip 视图、
# 七端齐全、文件头统计(含显式丢弃)正确还有一个值得抄的细节:README 用大段 [!WARNING] 说明"文件后缀就是引用方式的答案"
(-domainset.conf 配 DOMAIN-SET、-nonip.conf/-ip.conf 配 RULE-SET、IP 段加
no-resolve)——写反后 Surge 不会报错,流量会静默落到 FINAL。
好的文档不只写"怎么做",更写"做错会怎样、如何自查"。
3. 九道机器门禁:把正确性变成断言
engine/docs/MACHINE_GATES.md 是整套工程承诺的机器化清单:
| 门禁 | 命令 | 断言 |
|---|---|---|
| 单元与回归 | python -m unittest discover -s engine/tests |
Parser / renderer / Profile / 变化阈值 / 故障注入 |
| 七端等价性 | parity_check.py --root . --strict |
四端逐字节相同;Clash 去 UA;Egern/QX 去 PROCESS-NAME |
| 产物健康度 | health_check.py --root . |
非空、合法、无重复、确定性排序、头统计正确 |
| 语义多视图一致 | validate_views.py --root . |
视图类型合法、与 canonical 拆分一致、七端齐全 |
| 产物溯源 | verify_manifest.py --root . |
29 App + 七端 + supplement + 构建器 SHA256 完整一致 |
| Profile 完整性 | verify_profiles.py --root . |
七端配置可由 intent/templates 逐字节重建 |
| 跨 App overlap | overlap_check.py --root . |
相对人工基线不得出现新重叠 |
| 敏感模式 | secret_scan.py --root . |
PAT / 私钥 / 代理 URI / 订阅 URL / 本地绝对路径零容忍 |
| 实时重建 drift | build.py --verify-only --strict-diff |
重抓上游并逐字节比对 203 个产物 |
设计上有几个聪明的取舍:
- manifest 不含时间戳、commit、本地路径——只存内容指纹与统计。 同一输入必须产生逐字节相同的 manifest,"它昨天是对的"才可被验证。
- 普通 push/PR 只跑离线门禁;实时重建 drift 留给发布前/排障, 避免第三方瞬时网络变成所有 PR 的随机失败。
- 不缓存上游文本:发布构建仍实时读取,防止缓存掩盖上游真实变化。
4. 供应链变化门禁:变化必须被看见
规则仓库最大的风险不是"写错",而是上游悄悄变了。Blink 的做法:
- 写入前,把实时编译的 canonical 规则与已提交产物做集合比较;
- 默认阻断条件(任一项成立):
- 新增/删除规则数超过 20;
- 语义变化比例超过 20%;
- 出现此前不存在的新规则类型;
- 失败报告给出
+N/-N、变化比例、新类型和最多五条增删样例; - 人工审阅后显式放行:
python engine/scripts/build.py --write --accept-large-change注意 --accept-large-change 不关闭解析、renderer、parity、health、checksum
校验——它只跳过已经人工审阅的变化量阈值。"确认过的变化"和"没看过的变化"是两种东西。
写入保持原子性:所有 App 成功才更新产物。上游 404 / 超时 = 构建失败, 保留旧产物并暂停更新,而不是用缓存静默生成。
5. 配置层:意图驱动 + 可复现
配置层没有手写七份配置,而是:
engine/sources/profile/intent.yaml ← 语义意图(策略组/规则引用/能力声明)
↓ 模板渲染
Profiles/{Surge,Loon,Stash,Clash,Egern,Shadowrocket,QuantumultX}/...
- 单一订阅池组织,占位符内置——替换一条订阅 URL 即可复用;
- 能力映射只允许
FULL/ADAPTED(注释)/UNSUPPORTED(注释)三态, 禁止静默删除或伪造; verify_profiles.py断言七端配置可以从 intent + templates 逐字节重建。
配置是"意图",所以它不随规则每日更新:规则层自动,配置层人工确认, 自动化边界清晰到不会互相越权。
6. 分发与门户:门户也是数据
engine/portal/ 是一个 React + Vite 的门户,但它不是手写的内容站:
- 页面数据从生成产物 + 源清单派生(
gen_portal_stats.py→stats.json); - 无时间戳、随产物变化才变化——门户与规则集共享同一事实源,不存在"文案过期"。
接入体验上也下了功夫:每个 App 给一个"完整 .list",想要语义分段再给三视图; README 顶部就是七客户端的 quickstart 表格和官方引用片段。 降低接入成本本身也是产品的一部分。
7. 合规意识:个人项目也可以有边界
DISCLAIMER.md:仅供学习研究,使用前阅读;THIRD_PARTY_NOTICES.md:上游来源与许可清单合规;- 明确"禁止转载或发布至国内平台";
- 仓库扫描零敏感信息(这本身就是 secret_scan 门禁的产物)。
一个代理规则仓库,上游归属、许可、使用边界写得清清楚楚——这比多数开源项目 都"成熟"。合规不是大厂的专利,是工程素养。
8. 可以带走的五个工程思维
- 语义单一事实源,序列化只是下游。 七种客户端的差异被隔离在 renderer 层, 任何一个都不值得成为"再改一份"的借口。
- "正确"必须能被机器复述。 九道门禁全是可执行命令;文档描述的是承诺, 命令执行的是承诺。
- 显式大于静默。 降级要计数、不可能的状态要失败、写错引用方式要大声警告。
- 变化要审计,而不是感觉。 阈值阻断 + 人工显式放行;已知重叠固化基线, 新重叠自动阻断——"未知的冗余和已知的冗余是两种风险"。
- 边界清晰的管线。 规则层自动、配置层人工、门户派生。每一层只有一种职责, 自动化不会偷偷越权。
最后照例感谢 SukkaW——它的博客与 SukkaW/Surge 构建管线是 Blink 设计的思想源头。规则驱动的数据工程,值得每一份个人仓库学习。