Files
Cyrene_v2/internal/runtime/orchestration/audit/approval.go
T
AskaEth eb1828ee7a
CI / Backend (build / vet / test / cross-compile) (push) Has been cancelled
CI / Frontend (tsc + vite build) (push) Has been cancelled
docs(comments): 注释中文化 · internal/runtime/orchestration(29 文件,纯替换)
- 只翻注释/文档字符串:剥离注释后代码行逐字节不变(机械校验只剩允许的行尾注释形态)
- 裁决编号与 design 节号(02.4.3 / 11.4.2 / design/05 §9 等)逐字保留
- 复核:go build 绿;orchestration 及其子包(audit/comm)测试绿;gofmt 干净
2026-10-04 13:30:04 +08:00

119 lines
4.0 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"
"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
}