feat(orchestration/audit): 子代理上下文可审计查询(全量/尾部/关键词定位)+ 主代理代审授权(审计日志+小字)
This commit is contained in:
1 parent
71697ca4f8
commit
8b723009f9
5 files changed
+658
No files matched your search
@@ -0,0 +1,124 @@
|
||||
package audit
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"git.yeij.top/AskaEth/Cyrene_v2/internal/logger"
|
||||
)
|
||||
|
||||
// Sentinel errors returned by Approver. Use errors.Is to test for them.
|
||||
var (
|
||||
// ErrActionMissing is returned when the action is blank.
|
||||
ErrActionMissing = errors.New("audit: action is missing")
|
||||
// ErrUnknownActor is returned when the acting role name is blank. This
|
||||
// package has no role registry, so a blank name is the only "unknown" it
|
||||
// can detect; whether a role is truly authorized is decided upstream.
|
||||
ErrUnknownActor = errors.New("audit: actor is unknown")
|
||||
// ErrExplainMissing is returned when the explanation is blank.
|
||||
ErrExplainMissing = errors.New("audit: explanation is missing")
|
||||
)
|
||||
|
||||
// reviewerMain is the name recorded for the main agent that performs the review.
|
||||
const reviewerMain = "main"
|
||||
|
||||
// Approval is the immutable record of a single main-agent review-and-approve of
|
||||
// a subagent's sensitive action.
|
||||
type Approval struct {
|
||||
// Actor is the acting role name (角色名).
|
||||
Actor string
|
||||
// Action is the sensitive action being approved.
|
||||
Action string
|
||||
// Description is the explanation of what the actor wants to do (what the
|
||||
// action means). It is named Description rather than Explain because the
|
||||
// Explain method (below) returns the frontend notice.
|
||||
Description string
|
||||
// Allowed reports whether the action was granted.
|
||||
Allowed bool
|
||||
// Reviewer is the main agent that performed the review.
|
||||
Reviewer string
|
||||
// At is when the review-and-approve happened.
|
||||
At time.Time
|
||||
}
|
||||
|
||||
// Explain returns the short frontend notice describing the review outcome. For
|
||||
// an allowed action it is "「<actor> 想要干什么,我审查后批准了」"; for a denied
|
||||
// action it uses "我审查后拒绝了". Actor is interpolated in place of <actor>.
|
||||
func (a Approval) Explain() string {
|
||||
if a.Allowed {
|
||||
return "「" + a.Actor + " 想要干什么,我审查后批准了」"
|
||||
}
|
||||
return "「" + a.Actor + " 想要干什么,我审查后拒绝了」"
|
||||
}
|
||||
|
||||
// Approver records main-agent review-and-approve events and their audit logs.
|
||||
type Approver struct {
|
||||
log *slog.Logger
|
||||
}
|
||||
|
||||
// NewApprover builds an Approver that writes audit logs through log. The logger
|
||||
// is tagged with source "orchestration/audit". A nil logger is allowed, making
|
||||
// the approver record silently (no log output).
|
||||
func NewApprover(log *slog.Logger) *Approver {
|
||||
return &Approver{log: logger.WithSource(log, "orchestration/audit")}
|
||||
}
|
||||
|
||||
// ReviewAndApprove validates the request, checks ctx, writes a structured audit
|
||||
// log (who / what / when / result), and returns the recorded approval with
|
||||
// Allowed set to true.
|
||||
//
|
||||
// Whether an action is actually sensitive and whether it should be granted is
|
||||
// decided by the upper policy layer; this method only records, narrates and
|
||||
// returns the result model. It returns ErrUnknownActor / ErrActionMissing /
|
||||
// ErrExplainMissing for blank inputs and wraps ctx.Err() when ctx is canceled.
|
||||
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 writes the structured who/what/when/result audit record.
|
||||
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 rejects blank inputs with the matching sentinel.
|
||||
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
|
||||
}
|
||||
Reference in new issue
Block a user