1.48.0
- 【新增·Trojan明文请求头】读了参考实现与Xray之后确认了Trojan请求头的布局：56个ASCII十六进制
  字符（SHA224(密码)）+ CRLF + 命令(0x01 CONNECT / 0x03 UDP ASSOCIATE) + ATYP + 地址 + 端口 +
  CRLF。**裸Trojan**（不套TLS）时这个头是明文，可以精确匹配，而且能读出目标域名 —— 在此之前
  引擎只有"TLS套娃长度启发式"那一条判据，按定义看不到这种情形。判定结果带 method=bare-header，
  让规则作者能分清"精确判据"与"训练出来的启发式"（后者 1% 误报 / 10% 漏报）。
  如实说明：套在TLS里的Trojan（绝大多数部署）这个头是密文，仍然只能靠长度启发式。
- 【修复·该分析器里一段死代码】trojanStream 的 first 字段声明了、判断了、但从没被赋值 ——
  于是那句"不是TLS就放弃"从未执行，长度启发式被应用到**所有**流量上，而它是在"Trojan的
  TLS-in-TLS形态"上训练的。现在它真的执行了。
- 【修复·边界越界】流式解析在"恰好56字节"时读了缓冲区外的第57个字节。这条路径在引擎里
  **没有 recover()**，所以那不是漏报，是收包线程崩溃。（是我自己写的第一版，被边界测试当场抓住。）
- 【修复·域名里的控制字符】请求头里的域名会被打印到面板的目标列与日志里，含换行符时客户端
  可以伪造日志行。现在非可打印ASCII一律拒绝。

1.47.0
- 【修复·端口线索的误报】端口线索单独看**无法区分两种完全相反的流量**：自建服务开在非标端口
  （运营商不给80/443时很常见，完全正常）与借用别人的域名（可疑）。用户直接指出了这一点。
  现在把它与「SNI一致性」合起来判，分三级：
    · 域名确实解析到此处 → 只标「非标端口」，**不打警示** —— 这是该域名自己的部署；
    · 域名解析到别处 → 「端口异常且域名不符」（高风险色），这是借域名的典型形态；
    · 还没核验（校验未开或解析未回）→ 标「未核验」，并提示开启SNI一致性校验才能真正区分。
  分开显示的关键在于：DNS是唯一能区分两者的证据。借域名的人借到的是**别人的**域名，
  那个域名解析到的是真站、不是他这台机器；而自建的那些解析得到本机。
  反向纪律：unknown 绝不能被当成 match —— 那是把「还没查」读成「已确认正常」，
  误报是没了，判据也一并没了。有测试专门钉这一条。
- 【说明】端口线索与SNI一致性的适用范围判据已合并为同一份（内网目标、IP字面量、单标签主机名、
  .local 一律不判）。两份手写清单必然漂移，而漂移的那份总是更松的那份。

1.46.0
- 【新增】端口线索：判定"借来的域名出现在非标准端口上"。Reality一类隧道要借用某个真站的
  名字当SNI，而那个真站只在标准Web端口（443/8443等）提供HTTPS —— 所以"一个域名出现在
  高位端口上"本身就是线索。判据只用这条连接自己的事实，不查DNS、不发出任何外发流量、
  不需要开关，因此**默认开启**。线索直接标在流量记录的SNI列（⚠端口异常），并写进记录详情。
  它和「SNI与实际目标不一致」是两条独立的线索，典型形态（借域名+高位端口）会同时触发。
  边界如实说明：自建服务开在自定义端口上（公网NAS、企业站点）也会被标出来，需人工判断；
  标准端口表按Cloudflare公开的HTTP/HTTPS端口（80/8080/8880/2052/2082/2086/2095 与
  443/2053/2083/2087/2096/8443），表外即线索，不猜中间地带。
  为什么不做端口探测：去连真站的这个端口不通，推不出"不是真站"（真站可能有防火墙、限流、
  地域封锁），而探测本身还是可见的外发连接；而端口本身已经足够，所以这条判据不需要探测。
- 【界面】去掉侧栏底部的「拦截已开启 / 关闭 + 版本号」：顶栏已经有拦截状态芯片、版本号也在
  产品名下面，同一屏出现两次只是噪声。侧栏现在只剩菜单。
- 【修复】实时记录的类型定义补全 sni / sni_match / sni_resolved / port_clue —— 后端一直在发
  这几个字段，类型里没有，于是它们只能在表格里当any用。缺字段的类型不报错，只会让人以为
  后端没发。

1.45.0
- 【修复·识别出来了却显示成未知协议】面板的协议显示表只覆盖了 7 个分析器名字，而引擎注册了
  二十多个 —— VLESS、Shadowsocks、VMess、WireGuard、P2P 全都落到"未知协议"。
  "识别出来但界面上看不见"和"没识别出来"对操作员是同一件事：面板上的协议名就是他判断
  这个插件到底在做什么的全部依据。现在这张表按引擎的分析器清单逐项覆盖，并加了遍历式测试，
  以后新增分析器忘了配显示名会直接红。
- 【修复·普通 HTTPS 被显示成全加密流量】fet 是兜底分析器，它在"没识别出协议"时也会留下
  属性、并且明确说 yes=false（它自己的排除条件就是"这串字节看起来像 TLS/HTTP"）。
  原来只按"属性存在"判定，于是每条正常 HTTPS 都被记成加密代理。现在 fet 必须 yes=true
  才算命中，且具体协议永远优先于它。
- 【新增·P2P（BitTorrent）可管控】引擎新增两个分析器：p2p（TCP：BEP 3 协议握手 +
  HTTP tracker announce，属性带 info_hash，握手与 tracker 两条路径给出同一个值可互相对照）
  与 p2pudp（UDP：BEP 5 DHT + BEP 15 UDP tracker）。面板的 P2P 类目现在真的生成规则。
  如实说明边界：uTP（BEP 29）不检测 —— 它唯一的明文信号是两个字节的前缀，随机载荷也会撞上，
  宁可漏报也不给一个会误报的结论。
- 【新增·TLS套娃类目】外层 TLS 里再套一层 TLS 握手，这是 XTLS Vision 一类隧道**唯一**的
  可见信号。引擎里这个分析器一直存在，但没有任何规则引用它 —— 没有被引用就不会被实例化，
  也就是说它此前是一段谁都碰不到的代码。现在面板可以打开它（默认关、实验性、带
  experimental 标记，建议先"仅透视"观察误报再考虑拦截）。
- 【协议名不再写"家族"】VMess / Shadowsocks 直接叫名字。原来的"VMess家族"是内部措辞
  （判据是首包长度落在某个区间），操作员看不出这个开关到底拦的是什么。
- 【界面·流量记录详情】检测内容去掉底色与边框，弹窗改两列布局、检测内容限高可滚动。
  原来的弹窗在路由器屏幕上放不下（用户原话）。
- 【说明·为什么装在 TLS 里的 VLESS 认不出来】VLESS 的请求头是明文 —— 但那是在**不带 TLS**
  的情况下（面板上的「加密隧道」就对应这一种）。装在 TLS 里（含 Reality / Vision）时请求头
  是密文，任何 DPI 都读不出来，这不是实现不足而是协议本身的边界。那种情况可用的信号是
  「TLS套娃」（XTLS Vision 的套娃特征）与"SNI 与实际连接目标不一致"（Reality 借用他人
  证书会暴露这一点）。两者都只能算线索，需要人工复核。

1.44.0
- 【新增】系统设置里的「拦截效率」区块（原「大屏」需求）。接口 /api/v1/metrics 一直
  都在，这一版把它呈现出来：裁决/拦截速率、拦截率、已裁决流数、内存/协程/文件描述符/
  CPU累计/日志丢弃，加上协议识别分布与规则命中排行。
- 【不做成独立导航项】导航的项与顺序是产品明确固定的九项，为一块看板多开一项会改动
  那个约定；这块内容讲的是引擎自身的效率，与系统设置里的引擎状态放一起最自然。
- 【显示纪律·没采样到就说采样中】速率需要两个采样点才算得出。把"还没算出来"显示成 0，
  看板上会读成"没有流量"—— 那是假数据，不是空数据。
- 【显示纪律·口径跟着数字走】拦截率的分母是已裁决流数（不是总包数：总包数含
  SYN/FIN/重传，用它会重复计数同一连接）。这个口径由服务端随响应给出，界面照实显示，
  不自编一个更好看的解释。
- 【显示纪律·空看板要说清是"没命中"还是"没功能"】规则命中排行只统计**实际命中过**的
  规则，空的时候必须说明这一点，否则会让人以为功能坏了。
- 【显示纪律·CPU 是累计量不是占用率】占用率需要在一段时间内取两次差，这里如实标成
  "累计滴答"，不把它伪装成一个百分比。

1.43.0
- 【新增】规则导入导出。跟「批量录入」的分工：粘贴录入解决"从别处拿到一份清单"，
  导入导出解决"把现在这套原样搬走" —— 粘贴框只带值，带不了每条规则的动作、识别层、
  时段、备注、启用状态。
- 【约束·只动文件里出现的类别】一份只导出域名的文件，在"替换"模式下不会动 IP 与关键字
  规则。判据是字段在 JSON 里**存在与否**，不是它是否为空 —— 只导出域名时不会带上
  ip_rules 字段，导入端也就不会碰它。
- 【约束·替换拒绝空清单】用空清单清空一个类别必须显式允许。空清单在语义上确实是
  "全都不要了"，但被截断的文件也长成这样，这一步默认拒绝、宁可多问一次。
- 【约束·不信任文件内容】所有值都过一遍粘贴录入用的同一套解析器，无效值带原因逐条列出
  并计入 invalid。两套校验必然漂移，而漂移的那一套总是更松的那一套。
- 【约束·ID 重新生成】ID 是本地标识不是数据：照搬会撞号，而撞号的表现是规则互相覆盖
  且没有任何报错。
- 【约束·先全部校验再一次性写盘】多类别文件里有一个类别不合格就整份不落地 ——
  半套应用比不应用更难收拾（人会以为导入完成了）。
- 【约束·格式版本显式检查】来自更新版本的文件被拒，而不是尽力猜测：猜错的结果是静默
  丢掉不认识的字段。
- 【测试】核心判据是**往返无损**：导出 → 导入到空模型 → 再导出，两份内容必须一致
  （除本地 ID 与导出时刻）。漏一个字段不会报错，只会让搬过去的规则悄悄变样；
  逐条断言只能覆盖我想到的字段，往返比对覆盖整个模型。
- 【测试】另有一条钉住"只动文件里出现的类别"、一条钉住"空清单必须显式允许"、
  一条钉住"外来文件与更新版本被拒且不改动任何规则"。
- 【顺带修】导入被拒绝时成功标志必须为假。前端按 success 判断成败，若拒绝时仍为真，
  界面会显示"导入成功"而规则一条都没进来 —— 这是测试抓到的。

1.42.0
- 【新增】证书异常类目：报告"一张浏览器会拒绝的证书"（自签名，或证书与客户端请求的域名
  不符）。针对的是 TLS 外套的代理 —— VLESS+TLS、VMess+WS+TLS、Trojan 在客户端关闭证书
  校验（allowInsecure）时，服务端拿的通常正是这种证书：内层协议读不出来，但外层 TLS 上
  这点是可判的。引擎侧新增 certmeta 分析器（dpi-engine v0.9.0，已发版）。
- 【边界·实测而不是假设】**TLS 1.3 的证书是密文，什么都看不到**。同一张自签名证书，
  只换协议版本：TLS 1.2 的原始字节里能找到证书 CN，TLS 1.3 找不到（SNI 仍然可见）。
  所以这条判据只对 TLS ≤1.2 有效，是**补充**线索，不替代行为学检测；证书真实有效且域名
  真实的自建代理，在这里与正常 HTTPS 无法区分 —— 这是设计使然，不是待修的缺口。
- 【纪律】只报异常，正常证书一律不报。这样"certmeta 非空"只有一个含义。规则是照这些
  属性写的：一个属性如果有时表示"看过了没问题"、有时表示"看过了有问题"，那么另一条规则
  想排除它就必然把两种情况一起排除掉。
- 【定位】是线索，不是判决。自签名证书同样出现在路由器管理页、NAS、iLO 这些自建服务上，
  它们都不是代理。它报的是"这不可能是该域名的正常 HTTPS"，含义由人判断。
- 【默认】该类目默认关闭（不发规则）。自签名证书在内网很常见，默认拦会把正常服务切断；
  需要拦截时由操作员自己打开（可先只透视）。
- 【测试】夹具是**真机抓包**而不是手写字节：证书消息常常跨 TLS 记录，手搓的输入很容易
  搓出线上不可能出现的形态，用那种输入验证出来的判据什么也证明不了。A/B/C 三组的设计让
  反例无法"因为错误的原因通过"：B 与 C 共用同一份服务端抓包，只有客户端 SNI 不同 ——
  B 静默、C 报告，才说明决定结论的是域名比对，而不是分析器压根没解析证书。

1.41.2
- 【修改】浏览器标题统一为「应用层防火墙」，去掉后缀。涉及三处：新面板的 <title>（原
  「应用层防火墙 NaixiFW」）、旧面板的 HTML 初值、旧面板 i18n 的页面标题（简中/繁中；
  英文语系改为 Application Layer Firewall）。插件名、包名、界面顶栏文案均不变。
- 【加固】打包闸门里与标题相关的判据改成**全等**匹配。原先写的是"包含应用层防火墙"——
  这种判据对「加后缀」完全无感，正是它让「应用层防火墙 NaixiFW」一路通过。
  二进制身份判据也换了依据：原来查的那段文案已不再是标题，改用一段稳定的前端文案。

1.41.1
- 【修复·严重】**包在官方安装路径上装不上**：插件市场列表里看不到、也不会设开机自启。
  真因：插件市场的安装器把整条SQL当**一个命令行参数**交给sqlite3，而Linux对单个参数
  有硬上限（单参数字符串131072字节，超了execve直接失败）。我们的readme+changelog
  经base64后有145KB，于是sqlite3根本没跑起来，安装器只报
  「failed to write appmarket.db」——包解开、文件全就位、就是不注册，极具迷惑性。
  实测判据：同一台设备上，140000字节的单参数报「Argument list too long」，100000字节正常。
  修法：**包内只带最近的版本记录**（完整历史留在仓库），并加打包闸门核对SQL payload
  长度（base64后的readme+changelog+描述 必须 < 120000字节，留安全余量）。
- 【修复】`.pkg/manifest.json` 是安装时写入的注册副本，此前所有安装都是手工拷贝目录，
  **绕过了注册这一步**：设备上它一直停在旧值，插件市场显示的还是旧名字，插件也不在
  applist里。走官方安装器（naixi install）时它才会按包内manifest正确写入「应用层防火墙」。
- 【教训】"文件都在"不等于"装上了"。这个故障只有走官方安装器才暴露 ——
  此前每一轮验证都是手工拷贝，于是它一路漏到了现在。
- 【更正】本条目曾把原因记为"changelog里的ASCII单引号破坏SQL拼接"，那是错的：
  readme/changelog入库前会base64，引号进不了语句。真因是参数长度。
  保留这条更正，是因为"错的结论比没有结论更危险"——它会让人去改不相干的地方。

