Files
HomeAgent/internal/security/eventguard.go
JianFeeeee b850feaef2 feat: 段页式记忆流转、coldcontext 重命名与安全治理
记忆层:
- MemoryBlock 承载 FrameTS/PageSeq/PageKind/BlockRole/CallID
- coldcontext 归档与回填按完整页传递,工具调用参数和配对键不丢失
- 召回先估算总 token,超预算时按全局页相似度标记 inactive
- inactive 仅是 context 渲染期状态,不落盘、不进入 coldcontext,Prune 后重置
- Peek/确认/Remove 两阶段召回,失败保留冷上下文副本
- stablemem 记录 last_recalled_at,删块连带清理边

命名:
- internal/memory/document → internal/memory/coldcontext
- Doc/DocEvent/DocHit → ColdDoc/ColdEvent/ColdHit
- ContextToColdContext/RecentColdContexts/FindColdContexts
- 保留既有 JSON 标签、doc_ 文件前缀与 doc_count 状态键,兼容存量数据

安全治理:
- 工具策略门禁、安全配置、事件总线发布者身份与入站脱敏
- 声明式 SecurityAPI 与 QQ owner/guest profile
- 日志保留策略与相关 e2e/回归判据

验证:go build ./...、go vet ./...、go test ./...(62 包);
生产副本 e2e:7013 块迁移后 ID/向量/指纹 0 变化、0 缺失。
2026-10-29 23:53:48 +08:00

122 lines
4.1 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 security
// EventPolicy 作为**总线闸门**的实现(events.Guard)。
//
// ## 为什么这一层必须存在(用户 2026-10-28 的指正)
//
// 「eventbus 上所有事件都要接受安全模块的监控。」
//
// 在此之前,EventPolicy 只被 internal/sdk/events.go 调用 —— 而那是**四条**
// 事件路径中的**一条**。其余三条(s.Publish / s.Subscribe / s.EventBus())
// 拿的是裸 *events.Bus,策略完全不参与。于是「安全监控」只对合规者生效。
//
// 现在闸门挂在总线本体上(见 internal/events/bus.go 的 publishAs/subscribeAs),
// 本文件把 security 的策略与脱敏接上去。
//
// ## 分工(与既有设计一致)
//
// internal/security 策略 + 载荷过闸(**本文件**)
// internal/events 总线:每次发布/订阅都问一遍 Guard
// internal/sdk 适配:把「谁在调用」翻译成 events.Publisher
// sdk(公开) 只给接口与事件名
import (
"github.com/JianFeeeee/HologramAgent/internal/events"
)
// eventGuard 把 Runtime 的策略适配成 events.Guard。
type eventGuard struct {
rt *Runtime
}
// AsEventGuard 返回总线闸门实现。
//
// rt 为 nil 时返回 nil(调用方据此不装闸门)。**不**返回一个
// "永远放行"的假实现 —— 那会让"安全未装配"看起来像"安全已通过"。
func AsEventGuard(rt *Runtime) events.Guard {
if rt == nil {
return nil
}
return &eventGuard{rt: rt}
}
// AllowPublish 判定能否发布。
//
// ★ 内核身份(空 Name)一律放行:内核模块发布系统事件不需要授权,
// 它们本身就是可信执行体。但**载荷脱敏仍然执行**(见 ScrubPayload)——
// 内核也可能把敏感值拼进事件。
func (g *eventGuard) AllowPublish(p events.Publisher, eventType events.EventType) (bool, string) {
if p.IsKernel() {
return true, ""
}
d := g.rt.CheckEventPublish(p.Name, string(eventType))
return d.Allowed, d.Reason
}
// AllowSubscribe 判定能否订阅。
func (g *eventGuard) AllowSubscribe(p events.Publisher, eventType events.EventType) (bool, string) {
if p.IsKernel() {
return true, ""
}
d := g.rt.CheckEventSubscribe(p.Name, string(eventType))
return d.Allowed, d.Reason
}
// ScrubPayload 对出向载荷逐字段脱敏。
//
// ★ 对**所有**发布者生效(含内核):凭据闸门与身份无关。
// 内核模块把 api_key 拼进事件,和插件拼进去一样危险。
func (g *eventGuard) ScrubPayload(eventType events.EventType, payload map[string]interface{}) map[string]interface{} {
if g.rt == nil || payload == nil {
return payload
}
out := make(map[string]interface{}, len(payload))
for k, v := range payload {
if s, ok := v.(string); ok && s != "" {
clean, n := g.rt.PrepareOutbound(Inspection{
Direction: DirectionOutbound,
Stage: "event:" + string(eventType),
}, s)
if n > 0 {
out[k] = clean
continue
}
}
out[k] = v
}
return out
}
// ScrubInbound 对**入向**载荷逐字段清洗(投递给订阅者之前)。
//
// 与 ScrubPayload 对称,但用的是 Inbound 方向:
// 外部模块写进载荷的明文凭据在这里被换回别名。
//
// 为何必须在总线上做(而不是订阅方的适配层):
// 总线是**唯一的投递点**,而订阅入口有四五条。在适配层做就只好每条
// 各自实现一遍(实际就是这么发生的:sdk 侧曾有一份 scrubIn),
// 漏一条就是漏一条路径。安全模块持有总线 ⇒ 在这里做一次就覆盖全部。
func (g *eventGuard) ScrubInbound(eventType events.EventType, payload map[string]interface{}) map[string]interface{} {
if g.rt == nil || payload == nil {
return payload
}
out := make(map[string]interface{}, len(payload))
for k, v := range payload {
if s, ok := v.(string); ok && s != "" {
clean, n := g.rt.ScrubInbound(Inspection{
Direction: DirectionInbound,
Stage: "event:" + string(eventType),
}, s)
if n > 0 {
out[k] = clean
continue
}
}
out[k] = v
}
return out
}
// 编译期确认实现了 events.Guard。
var _ events.Guard = (*eventGuard)(nil)