feat(gateway): 面板人工加记忆钉 long + skill 解析去回显 + 可观测读侧脱敏

三处遗留(M37/M38/M39)同落 internal/services/gateway:

① adminAddMemory 显式传 RetentionTier=long(主人 2026-10-26 裁定「人工动作
  默认 long」)。此前不传档位,面板典型输入(短寒暄/一次性祈使/时效查询/句首
  时点/进行中)会被 resolveRetention 判 temp 并写 7 天有效期 —— 主人在面板上
  亲手加的记忆会静默消失。只钉这一条入口,Store.Add 默认仍由规则判定
  (自动抽取路径不放宽)。

② adminInstallSkillPlugin 解析失败不再打 err.Error():新建
  admin_plugins_skill_error.go,把失败折叠成「类别(yaml_syntax /
  yaml_type_mismatch / skill_invalid / skill_source)+ 行列号」,日志与 400
  响应用同一份摘要。解码器会把出错值前 7 个字符嵌进错误串(M38 #17),
  原样日志等于把上传内容写进 viewer 可读的日志面。

③ 读侧脱敏走既有 helper,不另写一套:
  - adminLogOf 的 msg 过 trace.RedactText(与写侧同一函数);
  - adminSpanOf 先过 trace.SanitizeRecord(REST 尾部与 observability_trace
    推送帧在同一处收口)。⚠️ 有意改变 admin 响应内容:props 变白名单投影 +
    omitted 摘要;字段名与帧形状不变。行数/级别/时间戳/来源、span 的
    status/耗时/时间/名字一律保住。

测试(每条都验过「删掉实现 ⇒ FAIL」):
  - 面板加记忆 ⇒ 落库 (retention_tier='long', expires_at='');对照组证明同一句
    内容走 Store.Add 仍判 temp;
  - 非法 YAML 含哨兵 ⇒ 响应与日志均无哨兵、都有类别与 line 2(内联/文件/语法/
    来源四类);
  - 日志页 msg 含 sk- 形状凭据 ⇒ [redacted],level/source/time 与非敏感正文保留;
  - trace 响应 props 正文 ⇒ omitted Digest、args_summary 打码,排障字段原样。

验收:go build ./... / go vet ./... 全绿;go test -count=1 -race
./internal/services/gateway/... 全绿。未 push(按任务口径)。
This commit is contained in:
AskaEth committed 2026-10-26 13:53:59 +08:00
1 parent a697fa0a1e
commit fa5d3d23bf
8 files changed
+686 -12

No files matched your search

@@ -27,6 +27,18 @@ type memoryAddRequest struct {
// 「添加记忆」路径:条目默认作用于 persona,因此行为与
// loop 已在写的「记住 X」路径一致。Scope 可省略(默认
// persona),或为 user|persona|project 之一;非法非空 scope 会被拒绝。
//
// 🔴 档位显式钉 `long`(主人 2026-10-26 裁定:「人工动作默认应为 `long`(人显式
// 加一条记忆 = 长期意图,不该被 7 天后静默清掉)」)。不传档位时 `memory.Store.Add`
// 会走 `resolveRetention` 的留存规则(N1~N7),面板最典型的输入恰恰落在反例里
// —— 短寒暄(「谢谢」)、一次性祈使、时效查询、句首时点、进行中状态都会被判
// `temp` 并写 7 天有效期,于是主人在面板上亲手加的一条记忆会静默消失。
// `resolveRetention` 对**显式档位**的处置是「以调用方为准」,因此这一位是真正的
// 钉死而不是提示。M37 已在包内钉过 `Store.RememberManual` 与
// `ManualExtractor.Extract` 两条入口;本处是同一裁定在**面板**这条入口上的落点。
//
// 只钉这一条入口:`Store.Add` 的默认值仍是「未分型 ⇒ 由规则判定」,自动抽取路径
// 绝不放宽(M37 复审口径)。
func (s *Server) adminAddMemory(w http.ResponseWriter, r *http.Request) {
var req memoryAddRequest
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
@@ -59,6 +71,8 @@ func (s *Server) adminAddMemory(w http.ResponseWriter, r *http.Request) {
PersonaID: personaID,
Content: content,
Scope: memory.Scope(rawScope),
// 人工动作 = 长期意图,见上方注释:显式档位优先于留存规则。
RetentionTier: memory.RetentionLong,
})
if err != nil {
s.auditClaims(r, "memory.add", "error")