From 969f3138fd030d041e1706416e36186c2a8f207f Mon Sep 17 00:00:00 2001 From: JianFeeeee Date: Fri, 16 Oct 2026 10:26:55 +0800 Subject: [PATCH] =?UTF-8?q?fix(channels):=20=E6=92=A4=E6=BA=90=E5=A4=B1?= =?UTF-8?q?=E8=B4=A5=E7=9A=84=E8=AF=8A=E6=96=AD=E5=BF=85=E9=A1=BB=E5=B8=A6?= =?UTF-8?q?=E7=9C=9F=E6=AD=A3=E5=8E=9F=E5=9B=A0=EF=BC=88=E4=B8=8D=E5=86=8D?= =?UTF-8?q?=E5=8F=AA=E8=AF=B4"=E6=97=A0=E8=BF=94=E5=9B=9E"=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## 我上一版(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 类判据教训:**判据只看"失败了",不看"为什么失败"**, 于是它无法区分"诊断可用"与"诊断是空话"。 --- .../core/channels_remove_source_perm_test.go | 7 +++++++ internal/lua/plugins/channels.lua | 21 +++++++++++-------- 2 files changed, 19 insertions(+), 9 deletions(-) diff --git a/internal/core/channels_remove_source_perm_test.go b/internal/core/channels_remove_source_perm_test.go index fedc6ee..a1cd94f 100644 --- a/internal/core/channels_remove_source_perm_test.go +++ b/internal/core/channels_remove_source_perm_test.go @@ -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) }) } diff --git a/internal/lua/plugins/channels.lua b/internal/lua/plugins/channels.lua index 81605e9..c50d88c 100644 --- a/internal/lua/plugins/channels.lua +++ b/internal/lua/plugins/channels.lua @@ -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