1.41.0
- 【新增·核心】**SNI与IP一致性校验**（用户第4点）。客户端在握手时自报域名（SNI），而它
  实际连的是自己解析到的地址 —— 两者本该一致。借域名握手那一类隧道（VLESS Reality的
  典型形态：借真站域名完成握手、却把连接指向自己的服务器）会在这里露出马脚。
  实现要点：
  · **旁路**，绝不进流量路径：观察只入队（永不阻塞，队列满就丢计数），解析由后台按限速
    发出。查DNS会阻塞且是外发流量，放在转发路径上就是断网。
  · 结果带TTL缓存（含失败缓存：查不到的域名不反复重试，每次重试都是一次外发流量）；
    缓存有上限并按最久未更新淘汰 —— 只写不淘汰会被历史域名占满，新流量全部查不到。
  · 结论有三种，且 **unknown必须与match分开**：解析还没回来时如实报"尚未校验"。
    把"还没查"显示成"正常"，等于把判据变成一张永远通过的检查表。
  · **默认关闭**：这是本插件唯一会主动外发DNS查询的功能，插件不替操作者决定要不要
    产生这类流量。开关走设置这条唯一路径落盘（否则重启后开关丢了、面板却显示开着）。
  · 内网/保留地址不查（在这些目标上的"不一致"没有意义，且白白外发查询），并单独计数，
    便于看出这条判据对多少流量根本用不上。
  · **把判据的边界随数据一起显示**：抓不到用真证书的代理（经典Trojan用自己的域名+真证书，
    解析结果与实际目标一致）；CDN/Anycast/多线解析会让正常站点也出现不一致。所以它输出的
    是**线索**，不是结论，落在面板上是"待人工复核"的一列。
  测试11条：一致/不一致/未查到不等于一致/内网不查/未启用不发查询/失败缓存不重试/TTL到期
  重查/并发去重/绝不阻塞/地址归一化/状态可解释。
- 【新增】面板：实时流表格加「SNI」列（不一致时给出两边地址与判据说明，供人工判断）；
  系统设置加「SNI与IP一致性校验」卡片（开关、状态、判据边界、不一致明细、清空）。
- 【新增】`/api/v1/sni-mismatch`（GET状态与明细 / POST开关 / DELETE清空）。
- 【新增】`/api/v1/metrics`：效率与性能指标的**接口**（按产品要求本轮只给接口、不设计页面）。
  引擎状态与运行时长、速率（每秒裁决数/拦截数，由后台定时采样算，不由接口现算 —— 接口是
  被人轮询的，间隔不固定，算出来的"每秒"没有统一口径）、拦截率（**分母是已裁决流数**，
  不是总包数：总包数含SYN/FIN/重传，用它会重复计数同一连接，这个口径写在返回值里）、
  资源占用（CPU滴答、RSS、协程数、打开的文件数）、协议命中分布、**规则命中排行**
  （哪条规则真在起作用；引擎侧计数有上限，规则名由操作者提供、可以很多，无限增长的map
  在常驻进程里就是泄漏）。
- 【修复】规则命中计数补上了：此前只有"按协议"的计数，无法回答"我写的这条规则到底有没有
  在命中"。

1.40.0
- 【新增·核心】**Shadowsocks与VMess的家族判定**（用户要的"尽可能区分加密代理"）。
  判据来自协议结构本身，不是拍脑袋的阈值：SS-AEAD首包 = salt(16或32) + 2字节长度 +
  密文 + 16字节tag，首块明文是地址头（域名目标5~257字节）→ 首包 ∈ [39,307]；
  VMess legacy请求头明文 = 79 + 地址 + padding，线上再套16字节IV → 首包 ∈ [99,415]。
  于是**两段区间的两端干净可分**：首包 ≤98只能是SS家族，≥308只能是VMess家族，
  **99~307之间不报任何家族** —— 把中间地带硬分给某一方，等于给出一个看起来确定、
  实际靠猜的结论，而它会被写进拦截规则。已知边界写在代码里：VMess自v4.28起默认
  用AEAD头，其线上格式与SS同形，短包无法区分。两条都标experimental，只用于观察
  与人工判断。
- 【修复·重要】**分析器"报出顺序"就是优先级**，这一点之前理解错了。引擎是"每个分析器
  报出属性 → 立刻求值规则 → 首个命中即定案"，所以**谁先报出属性谁就先定义这条流**。
  泛化的fet在列表最前面、且在第一个包就报"全加密"，于是兜底规则抢先定案，VLESS
  分析器根本来不及开口 —— 这才是"VLESS显示成加密代理"的真因（规则里写排除项也救不了：
  排除项要求vless == nil，而那一刻vless确实还没被报出来）。现在分析器列表按
  "具体在前、泛化兜底在后"重排，并在代码里写明顺序即优先级。
- 【修复】家族分析器一度"不命中时也报属性"，代价很大：① 规则里的排除项永远不成立，
  兜底类目再也接不住流量；② 面板取"这条流是什么"用的是属性是否存在，于是每条TCP流
  都被标成ssaead，连SSH都被改名。现在改成只在命中时报出，与引擎里其它分析器一致。
