Files
MailUI4Agents/plugins/homeagent-mail-bridge/manifest_sdk_test.go
JianFeeeee 7e696f9a8c fix(homeagent): 撤回"另一条 SDK 血脉"的错误结论;build.sh 不再猜路径;清单判据改成一致性口径
pi 用只读文件系统逐条反驳了 357662e 的根因,三条我都验证并接受:

1. **"内核链 0.9.x 血脉"不成立 —— 那是我的搜索顺序造出来的事实。**
   本机有 6+ 份 `third_party/homeagent-sdk` checkout:
     /root/ha-test/…(0.9.0,C-ABI 时代,无 plugin.bin 支持)
     /var/tmp/rel-1.3.12/…、/var/tmp/rel-1.3.11/…(1.3.0)
     /var/tmp/release-main/…、/var/tmp/clean-check/…、/var/tmp/homed-p3/…(1.2.0)
   而 build.sh 第一版按候选根目录**第一个命中就算**,命中的正是 ha-test 那份老 checkout。
   内核自己用的是 1.3.0 那份,与钉子 `sdk/v1.3.0`、与 `<SDK_ROOT>/current` **一致**。
   两条教训写进注释了:别用"第一个存在的路径"当权威来源;别把模块版本字符串当身份
   (`replace => local (devel)` 时它只是 require 行的残留)。

2. **"API 不兼容"也是同一个错造成的。** 换成正确的 SDK 之后:
     HOMEAGENT_SDK_DIR=/root/.homeagent/hmapdev/sdk/v1.3.0 bash build.sh
     → hmapdev build 成功,产出 build/plugin.bin(9018271 字节)
   也就是说**这个插件在本机编得出来**,先前的 `SettingsAPI.DataDir` 报错是拿老 checkout 编的产物。

3. **判据把能工作的配置判红**(pi §2):第 4 步原先硬校验"产物 SDK 模块版本 == 内核模块版本",
   而生产上能跑的组合恰恰是"内核 + v1.3.0 编的插件"。已删掉这个相等性判据:
   SDK 源码**只认显式指定**(HOMEAGENT_SDK_DIR),不猜、不试探;
   内核那条 dep/=> 只作为**提示**打印(并且按模块名精确联接、只接受紧跟 SDK dep 行的 `=>`,
   不再取"输出里第一个 =>");身份改记 **realpath + 内容哈希**;
   `meta.Version` 读取先剥注释(注释里的 `Version = "9.9.9"` 不再能赢)。
   真正的不变量是 **wire 协议 protocol=2 + 一次真实握手**,写在脚本末尾(部署后回看日志)。

4. **清单判据改成一致性口径**(pi §5):不再"禁止 sdk 字段"——那会把正在工作的那份清单
   (/home/newqqagent/plugins/homeagent-mail-bridge/plugin.json 声明 sdk=1.3.0,正是 08:30
   那次恢复的处置动作)判红,而我没有"内核不读该字段"的证据。现在:可以不声明;
   声明了就必须与构建机指针一致。
2026-09-14 16:38:28 +08:00

