fix(plugin): "只声明出站通道"的告警改为插件加载完成后判定(此前按注册顺序误报 qq)

## 现象

生产日志(v1.3.1 启动)出现:
`[plugin] qq 只声明了输出通道 "qq",已按双向通道兜底登记 inputch;若要明确意图请显式 RegisterInputChannel`
用户据此问"qq 插件你没更新?"

## 查证:qq 没漏,是我的判据错了

- SDK 示例 `example/qq/plugin.go`:`RegisterOutputChannel("qq")` 在 368 行、
  `RegisterInputChannel("qq", {NoMemory:true, Cleaner: inputCleaner})` 在 399 行 —— **先出站后入站**;
- 生产 `plugins/qq/plugin.bin`:版本 1.4.0,且二进制里含 `inputCleaner` 痕迹 ⇒ 确实调用了入站声明;
- 我的兜底告警在 **RegisterOutputChannel 的那一刻**判"有没有入站声明" ⇒ 对"先出站后入站"
  这种完全合法的写法必然误报(a2a/acp/weather 同理)。

## 修法

告警判据从"注册时刻"改为"**插件 Start 结束后最终声明了什么**":

- `regOutput` 只保留兜底登记(功能不变),不再告警;
- 新增 `warnOutputOnlyChannels(plugin)`,在插件加载/重载成功后统一判定:
  遍历该插件**最终**声明过的出站通道,只有始终没有对应入站声明的才告警,
  且措辞改为"内核已兜底登记 inputch,若这是有意为之可忽略"。
- 判据与顺序解耦后,告警才代表真实缺口(例:weather 的 `weather_out` 与
  `weather_in` 名字不同,出站名从未被声明为入站 —— 那条告警就是真的)。

## 验证

- 新增 `TestWarnOutputOnlyChannels`:①先出站后入站(qq 写法)**不告警**;
  ②只声明出站(weather 写法)**告警且只报那一个通道**。
- `go test ./internal/plugin/ ./internal/plugins/...` 全绿。
This commit is contained in:
JianFeeeee
2026-09-13 13:40:24 +08:00
parent 62d42150b3
commit d72141fc04
2 changed files with 77 additions and 4 deletions

View File

@ -0,0 +1,43 @@
package plugin
import (
"bytes"
"log"
"strings"
"testing"
)
// 延迟判定的语义:看的是"插件 Start 结束后最终声明了什么",
// 而不是"注册出站通道的那一刻有没有入站声明"。
//
// 为什么必须这样判:声明顺序自由 —— qq/weather 都是**先** RegisterOutputChannel
// **后** RegisterInputChannel,按注册时刻判会把它们误报成"只声明了输出通道"
// (实测发生过:用户据此以为 qq 插件没更新)。
func TestWarnOutputOnlyChannels(t *testing.T) {
r := NewRegistry()
var buf bytes.Buffer
oldOut := log.Writer()
log.SetOutput(&buf)
defer log.SetOutput(oldOut)
// ① 出站+入站都声明了(先出站后入站)⇒ 不该告警
r.noteChannel("qq", "qq", true)
r.noteChannel("qq", "qq", false)
r.warnOutputOnlyChannels("qq")
if s := buf.String(); s != "" {
t.Fatalf("qq 声明了入站通道,不应告警,实际: %s", s)
}
// ② 只声明出站 ⇒ 应告警,且只报这一个通道
buf.Reset()
r.noteChannel("weather", "weather_weather_out", true)
r.noteChannel("weather", "weather_weather_in", false)
r.warnOutputOnlyChannels("weather")
out := buf.String()
if !strings.Contains(out, "weather_weather_out") {
t.Fatalf("只声明出站的通道应被告警,实际: %q", out)
}
if strings.Contains(out, "weather_weather_in") {
t.Fatalf("已声明入站的通道不该被牵连,实际: %q", out)
}
}