refactor(trace): 拆 graph.go 471 行 → graph.go / graph_build.go / graph_props.go(纯搬移)
执行方案 DevDocs/audit/_plan.md 阶段 0 · G0-B:该文件距 500 行红线仅 29 行,而后续
11.4.2(memory/guardrail/orchestration 三类 span 生产者)等改动必然推进它 ⇒ 先拆。
- graph.go(159):对外类型与快照 API(FlowStatus* / FlowNode / FlowEdge / FlowGraph /
NodeSpec / RunGraph;NewRunGraph / Start / SeedInput / SeedOutput / Snapshot)
- graph_build.go(245):构图与折叠(Upsert / rememberParent / laneOf / merge / addEdge /
AddSpan / AddSpanStart / kindForSpan / labelForSpan / statusForFinished)
- graph_props.go(77):prop 白名单与脱敏(safeSpanProps / safePropKeys / summarize / truncate)
纯搬移机械证明:拆前与拆后(取非空、非 package、非 import 块行)排序后 **md5 相同、
diff 为空**;声明集合 28 条守恒;唯一新增文本 = 两个新文件的 package 行与 import 块。
`safePropKeys`(含 4bf1f7c 刚加入的 "persona")原样落在 graph_props.go,逐字节未动。
验证:go build/vet 绿;internal/runtime/trace + internal/services/gateway 测试绿(含
graph_persona_test 与 flow_update_ws_test 的 graph_update 线上帧成对契约回归);
linux/amd64 + darwin/arm64 交叉编译绿;三文件 159/245/77 行均 ≤500;gofmt -l 无输出。
This commit is contained in:
1 parent
32634feefa
commit
ec292081b2
3 files changed
+322
-312
No files matched your search
@@ -0,0 +1,77 @@
|
||||
package trace
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// maxSummaryRunes bounds how many runes of a user string / tool result are
|
||||
// rendered into a node label or prop summary, so sensitive or long content is
|
||||
// collapsed to a safe, dashboard-friendly prefix.
|
||||
const maxSummaryRunes = 80
|
||||
|
||||
// safeSpanProps carries only a whitelist of span props to the frontend, plus a
|
||||
// desensitized tool result summary. It never forwards raw arguments or secrets, and
|
||||
// every string is scrubbed of credential shapes and truncated, so long or sensitive
|
||||
// content cannot leak a full payload (design/19 §3).
|
||||
func safeSpanProps(rec SpanRecord) map[string]any {
|
||||
var props map[string]any
|
||||
for _, k := range safePropKeys {
|
||||
v, ok := rec.Props[k]
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
if props == nil {
|
||||
props = make(map[string]any)
|
||||
}
|
||||
props[k] = redactValue(v)
|
||||
}
|
||||
if rec.Kind == SpanKindTool {
|
||||
if v, ok := rec.Props["result"]; ok {
|
||||
if props == nil {
|
||||
props = make(map[string]any)
|
||||
}
|
||||
props["result_summary"] = RedactText(summarize(v))
|
||||
}
|
||||
}
|
||||
return props
|
||||
}
|
||||
|
||||
// safePropKeys are the span prop keys forwarded to a node's props.
|
||||
//
|
||||
// persona 是子代理分支根节点上的「目标角色 id」:orchestration/scheduler.go 只在
|
||||
// subagent_spawn 显式指定角色时才设置它,且该 id 必须先通过角色注册表校验
|
||||
// (ErrUnknownPersona 会拒绝未注册的 id,orchestration/persona.go),因此它是注册表里
|
||||
// 的角色标识,不是用户内容、不是提示词、更不是凭据——同一个 id 本来就随 GET /personas
|
||||
// 与会话材料对外可见。它是 Docs/api-client.md §3.5(v0.47)对客户端的承诺
|
||||
// (design/22 §8-2:让流程图显示「这个子代理是以谁的身份在替班」),漏出白名单就会被
|
||||
// safeSpanProps 静默丢掉。未指定角色派发时该键不存在,符合 v0.47「纯增量」的约定。
|
||||
var safePropKeys = []string{"stop_reason", "step", "error", "error_kind", "tool", "persona"}
|
||||
|
||||
// summarize renders an arbitrary value as a short text string for a prop summary.
|
||||
func summarize(v any) string {
|
||||
switch x := v.(type) {
|
||||
case nil:
|
||||
return ""
|
||||
case string:
|
||||
return x
|
||||
default:
|
||||
b, err := json.Marshal(x)
|
||||
if err != nil {
|
||||
return fmt.Sprintf("%v", x)
|
||||
}
|
||||
return string(b)
|
||||
}
|
||||
}
|
||||
|
||||
// truncate cuts s to at most max runes, appending an ellipsis when it overflows.
|
||||
func truncate(s string, max int) string {
|
||||
if max <= 0 {
|
||||
return s
|
||||
}
|
||||
r := []rune(s)
|
||||
if len(r) <= max {
|
||||
return s
|
||||
}
|
||||
return string(r[:max]) + "…"
|
||||
}
|
||||
Reference in new issue
Block a user