mirror of
https://gitcode.com/JianFeeeee/HomeAgent.git
synced 2026-11-01 06:47:29 +00:00
背景:.github/scripts/check-ci-gates.py 上一轮已写好并入仓(ed8f28e),
但它**没被任何 workflow 调用** —— `grep check-ci-gates .github/workflows/ci.yml`
零命中。等于判据只活在评审口径里,CI 实际上不跑它。本次把它接上。
## 一、ci.yml:新增 2 步(go job,插在 go vet 之后、go test 之前)
- `actions/setup-python@v7`(3.12)
- `CI 配置门禁自检(动态枚举 / 排除表 / 启动图判据不许静默退化)`
位置为什么在 go test ./... **之前**而不是之后:
· 该步骤是纯静态检查 —— 只读 ci.yml,秒级,无网络、无 cgo、
不建 SQLite、不占端口;而 go test ./... 要跑 20 分钟。
· 「门禁本身坏掉」应该第一时间红。放后面会让一次坏配置白白消耗
20 分钟机器时间才发现,也让失败信号埋在 93 个包的输出后面。
runner 的 pyyaml **不假设自带**:先 `python3 -c 'import yaml'` 探测,
缺了才 `pip install pyyaml`(幂等,几秒)。本机实测 pyyaml 6.0.3 已有,
但本机的 Python 环境不等于 runner —— 所以做探测而不是赌。
## 二、顺手纳管 internal/kernelboot/modules/registry.go 的注释改动
该文件 +6/−3 的改动是**纯注释**,内容为 11:08 那轮已拍板要记进文件头的说明:
真相源为什么刻意不扫 `kernelmod.Register(...)` 调用点 ——
删掉某模块的 init() 后调用点消失,源码侧与登记表侧会同时少一个,
比对照样相等 ⇒ 假绿。手写模块名清单同理。故真相源取
「目录+类型名复合键 + 四方法齐全」的类型,而不是要检测对象的入口。
★ 这点在 commit message 里点明,是为了不让它变成夹带:它不是本次新写的代码,
是上一轮已定但未提交的说明文字,与本次「门禁入 CI」同一主题(判据不许假绿)。
## 三、验证(先证能红,再证能绿,都不靠嘴说)
从 ci.yml 里**抽出该 step 的 run 块原样执行**(不是手敲等价命令):
· 变色前(动态枚举改回写死 ./internal/agent/core/ ./cmd/waiter/)
→ EXIT=1,报 `race job 应恰有一步用 go list ./... 动态枚举包(实际 0 步)`
· 还原后 → EXIT=0,报「CI 门禁自检通过:动态枚举 / 排除表校验 / 启动图判据步骤 / 交叉枚举 / 全部 run 步骤语法」
另在临时副本里抽查 5 个变异,4 个如期变红(写死枚举 / 去掉 -count=1 /
-race 不吃枚举 / cross 写死 cmd)。第 5 个「删掉排除表的原因校验」**没红** ——
已定位为脚本自身缺口,见下条,不在本次授权范围故未改。
## 四、已知遗留(未修,留给后续)
check-ci-gates.py 第 100 行用的是 `any("没有写原因" in r for ...)` ——
它只检查**提示文案还在**,不检查那段 `if [ -z "${reason// /}" ]` 校验逻辑本身。
于是把条件改成 `if false;` 而保留文案,断言仍然绿(实测 EXIT=0)。
判据被注释/文案顶包,与它自己在 docstring 里批判的「注释骗过 grep」是同一类错误。
属于该脚本的缺陷,本次任务授权只到「接进 CI」,故只记录不修。
105 lines
3.9 KiB
Go
105 lines
3.9 KiB
Go
// Package modules 是内核模块的**登记表**(不含任何具体模块的实现)。
|
||
//
|
||
// ## 设计意图(用户设计)
|
||
//
|
||
// > 每个内核模块显式各自声明依赖 … 最先初始化的 core 自然不依赖任何模块,
|
||
// > 下一步初始化的依赖 core 以此类推
|
||
//
|
||
// ## 怎么加一个新模块(这是本设计的**验收标准**)
|
||
//
|
||
// ① 新建一个文件(如 modules/mymod.go)
|
||
// ② 写一个类型,实现 kernelboot.Module(Name/Requires/Provides/Init)
|
||
// ③ 在同文件的 init() 里调 modules.Register(&MyMod{})
|
||
// ④ 在 `internal/kernelmod/all/all.go` 加一行空白 import —— **仅此而已**
|
||
//
|
||
// **不需要**编辑任何中央清单:依赖写在模块**自己**的 Requires() 里。
|
||
//
|
||
// ★ 为何要"防漏 import"的判据(见 `internal/kernelboot/modules/registry_test.go`):
|
||
// 忘了 import ⇒ 模块**根本不被编译进来** ⇒ 它不参与装配 ⇒ 依赖它的模块报
|
||
// "缺依赖"(响亮),但**不依赖它的模块会静默少了那个设施**
|
||
// (如插件注册表没建 ⇒ 插件全部加载不了,而错误信息指向插件而非根因)。
|
||
//
|
||
// 判据有两条(缺一不可):
|
||
//
|
||
// · `TestEveryRegisteredModuleIsCompiledIn` —— 用 AST 扫源码树里
|
||
// 「目录+类型名 复合键 + Name/Requires/Provides/Init 四方法齐全」的类型,
|
||
// 取其 Name() 与登记表做集合比对(独立真相源)。
|
||
// ★ 刻意**不**扫 `kernelmod.Register(...)` 调用点:删掉某模块的 init() 后
|
||
// 调用点消失,源码侧与登记表侧会同时少一个 ⇒ 比对照样相等 ⇒ 假绿。
|
||
// 手写模块名清单同理(清单会跟着漏),两者都不用。
|
||
// · `TestRealRegistryAssemblesWholeMachine` 等 —— 用真实登记表装配整机、
|
||
// 断言每个模块都进图且设施就绪、并严格逆序关闭(见同目录测试文件)
|
||
package modules
|
||
|
||
import (
|
||
"fmt"
|
||
"sort"
|
||
"sync"
|
||
|
||
"github.com/JianFeeeee/HologramAgent/internal/kernelboot"
|
||
)
|
||
|
||
var (
|
||
regMu sync.Mutex
|
||
registry []kernelboot.Module
|
||
seenName = map[string]bool{}
|
||
)
|
||
|
||
// Register 由模块在自己的 init() 里调用(自注册)。
|
||
//
|
||
// 重复注册同一模块名会**记录**并在 BuildModules 时报错 —— 不用 panic:
|
||
// panic 发生在 init 阶段,栈信息对使用者毫无意义(他只想知道哪个模块重名)。
|
||
func Register(m kernelboot.Module) {
|
||
if m == nil {
|
||
return
|
||
}
|
||
regMu.Lock()
|
||
defer regMu.Unlock()
|
||
if seenName[m.Name()] {
|
||
dupErr = append(dupErr, m.Name())
|
||
return
|
||
}
|
||
seenName[m.Name()] = true
|
||
registry = append(registry, m)
|
||
}
|
||
|
||
// dupErr 累积重名模块(BuildModules 时统一报告)。
|
||
var dupErr []string
|
||
|
||
// BuildModules 返回全部已登记模块(按名字排序,保证可复现)。
|
||
//
|
||
// ## 为什么排序
|
||
//
|
||
// 分层结果的**层内顺序**若依赖 map/切片顺序,同一份声明两次构建会得到
|
||
// 不同顺序 ⇒ 不可审计(设计 §2.5:图要能进 PR diff 比对)。
|
||
// 排序让结果稳定。
|
||
func BuildModules() ([]kernelboot.Module, error) {
|
||
regMu.Lock()
|
||
defer regMu.Unlock()
|
||
if len(dupErr) > 0 {
|
||
names := append([]string(nil), dupErr...)
|
||
sort.Strings(names)
|
||
return nil, fmt.Errorf("kernelboot/modules: 模块名重复注册: %v"+
|
||
"(同一个模块被 import 了两次?或两个模块取了同名)", names)
|
||
}
|
||
out := append([]kernelboot.Module(nil), registry...)
|
||
sort.Slice(out, func(i, j int) bool { return out[i].Name() < out[j].Name() })
|
||
if len(out) == 0 {
|
||
return nil, fmt.Errorf("kernelboot/modules: 没有任何模块被登记 —— " +
|
||
"通常是没有 import 模块包(见本文件顶部的'怎么加一个新模块')")
|
||
}
|
||
return out, nil
|
||
}
|
||
|
||
// Names 返回已登记模块名(诊断/判据用)。
|
||
func Names() []string {
|
||
regMu.Lock()
|
||
defer regMu.Unlock()
|
||
out := make([]string, 0, len(registry))
|
||
for _, m := range registry {
|
||
out = append(out, m.Name())
|
||
}
|
||
sort.Strings(out)
|
||
return out
|
||
}
|