- 【修复】兜底类目（加密代理）的判据补上排除项：它现在真的只接"未识别出具体协议的
  全加密流量"。排除项不会让兜底漏东西：分析器只在规则引用它时才加载，所以具体类目被
  停用时该属性为nil、排除项成立、兜底照样接住。
- 【修复】测试夹具（benchAnalyzers）曾是另抄的一份分析器清单，抄漏了VLESS与新分析器
  —— 端到端测试测的是"一套分析器"、线上跑的是"另一套"，而且抄漏不报错。现在夹具直接
  派生自生产清单。另加测试钉住"注册的分析器"与"规则校验允许的名字"两边一致（任一边
  漏了都会静默失效：要么规则写不出来，要么规则永不命中）。
- 【改进】面板协议清单按后端优先级重排，VMess / Shadowsocks从"旧版"升级为一等公民
  （它们现在有真实判据），兜底类目标注为兜底；自定义表达式的可用名列表补全。
1.39.0
- 【修复·重要】**VLESS被当成"全加密流量"认不出来**。规则顺序错了：泛化的encproxy
  （fet.yes）排在enctunnel（vless != nil）之前，引擎先命中泛化规则，于是VLESS永远
  显示成"加密代理"——明明引擎能认出它（VLESS请求头是明文，连目标域名都读得出来）。
  规则顺序在本引擎里**是判据的一部分**（按顺序求值、先命中者为准），所以泛化兜底必须
  排在具体协议之后，并且带排除项。已加测试钉住这个顺序。
- 【改进】尽可能区分加密代理，并把边界说清楚：能精确判定的（VLESS、Trojan、SOCKS、
  SSH、WireGuard、OpenVPN）各有专用分析器；判不准的（VMess请求头是加密的、
  Shadowsocks是纯随机字节）无法在DPI层区分，因此用同一个排除式表达、说明文字如实写成
  「无法区分VMess与Shadowsocks」——两条规则写不同名字却宣称能分清，才是真会误导人的。
  计数侧也修了：analyzerOf里fet曾排在tls之前、且**根本没有vless**，导致VLESS
  流被计入fet（这是"全加密流量"计数的另一半原因）。
- 【改进】**引擎日志改为全英文**：排查要能按关键字搜索、能直接贴进工单。面板「引擎日志」
  页读的就是它。流量记录（含命中规则详情）保持中文——那是给人看的，不是给grep看的。
- 【修复】面板会说"拦截当前是关闭的，所以不会有流量记录"，而引擎明明在运行、也能抓到包。
  判据错了：原来判的是保存下来的开关值，而真正决定有没有记录的是**引擎在不在跑**。
  现在按引擎状态判断，并区分两种情况：开关开着但引擎没起来（异常，去系统设置看原因）
  与开关确实关着（提示怎么打开）。
- 【新增】**DNS投毒的面板入口**。后端一直支持（dns_poisoning / 指定IPv4与IPv6），
  规则也一直会渲染，但面板上**没有地方填**——所以这条路等于不可用。现在系统设置里有了：
  开关、IPv4/IPv6两个地址、以及两个一键取值（黑洞0.0.0.0与::；回环127.0.0.1与::1）。
  黑洞是标准做法：客户端立即失败，且不会再去试别的地址；这条路不依赖任何页面或服务，
  也是之前那件"DNS劫持/跳转该怎么做"最省事的答案。
- 【改进】「内核功能开关」只在调试模式（日志级别debug）下显示。它是排查"某个列表为什么
  是空的"用的，平时挂在页面上只是噪声。

1.38.1
- 【修复·重要】**装个新版会把面板规则和流量日志全部清掉**。这些文件原来写在app/data下，
  而安装/升级会整目录替换 .pkg/app —— 数据还在，只是随着旧目录一起被删了，且不报任何错。
  现在用户数据（面板规则、自定义应用规则、流量留存）与config.yaml、rules.yaml放在一起
  （/etc/log/naixifw，持久分区），升级不再碰它。启动时会自动把旧位置里**有内容**的文件
  迁移过来（空文件不算数据，不参与"谁覆盖谁"的判断）。
- 【修复】0字节的panel-rules.json会被当成"文件损坏"而使整个规则页报错。空文件其实是
  "还没有任何规则"的合法状态，现在被容忍（内容真正损坏时仍然报错，不会悄悄清空规则）。
  这一点直接影响自救：面板是规则配错后唯一能改回来的入口，它不该因为一个空文件打不开。

1.38.0
- 【修复】**详情里的「检测内容」原来是黑底**。它用的是 .mono-block，而那个类被写成了深色
  代码块（深底浅字）—— 放进内容区就是浅色页面里的一块黑。已改为浅色（浅底、深字、细边框），
  规则原文与配置原文同样受益。
- 【修复·重要】**应用分析的实时流里「客户端/端口/目标/端口」四列永远是空的**。原因是数据源
  选错了：那四列来自内核的识别缓存，而内核缓存一行只有**一个**地址（`IP port appid
  priority proto`），从来不是"客户端 → 目标"一对，所以结构上就填不出来。现在实时流改用
  **引擎自己的记录**（完整五元组 + 协议 + 动作 + 时间），四列真的有了；内核缓存另立一栏
  「内核识别缓存」，并写明它只有单侧地址。前端类型AppFlowRow也一并改成后端真实的字段
  （原来写成client_ip/target_ip，与后端完全不是一回事 —— 这正是那四列空着的前端那一半）。
