package audit import ( "context" "errors" "fmt" "log/slog" "strings" "time" "git.yeij.top/AskaEth/Cyrene_v2/internal/logger" ) // Approver 返回的哨兵错误。用 errors.Is 判断它们。 var ( // ErrActionMissing 在 action 为空时返回。 ErrActionMissing = errors.New("audit: action is missing") // ErrUnknownActor 在扮演角色的名字为空时返回。本包没有角色注册表,因此空名字是它 // 唯一能检出的「未知」;某个角色是否真的获授权由上游决定。 ErrUnknownActor = errors.New("audit: actor is unknown") // ErrExplainMissing 在说明为空时返回。 ErrExplainMissing = errors.New("audit: explanation is missing") ) // reviewerMain 是执行该审查的主 agent 所记录的名字。 const reviewerMain = "main" // Approval 是对子代理敏感操作的一次主代理代审的不可变记录。 type Approval struct { // Actor 是扮演角色的名字(角色名)。 Actor string // Action 是被批准的敏感操作。 Action string // Description 是对扮演者想做什么的说明(该操作意味着什么)。它命名为 // Description 而非 Explain,因为 Explain 方法(见下)返回的是前端提示短句。 Description string // Allowed 报告该操作是否获准。 Allowed bool // Reviewer 是执行该审查的主 agent。 Reviewer string // At 是代审发生的时刻。 At time.Time } // Explain 返回描述审查结果的前端提示短句。获准时是 // "「 想要干什么,我审查后批准了」";被拒时用 "我审查后拒绝了"。 处会 // 代入 Actor。 func (a Approval) Explain() string { if a.Allowed { return "「" + a.Actor + " 想要干什么,我审查后批准了」" } return "「" + a.Actor + " 想要干什么,我审查后拒绝了」" } // Approver 记录主代理代审事件及其审计日志。 type Approver struct { log *slog.Logger } // NewApprover 构建一个经 log 写入审计日志的 Approver。该 logger 会被标注来源 // "orchestration/audit"。允许传入 nil logger,此时 approver 静默记录(无日志输出)。 func NewApprover(log *slog.Logger) *Approver { return &Approver{log: logger.WithSource(log, "orchestration/audit")} } // ReviewAndApprove 校验请求、检查 ctx、写入结构化审计日志(谁 / 什么 / 何时 / 结果), // 并返回记录下来的 approval,其 Allowed 置为 true。 // // 某项操作是否真的敏感、是否应予批准由上层策略决定;本方法只记录、叙述并返回结果模型。 // 入参为空时返回 ErrUnknownActor / ErrActionMissing / ErrExplainMissing,ctx 被取消时 // 包装 ctx.Err()。 func (a *Approver) ReviewAndApprove(ctx context.Context, actor, action, describe string) (*Approval, error) { if err := validateApproval(actor, action, describe); err != nil { return nil, err } if err := ctx.Err(); err != nil { return nil, fmt.Errorf("audit: review-and-approve: %w", err) } ap := &Approval{ Actor: actor, Action: action, Description: describe, Allowed: true, Reviewer: reviewerMain, At: time.Now(), } a.logAudit(ap) return ap, nil } // logAudit 写入结构化的「谁/什么/何时/结果」审计记录。 func (a *Approver) logAudit(ap *Approval) { if a.log == nil { return } a.log.Info("audit: review-and-approve", slog.String("actor", ap.Actor), slog.String("action", ap.Action), slog.String("explain", ap.Description), slog.Bool("allowed", ap.Allowed), slog.String("reviewer", ap.Reviewer), slog.Time("at", ap.At), ) } // validateApproval 以对应的哨兵错误拒绝空入参。 func validateApproval(actor, action, describe string) error { if strings.TrimSpace(actor) == "" { return fmt.Errorf("%w: actor is blank", ErrUnknownActor) } if strings.TrimSpace(action) == "" { return fmt.Errorf("%w: action is blank", ErrActionMissing) } if strings.TrimSpace(describe) == "" { return fmt.Errorf("%w: explanation is blank", ErrExplainMissing) } return nil }