Repository navigation
Releases: oekit/matrix-tools
Release list
v0.1.20
matrix-tools v0.1.20 —— consumer 链两处修复。
fix(tools):consumer 探针判据粒度对齐
两处判据错误,都只在真实 org 数据上暴露(夹具看不出):
- 假阳性:源码 import 的是子包路径(
github.com/cloudwego/kitex/server),
go.mod require 的是模块路径(github.com/cloudwego/kitex)⇒ 拿全等去比
永远匹配不上 ⇒ kitex 父仓被误判「替 kitexotel / kitex-istio 背重依赖」,
而它的 go.mod 是对的。修法:按模块前缀比(_imports_module)。 - 假阴性:嵌套模块排除只覆盖第一层 ⇒ kitexotel 在深度 4 ⇒ 它的 import
被算成父仓的 ⇒ 「父仓替子模块背依赖」这条判据对最深的嵌套模块完全失效。
修法:逐级向上找 go.mod,覆盖任意深度(_under_nested_module)。
顺带修 stdout 行缓冲:重定向/管道时日志全程 0 字节 ⇒ 进程被杀/超时时一个字都留不下。
自测新增两条自带反证样本的判据用例;变异验证:前缀比退回全等 ⇒ 用例变红。
fix(ci):consumer job 跨仓写回必须用 PAT
该 job 用无凭据的 HTTPS URL clone oekit.github.io ⇒ git push 报
could not read Username for 'https://github.com' ⇒ 从未绿过 ⇒ 站点消费面画像
冻结在 2026-10-21。且 push 失败发生在 "Open consumer issue" 之前 ⇒ 步骤失败即中断
⇒ 真违规从来没人被报过。
修法:token 取 MATRIX_AUDIT_TOKEN || OEKIT_PROJECT_TOKEN || GITHUB_TOKEN
(GITHUB_TOKEN 只对本仓有效,跨仓必须用 classic PAT);push 显式塞
http.extraheader(GH_TOKEN/GITHUB_TOKEN 环境变量只对 gh CLI 生效,git 不认);
失败时 ::error:: 给出可执行诊断;set -euo pipefail 让管道里的失败不被吞。
影响面
consumer_probe 的判据只喂给 CI 的 consumer job 与本地画像生成,
不影响发版链(matrix.py / audit.py / release_post.py)。
v0.1.19
fix(tools):远端 tag 读取加重试。
matrix._ls_remote_tags(url, timeout=60, attempts=3) —— 每个 URL 最多试 3 次,退避 0.5s / 1s / 2s。2026-10-27 跑 gormx v0.6.2 的发版后置链时,一次 middleware 的网络抖动让链在「站点 versions.json」那步直接死掉(版本行 PR 开了、站点与 BOM 都没开),手动 ls-remote 当场可读 ⇒ 瞬时抖动。
RemoteReadError,绝不降级成本地 tag(读到空 ≠ 读不到)。重试只吸收瞬时抖动。
remote_tags 是所有远端读的事实入口(审计 / BOM 生成 / 站点版本表 / Release 对账都走它)⇒ 一次抖动原本能打断其中任何一条链。
自测:tag_cache_selftest 补反证 7(瞬时失败必须重试)与反证 8(全失败仍抛错且确实重试过),变异验证(attempts=1 ⇒ 2 例变红)。
v0.1.18
matrix-tools v0.1.18:审计加实时进度行 + BOM「读不到」与「陈旧」分开报
修后台审计任务「Re-run audit for final truth」(bg_19b83328,900s 超时)暴露的两个问题。
一、超时后日志全丢
该任务的输出日志只有 59 字节(运行时那句 "Command timed out after 900 seconds"),
审计自己的输出一个字都没有。两个原因:
- stdout 块缓冲 ⇒ 实测整个运行期间日志文件 0 字节,只在进程退出时一次性落盘;
- 命令形状
... | grep ... | tail -15两端都缓冲,且开头白等 sleep 90。
改法:stdout 改行缓冲;新增 progress(),进度行写 stderr(stderr 默认行缓冲)——
写 stderr 不写 stdout 是刻意的:CI 里 audit.py > audit-report.txt 收集的是 stdout
(那份文件会被贴进漂移 issue),进度进的是日志,报告文件格式一个字不变。
进度分 7 个阶段,逐仓与 Release 对账逐仓报。
二、「读不到」混进了「漂移」
兄弟会话同名任务报出「❌ matrix.RemoteReadError: registry: 远端 tag 读不到…」——
那是 BOM 子进程崩了、audit 取它输出最后一行当漂移消息,读的人看不出这是读取失败
而不是产物陈旧(违反 #79)。
改法:gen_matrix_go.py 引入退出码契约(生产方是子进程、消费方是 audit.py):
0=一致 / 1=真漂移 / 2=读不到(判不了 ≠ 通过);读不到时给干净消息并返回 2,
不再抛裸 traceback;audit.py 用 classify_check() 分类,不靠猜消息文本。
三、该任务重跑(修正命令形状)
去掉 sleep 90、去掉管道过滤、日志直接落盘 ⇒ 54s、退出码 0、输出完整,
审计结论不变(24/24 仓成功 + ✓ 矩阵审计通过)。
详见 CHANGELOG v0.1.18。
v0.1.17
matrix-tools v0.1.17:远端 tag 读取加进程内缓存 + 并发预取
修掉后台审计任务反复以 900s 超时、输出为空的形态。
根因:同一份远端事实被重复读 ≈168 次(audit 的 4 个调用点 × 24 仓,加上
Release 对账与 BOM 子进程),单次读 1.0–2.1s(拥塞时 5–10s)⇒ 审计要 281s。
改动:
- matrix.py 新增默认关的进程内远端 tag 缓存(enable/disable/clear_tag_cache)
与并发预取 prefetch_remote_tags; - scripts/audit.py 接线:审计开头开启缓存 + 并发预取,读失败只记一条漂移;
- scripts/gen_matrix_go.py 每仓只读一次远端 tag;
- scripts/tag_cache_selftest.py(新,5 例反证)+ 接线进 audit.yml selftest job。
实测(同一台机器、同一网络条件):audit --remote-tags 281s → 48s / 49s(连跑两次,
5.8×);审计结论不变(24/24 仓远端读取成功 + ✓ 矩阵审计通过);BOM 产物零变化。
详见 CHANGELOG v0.1.17。
v0.1.16
变更记录
- fix(tools): BOM 生成器 Go 地板取不到就报错,不许兜底出假地板——v0.1.42 曾把 hertz 的 1.26.0 写成 1.24.0(#100)
背景
gen_matrix_go.py 生成 BOM 时版本取远端 tag、Go 地板取本地 clone,两路口径分裂:
- 版本:
gen_versions.latest_version→_remote_tags(远端) - 地板:
git -C <dir> show <tag>:go.mod(本地 clone 的 git 对象)
本地 clone 还没 fetch 到那个 tag 时(多仓并行发版、clone 刚建、网络不稳),
git show 直接失败 ⇒ 落到旧实现的兜底分支 return m.group(1) if m else "1.24.0"。
关键点:兜底值 1.24.0 不是「未知」而是一个具体的、看起来合法的地板 —— 它和
「真的是 1.24.0 的仓」在产物里长得一模一样,下游无从分辨。地板低报比不报更危险:
不报会让人去查,报一个具体数字会让人以为那就是事实。
已污染一个发布物
BOM v0.1.42 把 hertz 写成 Go: "1.24.0",而 hertz v0.8.4 / v0.8.5 的 go.mod
都是 go 1.26.0(触发条件:生成时本地 hertz clone 尚未 fetch 到 v0.8.5)。
v0.1.42 已发布、tag 不可撤,只能补发 v0.1.43 修正 —— 但根因仍在,下一个 BOM
版本只要再撞上「本地 clone 缺 tag」就会重犯。
改动
scripts/gen_matrix_go.py:go_floor()fail-loud —— 取不到go.mod、或
go.mod里没有go指令 ⇒ 抛 SystemExit,错误消息里带可执行的修复步骤
(git fetch --tags)与 issue 编号;不再有任何默认地板。这与
matrix.remote_tags已有的口径一致:「读到空」≠「事实为空」(#79)。scripts/gen_matrix_go.py:新增ensure_local_tag()—— 取地板前若本地缺该 tag,
补一次git fetch --tags(只在真缺时做,稳态零额外网络开销;fetch 失败不抛,
真因仍由go_floor报,它的消息里带 git 的原始 stderr,更可诊断)。scripts/gen_matrix_go_selftest.py(新):反证怎么构造 —— 把「取不到就报错」
改回「兜底」,自测必须失败。5 例覆盖:正常路径取到真地板 / 本地缺 tag 必须抛错
(且消息里不含任何看起来合法的地板值)/go.mod无go指令同样抛错 /
ensure_local_tag能从 origin 补取 / 稳态不做额外 fetch。无副作用、不联网、
不依赖工作区布局(临时目录造 fixture 仓,「远端」用本地 bare 仓当 origin)。
已接线进.github/workflows/audit.yml的 selftest job。
兼容性
- happy path 零行为变化 ——
gen_matrix_go.py --check实跑仍报
✓ oekit BOM 与远端 tag 一致(v0.1.43,35 模块)。 - 产物格式零变化(仍是
Go:字段),只有「取不到」时从「写假值」变成「报错退出」。
变异验证(实跑)
把兜底改回 return m.group(1) if m else "1.24.0" ⇒ 反证 1、2 两条用例立刻 FAIL
(应当抛 SystemExit,却正常返回了);还原后 5 例全绿。
关联 #100(产物侧已由 BOM v0.1.43 修正)。
v0.1.15
这次修的是什么
维护轮门禁此前从未生效——月度 workflow maintenance.yml(2026-10-19 加入,cron 0 4 5 * *)
至今 0 次运行、0 个维护轮 issue,而真实地板漂移是 20 仓(阈值 8):门禁到期不报,
且没人知道信号丢了。三个根因互相叠加:
maintenance_check.py不认MATRIX_BASE——CI 布局下 24 仓全部「读不到」。
脚本硬编码BASE = parent.parent.parent,而matrix.repo_dir(repo, base)的语义是
base or os.environ.get("MATRIX_BASE", ...)——base 非空就把环境变量短路掉。
CI 里仓克隆在$GITHUB_WORKSPACE/repos/<name>,三个候选路径一个都不命中
⇒ 24 仓全抛FileNotFoundError⇒ 被except Exception吞掉 ⇒MAINTENANCE_COUNT=0。- 后果是反向的:
count == '0'走的正是「关闭维护轮 issue」分支
⇒ 门禁方向反了——到期不报、反而关单。 - 标签
matrix-maintenance在本仓不存在 ⇒ 即使走对分支,issues.create也会
422 Validation Failed,issue 依然开不出来。(matrix-drift存在,所以audit.yml
的漂移 issue 开得出来——这就是两个 workflow 表现不一致的原因。)
连带:align_floors.py 是同一行硬编码,而它是维护轮唯一的整改工具
⇒ 定位错 ⇒ 整改命令打到错误目录。
改了什么
scripts/maintenance_check.py:base 解析认MATRIX_BASE(与audit.py同口径),
--base可显式覆盖,新增--repos单仓盘点;「读不到」与「真的没漂移」分开——
读不到的仓计入MAINTENANCE_UNDETERMINED,且不再打印「✓ 全矩阵地板零漂移」
(与 audit 在 #79 对 remote tag 的处理同口径:读不到 ≠ 事实为空)。scripts/align_floors.py:同样认MATRIX_BASE/ 新增--base/--repos。.github/workflows/maintenance.yml:显式传--base "$PWD/repos";
set -o pipefail让脚本崩溃传导为步骤失败(而不是被tee吞成 0);
关闭分支收紧为count == '0' && undetermined == '0'——只有确定没有漂移才关单;
计数缺失(脚本崩了)⇒ 明确失败、不改动任何维护轮 issue;
开 issue 前确保标签存在(自愈,不依赖仓外的带外设置)。scripts/maintenance_check_selftest.py(新):一道没人验证过的门禁,很可能自己就是
个假绿。自测用临时目录 + 临时 git 仓(不联网、不碰真仓)复刻 CI 布局,证明仓能
被认出、漂移能被算出来、仓读不到时不许输出「零漂移」、align_floors.py同样认
MATRIX_BASE。改前红 4/5、改后绿 5/5(同一命令行,失败原因只能是 bug 本身)。.github/workflows/audit.yml:自测接入既有selftestjob。README.md:补维护轮 workflow 说明与两个工具的 base 口径。
v0.1.14
这次加的是什么
嵌套模块的「多层子目录路径」支持——kitex v0.7.0 把 kitexotel / istio 拆成嵌套模块后,
工具链里五处「单层目录」假设全部算错。
Go 的子目录模块 tag 规则要求 tag 带完整子路径 ⇒ 这类嵌套模块的 tag 是
observability/tracing/instrumentation/kitexotel/v0.1.0,而工具链按「仓根下一层」解析:
| 位置 | 旧假设 | 修法 |
|---|---|---|
gen_versions.py |
tag.split("/", 1) |
改 rsplit(split 会把多层路径切成 path=observability / ver=tracing/…/v0.1.0) |
gen_matrix_go.py |
NESTED_ONLY_TAGS 正则只放行单层路径 |
放宽为允许多层,版本号改 rsplit 取最后一段 |
release_train.py |
嵌套模块的 CHANGELOG 取 module_dir.parent/CHANGELOG.md |
上溯到仓根——多层时原指向不存在的目录,发版被凭空拦下 |
verify_release_units.py |
llms.txt 嵌套条目正则只放行单层路径 | 名字与链接正则同步放宽 |
audit.py |
嵌套模块父仓地板门禁只扫「仓根下一层」 | 放宽为递归 **/go.mod(排除仓根本身) |
计数漏数也修了:count_units 用 r.glob("*/go.mod") 只扫一层 ⇒ 多层嵌套模块被漏数
(实测文件系统里有 11 个,单层 glob 只数出 10)⇒ 站点文案据此判漂移
(真实 11 / 36,误判 10 / 35)。改为递归扫 **/go.mod,排除仓根本身与
.git / vendor / third_party / testdata / examples(与共享 CI 的发现口径一致)。
口径变化
嵌套模块 9 → 11、发版单元 34 → 36(24 根 + 11 嵌套 + 1 伞仓)。
NESTED_MODULES 登记表补齐 kitex v0.7.0 拆出的两个嵌套模块——登记表是「应该有什么」的唯一声明,
缺登记会让三类判据失真(发版单元口径门禁判「口径分裂」红 / llms.txt 逐字节比对判站点段漂移红 /
嵌套模块安装行缺失 ⇒ Agent 装不上):
kitex/observability/tracing/instrumentation/kitexotel→kitexotel/v0.1.0kitex/istio→istio/v0.1.0
顺带把 BOM_RELEASE_UNIT 旁过期的计数注释(「裁决定死 34」)改成只写裁决不写绝对数——
裁决本身(伞仓必须计入发版单元口径)没变,变的是总数随嵌套模块增减。
附带
matrix.py 的 DESC 同步已发布事实:pkg 补 v0.23.0 起 Peek 非删除式读
(可选接口 PeekGetter 外置——读到过期条目只算不命中、不删除它,stale-if-error 兜底需要那份过期真值留在表内,pkg#72)、
data 补 v0.18.0 起 cache 壳 PeekGetter 别名 + cachetest 壳 2 个 stale-if-error 契约入口。
验证
changelog_check ✓ · verify_release_units ✅ 36 个发版单元 · verify_site_copy ✅(24 / 25 / 11 / 36)·
MODULES 24 | NESTED 11 | UNITS 36
v0.1.13
这次加的是什么
「发布序列倒挂」门禁——同一版本线内,后发布的 tag 版本号反而更低。
全矩阵 24 个模块仓实测,4 个仓有 5 个已发布版本命中:
| 仓 | 旁支版本 | 发布时刻 | 此前已发布的更高版本 | 其发布时刻 | 间隔 |
|---|---|---|---|---|---|
| kitex | v0.3.9 |
10-15 19:32 | v0.4.1 |
10-15 16:48 | +2h43m |
| kitex | v0.3.10 |
10-16 08:39 | v0.4.1 |
10-15 16:48 | +15h50m |
| redisx | v0.3.1 |
10-16 10:20 | v0.4.0 |
10-16 10:17 | +3m |
| middleware | v0.5.1 |
10-17 16:52 | v0.6.0 |
10-17 16:08 | +44m |
| network | v0.1.1 |
10-17 16:53 | v0.5.1 |
10-17 16:51 | +2m |
这些版本都已发布——tag 在远端、GitHub Release 在、Go module proxy 上可解析 ⇒ 删不得也挪不得(重推 = 已缓存消费方集体中毒,.github#5)。而矩阵(versions.json / llms.txt)只登记 latest 与 breakingIndex,不登记它们 ⇒ 对外口径与远端事实不一致。
为什么一直没被抓到
共享 CI 的 changelog_check.py 规则 1 查的是「CHANGELOG 小节按 semver 严格递减」——那是文档序;倒挂是发布序(事实序)。
network 的小节顺序是 v0.5.5 … v0.5.0 → v0.1.1,逐节递减 ⇒ 门禁绿;而事实序是 v0.5.1 先发布、v0.1.1 后发布。实测 4/4 仓的 changelog_check 全绿——两种序之间没有人守。
怎么加的
scripts/verify_version_line.py——独立 oracle:自己解析 semver、自己读 git 原始输出(git for-each-ref --sort=creatordate),刻意不复用任何现有解析逻辑(共享逻辑就有共同盲区,#31 复审原则)。
版本线 = (路径前缀, module 路径)——module 路径变更是「新模块」,新旧两条线各自单调(redisx 活例:旧路径github.com/oekit/redis与新路径…/redisx;不按路径分层,新线起点会被误判成回退)。
某仓一个 tag 都没有 ⇒ 判不了,不当通过。scripts/version_line_known.json——已登记的 5 处旁支版本,逐条写明事实与处置。未登记的新倒挂仍然判红——例外表是显式人工放行口,不是漏报口子。scripts/verify_version_line_selftest.py——变异自测 7 例:回退必须抓到(且参照是此前已发布的最高版本)/ 正常递增不误报 / 嵌套版本线独立 / module 路径变更后新线起点不误判 / 同一路径内的回退仍抓到 / 例外表只放行登记的那一处 / 无 tag 判不了。- 接入
audit.py(聚合层 ⇒ 随之进 CI 的 PR 门禁与周巡检);audit.yml的 selftest job 加入新自测。
明确不做
- 不删、不重推任何已发布 tag(= 已缓存消费方集体中毒)
- 不改已发布版本的内容语义——各仓只补事实声明:network#24 / kitex#33 / middleware#46 / redisx#31
关:#88
v0.1.12
这次修的是什么
「远端有没有这个 tag」这条事实,在 matrix-tools 里有三条读取路径,却有两种语义。
#79 把「读到空 ≠ 事实为空」在 GitHub API 侧(ghsafe)与 git 传输侧
(matrix.remote_tags)都做实了——读不到就抛 RemoteReadError。但另外两条
读取路径仍是旧语义:只看 git ls-remote 的 stdout 非空 ⇒ 读失败 = 「不存在」。
| # | 路径 | 用在哪 | 读失败时的行为 |
|---|---|---|---|
| 1 | matrix.remote_tags |
audit / gen_versions | 抛 RemoteReadError(#79) |
| 2 | release_train.tag_exists_remote |
发版检查面 a「同名 tag 查重」 | False =「不存在」⇒ 查重门禁静默放行 |
| 3 | close_issue.tag_on_remote |
关单守卫「tag 必须已存在于远端」 | 报「tag 不在远端」——把「读不到」说成「没有」 |
变异实测(修复前):同一个确实存在于远端的 tag,只因读法不同就从「存在」
翻成「不存在」——
| 读法 | 读成功 | 读失败 |
|---|---|---|
release_train.tag_exists_remote |
True |
False |
matrix.tag_exists |
True |
False |
怎么修的
matrix.remote_tag_exists:这条事实的唯一读法(走matrix.remote_tags:
canonical HTTPS 优先 → origin 兜底 → 失败抛RemoteReadError);
matrix.tag_exists收口到它,不再自带第三种语义。release_train.tag_exists_remote改走它;check_module接住RemoteReadError
⇒ fail-closed(不进打 tag 环节),错误信息写明「读不到 ≠ 不存在」。close_issue.tag_on_remote改走它;读不到 ⇒ 报「读不到远端 tag(不是不存在)」,
并明确不要用--force绕过(--force是刻意的越权 ⇒ 显式跳过核对、不发读请求)。
为什么值得单独修
- 发版查重形同虚设:
release_train的 docstring 写着这条检查的理由——
「重推 = 代理/sumdb 缓存集体中毒,.github#5」——恰在最需要它的时刻(SSH 掉线 /
网络抖动)放行。真正拦住重推的是git push自身(实测同名 tag 无--force推送被拒:
! [rejected] v1.0.0 -> v1.0.0 (already exists),远端 tag 指向不变)⇒ 不会真的
重推成功,但脚本会在git tag -a之后崩在 push、留下半截本地 tag,报错是
「already exists」而不是「查重时读不到远端」——排查被带偏。 - 关单守卫误报:
--force在脚本文案里就写着「确实要引用未发版本需--force」
⇒ 读失败被误报成「不在远端」,会把人推向--force,而真实原因可能只是网络抖了
一下 ⇒ 守卫生效的路径被误诊成「刻意的越权」。
验证
- 变异回归:M1
release_train退回旧语义 ⇒ K 转红;M2close_issue退回旧语义
⇒ M 转红;M3matrix退回旧语义 ⇒ I 转红;3/3 转红,还原后 14/14 全绿。 scripts/remote_reads_selftest.py加 6 组用例(I/J/K/L/M/N):三条读路径各自的
「读失败必抛」与「读成功必判存在」。该脚本已接进audit.yml的 selftest job
⇒ 自动进 CI 覆盖(PR #86 的 selftest job 已绿)。- 其余四个 selftest(close_issue / dependabot_dirs / ghsafe / verify_release_units)
全绿;audit.py --remote-tags仍✓ 矩阵审计通过(远端 tag 读取 24/24 仓成功);
PR #86 CI 全绿(含 audit job)。
Closes #85
v0.1.11
这次修的是什么
站点承诺 llms.txt 是「34 个发版单元」的机器可读索引(24 根 + 9 嵌套 + 1 伞仓
oekit),canonical 口径 gen_tables.count_units 也这么算。但发版单元门禁
verify_release_units 只校验 24 + 9 = 33 —— 伞仓压根没进判据。
伞仓 oekit 是货真价实的独立发版单元:有独立 tag(latest v0.1.29)、独立 GitHub
Release,下游 go get github.com/oekit/oekit@latest 是一次独立的版本获取。
变异实测(修复前):把站点 docs/public/llms.txt 里伞仓那段
(go get github.com/oekit/oekit@latest)整段删掉后重跑门禁——
✅ 发版单元门禁通过(33 个发版单元 = 24 根 + 9 嵌套;9 条嵌套安装行齐全)
exit=0
同样的变异对嵌套模块是红的 ⇒ 门禁的牙没长在伞仓上。而站点
docs/guide/ai-agent.md 正对外承诺「llms.txt 覆盖 34 个发版单元(… + 1 个 BOM
伞仓 oekit)」⇒ 这条承诺无人守(llms.txt 是 AI Agent 唯一会抓的机器可读索引)。
怎么修的
check_llms_units增加伞仓 oracle:llms.txt 必须含
go get github.com/oekit/oekit@latest安装行,与嵌套模块同等要求。
该函数已被audit.py导入 ⇒ 改动自动进 CI 覆盖面(audit.yml 的 audit job:
PR 门禁 + 周巡检),无需新增 CI 接线。check_filesystem_oracle增加伞仓的物理证明:目录在 ⇒ 必须真有独立vX.Y.Ztag
(与嵌套模块同款判据);目录没检出不报(本地少 clone 一个不该假红)。- 口径统一到 canonical:新增
release_unit_count()= 24 根 + 9 嵌套 + 1 伞仓 = 34。
伞仓数不写死常量(判定依据是BOM_REPO不在矩阵登记表里,它若哪天被收进矩阵模块,
口径自动变 0,不会两处各说各话)。 - 门禁额外做口径交叉核对:完整检出时拿文件系统推导(
count_units)与 canonical
口径对比,不一致即判红 —— 同一个术语不许对外报出两个数。
验证
- 变异回归:修复前同一变异
exit=0(绿)→ 修复后exit=1并点名伞仓:❌ 发版单元门禁未通过(应覆盖 34 个发版单元 = 24 根 + 9 嵌套 + 1 伞仓): - llms.txt 缺伞仓 BOM 仓 oekit 的 go get 安装行(`go get github.com/oekit/oekit@latest`)… - 新增
scripts/verify_release_units_selftest.py(纯内存 fixture;不联网、不依赖工作区
布局、无副作用):合法 fixture 零问题 / 删掉伞仓安装行必红 / 删掉嵌套安装行必红 /
删掉根模块条目必红;另钉住 34 这个口径。接入audit.yml的 selftest job
(与 close_issue / dependabot_dirs / ghsafe / remote_reads 四个既有 selftest 并列)。 - 回归:五个 selftest 全绿、
py_compile全仓通过、audit.py --remote-tags仍
✓ 矩阵审计通过(远端 tag 读取 24/24 仓成功)、PR #83 CI 全绿(含 audit job)。
Closes #82