Files
Cyrene_v2/internal/services/gateway/admin_approval.go
T
AskaEth 63236c9b00
CI / Backend (build / vet / test / cross-compile) (push) Has been cancelled
CI / Frontend (tsc + vite build) (push) Has been cancelled
feat(capability,gateway): 工具策略落盘 + 主代理代审真接线(裁决 1.1 核对/1.3/10.4.2/13.4.3)
- 1.1(核对):registry 默认两布尔 false 已一致,零代码改动(设计文档已按裁决改写)
- 1.3 + 10.4.2:新增迁移 0012_tool_policy(tool_policies 表,表名复数 + idx_tool_policies_name);
  新增 registry.PolicyStore/SQLitePolicyStore,启动 Load 覆盖注册初值、SetPolicy 先落库后改内存
  (落盘失败保持旧值并报错);面板保存路径不变但从此重启仍在
- 13.4.3:代审真接线 —— restrict.SensitiveCall 结构化判定(terminal / file write / http POST)、
  restrict.Gate/Reviewer/Resolver(无代审者或代审报错一律 fail-closed 拦截),
  组合根装进 loop.Options.Hooks 的 CallAdmit;gateway.RecordApproval 落 audit_logs
  (who=角色名 / what=approval.<工具> / result=granted|denied)并广播 approval_notice 帧
- 前端:代审小字「xxx 想要干什么,我审查后批准了」进聊天流顶部(帧→store→组件,文案单一来源
  复用 audit.Approval.Explain()),附纯 node 逻辑测试
- 复核:迁移在 data/cyrene.db **副本**上 up/down/up 各一次通过;TestRegistry_PolicyStoreSurvivesRestart
  与 TestAdminSetPolicySurvivesRestart 证明「重启后策略仍在」;terminal 触发代审 + 审计有行有端到端断言
2026-10-04 12:30:16 +08:00

71 lines
2.8 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 gateway
import (
"context"
"time"
"git.yeij.top/AskaEth/Cyrene_v2/internal/capability/restrict"
)
// 本文件是 design/11 §4「敏感操作代审」的对外侧实现(裁决 13.4.3):
// loop 的工具准入钩子判定出「该敏感操作已获(或未获)代审」后,组合根
// 把记录交给这里——① 落审计链 audit_logs(who/what/when/result),
// ② 向 owner/admin 连接广播小字帧。判定与代审本身在
// internal/capability/restrict(Gate);本文件只管「记录 + 呈现」。
// wsApprovalNotice 是主代理代审小字的 WS 事件类型(design/11 §4)。
// 它携带内容(角色名、动作、文案),因此 design/11 §7.1 的帧门默认
// fail-closed:只投递给 owner + admin 连接,viewer 收不到,也不进那 7 个
// 状态机信号白名单。
const wsApprovalNotice = "approval_notice"
// 代审在审计链里的 who/what/result 口径(design/11 §8):
// who = 想动手的角色名,what = "approval.<工具名>",result = granted|denied。
// 代审动作批准/拒绝**各落一行**,因此前缀固定、结果二分。
const (
approvalAuditPrefix = "approval."
approvalResultGrant = "granted"
approvalResultDeny = "denied"
)
// RecordApproval 记录一次主代理代审(design/11 §4 §8;裁决 13.4.3)。
//
// ① 审计:把 who(角色名)/ what(approval.<工具名>)/ when / result
// (granted / denied)追加进 audit_logs —— 代审动作批准与拒绝各落一行,
// 供管理面板「可观测性 → 审计」与通知 feed 查询。
// ② 前端小字:广播 approval_notice 帧,携带 design/11 §4 的固定文案
// 「xxx(角色名) 想要干什么,我审查后批准了」(explain),以及工具名、
// 操作类别与动作摘要(detail,客户端可作副文本或忽略)。
//
// 审计写入用 context.WithoutCancel:代审发生在一次 Run 内部,而 Run 的 ctx
// 可能已被取消(用户中断)——已经发生的代审仍然必须留痕。审计存储坏掉时
// gateway.audit 只记一条日志,不影响代审已经成立的结论。
//
// 方法对 nil 接收者安全(组合根在网关构建完成前调用它时不会 panic)。
func (s *Server) RecordApproval(ctx context.Context, ev restrict.ApprovalEvent) {
if s == nil {
return
}
if ctx == nil {
ctx = context.Background()
}
result := approvalResultDeny
if ev.Allowed {
result = approvalResultGrant
}
s.audit(context.WithoutCancel(ctx), ev.Actor, approvalAuditPrefix+ev.Tool, result)
s.hub.broadcast(wsEvent{
Type: wsApprovalNotice,
SessionID: ev.SessionID,
Data: map[string]any{
"actor": ev.Actor,
"tool": ev.Tool,
"op": string(ev.Op),
"detail": ev.Detail,
"explain": ev.Explain,
"allowed": ev.Allowed,
"at": ev.At.UTC().Format(time.RFC3339Nano),
},
})
}