Skip to content

[feature] 溯源读出口的方向 2:按 session_id 列那一会话的蒸馏窗口(从 #421 拆出) #524

Closed

Description

@modusensus

这是 #421 剩下的那一格

#421 里写了两个形态:

缺的只有入参方向,存储层已经具备

合并后的 main 上实查:

  • parseProvenanceQuery(dsh-mneme/src/provenance-http.js:85)只认 memoryId;session_id 只出现在响应里(每条窗口带 link.session_id,见 :113)。
  • 底下两层是现成的:store.provenanceFor(memoryId)(src/store.js:2653)与 store.distillRecordsFor(sessionId, seqStart, seqEnd)(:2667);memory_provenance 上 idx_memory_provenance_session(:297)本来就有——按会话反查不缺索引。

所以这是路由与答复层的活,不是存储层的活。

动手前要先定的四件事

  1. 入参形状:?sessionId= 复用同一条路由,还是另开一条?我倾向复用——多一条路径就多一处要登记的地方(SECURITY.md 两张表、README 路由表、security-doc 的清单锁)。代价是同一端点两种主键,参数校验必须互斥且说清:都缺 → 400,都给 → 400 还是择一,得写死一种,别让调用方猜。
  2. 必须有上限。一次会话可能有几十个窗口,每窗口的原文整段返回,等于把「按会话导出全量转录」顺手做完——那正是我们归到 token-write / 受门控读取那一类的全库读取换了个形状。这条如果没有 limit 就不该上。
  3. 门与状态码沿用 feat(api): distill_records 补 HTTP 读出口,记忆溯源可查(#421) #509 那套阶梯:401 → 400 缺参 → 409 distill-records-disabled → 503 provenance-unavailable → 200,判据继续用那一份 isDistillRecordsEnabled(与写侧 src/summarize.js:988 逐字同源,test/provenance-http.test.js 有锁),不在第二处再算一遍。
  4. 文档跟着登记:SECURITY.md 中英两张清单 + README 主要路由表 + docs/DAEMON.md 的路由面枚举。这件事在 [bug] security-doc.test.js 的 bullet() 只校验第一条命中:SECURITY.md 里重复出来的路由清单副本对守卫隐形(#509 撞到) #523 修完之后才真正有守卫兜着——现在那份清单重复一份是不会红的。

关联

Activity

  1. modusensus commented on Nov 2, 2026

    @modusensus
    MemberAuthor

    方向 2 做完了:PR #546(feat/524-provenance-session)。

    GET /memory/provenance?sessionId=<sid>&limit=,与方向 1 共用同一条路径。你列的四件事逐条对应如下。

    ① 入参形状:写死成 400 ambiguous-id

    你留的开放项是「都给 → 400 还是择一」。我选了 400,因为这端点两个方向的答复形状不同(memoryId / sessionId 各认领自己那一侧),择一意味着调用方可以发一个自己也不知道会被读哪个主键的请求。静默选择在这种「两个答案形状都不一样」的接口上只会推迟到出事之后才被发现。

    复用同一条路径而不新开:多一条路径就多四处要登记的地方(SECURITY.md 两张表、包 README 路由表、test/security-doc.test.js 的清单锁),而两个方向读的是同一批数据。顺带一个发现——SECURITY.md 只登记路径、不登记 query 形状,所以方向 2 在那两张表里一行都不用加。这条也让「同路径」这个选择在文档面上自己兑现了一次。

    ② limit:不只是「必须有」,默认值与上界也定死了

    默认 20、上界 100。取法:本机实测一个蒸馏会话的窗口数在个位数,20 已覆盖正常排查;100 比列表类接口(MAX_PAGE_LIMIT 1000 / MAX_SEARCH_TOP_K 200)小一档,因为每个窗口带的是一整段原文而不是一行元数据。

    只收十进制整数字面量,不走 Number()——它把 "" / " " / 0x10 / 1e3 都化成数,于是 ?limit= 会静默变成 0 个窗口(reclaim-http.js 的 parseReclaimQuery 为同一理由立过这条规矩)。越界也给 400 不截到 100。

    另外把你这条诉求的后半截补上了:truncated。少了它,被截断的答复与「本来就只有这么多」逐字节相同,调用方会把半份转录当成全部。session 侧向 store 要 limit + 1 行、多出来那条只当探针不出口;memory 侧 provenanceFor 本来就全量返回,按长度判。

    ③ 门与状态码:阶梯沿用 #509,但 503 改成按方向判

    400(missing-id / ambiguous-id / invalid-limit)→ 409 → 503 → 200,判据仍是 isDistillRecordsEnabled、与 src/summarize.js 逐字同源。

    改的一处:503 原来判「service 上有没有那两个方法」,现在分 REQUIRED_METHODS = { memory: [...], session: [...] }。只缺 session 那侧方法时方向 1 仍然 200——把一个方向的问题报成整张脸坏了,会让人去查一个没坏的部署。

    ④ 文档登记:四处 + CHANGELOG

    dsh-mneme/README.md 路由表、docs/DISTILL.md §10(新增三段:两个读方向共用同一条路径 / 为什么不新开一条路径 / limit 与 truncated)、docs/DAEMON.md(路由面 + 状态码阶梯 + 一整段方向说明)、CHANGELOG.md 的 [Unreleased] / 🆕 新增。

    一个你正文里没点名、但动手时必须先定的实现细节

    方向 2 不能逐链接返回。 memory_provenance 的粒度是「一条记忆 → 一个窗口」,而一次蒸馏窗口通常产出多条记忆(saveMemoryProvenance 在 src/summarize.js 的写窗口循环里按条调用),照原样列会让「这个会话蒸了几次」这个唯一有意义的计数被重复行淹没。所以新增的 store.distillWindowsFor 走 GROUP BY session_id, seq_start, seq_end,带 COUNT(*) AS link_count。

    两个分组键的选择都是踩过才定下来的:

    • window_id 不能当分组键:它是逐条记录生成的 session:<id>:<seq>,一行一个 seq,拿它分组会退化成「列原文」——那样方向 2 就退化成方向 1 换个入口了。
    • ORDER BY seq_start 而不是 created_at:seq 是事件序号、就是会话内的先后,按它排才是「这个会话按时间蒸了哪几段」;按 created_at 会把同一批重跑的行打散。

    link_count 只说明「这个窗口派生出几条记忆」,刻意不回 memory_id 列表——方向 1 已经是「记忆 → 窗口」,把 id 也塞进这一侧会让两个方向不再正交(同一份映射有两条读法就得有两处维护),真要逐条查仍走方向 1。

    回归测试 +16,全量 2623 pass / 0 fail

    test/provenance-http.test.js +11(两个主键都缺/都给/裁空白、limit 十二种畸形与越界、session 方向一行一窗口 + link_count + 顺序、两方向各自的 truncated、503 按方向判、开关关时 session 方向也 409 且零 SQL);test/api.test.js +3(GROUP BY 并行 + link_count=2 + 跨会话不串、两键都给 400、limit=0 400 与 limit=2 truncated);test/standalone-api.test.js +2(?sessionId= 列窗口、两键都给 400 且 limit 六种畸形全 400)。


    #524 我留着没关,等你过一眼。如果你觉得第 ⑤ 件事该加(比如给方向 2 也支持「只要窗口元数据、连原文都不要」的轻量形态),这个 PR 可以先不合——那个方向会动到 records 字段的有无,属于形状变更,不该混进来。

  2. added a commit that references this issue on Nov 2, 2026
  3. modusensus commented on Nov 2, 2026

    @modusensus
    MemberAuthor

    实现已合入 main(PR #546,a43e7ee),走方向 2:

    • 读出口 ✅ GET /memory/provenance?sessionId=<sid>&limit=,与方向 1 共用同一路径,按选择器分流(src/provenance-http.js +156)
    • 两档契约 ✅ REQUIRED_METHODS 分 memory / session 两档,:113
    • 边界处理 ✅ limit 默认 20 / 上限 100;truncated: true;400 missing-id / ambiguous-id / invalid-limit
    • 回归测试 ✅ test/provenance-http.test.js +175

    CHANGELOG [Unreleased] 有对应条目,自述与代码一致。


Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions