Files
HomeAgent/internal/plugin/proc/priority_clamp_test.go
JianFeeeee f3232000f4 feat(scheduler): L4 也归内核级插件 —— WebUI 终止按钮可用“立即打断”
上一提交把 L4 写成“内核独占(panic / selfip)”,漏了内核级插件这类来源。
用户澄清:**内核级插件应当能声明 L4,用于实现中断能力**,例如 WebUI 的终止按钮。

判据(两道闸,纵深防御):
  1. proc 桥(外部进程唯一入口)一律把 L4 夹到 L3。在这里夹而不是只按 source 判,
     是因为 source 是插件自报字段、可以冒名;本函数所在位置能确知“来自外部进程”。
  2. core:isKernelLevelSource(source) 查 pluginReg.IsBuiltinPlugin,只有编译期内置
     插件(init() 自注册的工厂)才承认 L4。
source 约定 `插件名` 或 `插件名/实例`(webui/<deviceID>),判据取第一段——
否则带设备身份的 WebUI 来源会被误判成外部插件而拿不到 L4。

改动:
- core: interruptLevel(evt, privileged bool);新增 isKernelLevelSource;
  requestPreempt 不再夹取(级别已由 interruptLevel 解析,否则内核级插件的 L4 被削掉)。
- eventloop: 传入 a.isKernelLevelSource(evt.Source)。
- proc 桥: 新增 clampExternalPriority,pubSdkInjectOpts 一律夹取。
- internal/sdk: 再导出 PriorityL1..L4(内置插件用 sdk.PriorityL4)。
- webui handleChatInterrupt(终止按钮)声明 PriorityL4。
- timer 声明 PriorityL3:定时器是“时钟那种实时工作”,比 QQ 那类可无限等待的
  异步消息高(L1)——这是对用户“它不是时钟那种实时工作”的直接推论,可改。
- 测试: L4 特权矩阵(非特权夹取 / 特权承认)、source 判据(内置、内置/实例、
  外部、空、前缀不误匹配)、内核级插件 L4 一路到达调度器、proc 夹取两条。
- 设计稿 §2/§3.2/§11.1/§14/§15 按“L4 = 内核 + 内核级插件”更正。

验收:go build/vet 干净;go test ./... 37 包 ok 0 FAIL;-race 全绿(含 webui/timer)。
2026-09-13 07:18:03 +08:00

49 lines
1.7 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 proc
// 外部插件(走 proc 桥的独立进程/动态库)**不是内核级插件**
// 因此不能声明 L4 —— “立即打断”能力只属于编译期内置插件(如 WebUI 终止按钮)。
//
// 在这里夹取而不是只在内核里按 source 判,是因为 source 是插件自报字段、可以冒名;
// 本函数所在位置能确知“这来自外部进程”。内核侧的 isKernelLevelSource 是第二道闸。
import (
"testing"
pubsdk "gitcode.com/JianFeeeee/homeagent-sdk/sdk"
)
func TestClampExternalPriority_RejectsL4(t *testing.T) {
cases := []struct {
in string
want string
}{
{"L4", pubsdk.PriorityL3}, // 越权 → 夹到 L3
{"l4", pubsdk.PriorityL3}, // 大小写都要夹
{"L3", pubsdk.PriorityL3},
{"L2", pubsdk.PriorityL2},
{"L1", pubsdk.PriorityL1},
{"", ""}, // 未声明保持空(内核按默认级处理)
{"紧急", "紧急"}, // 未知值原样传给内核,由内核降级为 L1 并留痕
{"L9", "L9"}, // 同上
}
for _, c := range cases {
if got := clampExternalPriority(c.in); got != c.want {
t.Fatalf("clampExternalPriority(%q)=%q期望 %q", c.in, got, c.want)
}
}
}
// 贯穿 pubSdkInjectOptsRPC 报文里的 priority 必须经过夹取才落到 InjectOptions。
func TestPubSdkInjectOpts_ClampsPriority(t *testing.T) {
got := pubSdkInjectOpts(true, "prune", "cleaner", "L4")
if got.Priority != pubsdk.PriorityL3 {
t.Fatalf("经桥后的优先级=%q期望 L3", got.Priority)
}
if !got.NoMemory || got.ContextPolicy != "prune" || got.CleanerName != "cleaner" {
t.Fatalf("其它字段被改动:%+v", got)
}
if l2 := pubSdkInjectOpts(false, "", "", "L2"); l2.Priority != pubsdk.PriorityL2 {
t.Fatalf("L2 应原样通过,实际 %q", l2.Priority)
}
}