- 【合并】**「应用分析」与「应用管控」合成一页「应用」**。两页讲的是同一个对象（应用），
  只是动作不同：看哪个在跑vs拦不拦它。分成两页的代价是——在分析页看到一个该拦的应用，
  得切到另一页再找一遍。现在同一页里「看到就能拦」（页签：实时流 / 管控 / 主机 / 命中排行）。
- 【改进】左侧导航底部不再重复「引擎运行中」—— 顶栏已经有一个一模一样的芯片，同一句话在
  同一屏出现两次等于噪声。侧栏改显示**拦截是否开着**与版本号（拦截是改变行为的开关，
  引擎是否在跑只是结果）。
- 【修复】**右上角时钟不会自动更新、也没有日期**。原来它只在30秒轮询里顺手更新一次
  （看着像卡住），而且只显示"17:11:49"。现在每秒自走，并显示完整日期时间。
- 【新增】**规则来源：独立（FAT）/ 同步（FIT）**，参考爱快AP的两种形态。独立 = 每台
  路由器自己在面板上配（原来的行为）；同步 = 规则由上级统一下发，各台按时拉取 ——
  多台设备要保持一样的拦截行为时，不用一台台点。
  · 配置：控制器地址（只允许http或https，内网地址可用）、间隔（默认30分钟）、访问令牌
    （Authorization: Bearer）、「立即同步」（第一次必须能当场验证拉得通，不能等定时器）。
  · 三条安全契约：**拉不到 / 解析不了 / 校验不过，一律保留上一份已经生效的规则**
    （绝不因为一次网络抖动就把规则清空 = 全网放行）；**先校验后落盘**（引擎拒绝规则的
    方式是它自己起不来，代价是整网）；**内容没变不重写闪存**（路由器上是持久分区）。
  · 不跟随重定向（以root身份执行的请求，不能让别人用302指定去哪）；响应体积上限3MB。
  · 状态可见：上次同步时间 / 结果 / 条数 / 失败原因都写在面板上，同步模式时规则页顶部有
    横幅说明"这里改的会被覆盖"，避免改完才发现白改。

1.37.1
- 【修复·重要】**协议类目的键在前端与后端不一致**，导致"删除"被拒、而"生效终端"静默失效。
  前端写的是含下划线的enc_proxy / enc_tunnel，渲染时用的却是encproxy / enctunnel，
  于是：点「删除加密代理」报「未知的协议类目enc_proxy」（你说"这样是不对的"，就是它）；
  而把生效终端配在这两类上，键对不上、按空处理 —— 规则看起来配好了，实际对全屋生效。
  现在键只认一份（spec.ProtoKeys），历史写法做归一：已经存进配置的老键会自动修正，
  不必重新删一遍。
- 【新增】**协议支持自定义表达式**（像Cloudflare的自定义规则）。规则类型里多了
  「协议表达式（自定义）」：自己写匹配表达式，动作可选 拦截 / 仅记录 / 放行（放行用来
  做例外），并可限定生效终端与生效时段。表达式在**保存时**用引擎的真解析器编译一遍 ——
  引擎拒绝规则的方式是自己起不来，那时错误会以"引擎没运行"的形式出现，排查方向完全错。
  弹窗里给了可用的名字（就是分析器：dns / tls / http / quic / fet / trojan / ssh / socks /
  vless / wireguard / openvpn / tailscale / p2p）与字段、函数清单。
- 【改名】界面上显示的「奶昔防火墙」改为「应用层防火墙」（新面板顶栏与标题、旧面板标题与
  停止横幅、多语言词条、插件注册名display_name）。其余不变。
- 顺带把动作词表钉对：自定义协议规则里"放行"渲染成引擎认识的allow。
- 测试：新增协议键归一（含"老键删除要真的生效""老键生效终端要真的限制住"）、
  自定义协议规则渲染（三种动作、停用不渲染、非法时段按全天、生成结果必须可编译）。

1.37.0
- 【修复·重要】**流量日志的「实时」从来不工作**，三个原因叠在一起：
  ① 实时流用的是浏览器自带的EventSource，而它**无法携带认证信息** —— 接口在
  HTTP Basic之后，EventSource拿到的是401，页面只显示"连接中/已断开"；
  ② 服务端在同一路流上发两种事件（引擎日志log、流量记录audit），而前端**只监听
  log**，所以就算连上了也看不到任何一条流量记录；③ 拦截关闭时引擎不运行，本来就
  没有记录，而页面不会说明这一点，只显示一张空表。
  现在：改用能带凭据的fetch流、两种事件都监听、状态区分"连接中/已认证失败/已断开"，
  并在拦截关闭时直接写明"不会有流量记录，请先打开拦截"。
- 【修复】**流量记录的日期时间缺日期**。记录里只存了"17:11:49"，跨天的日志在页面上
  无法区分是哪一天。现在存完整日期时间。
