feat(agent/core): 中断向量表 —— 三个 L4 源统一登记,行为零变化

用户原话:「需要添加中断向量系统,独立成中断子模块就是为了方便添加这个
中断向量系统」。本次落地该目的。

## 为什么需要向量表

拆分前"有哪些内核中断、各自什么语义"散落在三个裸调用点上:

    raiseKernelInterrupt("kernel",          "kernel", "任务 #N panic…")
    raiseKernelInterrupt("kernel/overflow", "kernel", "上下文超页…")
    raiseKernelInterrupt("child/"+rc.id,    "kernel", "驻留子已满…")

三者差异**只在 source 字符串**上,语义契约全靠注释与调用方自律。
最能说明问题的是 panic 的递归保护:

    if t.Class == TaskInterrupt && t.Level >= LevelCritical { return }
    // 否则同一个 panic 会自我放大成中断风暴

这条规则只属于"panic 中断",却硬编码在 reportTaskPanic 里。
**新增一种内核中断时,没人会记得要不要也加递归保护**。

## 设计

interruptVector 声明一条中断源的完整契约:
  ID / Level / Source / Guard / Build

- Level 由条目自己声明(不再写死 L4)
- Guard 收容前置判定 —— panic 的递归保护落在这里
- Build 构造正文(模型唯一可见内容)
- 未登记 ID **panic**,不静默降级(打错 ID 会变成"中断发了但没人认领")

## 改动

新增 interrupt_vectors.go + interrupt_vectors_test.go(7 条判据)
三个调用点全部改为 raiseVector(id, args)
新增 raiseKernelInterruptAt / newKernelInterruptTaskAt 以表达条目声明的级别

## 行为零变化(刻意)

- 三个源的 source/正文/级别与改动前逐字一致
- ExpectsReply 字段**未实现**:按计划 §7.5/§7.6,它属于行为变更
  (内核提示不再引起模型整轮回复),必须单独提交、单独验证

## 变异验证

- 去掉 panic 递归保护 ⇒ TestVectors_PanicGuardBlocksRecursiveL4 红
- 调用点绕过向量表 ⇒ TestVectors_PanicSourceComesFromTable 红
  (source="kernel-direct",期望 "kernel")

## 验证

go build ./... / go test ./internal/agent/core/ / go test -race 全通过
This commit is contained in:
JianFeeeee
2026-10-25 14:45:36 +08:00
parent 01bef35c89
commit 8d5e31f7c0
6 changed files with 390 additions and 12 deletions

View File

@ -353,7 +353,7 @@ internal/agent/core/
| 1.5 切断反向依赖 | ⏳ 部分 | 已建立**中断栈状态钩子**(`stackNotify`)实现单向通告;`maxQueue` 仍约束中断队列(见下) |
| 2 结构收敛 | ✅ 完成 | 划界落实:机制在 `interrupt.go`,策略在 `scheduler.go` |
| 3 验证 | ✅ 完成 | 全仓 `go test -p 1 ./...` 通过;`-race` 三关键包绿;变异验证通过 |
| 中断向量表 | ⏳ 未做 | §7 已设计,按 §7.6 顺序应在拆分完成后单独做 |
| 中断向量表 | ✅ 完成 | `interrupt_vectors.go`:三个 L4 源改为查表;panic 递归保护收进 `Guard`;两次变异验证均变红 |
### 最终边界(用户第四次裁定:队列归调度器)
@ -410,6 +410,46 @@ internal/agent/core/
---
### 中断向量表落地(2026-10-25,本次拆分的真正目的)
新增 `internal/agent/core/interrupt_vectors.go`:
```go
type VectorID string
const (
VectorTaskPanic, VectorContextOverflow, VectorChildContextFull
)
type interruptVector struct {
ID VectorID
Level Level // 每条声明自己的级别(内核源 L4)
Source func(vectorArgs) string // 来源标识(child/<id> 这类动态的在闭包里拼)
Guard func(vectorArgs) bool // 前置判定:panic 的递归保护收在这里
Build func(vectorArgs) string // 正文(模型唯一可见内容)
}
```
三个裸调用点全部改为 `raiseVector(id, args)`:
| 原调用点 | 现在 |
| --- | --- |
| `reportTaskPanic` 里硬编码递归保护 + 调 `raiseKernelInterrupt` | `raiseVector(VectorTaskPanic, …)`,保护在 `Guard` |
| `raiseContextOverflow` | `raiseVector(VectorContextOverflow, …)` |
| `handleChildContextFull` | `raiseVector(VectorChildContextFull, …)` |
**未登记 ID 会 panic**,不静默降级——否则打错 ID 会变成"中断发了但没人认领"。
判据 `interrupt_vectors_test.go`(7 条):表结构完整性、未登记被拒、
递归保护生效、**调用点确实走向量表**(行为验证 source 来自表)。
后两条都做过变异验证:去掉递归保护 ⇒ 红;调用点绕过表 ⇒ 红
(`source="kernel-direct"`,期望 `"kernel"`)。
> ⚠️ `ExpectsReply` 字段**未实现**。按 §7.5/§7.6,它属于行为变更
> (内核提示不再引起模型整轮回复),必须单独提交、单独验证。
> 本次只完成了"规则集中"这一步,行为零变化。
---
## 三、实施步骤
### 阶段 0:安全网(先做,不可跳过)