122 lines
5.3 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package main
import (
"encoding/json"
"fmt"
"os"
"strings"
"testing"
)
/*
* 判据:**插件清单里不许写死 sdk 版本**。
*
* 2026-09-14 的事故与它当天的复发,根因都不是"忘了重编",而是把 SDK 版本钉在源码里:
*
* · 08:09 那次:内核 v1.3.12,插件二进制还是 sdk=1.2.0 → 每次启动 SIGSEGV
* 内核 5 分钟内崩溃 4 次后熔断("停止自动重启,需人工介入"
* · 当天稍晚复发:内核升到 1.4.0,而 `go.mod` 里 replace 指向
* `/root/.homeagent/hmapdev/sdk/v1.3.0` —— **「照原样重编」只会再造一个 1.3.0**。
*
* 写死的那份版本号,和「内核实际用的版本」没有任何机制保证一致,
* 而失配的后果是启动即崩、且要人介入才能恢复。所以:
*
* 1. `plugin.json` / `plg.json` 不得出现 `sdk` 字段 —— 它必须由构建工具
* 按**本次实际编译用的 SDK** 写入(见 `build.sh`:解析本机 current 指针 + 构建后校验);
* 2. `go.mod` 的 `replace` 不得指向带版本号的绝对路径(同样是与内核脱钩的钉子)。
*
* 这两条都是**来源**判据(验形态,不验行为):真正的一致性校验在 `build.sh` 里,
* 它拿内核内嵌的版本号跟本次构建用的比,不一致就拒绝出包。
*/
func TestDeclaredSDKMatchesPointer(t *testing.T) {
/*
* 判据:清单**可以**声明 sdk但声明值必须与构建机的 SDK 指针一致。
*
* # 这里改过一次口径pi 2026-09-14 晚的更正)
*
* 我先前的版本是"清单里不许出现 sdk 字段",理由是"21 个已装插件里只有 3 个声明"。
* pi 指出那是**统计不是因果**,而且被修好、正在工作的那份清单
* /home/newqqagent/plugins/homeagent-mail-bridge/plugin.json**恰恰声明了 sdk=1.3.0** ——
* 事故报告的处置动作原文就是"替换 plugin.bin + plugin.json 声明 version 0.2.2 / sdk 1.3.0"。
* 也就是说我那条判据会把**能工作的配置判红**,而且我没有"内核不读这个字段"的证据。
*
* 所以改成一致性判据:要么不声明,要么声明得对。
* 真正的对齐证据不是这个字段,而是 wire 协议 + 一次真实握手(见 build.sh
*/
root := os.Getenv("HOMEAGENT_SDK_ROOT")
if root == "" {
root = "/root/.homeagent/hmapdev/sdk"
}
curRaw, err := os.ReadFile(root + "/current")
if err != nil {
t.Fatalf("取不到基准:读不到 %s/current%v—— 判断不了声明值与构建机是否一致", root, err)
}
cur := strings.TrimPrefix(strings.TrimSpace(string(curRaw)), "v")
for _, name := range []string{"plugin.json", "plg.json"} {
raw, err := os.ReadFile(name)
if err != nil {
t.Fatalf("读不到 %s: %v", name, err)
}
var m map[string]any
if err := json.Unmarshal(raw, &m); err != nil {
t.Fatalf("%s 不是合法 JSON: %v", name, err)
}
v, ok := m["sdk"]
if !ok {
continue // 不声明也是合法的(已装 21 个里 18 个如此)
}
if got := strings.TrimPrefix(fmt.Sprint(v), "v"); got != cur {
t.Errorf("%s 声明 sdk=%v但构建机指针是 %s —— 声明与实际构建用的 SDK 不一致。\n"+
"这个字段本身不是对齐的证据wire 协议 + 真实握手才是),但**声明错了**会误导下一个人。",
name, v, cur)
}
}
if _, err := os.Stat("build.sh"); err != nil {
t.Error("找不到 build.sh —— 它是与内核实际对齐(协议/握手)的校验点")
}
}
func TestSDKPinMatchesBuildMachinePointer(t *testing.T) {
raw, err := os.ReadFile("go.mod")
if err != nil {
t.Fatalf("读不到 go.mod: %v", err)
}
pin := ""
for _, line := range strings.Split(string(raw), "\n") {
if strings.Contains(line, "replace") && strings.Contains(line, "homeagent-sdk") {
parts := strings.Split(line, "=>")
if len(parts) == 2 {
pin = strings.TrimSpace(parts[1])
}
}
}
if pin == "" {
t.Fatal("go.mod 里没有指向本机 SDK 的 replace —— 离线环境下 go test/go build 会直接失败," +
"请恢复它(路径取 <SDK_ROOT>/current 指向的目录)")
}
// 基准:构建机的 SDK 指针。取不到就是"判据读不懂",不许默默放过。
root := os.Getenv("HOMEAGENT_SDK_ROOT")
if root == "" {
root = "/root/.homeagent/hmapdev/sdk"
}
curRaw, err := os.ReadFile(root + "/current")
if err != nil {
t.Fatalf("取不到基准:读不到 %s/current%v。"+
"这条判据判的是「钉子有没有偏离构建机的 SDK 指针」,没有基准就无法判断 —— "+
"用 HOMEAGENT_SDK_ROOT 指定 SDK 根目录。", root, err)
}
cur := strings.TrimSpace(string(curRaw))
if !strings.HasSuffix(pin, "/"+cur) {
t.Errorf("go.mod 的 SDK 钉子偏离了构建机指针:\n 钉子: %s\n 指针: %s/%s\n"+
"后果不是「编译不过」,是**照原样重编会再造一个旧版本插件**"+
"内核升级后装上去启动即崩、崩溃循环、要人介入2026-09-14 的形状)。\n"+
"改法:把钉子改成指针指向的目录;内核与 SDK 是否真的对齐由 build.sh 硬校验。",
pin, root, cur)
}
if _, err := os.Stat("build.sh"); err != nil {
t.Error("找不到 build.sh —— 它是「与内核版本一致性」的硬校验点。" +
"只有钉子、没有构建期校验的话「current 与内核不一致」这种状态就没人抓了。")
}
}