- 只翻注释/文档字符串:剥离注释后代码行逐字节不变(机械校验只剩允许的行尾注释形态) - 裁决编号与 design 节号(02.4.3 / 11.4.2 / design/05 §9 等)逐字保留 - 复核:go build 绿;orchestration 及其子包(audit/comm)测试绿;gofmt 干净
119 lines
4.0 KiB
Go
119 lines
4.0 KiB
Go
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> 想要干什么,我审查后批准了」";被拒时用 "我审查后拒绝了"。<actor> 处会
|
||
// 代入 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
|
||
}
|