## 这是 #421 剩下的那一格 #421 里写了两个形态: - **方向 1**:给定 `memory_id` → 取 `memory_provenance` 的区间 → 按区间取出 `distill_records` 的原文行。**已由 #509 落地**(`GET /api/dsh-mneme/memory/provenance?memoryId=`,两张皮共用同一份 `src/provenance-http.js`)。 - **方向 2**:给定 `session_id` → 列出那一会话里的蒸馏窗口。**还没做。** issue 当时写明那是形态建议不是实现要求,所以我把它单拎出来,不混进 #509。 ## 缺的只有入参方向,存储层已经具备 合并后的 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. **门与状态码沿用 #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` 的路由面枚举。这件事在 #523 修完之后才真正有守卫兜着——现在那份清单重复一份是不会红的。 ## 关联 - #421(本条从它拆出)、#509(方向 1 已交付)、#523(清单锁的缺口) - AGENTS.md 自查 9(新路由必须声明 `policy` 并同步 SECURITY.md 那张表)
Activity
modusensus commentedon Nov 2, 2026
方向 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_LIMIT1000 /MAX_SEARCH_TOP_K200)小一档,因为每个窗口带的是一整段原文而不是一行元数据。只收十进制整数字面量,不走
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=0400 与limit=2truncated);test/standalone-api.test.js+2(?sessionId=列窗口、两键都给 400 且 limit 六种畸形全 400)。#524 我留着没关,等你过一眼。如果你觉得第 ⑤ 件事该加(比如给方向 2 也支持「只要窗口元数据、连原文都不要」的轻量形态),这个 PR 可以先不合——那个方向会动到
records字段的有无,属于形状变更,不该混进来。Merge pull request #546 from slow-stack/feat/524-provenance-session
modusensus commentedon Nov 2, 2026
实现已合入 main(PR #546,
a43e7ee),走方向 2:GET /memory/provenance?sessionId=<sid>&limit=,与方向 1 共用同一路径,按选择器分流(src/provenance-http.js+156)REQUIRED_METHODS分memory/session两档,:113limit默认 20 / 上限 100;truncated: true;400 missing-id/ambiguous-id/invalid-limittest/provenance-http.test.js+175CHANGELOG
[Unreleased]有对应条目,自述与代码一致。