63 lines
1.9 KiB
Go
63 lines
1.9 KiB
Go
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
|
||
}
|