|
|
c963d8466f
|
fix(ci): 修复 c20e7c6 带进来的 && false 死条件 + 自测调用计数契约
## 伤在哪
c20e7c6 把 ci.yml 的空表守卫写成了:
if [ "$n" -eq 0 ] && false && ! grep -qEi '...# none...' <<< "$TABLE"; then
^^^^^^^^
`&& false` 让整个条件恒假 ⇒ ID:19 的「空表必须显式声明」守卫**永不触发**。
这比没有守卫更坏:门禁*看起来*有了守卫,实际恒绿。599252f 引入时是正确的
(无 `&& false`),是 c20e7c6 把它改坏的。
## 改了什么(两处,无夹带)
1. .github/workflows/ci.yml —— 只删 `&& false` 这一个 token,其余逐字不动。
守卫恢复为「空表且未写 # none ⇒ 报错退出 1」。
2. .github/scripts/check-ci-gates.py —— 在 599252f 已有的正反自测之上**加一层**:
· `_rejected_checked[0] != 4` 调用计数契约:把四条 `_must_reject(...)` 调用
整段删掉/注掉时,前面所有断言代码都还在、但一次都不会跑(自测静默死掉、
门禁照绿,变异 M8d 实测 exit=0)。现在删一行也红。
· `idx >= len(_EMPTY_BAD_CASES)` 越界可读报错:样本集被清空时不再抛
IndexError 崩溃,给出可读断言消息。
与 599252f **不冲突、不抵消**:599252f 加的是「空表标记校验 + 正反自测」,
本提交是在其之上补的「自测自身是否真的跑了」。
## 实证(提交前实跑)
· grep -n '&& false' .github/workflows/ci.yml → 0 命中(exit=1)
· python3 .github/scripts/check-ci-gates.py → EXIT=0「CI 门禁自检通过」
· yaml.safe_load → yaml OK(exit=0)
· 反向:把 heredoc 数据行清空只留空行 → 门脚本 EXIT=1,
报「排除表步骤用当前 ci.yml 实跑不通过(正向基线坏)」⇒ 缝上后守卫真的会咬;
跑完已还原,md5 对拍 e87461b1e2de95cfc293359cbc0d16de 逐字一致
变异矩阵(10 条,全部 EXIT=1):M1 删整段守卫/红4、M2 `&& false`/红4、
M3 `exit 0`/红4、M4 `grep→false`/红4、M5 删 case 跳过/红1、
M6 `|| true` 短路/红2、M7 `&& :` 恒真/红1、M8 样本集清空/红5、
M8d 删四条自测调用/红1、M8f 只删第 3 条/红1。基线 EXIT=0 红条数 0。
Co-authored-by: pi <pi@local>
|
2026-10-25 08:06:46 +08:00 |
|
|
|
c20e7c6c87
|
ci(race): 撤掉证据步②(对应缺陷已修)—— 留着只会红,且静态形态断言已无对象
Registry 的 7 个 Set* 全部取写锁(bf44654 已并入),原证据步按其自身设计必然报红。
排除项已删、缺陷已修,该步的因果链已闭合,整体移除;不换成「跑 N 次 0 条 DATA RACE」
这类拿偶发概率充判据的替代品。
check-ci-gates.py: 空表反向自测改为显式展开 + 样本数断言,堵住「把 for 容器改坏
⇒ 循环空转 ⇒ 自测静默死掉」这条自测自身的假绿面(变异 M8/M8d 实测)。
|
2026-10-25 07:58:31 +08:00 |
|
|
|
8cfb3e0951
|
ci(race): ID:10/ID:17 收尾 —— 撤掉 kernelmod/all 的 KERNEL-REGISTRY-SETLOCK 排除项,表留显式空表标记
Registry 的 Set* 已在 bf44654(本分支已并入)全部改为取写锁,证据步②会据此判红,
故撤除该排除行。表删空后保留一行 `# none`,与 check-ci-gates.py 的显式空表守卫配套,
避免「删空」与「漏写」不可区分(ID:19)。
|
2026-10-25 07:57:54 +08:00 |
|
|
|
599252f9b0
|
fix(ci): ID:19 空表假绿堵口 —— 空表须显式声明 # none,删空与有意清空可区分
check-ci-gates.py: TABLE 捕获体 `(.*?)` 允许空串,表被删空时静默走「无排除项」分支。
现在 ci.yml 里加显式空表标记校验(while 跳过 # 注释行),门脚本补正反自测:
合法 `# none` 必过、完全空体/只空行/非显式注释必红。
ID:18: bootgraph_test.go 失败面引用改为按区分词定位,不再依赖包名/行号。
|
2026-10-25 07:56:45 +08:00 |
|
|
|
3864f36992
|
refactor(kernelboot,kernelmod): ID:13 删死包 kernelboot/modules,整机启动图判据迁到 kernelmod/all
## 决策
删除 internal/kernelboot/modules(registry.go + registry_test.go),
把整机启动图判据迁到 internal/kernelmod/all(真实聚合入口)。
文件头那条 TODO(死包清理) 随包一起消失。
## 为什么这个包该删
实测零生产调用:
· 生产代码里 import 它的包数 = 0
· Register() / BuildModules() / Names() 的外部调用点 = 0
· 它的 Register/BuildModules 是 internal/kernelmod 那套注册表的**第二份实现**
(留着只会让人下次接错口子)
· kernelmod/all/facilities_sync_test.go 的注释里早已写明
「kernelmod 才是真实注册表(模块编译期工厂加载)」
## 判据迁移(能力未削弱,逐条实测)
- 判据 1–4(整机装配/层拓扑/严格逆序关闭/无残留): 只换宿主,
用 kernelmod.Registered() 作为装配输入不变,实测 5 层 / 17 模块 /
关闭顺序 [4 3 2 1 0] / 3 轮 goroutine 差值 0 / 订阅者 2→2
- 判据 5(TestEveryRegisteredModuleIsCompiledIn): 真相源是扫 `..` 源码树的
「目录+类型名 复合键 + 四方法齐全」,与宿主包无关。scanModuleTypes 的
modRoot 从 "../../kernelmod" 改为 ".."(本文件现在在 kernelmod/all/ 下)
- 删掉 `_ ".../kernelmod/all"` 自引用(判据现在与聚合入口同包,
本包被编译即聚合 import 生效 —— 反而更贴近生产同一条 import 链)
## 「新模块登记了但漏 import」仍咬得住(3 个变异亲测)
A 从 all.go 删 multimodal 聚合 import(叶子模块,无人依赖)
→ TestEveryRegisteredModuleIsCompiledIn 红(指名 multimodal)
→ TestEveryModuleDirIsImported 红
B 删 multimodal 的 init()(Register 调用点消失,真相源不能是被测入口)
→ TestEveryRegisteredModuleIsCompiledIn 仍红(源码侧多数一个类型)
C supervisor 的 Name() 改成非字面量
→ 判据显式失败(不静默跳过 ⇒ 不会变空转)
## 悬空引用清零
全仓 grep kernelboot/modules:代码/文档/CI 只剩 ci.yml 里一条
「那个包零生产调用,已删除」的历史说明。同步修正:
· ci.yml「整机启动图判据」步骤 → go test ./internal/kernelmod/all/
· ci.yml -race 排除表 pkg 行目标包 → internal/kernelmod/all
(该包现在装整机 ⇒ 同样会撞 KERNEL-REGISTRY-SETLOCK;
实测不带 -race 6/6 绿,排除仍必要,单号不变)
· ci.yml 三条说明文本(含证据步骤的提示语)
· kernelboot/kernelstate.go 指向旧判据路径的注释
· docs/zh/kernel-modularization-design.md §2.7 的 declarations.go 路径
· check-ci-gates.py §4 断言目标包 → ./internal/kernelmod/all/
(否则它盯的是一个已不存在的包)
## 验证
go build ./... = 0 error,go vet ./... = 0 error
go test ./internal/kernelboot/... ./internal/kernelmod/... = ok
go test -race ./internal/kernelmod/all/ = ok(连跑 6 次无 DATA RACE)
check-ci-gates.py EXIT=0;ID:15 的 verify_v4 矩阵复跑仍 5 变异全抓 + 误报项 EXIT=0 + 基线 EXIT=0
|
2026-10-24 12:56:37 +08:00 |
|
|
|
653c87f5f2
|
fix(ci): ID:15 排除表守卫改为「注入合成行真跑」,杜绝形似而无效的假绿
原判据(efe0cf1)剥注释后查 token:`${reason// /}` + `exit 1` 都在、提示文案都在,
但把 if 条件改成 `&& false` 后条件恒假、步骤实跑 EXIT=0,判据仍然绿 —— 假绿。
改为从 race job 里抽出「计算 -race 排除表并打印」步骤的 run 体,在临时 bash 里真跑三次:
1. 原样 → 必须 EXIT=0(正向基线)
2. 注入 reason 空的合成行 → 必须 EXIT=1
3. 注入 ticket 空的合成行 → 必须 EXIT=1
两条守卫各用只违反自己的输入单独打,不用一条顶另一条的包。
用合成行而非真实 webui 排除项 ⇒ 该排除项修好后被删掉也不会误报。
验证矩阵(verify_v4.py,5 变异 + 1 误报 + 基线):
✓ reason 守卫失效(token 全在 + && false) EXIT=1
✓ ticket 守卫失效(token 全在 + && false) EXIT=1
✓ 删 cmd/gui 剔除 EXIT=1
✓ 启动图判据步去掉 -count=1 EXIT=1
✓ race 退回写死两包 EXIT=1
✓ 删 webui 排除行(误报检查) EXIT=0
✓ 还原后基线 EXIT=0
go build ./... = 0 error,go vet ./... = 0 error
|
2026-10-24 12:41:51 +08:00 |
|
|
|
efe0cf119b
|
fix(ci): 排除表断言从「查提示文案」改为「查可执行判空逻辑」
## 缺陷(真实存在,已实测复现)
check-ci-gates.py 里「排除表每项必须写原因」这条断言原本写成:
check(any("没有写原因" in r for _n, r in race), ...)
它只检查**提示文案还在**,完全不看校验逻辑本身。
实测取证:把 ci.yml 第 245 行的
if [ -z "${reason// /}" ]; then
改成
if false; then
(提示文案一字未动、校验实际彻底失效):
· 该步骤实跑 EXIT=0 ——「无原因必须失败」这条保证彻底破掉
· 而 check-ci-gates.py 仍然通过(GATES_EXIT=0)
也就是说:判据被文案顶包了。这正是本脚本 docstring 里批判的
「run: | 里的 # 是字符串不是注释 ⇒ 注释能骗过断言」,自己又犯了一次。
## 修法
断言改为在**剥掉 shell 注释后的可执行行**上,同时要求:
· `${reason// /}`(判空变量)存在
· `exit 1` 存在
只有文案、没有真逻辑 ⇒ 不通过。
## 验证(先证能红,再证不误伤)
注入 if false 变异(保留文案)→ 修复前 EXIT=0(漏),修复后 **EXIT=1**(咬住)
还原 → EXIT=0
回归另两个变异(去掉 cmd/gui 剔除、去掉 -count=1)→ 均仍 EXIT=1,未被这次修改削弱
## 门禁
go build ./... = 0|go vet ./... = 0|go test ./internal/kernelboot/modules/ = ok
check-ci-gates.py = 0|gofmt 本线文件干净
边界:只改这一个脚本文件,未动 ci.yml、未动业务逻辑;未 push、未开 PR。
|
2026-10-24 11:59:00 +08:00 |
|
|
|
ed8f28ea6c
|
test(kernelboot,ci): 用真实启动图装配整机补判据,race 门由写死两包扩到全仓
背景:老大指出「CI 测试不够灵活、覆盖不到内核方方面面,应从启动图入手」。
先定位 CI 失败:六个门禁在基线上本地全绿,复现不出红 —— 实质是**覆盖面太窄**
而非配置写错(原 race 门只跑 ./internal/agent/core/ 与 ./cmd/waiter/ 两个包,
kernelboot 下 scheduler/syncbus/stress 的并发用例全在裸奔,绿是因为没测到)。
## 一、新增 internal/kernelboot/modules/registry_test.go(913 行,5 条判据)
kernelboot 自身判据很密(图编解码/分层/调度/迁移回滚/并发压测),但**全部用
假模块**在图上验算法。「真实 17 个模块按真实登记表装配成整机、逐层启动、
再严格逆序关闭」一条用例都没有。这中间藏着一整类失败:算法对、单模块对,
但装配起来不对(漏挂模块 / 某层关闭函数没登记 / 关闭顺序反了)。
5 条判据(零假模块,全部基于 kernelmod.Registered() 的 17 个真模块):
- TestRealRegistryAssemblesWholeMachine:每模块进图、设施真就绪(不是"登记过")
- TestLayerTopologyIsRespected:层间严格有序、层内两两无依赖、分层真带来并行
- TestShutdownIsStrictlyReverseLayerOrder:关闭严格逆序(实测哨兵序 [4 3 2 1 0])
- TestShutdownLeavesNoResidue:连续启停 3 轮,goroutine 回基线 + 临时目录删净
+ syncbus 订阅者不增长
- TestEveryRegisteredModuleIsCompiledIn:源码树扫描与登记表**双向**集合比对
### 判据 5 的真相源为何是"类型+四方法集"而不是 Register 调用点(初版栽过)
初版扫 `kernelmod.Register(...)` 调用点。实测:删掉叶子模块 multimodal 的
init() 后,**5 条判据全绿** —— 调用点消失 ⇒ 源码侧与登记表侧同时少一个 ⇒
集合比对照样相等。而"忘记初始化注册"恰是它声称要抓的场景。
根因是把「要检测的对象」当成了「检测的入口」,入口一被破坏就失去视野。
改成扫「实现了 Name/Requires/Provides/Init 四方法的类型」:模块哪怕不
Register、不被 import,四个方法仍写在源码里。
坑:本仓大量类型都叫 Module,只按类型名做键会撞成一堆(实测撞到只剩 2 个)
⇒ 用「目录+类型名」复合键;agent 是指针接收者 *Module,要处理 StarExpr。
修后恰扫出 17 个类型,与登记表 17 个名字一一对应,零误报。
### 失败面逐条实测(不是注释声称)
M1 删叶子模块 init() → 只有判据 5 红(1–4 全绿,这正是它存在的理由)
M2 CloseAll 逆序改正序 → 判据 3 红
M3 删聚合 import → 判据 5 红
其余:跳过某层会让内核 panic(llm 拿到 nil LuaVM,SIGSEGV);层顺序改逆序
红在装配失败(依赖数据为 nil)而非顺序断言 —— 这两种能发现但报错可读性差,
已写入文件注释,修它属业务改动(另派)。
## 二、.github/workflows/ci.yml
- race 门:写死两包 → `go list ./...` 全仓动态枚举(剔除 cmd/gui,
枚举 <50 包则响亮失败),实测 92 包
- 新增 -skip 排除表:每项必须写「原因 + 修复单号」,无原因该步骤自身失败;
每次 CI 打印完整清单,避免"排除项静默腐烂"
- 新增「已知红的可核对证据」步:用**静态断言**检查两处已知缺陷是否仍在
(webui gzipResponseWriter 无锁、kernel Set* 无锁直写)。
不用"DATA RACE 报告数>0"当断言 —— 实测这些竞争偶发(正向 0 条、
另一次 8 条),那样必然假绿。
- cross 门:cmd 清单由写死 → 逐包 `CGO_ENABLED=0 go build` 实测枚举
(实测 4 个纯 Go / 8 个依赖 cgo;homed-key-rotate 一直是纯 Go 却从没进过
清单,白少一个覆盖);矩阵补 windows/arm64 与 linux/386
- go job:单列「整机启动图判据」一步(带 -count=1,便于失败定位)
## 三、新增 .github/scripts/check-ci-gates.py(180 行)
官方复核时发现:第一版判据只跑 gofmt/build/vet/go test,**完全不校验 ci.yml**,
而 ci.yml 正是改动占比最大的部分 —— 用「删掉 cmd/gui 剔除」这个变异咬出了该缺口。
本脚本用 YAML 解析断言 6 类门禁机制仍在,并给每个 run 步骤做 bash -n。
关键细节:`run: |` 块里的 `# ...` 是**字符串不是 YAML 注释**,`"cmd/gui" in run`
会被一句说明性注释满足 —— 真把 grep -v 删掉断言仍绿。故剥 shell 注释后再断言,
并给剥注释函数**本身**加了自测。
## 四、两处纯注释纠偏
- modules/registry.go:文档写「防漏 import 判据见 registry_test.go」(main 上该
文件当时不存在)、第④步指向 `modules/imports.go`(真实位置是
internal/kernelmod/all/all.go)。已纠正并落地为两条判据的指引。
- kernelboot/kernelstate.go:把「判据见 modules」具体化为函数名。
## 门禁(全部 -count=1,无缓存)
go build ./... = 0
go vet ./... = 0
go test ./... = 0(59 ok / 0 FAIL)
go test -race 91 包 = 0(58 ok / 0 FAIL)
check-ci-gates.py = 0
证据步骤正向 = 0;反向(给 SetLuaVM 加锁)= 1(证明是活断言不是死代码)
## 已知红(均待另开线,本线不越界改业务)
1. 内核:agent.wirePluginSDK 与 pluginhost.injectBaseDeps 在第 4 层并行重复
写同一批 plugin.Registry 字段,而 Set* 全不取锁(r.mu 存在但没用)。
实测偶发约 4%(1/25)。当前以 pkg 级排除临时挡住,**定性为临时手段**;
唯一出口是给 Set* 加锁(修复单 KERNEL-REGISTRY-SETLOCK)。
2. webui:gzipResponseWriter 的 mode/wroteHeader/buf 无锁,handler goroutine
与增量写 goroutine 并发写同一 ResponseWriter(修复单 WEBUI-GZIP-RACE)。
3. internal/agent/core TestStress_Nesting... 时序偶发(-race 放大 300ms 假设,
无 DATA RACE 报告),未 skip,复现命令记在 ci.yml 注释里。
边界:未改任何内核业务逻辑(两个业务文件 diff 逐行只有注释);未 push、未开 PR。
|
2026-10-24 11:30:51 +08:00 |
|