docs(session): state incomplete catalog evidence limits
The child-catalog README did not record the lasting missing-entry consequence under Known Limitations, and its validation wording implied that duplicate or unsupported descriptors had their discovery fields checked. The converter only checks those fields when exactly one supported own descriptor is available. Specify that distinction, retain creation-time checks for existing entries, and state that current V4 reads do not rescan children to repair an omitted entry. Document the first-descriptor composition reader and last-descriptor identity projection accurately: multiple own records violate the establishing provider's exactly-once rule, so the backfill policy does not choose between those consumer policies. This corrects the rationale without changing migration admission. Update the paired English/Chinese package docs and decision note together. Validation: doc-sync 41/41, full lint, and owner tests passed; production source, frozen generations, persistence schema, and recorded fixtures are unchanged.
This commit is contained in:
1 parent
ade7035fd8
commit
e838401a9b
9 files changed
+22
-16
No files matched your search
+2
-2
@@ -2,5 +2,5 @@
|
||||
# side as of the last confirmed-consistent state. Both languages carry equal authority;
|
||||
# after editing either side, bring the other along and re-record with:
|
||||
# pnpm run verify-translation-pairing --write .agents/notes/implemented/bug-fix/2026-09-19-v3-incomplete-child-catalog-evidence.md
|
||||
2026-09-19-v3-incomplete-child-catalog-evidence.md: ec6bb9c0c4bed133a758b528c579285caebbdcf0
|
||||
2026-09-19-v3-incomplete-child-catalog-evidence.zh.md: 8382be6ed28801e2b9ab925740262b4bdcf314a8
|
||||
2026-09-19-v3-incomplete-child-catalog-evidence.md: 22ca91502425257ed30aa4962f5a38497b8a8a92
|
||||
2026-09-19-v3-incomplete-child-catalog-evidence.zh.md: c11e6de2c27b9892a72c28f327b6d06834d84038
|
||||
+4
-2
@@ -12,13 +12,15 @@ A valid V3 child log can have no own descriptor, an unsupported descriptor versi
|
||||
|
||||
The V3→V4 migration appends a missing parent catalog entry only when exactly one supported own child descriptor supplies its discovery fields. Other descriptor counts and unsupported versions contribute no new catalog fact. Existing parent entries and child events remain intact. This replaces only the missing-evidence refusal in the [adjacent migration decision](../architecture/2026-08-31-released-session-format-migrations.md).
|
||||
|
||||
Known child creation times must still match existing parent entries. Available unambiguous mode and label fields must also agree. Invalid known descriptor fields, corrupt logs, unreadable membership, unsupported selected generations, and changed source revisions retain their existing failures.
|
||||
Known child creation times must still match existing parent entries. Mode/label and descriptor fields are checked only for exactly one supported own descriptor. Invalid fields in that descriptor, corrupt logs, unreadable membership, unsupported selected generations, and changed source revisions retain their existing failures.
|
||||
|
||||
## Alternatives considered
|
||||
|
||||
**Refuse the whole parent.** Discovery metadata is insufficient to justify making valid parent history unavailable.
|
||||
|
||||
**Choose a descriptor or infer defaults.** Multiple descriptors do not establish which identity was admitted. Invented mode or label fields would become durable catalog facts without evidence.
|
||||
**Choose the first or last descriptor.** [`foldSubagentDescriptor()`](../../../../packages/subagent/subagent/src/descriptor.ts) takes the first under the establishing provider’s exactly-once rule; the [identity projection](../../../../packages/subagent/subagent/src/projection.ts) takes the last to override inherited identities. Multiple own records violate that rule. Backfill requires one own record rather than choosing between these consumer policies.
|
||||
|
||||
**Infer missing fields.** Invented mode or label fields would become durable catalog facts without evidence.
|
||||
|
||||
## Consequences
|
||||
|
||||
|
||||
+4
-2
@@ -12,13 +12,15 @@ Status: implemented
|
||||
|
||||
V3→V4 迁移只在恰好一个受支持的自身子 descriptor 提供发现字段时,追加缺失的父目录项。其他 descriptor 数量及不受支持的版本不贡献新的目录事实。已有父目录项和子事件保持完整。这只替代[相邻迁移决策](../architecture/2026-08-31-released-session-format-migrations.zh.md)中的证据缺失拒绝规则。
|
||||
|
||||
已知子创建时间仍必须与已有父目录项一致。可获得且明确的 mode 和 label 字段也必须一致。已知 descriptor 字段无效、日志损坏、成员关系不可读、所选代际不受支持以及源修订变化仍保留原有失败行为。
|
||||
已知子创建时间仍必须与已有父目录项一致。只有恰好一个受支持的自身 descriptor 时,才校验其字段并比较 mode/label。该 descriptor 字段无效、日志损坏、成员关系不可读、所选代际不受支持以及源修订变化仍保留原有失败行为。
|
||||
|
||||
## Alternatives considered
|
||||
|
||||
**拒绝整个父 Session。** 发现元数据不足以成为禁止读取有效父历史的理由。
|
||||
|
||||
**选择一个 descriptor 或推断默认值。** 多个 descriptor 无法确定哪个身份已被准入。推断的 mode 或 label 会在没有证据的情况下成为持久目录事实。
|
||||
**选择首个或末个 descriptor。** [`foldSubagentDescriptor()`](../../../../packages/subagent/subagent/src/descriptor.ts) 依据建立提供方恰好写入一次的规则取首个;[身份投影](../../../../packages/subagent/subagent/src/projection.ts)取末个以覆盖继承身份。多个自身记录违反该规则。补填要求一个自身记录,而不在这两种消费者策略之间做选择。
|
||||
|
||||
**推断缺失字段。** 推断的 mode 或 label 会在没有证据的情况下成为持久目录事实。
|
||||
|
||||
## Consequences
|
||||
|
||||
|
||||
@@ -2,5 +2,5 @@
|
||||
# side as of the last confirmed-consistent state. Both languages carry equal authority;
|
||||
# after editing either side, bring the other along and re-record with:
|
||||
# pnpm run verify-translation-pairing --write packages/session/session-format-v3-to-v4/README.md
|
||||
README.md: c067355a8189cf3a23d79c5d59577434b3aa1c92
|
||||
README.zh.md: 4279967f2cb196dd8de40d6689536d3f51098673
|
||||
README.md: 5a251c8430e358f16216a0fdd09497bd29b463d8
|
||||
README.zh.md: c706e7f99d097e1ca6c1b4db2653ed9b7a6f1955
|
||||
@@ -145,8 +145,8 @@ There is no recursive source search. Captured request text, assistant replay sta
|
||||
| One descriptor with version 1 | Require string provider and label; derive `mode: 'continuable'`. |
|
||||
| One descriptor with version 2 or 3 | Require string provider; use its mode and optional label under catalog rules. |
|
||||
| Zero descriptors, or an unsupported descriptor version | May retain an existing parent entry; cannot create a missing entry. |
|
||||
| More than one own descriptor | Preserve the child; no unambiguous discovery fact is available for backfill. |
|
||||
| Existing own parent entry | Retain it and its extensions; require matching child creation time and any available supported mode/label. |
|
||||
| More than one own descriptor | Retain an existing parent entry without mode/label comparison; do not create a missing entry. |
|
||||
| Existing own parent entry | Retain it and its extensions; require matching child creation time and mode/label from exactly one supported own descriptor, when available. |
|
||||
| Missing own parent entry with complete supported evidence | Append a version-0 catalog fact with child id, creation time, mode, and optional label. |
|
||||
| Missing own parent entry without complete evidence | Preserve the parent without inventing a catalog entry. |
|
||||
|
||||
@@ -334,6 +334,7 @@ The edge preserves the recorded request prefix. Provider cache availability and
|
||||
- **Nested historical tool results** — migration refuses results containing another tool-result wrapper because flattening loses its call identity and error status. The original generation remains intact and no V4 successor is published; those histories need a preserving conversion before they can resume.
|
||||
- **Historical tool-result extensions** — outer message JSON properties remain own data properties, including `__proto__` and `constructor`. Unknown wrapper fields have no defined V4 destination and refuse migration. Existing outer `toolCallId` or `isError` fields must agree with the lifted result; conflicts refuse without publishing a successor.
|
||||
- **Retained child logs required** — a parent alone cannot recover unrecorded child ids, creation times, or descriptors. Deleted children cannot be reconstructed from tool arguments; existing parent catalog records remain.
|
||||
- **Missing historical catalog entries** — without exactly one supported own descriptor, an absent parent entry is not backfilled. The child log remains readable by id; current V4 reads do not rescan children to repair that omission.
|
||||
- **Storage scope** — facts cover recognizable children within the same persistence root. Cross-root import and corrupt-log repair are outside this migration.
|
||||
|
||||
<a id="dev-note"></a>
|
||||
|
||||
@@ -145,8 +145,8 @@ V3 未知内容标签变为 `plugin:<original-type>`,其他字段原样保留
|
||||
| 一个 version 1 descriptor | 要求字符串 provider 与 label;导出 `mode: 'continuable'`。 |
|
||||
| 一个 version 2 或 3 descriptor | 要求字符串 provider;按目录规则使用其 mode 和可选 label。 |
|
||||
| 没有 descriptor,或 descriptor 版本不受支持 | 可以保留已有父目录项;不能创建缺失项。 |
|
||||
| 多个自身 descriptor | 保留子 Session;没有可用于补填的明确发现事实。 |
|
||||
| 已有自身父目录项 | 保留条目及其扩展;要求子创建时间以及可用且受支持的 mode/label 一致。 |
|
||||
| 多个自身 descriptor | 保留已有父目录项,不比较 mode/label;不创建缺失项。 |
|
||||
| 已有自身父目录项 | 保留条目及其扩展;要求子创建时间一致,并在恰好一个受支持的自身 descriptor 可用时比较其 mode/label。 |
|
||||
| 缺少自身父目录项,且受支持证据完整 | 追加带 child id、创建时间、mode 和可选 label 的 version-0 目录事实。 |
|
||||
| 缺少自身父目录项,且证据不完整 | 保留父 Session,不凭空创建目录项。 |
|
||||
|
||||
@@ -334,6 +334,7 @@ Fork 种子构造归核心 Session 所有,不属于此迁移。原生 V4 接
|
||||
- **历史嵌套工具结果**——迁移会拒绝包含另一层 tool-result 包装的结果,因为展平会丢失其调用身份和错误状态。原始代际保持不变,也不会发布 V4 后继;这些历史需要能保留信息的转换才能继续运行。
|
||||
- **历史工具结果扩展**——外层消息的 JSON 属性仍保留为自有数据属性,包括 `__proto__` 和 `constructor`。未知 wrapper 字段没有已定义的 V4 存放位置,因此拒绝迁移。已有外层 `toolCallId` 或 `isError` 字段必须与提升后的结果一致;冲突时拒绝且不发布 successor。
|
||||
- **依赖保留的子日志**——仅凭父日志无法恢复未记录的子 id、创建时间或 descriptor。删除的子 Session 无法从工具参数恢复;已存在的父目录记录仍保留。
|
||||
- **历史目录项缺失**——没有恰好一个受支持的自身 descriptor 时,不补填父目录缺失项。子日志仍可按 id 读取;当前 V4 读取不会重新扫描子日志来补齐该条目。
|
||||
- **存储范围**——事实只覆盖同一持久化根目录内可识别的子 Session。跨根目录导入和损坏日志修复不属于此迁移。
|
||||
|
||||
<a id="dev-note"></a>
|
||||
|
||||
@@ -2,5 +2,5 @@
|
||||
# side as of the last confirmed-consistent state. Both languages carry equal authority;
|
||||
# after editing either side, bring the other along and re-record with:
|
||||
# pnpm run verify-translation-pairing --write packages/session/session-persistence-jsonl/README.md
|
||||
README.md: 5907a09a109f2703f67817a20608499e38840dfd
|
||||
README.zh.md: 0720388f29934579efa42a9a9b85e74f80651176
|
||||
README.md: 96c333d9f845943ff44c8d5d539c40d07b9dc0f6
|
||||
README.zh.md: 71126dd909c92aabdee5c07d23505f708dc5e038
|
||||
@@ -81,7 +81,7 @@ The current-generation scanner applies the current codec owner’s structural ad
|
||||
|
||||
`open(id, 'read'|'write')` selects the highest canonical generation. Current input follows the ordinary fast path. For historical input, a read open decodes and migrates the source once, validates the current logical result, and returns it without publishing a successor. A write open reuses that revision-keyed preparation when available, or performs the same preparation, then encodes a same-directory temporary file in bounded chunks, verifies it in a Worker Thread, rechecks the source revision, and publishes the current successor without overwrite before returning. The source remains byte-identical. Source drift after preparation rejects that write open without replacing the logical history already returned to readers; a later write open prepares the new revision. The backend marks decoded event graphs `shared-frozen` when it freezes them before memoization; every nested object and array is frozen, and handle reads and slices preserve that state, including empty slices. Only an unmaterialized pending log reports `detached`. `stat(id)` and `list()` select and translate only the highest generation header without reading event rows or starting migration; snapshots carry the selected file’s `sizeBytes` and a best-effort revision. Current revisions identify that file; historical revisions also fingerprint the selected files across the persistence root, so child changes invalidate cached logical events. Fingerprinting reads filesystem metadata only; unrelated changes conservatively invalidate historical revisions. One `list()` call shares a corpus fingerprint across its historical entries. With `compression: 'none'`, the log is newline-delimited text an external reader can consume directly; the compressed default must be read through the backend.
|
||||
|
||||
Historical body preparation completes the parent catalog through [V3→V4](../session-format-v3-to-v4/README.md). It scans headers, decodes only direct children one at a time, and retains compact descriptor evidence plus source revisions. It rechecks membership and revisions before returning preparation, reusing it, and publishing. Source drift invalidates preparation: read open retries preparation once automatically, while write open refuses publication. Diagnostics name the changed, added, removed, or unreadable child path. An unsupported selected generation or unreadable header anywhere else in the scan refuses migration because it cannot establish complete membership. Ordinary listing still skips those files. Missing, unsupported, or multiple child descriptors prevent only that child’s catalog backfill; existing parent entries and child logs remain available. Invalid known descriptor fields still report an unsupported format; malformed child events, JSON records, and compressed data report corruption at the child path. Filesystem errors and cancellation retain their original errors. Current V4 opens bypass this scan and validate catalog fields, uniqueness, and current delivery ownership before exposing events.
|
||||
Historical body preparation completes the parent catalog through [V3→V4](../session-format-v3-to-v4/README.md). It scans headers, decodes only direct children one at a time, and retains compact descriptor evidence plus source revisions. It rechecks membership and revisions before returning preparation, reusing it, and publishing. Source drift invalidates preparation: read open retries preparation once automatically, while write open refuses publication. Diagnostics name the changed, added, removed, or unreadable child path. An unsupported selected generation or unreadable header anywhere else in the scan refuses migration because it cannot establish complete membership. Ordinary listing still skips those files. Missing, unsupported, or multiple child descriptors prevent only that child’s catalog backfill; existing parent entries and child logs remain available. Invalid fields in exactly one supported own descriptor still report an unsupported format; malformed child events, JSON records, and compressed data report corruption at the child path. Filesystem errors and cancellation retain their original errors. Current V4 opens bypass this scan and validate catalog fields, uniqueness, and current delivery ownership before exposing events.
|
||||
|
||||
Historical `stat` and `list` revisions require metadata work proportional to the root’s Session count. Fresh body preparation scans all selected headers and decodes direct-child bodies; memo reuse still scans membership and checks revisions. Read-only access never publishes an upgrade, so cold processes and evicted preparations repeat that work. Current V4 body reads and revisions avoid the historical corpus scan. See the [measured costs and diagnostic command](../../../.agents/notes/implemented/architecture/2026-08-31-released-session-format-migrations.md#catalog-scan-measurements).
|
||||
|
||||
|
||||
@@ -81,7 +81,7 @@ kind: "package-reference"
|
||||
|
||||
`open(id, 'read'|'write')` 选择最高规范 generation。当前格式输入走普通快速路径。对于历史输入,只读 open 会单遍解码并迁移源、校验当前逻辑结果,然后在不发布后继的情况下返回。写 open 会在可用时复用按 revision 为键的 preparation,否则执行同一套 preparation,再按有界分片编码同目录临时文件、在 Worker Thread 中校验、复查源修订,并在返回前以不覆盖方式发布当前后继。源保持逐字节不变。如果源在 preparation 后发生变化,该次写 open 会失败,已经返回给读方的逻辑历史不会被替换;后续写 open 会针对新的 revision 重新执行 preparation。后端在 memo 化前冻结已解码的 event graph,并在此时将其标记为 `shared-frozen`;每个嵌套对象和数组都会冻结,句柄读取和 slice 即使为空也保留该状态。只有尚未实体化的 pending 空日志报告 `detached`。`stat(id)` 与 `list()` 只选择并转换最高 generation 的 header,不读取事件行,也不启动迁移;快照携带所选文件的 `sizeBytes` 与尽力而为的修订号。当前格式修订号标识该文件;历史格式修订号还包含持久化根目录中所选文件的指纹,因此子日志变化会使缓存的逻辑事件失效。指纹只读取文件系统元数据;无关变化也会保守地使历史修订号失效。一次 `list()` 为其历史条目共用一个全库指纹。选择 `compression: 'none'` 后,日志是外部读取方可直接消费的换行分隔文本;压缩默认值必须经后端读取。
|
||||
|
||||
历史正文准备通过 [V3→V4](../session-format-v3-to-v4/README.zh.md) 补齐父目录:扫描 header,仅逐个解码直属子 Session,保留紧凑的 descriptor 证据及来源修订。在准备返回、复用和发布前重新检查成员集合与修订。来源变化会使准备缓存失效:只读打开自动重新准备一次,写打开则拒绝发布。诊断指出发生变化、新增、删除或无法读取的子日志路径。扫描中其他任何被选中的不支持代际或不可读 header 都会拒绝迁移,因为无法确认成员集合完整。普通列举仍跳过这些文件。子 descriptor 缺失、版本不受支持或存在多个时,只跳过该子 Session 的目录补填;已有父目录项和子日志仍可读取。已知 descriptor 字段无效仍会报告格式不支持;子事件、JSON 记录或压缩数据畸形会在子日志路径报告损坏。文件系统错误与取消保留原始错误。当前 V4 打开跳过此扫描,并在暴露事件之前检查目录字段、唯一性及当前投递归属。
|
||||
历史正文准备通过 [V3→V4](../session-format-v3-to-v4/README.zh.md) 补齐父目录:扫描 header,仅逐个解码直属子 Session,保留紧凑的 descriptor 证据及来源修订。在准备返回、复用和发布前重新检查成员集合与修订。来源变化会使准备缓存失效:只读打开自动重新准备一次,写打开则拒绝发布。诊断指出发生变化、新增、删除或无法读取的子日志路径。扫描中其他任何被选中的不支持代际或不可读 header 都会拒绝迁移,因为无法确认成员集合完整。普通列举仍跳过这些文件。子 descriptor 缺失、版本不受支持或存在多个时,只跳过该子 Session 的目录补填;已有父目录项和子日志仍可读取。恰好一个受支持的自身 descriptor 字段无效时,仍会报告格式不支持;子事件、JSON 记录或压缩数据畸形会在子日志路径报告损坏。文件系统错误与取消保留原始错误。当前 V4 打开跳过此扫描,并在暴露事件之前检查目录字段、唯一性及当前投递归属。
|
||||
|
||||
历史格式的 `stat` 与 `list` 修订号需要与根目录 Session 数量成正比的元数据工作。首次正文准备扫描所有所选 header 并解码直属子正文;复用准备缓存仍扫描成员集合并检查修订。只读访问从不发布升级,因此冷进程与被淘汰的准备缓存会重复这些工作。当前 V4 正文读取和修订号避开历史全库扫描。见[实测成本与诊断命令](../../../.agents/notes/implemented/architecture/2026-08-31-released-session-format-migrations.zh.md#catalog-scan-measurements)。
|
||||
|
||||
|
||||
Reference in new issue
Block a user