fix(channels): 撤源失败的诊断必须带真正原因(不再只说"无返回")

## 我上一版(v2.0.28)自己引入的缺陷
内核桥的签名是 `call(name, args) -> (result, nil) | (nil, err)` ——
**失败时第一个返回值是 nil,原因在第二个返回值里**。
我写成 `local r = call(...)` 再读 `r.ok` ⇒ 恒为 nil ⇒ 日志只说
「撤源(重建)失败 channels-buddy-buddy-main: 无返回」。

**一句不可操作的诊断比没有更糟**:它把"权限没授予 / 超时 / 拒删最后一个源"
三种完全不同的原因(对应三种完全不同的处置)压成同一句空话。生产实测已复现
这条日志(部署时全量重同步,权限尚未授予)。

## 修法
两个撤源点都改成接**两个返回值**,日志带上内核给出的原文原因:
  `kernel action "remove_source" is not granted to plugin "channels";
   enable it in the plugin's kernel permissions`
并把"要授予哪个动作、声明在 plugin.kernel"一并写进提示。

## 判据收紧(旧判据咬不住这个变异)
原来只断言日志含"撤源失败"和"remove_source" —— **只接第一个返回值的版本照样通过**。
新增断言:日志必须含内核给的**真实原因**(`not granted`)。
这是本会话第 9 类判据教训:**判据只看"失败了",不看"为什么失败"**,
于是它无法区分"诊断可用"与"诊断是空话"。
This commit is contained in:
JianFeeeee
2026-10-16 10:26:55 +08:00
parent 2d2c27c430
commit a82ae2b60e
2 changed files with 19 additions and 9 deletions

View File

@ -48,6 +48,13 @@ func TestAccountDeleteRemovesItsSource(t *testing.T) {
if !strings.Contains(joined, "remove_source") {
t.Fatalf("记录里应点明是哪个内核动作被拒(便于给运维可操作提示),实得: %s", joined)
}
// ★ 必须带上**真正的原因**(not granted),而不是"无返回"这类空话。
// 内核桥失败时**第一个返回值是 nil、原因在第二个返回值里** ——
// 只接第一个返回值就会把原因丢掉(我第一版正是如此)。
if !strings.Contains(joined, "not granted") {
t.Fatalf("记录里必须含内核给出的**真实原因**(not granted),"+
"否则运维无从下手(只接 call 的第一个返回值就会丢掉原因);实得: %s", joined)
}
t.Logf("✓ 撤源失败已可见: %s", joined)
})
}

View File

@ -3654,14 +3654,14 @@ local function syncAccountSource(provider, accountId)
-- ⚠ 这里失败**可容忍**:AddSource 是**同名 upsert**,add 会把旧的覆盖掉。
-- 但这正是它掩盖 remove_source 权限缺失的原因(纯删除路径才暴露),
-- 故失败仍记一笔(低频,不会刷屏)。
local rr = __llmsproxy_kernel.call("remove_source", { name = name })
if not (rr and rr.ok) then
local rr, rerr = __llmsproxy_kernel.call("remove_source", { name = name })
if rr == nil or rr.ok ~= true then
local l = plugin.state.log
local last = type(l) == "table" and l[1] and l[1].msg or ""
-- 只在**上一次不是同一条**时记,避免每次同步都刷一条
if not tostring(last):find("撤源(重建)失败", 1, true) then
logEvent("撤源(重建)失败 " .. tostring(name) .. ": "
.. tostring(rr and (rr.error or rr.err) or "无返回"))
.. tostring(rerr or "(无错误信息)"))
end
end
local src = {
@ -3713,12 +3713,15 @@ end
-- 故这里把失败**记进事件日志**,让它在面板上可见,而不是只存在于一个被丢弃的返回值里。
local function dropAccountSource(provider, accountId)
local name = accountSourceName(provider, accountId)
local r = __llmsproxy_kernel.call("remove_source", { name = name })
local ok = r and r.ok
if not ok then
local err = (r and (r.error or r.err)) or "无返回"
logEvent("撤源失败 " .. tostring(name) .. ": " .. tostring(err)
.. "(若提示 not granted,需给 channels 插件授予 remove_source 权限)")
-- ★ 内核桥是 `call(name, args) -> (result, nil) | (nil, err)`:
-- 失败时**第一个返回值是 nil**,原因在**第二个返回值**里。
-- 我第一版写成 `local r = call(...)` 再读 `r.ok` ⇒ 恒 nil ⇒ 日志只说"无返回",
-- 把真正原因(not granted / 超时 / 拒删最后一个源)丢掉 —— 一句不可操作的
-- 诊断比没有更糟。两个返回值都要接。
local r, err = __llmsproxy_kernel.call("remove_source", { name = name })
if r == nil or r.ok ~= true then
logEvent("撤源失败 " .. tostring(name) .. ": " .. tostring(err or "(无错误信息)")
.. " | 若为 not granted,需给 channels 插件授予 remove_source(声明在 plugin.kernel)")
return false, err
end
return true