# Agent Note: 会话查询关系追踪

Status: implemented

[English](2026-07-13-session-query-tracing.md) | 中文

## 问题

会话关系分散编码在不可变 header、位置式表面操作和已记录的来源数组中。消费方如果直接重建这些关系，就必须重复实现语料优先级、表面折叠、格式错误日志的处理、确定性的谱系顺序和克隆。位置替换与来源属于不同的图，因此把两者合并为一种通用边类型也会丢失含义。

## 决策

`ctx.sessionQuery` 除精确读取外，还公开 `traceSession(sessionId)` 和 `traceEvent({ sessionId, seq })`。两者都是基于现有「实时数据优先」语料的一次性视图：会话追踪读取一次完整语料列表，事件追踪读取一份逻辑日志并执行一次规范表面折叠。服务在调用结束后不会保留谱系、反向索引或替换状态。

`SessionLineageTrace` 返回目标、按从直接父级到外层父级排序的已知父级，以及递归的后代树；同级节点先按创建时间排序，再按 session id 排序。`complete: true` 会携带已知根节点；`complete: false` 会携带第一个无法解析的父级 id。与目标相连的循环会以 `SESSION_QUERY_INVALID_LINEAGE` 失败。

`SessionEventTrace` 将位置关系与来源关系分开保留。`replacedBy` 是直接的位置替换者，`replacementChain` 沿替换者追踪至最终节点，`replacedEventSeqs` 则列出目标直接移除的真实表面节点。`sourceEventSeqs` 保留日志中直接来源的顺序，而 `derivedEventSeqs` 按日志顺序列出后续的直接反向引用。来源关系不会传递展开。

## 校验边界

事件追踪会在分析表面之前检查目标是否存在。随后，事件列表与追踪都会使用 `dsh-session` 的单遍表面折叠，对加载的日志整体进行接受或拒绝：事件 seq 从零开始且连续；表面标记符合事件类型的适用范围；只有表面事件类型可以携带来源；存在的数组必须非空且没有重复项；每个来源必须是更早的 seq；每次位置替换必须指明并引用它所移除的全部表面节点。任何契约违例都使用 `SESSION_QUERY_INVALID_SURFACE`；系统不存在只用于分类、要求更弱的表面标准。

所有返回的记录与数组都与内部状态分离。已知的实时事件追踪绝不查询持久化；持久化事件追踪保留精确读取所要求的列表／加载一致性检查。会话谱系必然属于跨语料操作，因此也保留跨语料的持久化失败语义。

## 考虑过的替代方案

- **公开独立的追踪辅助函数**：不予采纳，因为源优先级与状态分离边界属于 `ctx.sessionQuery`；公开辅助函数会诱使调用方绕过该边界。
- **合并替换边与来源边**：不予采纳，因为位置替换可以遮蔽表面节点，同时引用不在表面上的构造输入，而消费方需要区分这两种含义。
- **返回传递来源闭包**：不予采纳，因为这会掩盖日志中直接记录的证据、增大结果，并让一条遥远的格式错误边改变原本局部的输出。
- **在格式错误的来源关系上返回尽力而为的追踪结果**：不予采纳，因为结构上看似合理的局部图会显得具有权威性。当规范的关系契约损坏时，精确检查会明确报错。

## 后果

消费方无需缓存或引入第二份语料，即可获得确定性的关系视图。事件追踪每次调用都会执行全日志校验和分配，而谱系追踪每次调用都会列出完整的逻辑语料。这些成本让真源保持明确，并且与承载内容的全文搜索及过滤 API 相互独立。

该功能具备单元测试和服务层覆盖率，但没有快照或端到端 fixture（测试前置数据），因为它没有引入面向模型的消费方、transcript（文本记录）变更或跨进程协议。