- 【改进】**流量日志页重做**：一张简要的表（日期时间 / 来源协议 / 来源IP:端口 /
  目标IP:端口 / IP归属 / 动作），点任意一行看详情 —— **检测内容放在弹窗里**，
  列表保持简短（你说后面要接大屏，列表越短越好排）。筛选补上：动作、协议、
  关键字；「引擎日志」单独一个页签，级别筛选在那里（多选，真实生效）。
  IP归属只查当前页看得见的行，不整屏外发。
- 【改进】**日志留存改为等级驱动，不再是一个写死的条数**。以前内存环硬上限500条，
  重启即空。现在：记录**落盘**在 /etc/log/naixifw/traffic.jsonl（与爱快自身日志同在
  /etc/log持久分区），每8MB轮转、保留若干个（默认5个 ≈ 40MB），实际上限由磁盘
  决定。**记什么**由日志等级决定：debug全记（含放行）、info记拦截+仅记录（默认）、
  warn只记拦截、error不记流量记录。等级与留存文件数都在option.json里（安装时可选，
  面板改等级后以面板为准）。内存环也按等级放大（info 1万 / debug 2万），
  因为它是实时看板的数据源，不是留存。
- 【改进】**规则管理：协议规则可以删除，不只是停用**。删除后该类目从规则列表消失
  （停用只是关掉开关），列表上方会出现"已删除的协议类目"并提供**恢复** ——
  删除必须可逆，否则误删只能手改配置文件。
- 【新增】**规则的「生效终端」**：新增/编辑规则时可以像选设备一样挑局域网机器
  （列出IP、MAC、主机名），只对这些机器生效。目前用在协议规则上：例如只封办公室
  那台机器的代理，而不是全屋一刀切。留空 = 对所有终端生效。设备列表来自设备自己的
  ARP表与主机表，不联网。
- 【规范】协议类目的合法键由一处集中定义（spec.ProtoKeys），保存时校验"删除的类目"
  与"生效终端"的键 —— 这两个字段写错时渲染会静默跳过，表现是"我明明删了，规则还在"。

1.36.1
- 【修复·重要】**点「启用」报「找不到该规则」**。面板的启用开关把规则id放在请求体里，
  而接口只从URL查询参数里读 —— 读到的永远是空串，于是每次都报"这条规则不存在"，
  而规则明明就在列表里。这个错很容易被当成数据问题去查（其实规则一直都在，
  是参数没读到）。现在两种写法都支持：面板发请求体、命令行脚本发 `?id=`。
  影响范围：域名规则、IP规则的启用开关。命令行的删除走的是查询参数，一直是好的。
- 【修复·重要】**端口规则从来没能保存过**。前端「端口规则」页签点保存发的是
  `POST /api/rules/port`，而路由表里从来没有这一条 —— 请求打到未注册的路径上是404。
  面板会弹一句"请求失败"，但很容易被当成偶发问题，而不是"这个功能根本没有后端"。
  现在接口补齐（增/删/查），并把校验做全：端口范围1-65535、协议名、方向、生效时段。
- 【改进】**规则管理改成直接写WAF规则：域名 / IP / 端口 / 协议 四合一**。
  以前是四个页签、四套表单，"拦某个东西"要先去对页签、再对字段名。现在是一张表、
  一个弹窗：先选匹配类型（域名 / IP地址 / IP网段 / 国家或地区 / 端口 / 协议 / 表达式），
  再填值，动作统一（拦截 / 放行 / 仅记录）。协议类目就是原来的「协议屏蔽」，
  作为一类规则出现在同一个列表里；协议规则的"编辑"即改动作。
  批量管理照旧保留：批量录入按类型分派，多选批量启用/停用/删除按类目分组一次提交
  （不是逐条热加载）。
- 【改进】端口规则补齐**生效时段**，四类规则的能力现在一致。之前统一了界面却留着
  一个"端口不能按时段生效"的缺口，说不通。
- 【修复】界面改回浅色。上一版把左侧导航与顶栏压成深色，而antd的菜单仍是浅色主题
  （深灰字、浅蓝选中底），深底配深字，整屏看起来"黑乎乎"、像配色坏了。
  这是我加的（当时想模仿网络安全设备的控制台），产品不要深色，已改回跟随antd的
  浅色主题。状态芯片与指标条保留 —— 那是功能，与环境色无关。
- 【规范】顶部状态芯片与指标条保留并改为浅色配色。原来那五件事散在各页
  （拦截开关在规则管理、引擎状态在侧栏底部、模式在系统设置），切页面才拼得出全貌；
  "规则建好了但拦截没开"正是最该被一眼看穿的失误。

1.36.0
- 【修复·重要】**网段规则以前从来没生效过**。网段和单个地址都被写成 `in ["..."]`，而 `in`
  做的是**字符串相等**比较 —— `"192.168.50.1" in ["192.168.50.0/24"]` 恒为假。也就是说：
  面板上一条"已启用"的网段拦截规则，从来没有拦下过任何流量，而且不报任何错。现在网段用
  引擎的 `cidr()` 函数（掩码比较），国家或地区用 `geoip()`。
