Files
Cyrene_v2/internal/runtime/orchestration/audit/note.go
T
AskaEth 20160c66d8
CI / Backend (build / vet / test / cross-compile) (push) Has been cancelled
CI / Frontend (tsc + vite build) (push) Has been cancelled
feat(security): 代审口径 A 落地(仅子代理发起的敏感调用做实质判定 + fail-closed + 理由进审计与小字)
2026-10-05 12:12:31 +08:00

63 lines
1.9 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 audit
import (
"context"
"strings"
"sync"
)
// DecisionNote 把本次代审的**判定理由**带出冻结的调用契约:代审门(capability/restrict)
// 的 Reviewer 只回 (allowed, error),而前端小字要写清「为什么被拒」,因此理由经这条
// **按调用**(不是跨调用)的通道交给组合根,由它拼进小字。零值可用;全部方法对 nil 安全。
type DecisionNote struct {
mu sync.Mutex
reason string
}
// WithDecisionNote 在 ctx 上挂一个全新的按调用理由盒,供本次代审链路写入:
//
// ctx, note := audit.WithDecisionNote(ctx)
// decision := gate.Admit(ctx, call.Name, call.Args) // 代审在内部写入理由
// if r := note.Reason(); r != "" { …拼进前端小字… }
//
// 每次工具调用挂一个新的盒子,因此并发的两次代审绝不互相覆盖理由。
func WithDecisionNote(ctx context.Context) (context.Context, *DecisionNote) {
note := &DecisionNote{}
return context.WithValue(ctx, decisionNoteKey{}, note), note
}
// decisionNoteKey 是理由盒的 context key。
type decisionNoteKey struct{}
// DecisionNoteFrom 返回 ctx 上挂着的理由盒;未挂载时返回 nil(各方法对 nil 安全)。
func DecisionNoteFrom(ctx context.Context) *DecisionNote {
if ctx == nil {
return nil
}
note, _ := ctx.Value(decisionNoteKey{}).(*DecisionNote)
return note
}
// SetReason 记录本次代审的判定理由。空白理由被忽略,且**绝不覆盖**已有的非空理由:
// 一次调用只有一个判定结论,后到的写入不能把先到的事实改掉。
func (n *DecisionNote) SetReason(reason string) {
if n == nil || strings.TrimSpace(reason) == "" {
return
}
n.mu.Lock()
defer n.mu.Unlock()
if n.reason == "" {
n.reason = reason
}
}
// Reason 返回已记录的理由;未记录时返回 ""。
func (n *DecisionNote) Reason() string {
if n == nil {
return ""
}
n.mu.Lock()
defer n.mu.Unlock()
return n.reason
}