docs(comments): 注释中文化 · internal/runtime/orchestration(29 文件,纯替换)
- 只翻注释/文档字符串:剥离注释后代码行逐字节不变(机械校验只剩允许的行尾注释形态) - 裁决编号与 design 节号(02.4.3 / 11.4.2 / design/05 §9 等)逐字保留 - 复核:go build 绿;orchestration 及其子包(audit/comm)测试绿;gofmt 干净
This commit is contained in:
1 parent
fe90f64a56
commit
eb1828ee7a
29 files changed
+751
-949
No files matched your search
@@ -11,43 +11,40 @@ import (
|
||||
"git.yeij.top/AskaEth/Cyrene_v2/internal/logger"
|
||||
)
|
||||
|
||||
// Sentinel errors returned by Approver. Use errors.Is to test for them.
|
||||
// Approver 返回的哨兵错误。用 errors.Is 判断它们。
|
||||
var (
|
||||
// ErrActionMissing is returned when the action is blank.
|
||||
// ErrActionMissing 在 action 为空时返回。
|
||||
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 在扮演角色的名字为空时返回。本包没有角色注册表,因此空名字是它
|
||||
// 唯一能检出的「未知」;某个角色是否真的获授权由上游决定。
|
||||
ErrUnknownActor = errors.New("audit: actor is unknown")
|
||||
// ErrExplainMissing is returned when the explanation is blank.
|
||||
// ErrExplainMissing 在说明为空时返回。
|
||||
ErrExplainMissing = errors.New("audit: explanation is missing")
|
||||
)
|
||||
|
||||
// reviewerMain is the name recorded for the main agent that performs the review.
|
||||
// reviewerMain 是执行该审查的主 agent 所记录的名字。
|
||||
const reviewerMain = "main"
|
||||
|
||||
// Approval is the immutable record of a single main-agent review-and-approve of
|
||||
// a subagent's sensitive action.
|
||||
// Approval 是对子代理敏感操作的一次主代理代审的不可变记录。
|
||||
type Approval struct {
|
||||
// Actor is the acting role name (角色名).
|
||||
// Actor 是扮演角色的名字(角色名)。
|
||||
Actor string
|
||||
// Action is the sensitive action being approved.
|
||||
// Action 是被批准的敏感操作。
|
||||
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 是对扮演者想做什么的说明(该操作意味着什么)。它命名为
|
||||
// Description 而非 Explain,因为 Explain 方法(见下)返回的是前端提示短句。
|
||||
Description string
|
||||
// Allowed reports whether the action was granted.
|
||||
// Allowed 报告该操作是否获准。
|
||||
Allowed bool
|
||||
// Reviewer is the main agent that performed the review.
|
||||
// Reviewer 是执行该审查的主 agent。
|
||||
Reviewer string
|
||||
// At is when the review-and-approve happened.
|
||||
// At 是代审发生的时刻。
|
||||
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>.
|
||||
// Explain 返回描述审查结果的前端提示短句。获准时是
|
||||
// "「<actor> 想要干什么,我审查后批准了」";被拒时用 "我审查后拒绝了"。<actor> 处会
|
||||
// 代入 Actor。
|
||||
func (a Approval) Explain() string {
|
||||
if a.Allowed {
|
||||
return "「" + a.Actor + " 想要干什么,我审查后批准了」"
|
||||
@@ -55,26 +52,23 @@ func (a Approval) Explain() string {
|
||||
return "「" + a.Actor + " 想要干什么,我审查后拒绝了」"
|
||||
}
|
||||
|
||||
// Approver records main-agent review-and-approve events and their audit logs.
|
||||
// Approver 记录主代理代审事件及其审计日志。
|
||||
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).
|
||||
// NewApprover 构建一个经 log 写入审计日志的 Approver。该 logger 会被标注来源
|
||||
// "orchestration/audit"。允许传入 nil logger,此时 approver 静默记录(无日志输出)。
|
||||
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.
|
||||
// ReviewAndApprove 校验请求、检查 ctx、写入结构化审计日志(谁 / 什么 / 何时 / 结果),
|
||||
// 并返回记录下来的 approval,其 Allowed 置为 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.
|
||||
// 某项操作是否真的敏感、是否应予批准由上层策略决定;本方法只记录、叙述并返回结果模型。
|
||||
// 入参为空时返回 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
|
||||
@@ -94,7 +88,7 @@ func (a *Approver) ReviewAndApprove(ctx context.Context, actor, action, describe
|
||||
return ap, nil
|
||||
}
|
||||
|
||||
// logAudit writes the structured who/what/when/result audit record.
|
||||
// logAudit 写入结构化的「谁/什么/何时/结果」审计记录。
|
||||
func (a *Approver) logAudit(ap *Approval) {
|
||||
if a.log == nil {
|
||||
return
|
||||
@@ -109,7 +103,7 @@ func (a *Approver) logAudit(ap *Approval) {
|
||||
)
|
||||
}
|
||||
|
||||
// validateApproval rejects blank inputs with the matching sentinel.
|
||||
// validateApproval 以对应的哨兵错误拒绝空入参。
|
||||
func validateApproval(actor, action, describe string) error {
|
||||
if strings.TrimSpace(actor) == "" {
|
||||
return fmt.Errorf("%w: actor is blank", ErrUnknownActor)
|
||||
|
||||
@@ -11,7 +11,7 @@ import (
|
||||
"git.yeij.top/AskaEth/Cyrene_v2/internal/logger"
|
||||
)
|
||||
|
||||
// newSilentApprover builds an Approver whose audit log is discarded.
|
||||
// newSilentApprover 构建一个审计日志被丢弃的 Approver。
|
||||
func newSilentApprover() *Approver {
|
||||
return NewApprover(logger.New(logger.Options{Out: io.Discard}))
|
||||
}
|
||||
|
||||
@@ -1,24 +1,19 @@
|
||||
// Package audit provides the subagent-auditing primitives specified by the
|
||||
// Cyrene orchestration design (DevDocs/design/05-子代理.md §3 and §6) and the
|
||||
// security/audit requirements in DevDocs/design/11-安全与权限.md §4 and §8.
|
||||
// Package audit 提供 Cyrene 编排设计所规定的子代理审计原语
|
||||
// (DevDocs/design/05-子代理.md §3 与 §6),以及
|
||||
// DevDocs/design/11-安全与权限.md §4 与 §8 里的安全/审计要求。
|
||||
//
|
||||
// It has two halves:
|
||||
// 它有两半:
|
||||
//
|
||||
// - ContextAuditor gives a main agent read-only, traceable access to a
|
||||
// subagent's context (a []model.Message) so it can audit and pinpoint what
|
||||
// the subagent did: the full context, a tail window, or a keyword hit with
|
||||
// its surrounding lines. It is a pure value type over the message list the
|
||||
// orchestral layer hands it; it holds no storage and no policy.
|
||||
// - ContextAuditor 给主 agent 只读、可追溯地访问某个子代理的上下文
|
||||
// (一个 []model.Message),使其能审计并定位子代理做过什么:整段上下文、
|
||||
// 尾部窗口,或带上下行的关键词命中。它是作用于编排层交给它的消息列表之上的
|
||||
// 纯值类型;不持有存储,也不持有策略。
|
||||
//
|
||||
// - Approver records a main-agent review-and-approve (代审授权) of a
|
||||
// subagent's sensitive action. It writes a structured audit log (who / what
|
||||
// / when / result) and produces the short frontend notice "「xxx 想要干什么,
|
||||
// 我审查后批准了」". Whether an action is actually sensitive and whether it is
|
||||
// allowed is decided by the upper policy layer; this package only records,
|
||||
// narrates, and returns the result model — it never judges permission.
|
||||
// - Approver 记录对子代理敏感操作的「主代理代审授权」。它写入结构化审计日志
|
||||
// (谁 / 什么 / 何时 / 结果),并产出前端短句「「xxx 想要干什么,
|
||||
// 我审查后批准了」」。某项操作是否真的敏感、是否获准由上层策略决定;本包只
|
||||
// 记录、叙述并返回结果模型 —— 绝不判断权限。
|
||||
//
|
||||
// The package is a leaf: it depends only on the standard library,
|
||||
// internal/runtime/model and internal/logger, and never imports the agent loop
|
||||
// or other runtime packages, so it can sit inside runtime/orchestration without
|
||||
// an import cycle.
|
||||
// 本包是叶子:只依赖标准库、internal/runtime/model 与 internal/logger,绝不导入 agent
|
||||
// 循环或其他运行时包,因此能安放在 runtime/orchestration 内而不形成导入环。
|
||||
package audit
|
||||
@@ -9,52 +9,47 @@ import (
|
||||
"git.yeij.top/AskaEth/Cyrene_v2/internal/runtime/model"
|
||||
)
|
||||
|
||||
// ErrQueryEmpty is returned by ContextAuditor.Keyword when the query is empty
|
||||
// or blank. Use errors.Is to test for it.
|
||||
// ErrQueryEmpty 在 ContextAuditor.Keyword 的 query 为空或全为空白时返回。用 errors.Is
|
||||
// 判断它。
|
||||
var ErrQueryEmpty = errors.New("audit: query is empty")
|
||||
|
||||
// contextRadius is how many messages on each side of a keyword hit are included
|
||||
// in the surrounding window (one above and one below).
|
||||
// contextRadius 是关键词命中处两侧各纳入多少条消息作为上下行窗口(上下各一条)。
|
||||
const contextRadius = 1
|
||||
|
||||
// Located describes a single keyword match within a subagent context.
|
||||
// Located 描述子代理上下文中的单次关键词命中。
|
||||
type Located struct {
|
||||
// Line is the 1-based index of the matched message in the full context.
|
||||
// Line 是命中消息在完整上下文中的 1 基下标。
|
||||
Line int
|
||||
// Surrounding is a window centered on the matched message: the message
|
||||
// immediately before it, the matched message, and the message immediately
|
||||
// after it, clamped at the context boundaries. It always contains the
|
||||
// matched message.
|
||||
// Surrounding 是以命中消息为中心的窗口:紧邻其前的那条消息、命中消息本身,
|
||||
// 以及紧随其后的一条消息,在上下文边界处截断。它始终包含命中消息。
|
||||
Surrounding []model.Message
|
||||
}
|
||||
|
||||
// ContextAuditor provides read-only, traceable access to a subagent's context.
|
||||
// It snapshots the message list at construction so later mutation by the caller
|
||||
// does not change what the auditor reports. It is a value type that holds no
|
||||
// storage or policy; the orchestral layer decides which context to inspect.
|
||||
// ContextAuditor 提供对某个子代理上下文的只读、可追溯访问。它在构造时快照消息列表,
|
||||
// 因此调用方此后的修改不会改变审计器所报告的内容。它是值类型,不持有存储或策略;由
|
||||
// 编排层决定要检视哪段上下文。
|
||||
type ContextAuditor struct {
|
||||
messages []model.Message
|
||||
}
|
||||
|
||||
// NewContextAuditor snapshots messages so subsequent caller mutations do not
|
||||
// affect the auditor. A nil input is allowed and yields an empty context.
|
||||
// NewContextAuditor 快照 messages,使调用方后续的修改不影响审计器。允许传入 nil,
|
||||
// 此时得到一个空上下文。
|
||||
func NewContextAuditor(messages []model.Message) ContextAuditor {
|
||||
cp := make([]model.Message, len(messages))
|
||||
copy(cp, messages)
|
||||
return ContextAuditor{messages: cp}
|
||||
}
|
||||
|
||||
// Full returns a defensive copy of the entire context.
|
||||
// Full 返回整段上下文的防御性拷贝。
|
||||
func (a ContextAuditor) Full() []model.Message {
|
||||
cp := make([]model.Message, len(a.messages))
|
||||
copy(cp, a.messages)
|
||||
return cp
|
||||
}
|
||||
|
||||
// Tail returns the last n messages of the context in their original order.
|
||||
// Tail 按原顺序返回上下文最后 n 条消息。
|
||||
//
|
||||
// n <= 0 yields an empty, non-nil slice; n >= len returns a copy of the full
|
||||
// context. The result is a defensive copy.
|
||||
// n <= 0 得到空但非 nil 的切片;n >= len 返回整段上下文的拷贝。结果是防御性拷贝。
|
||||
func (a ContextAuditor) Tail(n int) []model.Message {
|
||||
if n <= 0 {
|
||||
return []model.Message{}
|
||||
@@ -68,13 +63,11 @@ func (a ContextAuditor) Tail(n int) []model.Message {
|
||||
return out
|
||||
}
|
||||
|
||||
// Keyword locates the first message whose text contains query, using a
|
||||
// case-insensitive substring match against the message's text parts (image
|
||||
// payloads are ignored). It returns the message's 1-based index and a
|
||||
// surrounding window, or nil, nil when there is no match.
|
||||
// Keyword 定位首个文本包含 query 的消息,对消息的文本分段做大小写不敏感的子串匹配
|
||||
// (图片载荷被忽略)。它返回该消息的 1 基下标与一个上下行窗口;无命中时返回
|
||||
// nil, nil。
|
||||
//
|
||||
// It returns ErrQueryEmpty for a blank query and wraps ctx.Err() when ctx is
|
||||
// canceled. Only the earliest match is reported.
|
||||
// query 为空时返回 ErrQueryEmpty,ctx 被取消时包装 ctx.Err()。只报告最早的命中。
|
||||
func (a ContextAuditor) Keyword(ctx context.Context, query string) (*Located, error) {
|
||||
if strings.TrimSpace(query) == "" {
|
||||
return nil, fmt.Errorf("%w: query is blank", ErrQueryEmpty)
|
||||
@@ -91,8 +84,7 @@ func (a ContextAuditor) Keyword(ctx context.Context, query string) (*Located, er
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// window returns up to 2*contextRadius+1 messages centered on index i, clamped
|
||||
// to the context boundaries.
|
||||
// window 返回以 index i 为中心、至多 2*contextRadius+1 条消息,并在上下文边界处截断。
|
||||
func (a ContextAuditor) window(i int) []model.Message {
|
||||
lo := i - contextRadius
|
||||
hi := i + contextRadius
|
||||
@@ -110,7 +102,7 @@ func (a ContextAuditor) window(i int) []model.Message {
|
||||
return out
|
||||
}
|
||||
|
||||
// messageText returns the concatenated text of a message's text parts.
|
||||
// messageText 返回一条消息各文本分段拼接后的文本。
|
||||
func messageText(m model.Message) string {
|
||||
var b strings.Builder
|
||||
for _, c := range m.Content {
|
||||
|
||||
@@ -8,7 +8,7 @@ import (
|
||||
"git.yeij.top/AskaEth/Cyrene_v2/internal/runtime/model"
|
||||
)
|
||||
|
||||
// texts builds a context of text-only messages with the given body texts.
|
||||
// texts 用给定的正文文本构建一个仅含文本消息的上下文。
|
||||
func texts(role model.Role, bodies ...string) []model.Message {
|
||||
out := make([]model.Message, 0, len(bodies))
|
||||
for _, b := range bodies {
|
||||
@@ -17,8 +17,8 @@ func texts(role model.Role, bodies ...string) []model.Message {
|
||||
return out
|
||||
}
|
||||
|
||||
// sameMessage compares a message by role and concatenated text; the test
|
||||
// messages are text-only, so this is sufficient for equality.
|
||||
// sameMessage 按角色与拼接后的文本比较两条消息;测试消息都是纯文本,因此这足以判定
|
||||
// 相等。
|
||||
func sameMessage(a, b model.Message) bool {
|
||||
return a.Role == b.Role && messageText(a) == messageText(b)
|
||||
}
|
||||
@@ -29,7 +29,7 @@ func TestNewContextAuditor_Snapshot(t *testing.T) {
|
||||
msgs := texts(model.RoleUser, "alpha", "bravo", "charlie")
|
||||
aud := NewContextAuditor(msgs)
|
||||
|
||||
// Mutating the caller's slice after construction must not affect the audit.
|
||||
// 构造之后再改调用方的切片,不得影响审计结果。
|
||||
msgs[0].Role = model.RoleSystem
|
||||
got := aud.Full()
|
||||
if got[0].Role != model.RoleUser {
|
||||
|
||||
Reference in new issue
Block a user