- 【修复】表达式校验不认识引擎自带的函数，把 `cidr()` / `geoip()` / `geosite()` / `lookup()`
  当成"未知名字"拒掉——想拦网段的人只能退回去写单个地址。校验层现在认识这些函数。
- 【新增】**QUIC（HTTP/3）识别层**。这解释了那个很怪的现象：curl明明已经收到RST，
  浏览器却还能打开站点。curl走TCP，被 `REJECT --reject-with tcp-reset` 干净切断；
  而现代浏览器对支持HTTP/3的站点优先走QUIC（UDP 443），那条路径**根本不经过TCP规则**，
  而且QUIC没有RST这回事（只有丢包或超时）。域名规则的识别层现在多一项QUIC，
  解析的就是 `quic.req.sni`。没勾这一项的拦截规则会在列表里标出「缺QUIC」并提示补勾。
- 【改进】域名规则的「识别层」按**推荐值**默认勾选（DNS + TLS + QUIC），并用cookie
  记住上次的选择（编辑时不覆盖偏好）。默认值错的最坏后果是"以为拦了整个站点、实际少拦一层"。
- 【改进】新增域名规则、新增IP规则都改成**弹窗**打开，新增与编辑共用同一个弹窗，
  不再把表单直接铺在页面上。列表里每条都能「编辑」「删除」。
- 【新增】IP规则的**匹配类型**：IP地址 / IP网段 / 国家或地区 / 协议匹配 / 表达式（进阶）。
  带 `/` 的老规则会自动认成网段——不认的话升级后这些规则会因为值为空而被跳过，
  等于拦截规则凭空消失。
- 【新增】每条规则都能设**生效时段**，例如 `22:00-08:00`（跨天）或 `09:00-18:00`（当天），
  留空即全天有效。跨天的判定是单独写的并且有边界测试：写成 `开始<=现在<=结束` 会恒为假，
  表现是"到点了却不生效"，不报任何错。时段非法时保存会被拒（宁可让操作员改对，
  也不能让他以为只在夜间生效、实际整天在拦）。
  时段的实现方式是**到点重算规则文件并热加载**（引擎的表达式环境里没有时间，规则只有
  匹配或不匹配两种结果，所以"几点到几点生效"没法写成一条规则）。守护进程每分钟比对一次，
  只有内容真的变了才写盘——路由器上是持久分区，无谓重写等于磨闪存。
- 【改进】**协议屏蔽细分为七类**：加密代理 / 加密隧道 / Trojan / SOCKS / P2P / WireGuard /
  OpenVPN（SSH保留）。原来的Shadowsocks类 与VMess其实共用同一个判据，面板上是两个
  开关、实际同一个分析器，那样的粒度写不出有意义的规则。每一类都对应引擎里真实存在的
  分析器；**P2P当前不可用**（引擎没有对应分析器），那一行直接标出来并说明用什么替代，
  而不是给一个拨了不会产生任何规则的开关。默认全部关闭，建议先只开「监测」观察。
- 【新增】**自维护威胁情报**。原来的威胁情报页只是读设备内核里的IOC计数——那份清单随
  固件走、由厂商维护，插件只能读不能改。现在改成自己的清单：域名 / IP / 网段 / 关键字
  四类，可编辑、可停用、可设生效时段，并且**条目直接参与拦截**（渲染成引擎规则并立即
  热加载），不只是看板。域名类默认四条路径一起拦（DNS / TLS / QUIC / HTTP）——情报条目的
  语义是"这个域名整体不可信"，漏一条路径就等于还能从那里绕过去。设备自带的那份保留为
  只读卡片，它的命中计数对判断"有没有被打"有用。
- 【改进】左侧导航固定为十项：规则管理 / 流量日志 / 网站记录 / 应用分析 / 应用管控 /
  行为审计 / 应用规则库 / 威胁情报 / 关键字阻断 / 系统设置。「应用管控」紧跟「应用分析」
  —— 先看哪个应用在跑，再决定拦不拦，两件事挨着。
- 【改进】界面顶部加了**常驻状态栏与指标条**：拦截开没开、跑在哪个模式、RST开没关、
  队列怎么收、引擎活着没，都在视野里，不依赖当前在哪一页。之前这些散在各页，
  "规则建好了但拦截没开"这类失误要切页面才能发现。数字只显示服务端确实给出的值，
  缺一个就显示「-」而不是用0冒充（0会被读成"没有任何流量"，含义完全不同）。
- 【修订】「应用管控」页面里两句话还在说旧行为（"规则会写进爱快的配置"、"规则是系统级的"），
  而1.35.0起应用规则已改由插件自管、不写系统配置库。这两句改对了 —— 面板上留一句
  过时的话，比缺一句话更容易让人误判：按旧描述去排查，会找错方向。
- 【规范】面板与提示文案里中文与英文、数字之间不再留空格。这条同时加了闸门，而且判据
  是脚本而不是grep——`grep -P` 的汉字范围写法在本环境里返回0匹配，看起来像"没问题"，
  其实判据根本没生效。


（本包内只保留最近的版本记录，完整历史见项目仓库的 changelog。
原因：插件市场的安装器把整条记录作为一个命令行参数交给 sqlite3，而系统对
单个参数有硬上限（131072 字节）——包内文本过大会导致注册失败。）
