docs(session): nest historical formats under persistence changes

This commit is contained in:
Tianyi Cui committed 2026-09-13 12:19:46 +08:00
1 parent 765c87fb87
commit 38070996c1
46 files changed
+161 -145

No files matched your search

@@ -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/process/2026-08-26-ban-ambiguous-origin-label.md
2026-08-26-ban-ambiguous-origin-label.md: a1379d8d0fbd9d8da124dd6885e543450e714078
2026-08-26-ban-ambiguous-origin-label.zh.md: af57195eb365c385876e9c40b9e5861a496a5b9c
2026-08-26-ban-ambiguous-origin-label.md: b4c161a8a963e0aebdb3dcf24b8aac028429f349
2026-08-26-ban-ambiguous-origin-label.zh.md: 2d592f44429d8622b5a42c55c7f92eae6da96800
@@ -18,7 +18,7 @@ The rule applies to source, tests, documentation, active Agent Notes, prompts, s
`verify-concrete-terms` scans tracked filenames, symlink targets, and text case-insensitively after NFKC normalization. It runs as a quick leaf of `doc-sync`, and its tests prove rejection in prose, identifiers, and paths. Vendored sources and frozen archived Agent Notes remain excluded because their repository policies prohibit direct edits.
Historical persistence evidence retains identifiers from its selected source tree. The check excludes release schema JSON under `docs/persistence-changes/releases/` and `docs/persistence-formats/vN.schema.json`. In `vN.md` and `vN.zh.md`, only the schema content inside one valid pair of `persistence-format-schema` markers is exempt; `verify-persistence-formats` requires that content to match the generated historical schema. Authored prose, current catalogs, and current schemas remain checked. Renaming a captured identifier would misrepresent the historical declarations; the [persistence history decision](2026-09-11-persistence-type-history.md) owns that evidence.
Historical persistence evidence retains identifiers from its selected source tree. The check excludes release schema JSON under `docs/persistence-changes/releases/` and `docs/persistence-changes/historical-formats/vN.schema.json`. In `vN.md` and `vN.zh.md`, only the schema content inside one valid pair of `persistence-format-schema` markers is exempt; `verify-persistence-formats` requires that content to match the generated historical schema. Authored prose, current catalogs, and current schemas remain checked. Renaming a captured identifier would misrepresent the historical declarations; the [persistence history decision](2026-09-11-persistence-type-history.md) owns that evidence.
This decision partially supersedes the earlier decision's rejection of fixed word bans and identifier renames for this one token. The earlier decision remains active for all other abstract language and for choosing each replacement according to its local meaning.
@@ -18,7 +18,7 @@ Status: implemented
`verify-concrete-terms` 会在 NFKC 规范化后,不区分大小写地扫描已跟踪文件名、符号链接目标和文本。它作为 `doc-sync` 的快速叶子门禁运行;对应测试证明行文、标识符和路径中的违规都会被拒绝。vendored 源码与冻结的 archived Agent Note 仍被排除,因为各自的仓库规则禁止直接编辑这些内容。
历史持久化证据保留选定源码树中的标识符。检查排除 `docs/persistence-changes/releases/` 下的发行版 schema JSON 和 `docs/persistence-formats/vN.schema.json`。在 `vN.md` 与 `vN.zh.md` 中,只有一组有效 `persistence-format-schema` 标记内的 schema 内容获得豁免;`verify-persistence-formats` 要求该内容匹配生成的历史 schema。人工说明、当前目录和当前 schema 仍接受检查。重命名已记录的标识符会歪曲历史声明;[持久化历史决策](2026-09-11-persistence-type-history.zh.md)负责这类证据。
历史持久化证据保留选定源码树中的标识符。检查排除 `docs/persistence-changes/releases/` 下的发行版 schema JSON 和 `docs/persistence-changes/historical-formats/vN.schema.json`。在 `vN.md` 与 `vN.zh.md` 中,只有一组有效 `persistence-format-schema` 标记内的 schema 内容获得豁免;`verify-persistence-formats` 要求该内容匹配生成的历史 schema。人工说明、当前目录和当前 schema 仍接受检查。重命名已记录的标识符会歪曲历史声明;[持久化历史决策](2026-09-11-persistence-type-history.zh.md)负责这类证据。
本决策针对这一个术语,部分取代了旧决策中不采用固定禁词和标识符重命名的选择。旧决策仍适用于其他抽象语言,也仍负责要求根据每处使用的具体含义选择替代名称。
@@ -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/process/2026-09-11-persistence-type-history.md
2026-09-11-persistence-type-history.md: f6dc5ed5415d9d53ccd37ffcab80cbc521bc2443
2026-09-11-persistence-type-history.zh.md: b58f91af993ba123823eac15df4189bbbeb75fec
2026-09-11-persistence-type-history.md: e51e9d565718a752ad4209010665f244725db102
2026-09-11-persistence-type-history.zh.md: 10dfd0565c710c2714c8f87dd290eb351c572a90
@@ -20,7 +20,7 @@ The new document kind preserves the compatibility reasoning and evidence for a h
Retrospective [release comparisons](../../../../docs/persistence-changes/releases/README.md) use a separate `persistence-release` kind and pinned tag manifest. They retain changed after schemas and reconstruct adjacent releases, including unchanged tags. Earlier releases predate these acknowledgement rules, so their classifications are informational and their observed versions are not rewritten to satisfy a modern version floor. Historical parsing preserves optional surface operations; the current parser and acknowledgement chain remain strict. This separation permits approximate backfills without turning them into authorization for current changes.
The [format references](../../../../docs/persistence-formats/README.md) preserve complete inventories for every historical integer below the writer constant. The current writer retains the existing generated catalog. Coverage is derived from the constant rather than a hand-maintained version manifest, so advancing the writer requires archiving its predecessor. The archive command copies the validated current inventory, retains its root-reachable definitions, removes source line numbers, and refuses to overwrite an existing snapshot. Source checkpoints remain explicit because event additions and optional payload changes can occur within one format; these snapshots do not authorize current changes or claim migration safety. Historical identifiers are retained in machine schemas and exactly generated schema regions, while current terminology checks still cover the surrounding authored prose.
The [format references](../../../../docs/persistence-changes/historical-formats/README.md) preserve complete inventories for every historical integer below the writer constant. The current writer retains the existing generated catalog. Coverage is derived from the constant rather than a hand-maintained version manifest, so advancing the writer requires archiving its predecessor. The archive command copies the validated current inventory, retains its root-reachable definitions, removes source line numbers, and refuses to overwrite an existing snapshot. Source checkpoints remain explicit because event additions and optional payload changes can occur within one format; these snapshots do not authorize current changes or claim migration safety. Historical identifiers are retained in machine schemas and exactly generated schema regions, while current terminology checks still cover the surrounding authored prose.
Record commands generate the bilingual catalog and consistency records from repository-owned templates. Authors can supply the two languages' summary, compatibility reasoning, and actual verification evidence as structured input. Generation supplies identifiers, digests, and snapshots; it never invents a compatibility explanation or test result. Structured check output retains a nonzero failure exit status and reports stable change kinds independently of human-readable descriptions.
@@ -20,7 +20,7 @@ Status: implemented
回溯性的[发行版本比较](../../../../docs/persistence-changes/releases/README.zh.md)使用独立的 `persistence-release` 类型和固定 tag 清单。它们保留变化后的 schema,并重建相邻版本,包括未变的 tag。早期版本先于这些确认规则,因此其分类只提供信息,观察到的版本号不会为满足当前最低版本要求而被改写。历史解析保留可选的 surface 操作;当前解析器和确认链仍保持严格。这一区分允许近似回填,同时不会将其变成当前变更的授权。
[格式参考](../../../../docs/persistence-formats/README.zh.md)为低于写入器常量的每个历史整数版本保留完整目录。当前写入器继续使用现有生成目录。覆盖范围由常量推导,而非手工维护的版本清单,因此推进写入器必须归档其前驱。归档命令复制已验证的当前目录,保留从根类型可达的定义,移除源码行号,并拒绝覆盖已有快照。同一格式内可能新增事件或改变可选载荷,因此来源检查点保持明确;这些快照不授权当前变更,也不声称迁移安全。机器 schema 与精确生成的 schema 区间保留历史标识符,当前术语检查仍覆盖周围的人工说明。
[格式参考](../../../../docs/persistence-changes/historical-formats/README.zh.md)为低于写入器常量的每个历史整数版本保留完整目录。当前写入器继续使用现有生成目录。覆盖范围由常量推导,而非手工维护的版本清单,因此推进写入器必须归档其前驱。归档命令复制已验证的当前目录,保留从根类型可达的定义,移除源码行号,并拒绝覆盖已有快照。同一格式内可能新增事件或改变可选载荷,因此来源检查点保持明确;这些快照不授权当前变更,也不声称迁移安全。机器 schema 与精确生成的 schema 区间保留历史标识符,当前术语检查仍覆盖周围的人工说明。
记录命令从仓库内模板生成双语目录和一致性记录。作者可以将两种语言的概述、兼容性说明和实际验证证据作为结构化输入。生成过程提供标识符、摘要和快照,不编造兼容性说明或测试结果。结构化检查输出保留非零失败退出码,并报告独立于可读描述的稳定变更种类。
+1 -1
View File
@@ -56,7 +56,7 @@ The `kind` frontmatter field selects exactly one README template. Every kind in
- `package-bundle` → [templates/package-bundle.md](templates/package-bundle.md): a package declaring `dsh.bundle.patch` — the verified `dsh plugin` install path, layer semantics, patch document.
- `persistence-change` → [templates/persistence-change.md](templates/persistence-change.md): a dated record in `docs/persistence-changes/` — acknowledge a detected type transition with per-root predecessors and generated after schemas.
- `persistence-release` → [templates/persistence-release.md](templates/persistence-release.md): a pinned tag in `docs/persistence-changes/releases/` — compare reconstructed historical types without claiming compatibility acknowledgement.
- `persistence-format` → [templates/persistence-format.md](templates/persistence-format.md): a historical Session format checkpoint in `docs/persistence-formats/`, with complete schemas; the current writer uses the existing catalog.
- `persistence-format` → [templates/persistence-format.md](templates/persistence-format.md): a historical Session format checkpoint in `docs/persistence-changes/historical-formats/`, with complete schemas; the current writer uses the existing catalog.
Open the template before writing and follow its skeleton and rules; it states what the kind is, how the page is structured, and the fact checks each section owes. Add a new kind only together with a distinct template file, a documented repository position or declared owner, and a focused check that maps documents to it.
@@ -29,7 +29,7 @@ kind: "package-reference"
## The kind system
`kind` selects the document template directly; every kind maps to exactly one template that exists in this skill, and no template exists without a kind. Files named `docs/persistence-changes/YYYY-MM-DD-slug.md` use `persistence-change`; files named `docs/persistence-changes/releases/dsh-v*.md` use `persistence-release`. Files named `docs/persistence-formats/vN.md` use `persistence-format`. Their Chinese siblings use the same kind. Folder READMEs are indexes, not records. Derive package README kinds mechanically, in this order:
`kind` selects the document template directly; every kind maps to exactly one template that exists in this skill, and no template exists without a kind. Files named `docs/persistence-changes/YYYY-MM-DD-slug.md` use `persistence-change`; files named `docs/persistence-changes/releases/dsh-v*.md` use `persistence-release`. Files named `docs/persistence-changes/historical-formats/vN.md` use `persistence-format`. Their Chinese siblings use the same kind. Folder READMEs are indexes, not records. Derive package README kinds mechanically, in this order:
1. The README is `packages/README.md` or `packages/<group>/README.md` → `package-group`.
2. The package manifest declares `dsh.bundle.patch` → `package-bundle`.
@@ -44,7 +44,7 @@ kind: "package-reference"
| `package-bundle` | `packages/<group>/<package>/README.md` declaring `dsh.bundle.patch` | [package-bundle.md](../templates/package-bundle.md) | Installable layer: the verified `dsh plugin` install path, layer semantics, and patch document. |
| `persistence-change` | `docs/persistence-changes/YYYY-MM-DD-slug.md` and its Chinese sibling | [persistence-change.md](../templates/persistence-change.md) | Historical acknowledgement: mechanically detected type changes, per-root predecessor references, compatibility decision, and generated after schemas. |
| `persistence-release` | `docs/persistence-changes/releases/dsh-v*.md` and its Chinese sibling | [persistence-release.md](../templates/persistence-release.md) | Retrospective observation: pinned tag, adjacent before/after digests, actual version constants, and changed after schemas; no compatibility acknowledgement. |
| `persistence-format` | `docs/persistence-formats/vN.md` and its Chinese sibling | [persistence-format.md](../templates/persistence-format.md) | Selected historical format: source evidence, complete root schemas and reachable types; the current writer uses the generated catalog. |
| `persistence-format` | `docs/persistence-changes/historical-formats/vN.md` and its Chinese sibling | [persistence-format.md](../templates/persistence-format.md) | Selected historical format: source evidence, complete root schemas and reachable types; the current writer uses the generated catalog. |
Before assigning `package-library` or `package-bundle`, inspect the facts: read `package.json` for `dsh.bundle.patch` and `src/index.ts` for the entry shape (`apply` export or a default service export is a plugin; a plain module API is a library). `dsh plugin --profile <name> add <package>` installs any npm dependency, but the profile reconcile activates a layer only for a package that declares `dsh.bundle`; never present that command as an install path for a library or a plain plugin. The documentation check derives the expected kind from these same facts, rejects another value, and rejects `name`, `audience`, `tags`, and README-local `i18n` metadata. Add a new kind only with a distinct template, an unambiguous repository position or declared owner, and a focused check that maps documents to it.
@@ -1,6 +1,6 @@
# Template: persistence-format
Use this kind for `docs/persistence-formats/vN.md` and its Chinese sibling. Each record describes one explicitly selected historical Session format checkpoint with complete declared types. It is a format reference, not a release record or compatibility acknowledgement. The checkout writer uses the existing current catalog.
Use this kind for `docs/persistence-changes/historical-formats/vN.md` and its Chinese sibling. Each record describes one explicitly selected historical Session format checkpoint with complete declared types. It is a format reference, not a release record or compatibility acknowledgement. The checkout writer uses the existing current catalog.
## Frontmatter
@@ -22,4 +22,4 @@ kind: persistence-format
7. **Verification and limitations:** state actual extraction evidence, coverage limits, and the offline verifier. Preserve historical optional fields and opaque declarations without claiming codec admission or migration safety.
8. **Dev Note:** reserve non-authoritative working context, or write `None.`.
The [format index](../../../../docs/persistence-formats/README.md) owns coverage and maintenance. `pnpm run verify-persistence-formats --write` validates schemas before refreshing generated regions and pairing records. It cannot create historical evidence or repair machine data. Keep every older integer documented when the current writer advances; do not duplicate the current catalog under a historical filename.
The [format index](../../../../docs/persistence-changes/historical-formats/README.md) owns coverage and maintenance. `pnpm run verify-persistence-formats --write` validates schemas before refreshing generated regions and pairing records. It cannot create historical evidence or repair machine data. Keep every older integer documented when the current writer advances; do not duplicate the current catalog under a historical filename.
+1 -1
View File
@@ -16,4 +16,4 @@
docs/persistence-schema.json linguist-generated=true
docs/persistence-changes/*.schema.json linguist-generated=true
docs/persistence-changes/releases/*.schema.json linguist-generated=true
docs/persistence-formats/*.schema.json linguist-generated=true
docs/persistence-changes/historical-formats/*.schema.json linguist-generated=true
+1 -1
View File
@@ -24,7 +24,7 @@ Each fact has one home: the tier whose job it is; elsewhere, link there.
| [subsystems/](subsystems/README.md) | One reference page per subsystem: type definitions, semantics, and the generated Cordis API | Behavior narration (→ architecture.md) |
| [Agent Notes](../.agents/notes/README.md) | Active decision records: the why, what-was-given-up, and required verification; `implemented/` notes describe shipped reality in present tense | Migration plans, acceptance-task checklists, fixture walkthroughs, and spec-speak ("should…") once the decision has shipped; archived notes are frozen history, never current authority |
| [postmortem/](postmortem/README.md) | Incident stories — the only tier where war-story narrative belongs | — |
| [Persistence history](persistence-changes/README.md) and [format references](persistence-formats/README.md) | Type-change acknowledgements, release comparisons, and complete historical format schemas | Behavior-only changes; current runtime contracts |
| [Persistence history](persistence-changes/README.md) and [format references](persistence-changes/historical-formats/README.md) | Type-change acknowledgements, release comparisons, and complete historical format schemas | Behavior-only changes; current runtime contracts |
| [cookbook/](cookbook/adding-a-package.md) | Step-by-step how-tos with numbered verify steps | Design rationale (→ the Agent Note each guide links) |
| [user/](user/index.md) | Product-facing guides published by the documentation website | Generated reference tables, contributor procedures, decision history |
| Package README | The per-package contract: config, semantics, limitations, extension points, and [Model Experience](cookbook/adding-a-package.md#4-write-the-package-readme) | JSDoc restatement, generated-catalog restatement (event/tool tables), other packages' concerns |
@@ -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 docs/cookbook/adding-a-session-format-version.md
adding-a-session-format-version.md: 2b939a708221d900fd50e4cca2afaae1ac04daeb
adding-a-session-format-version.zh.md: ee5dac62b45e85e42e9a0a73d5f7248438b4116a
adding-a-session-format-version.md: 6ebc6bd2062acd88dab953bc227bc1046b13f083
adding-a-session-format-version.zh.md: c66a045303f96c2ef237cf3b1c1dc598e9c8f5f6
@@ -27,7 +27,7 @@ Released codecs and migration semantics remain frozen. Do not amend a released e
Use disposable, isolated Harness homes for unreleased N+1 integration testing. An interim N+1 file already has the target writer version, so a later edit to N→N+1 will not migrate that file again. Re-run from unchanged historical input in a fresh test home; never repair this by rewriting a committed generation or reusing a real user's home.
Before changing the writer, use the [archive command](../persistence-formats/README.md#maintenance) to preserve its complete persistence schema, then add the bilingual format reference under `docs/persistence-formats/vN.*`. Preserve all earlier records. The format coverage check requires every integer below the new writer to have its own document; the current generated catalog covers only the new writer.
Before changing the writer, use the [archive command](../persistence-changes/historical-formats/README.md#maintenance) to preserve its complete persistence schema, then add the bilingual format reference under `docs/persistence-changes/historical-formats/vN.*`. Preserve all earlier records. The format coverage check requires every integer below the new writer to have its own document; the current generated catalog covers only the new writer.
<a id="add-an-identity-edge"></a>
## 2. Add an identity edge
@@ -27,7 +27,7 @@
未发布 N+1 的集成测试应使用可丢弃、相互隔离的 Harness home。中间版本产生的 N+1 文件已标为目标写入器版本,因此后续对 N→N+1 的修改不会再次迁移该文件。请在全新测试 home 中从未变更的历史输入重新运行;绝不通过改写已提交代际或复用真实用户 home 来修复这个问题。
变更写入器之前,使用[归档命令](../persistence-formats/README.zh.md#maintenance)保留其完整持久化 schema,再在 `docs/persistence-formats/vN.*` 下添加双语格式参考。保留所有更早的记录。格式覆盖检查要求低于新写入器的每个整数版本都有独立文档;当前生成的目录只覆盖新写入器。
变更写入器之前,使用[归档命令](../persistence-changes/historical-formats/README.zh.md#maintenance)保留其完整持久化 schema,再在 `docs/persistence-changes/historical-formats/vN.*` 下添加双语格式参考。保留所有更早的记录。格式覆盖检查要求低于新写入器的每个整数版本都有独立文档;当前生成的目录只覆盖新写入器。
<a id="add-an-identity-edge"></a>
## 2. 添加恒等迁移边
+2 -2
View File
@@ -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 docs/persistence-catalog.md
persistence-catalog.md: 5668543d48d613ee7d6a485c35e28fd626a1be4d
persistence-catalog.zh.md: 2fcfd89e9579cfdaa2755db5bf8b7795ad5c6502
persistence-catalog.md: acb64b7be1715b58f9fbcdebfaaadf709865c15b
persistence-catalog.zh.md: fab7c7b9f4b8c3ef9edb97035d23accb6ae378b3
+1 -1
View File
@@ -13,7 +13,7 @@ The envelope carries `type`, `seq`, `time`, `data`, optional `ignorable`, and co
The [machine inventory](persistence-schema.json) contains every reachable normalized type and its SHA-256 digest. Root digests include referenced types. Comments, source locations, alias names, erased brands, readonly markers, and harmless field, union, or intersection reordering do not affect these fingerprints. Tuple order, property names, value types, and optionality do. Catalog text and source locations can still produce a diff when digests stay unchanged.
The [format references](persistence-formats/README.md) cover every historical Session format. The [change records](persistence-changes/README.md) acknowledge exact transitions using snapshots kept in this tree. Follow the [review workflow](cookbook/reviewing-persistence-type-changes.md) to classify a change and record it. These checks cover declared type structure; opaque payload contents and behavior without type changes are outside their scope.
The [format references](persistence-changes/historical-formats/README.md) cover every historical Session format. The [change records](persistence-changes/README.md) acknowledge exact transitions using snapshots kept in this tree. Follow the [review workflow](cookbook/reviewing-persistence-type-changes.md) to classify a change and record it. These checks cover declared type structure; opaque payload contents and behavior without type changes are outside their scope.
| Root | Kind | SHA-256 | Resolved type |
|---|---|---|---|
+1 -1
View File
@@ -15,7 +15,7 @@
[机器可读目录](persistence-schema.json)包含所有可达的规范化类型及其 SHA-256 摘要。根类型的摘要涵盖引用类型。注释、源码位置、别名、擦除的品牌标记、readonly 标记以及无语义变化的字段、联合类型或交叉类型重排不影响摘要;元组顺序、属性名称、值类型和可选性会影响摘要。摘要不变时,目录文本和源码位置仍可能产生 diff。
[格式参考](persistence-formats/README.zh.md)覆盖每个历史 Session 格式。[变更记录](persistence-changes/README.zh.md)通过保存在本源码树中的快照确认精确的类型转换。按照[评审流程](cookbook/reviewing-persistence-type-changes.zh.md)分类并记录变更。这些检查覆盖已声明的类型结构;不透明载荷的内部内容和未改变类型的行为变更不在检查范围内。
[格式参考](persistence-changes/historical-formats/README.zh.md)覆盖每个历史 Session 格式。[变更记录](persistence-changes/README.zh.md)通过保存在本源码树中的快照确认精确的类型转换。按照[评审流程](cookbook/reviewing-persistence-type-changes.zh.md)分类并记录变更。这些检查覆盖已声明的类型结构;不透明载荷的内部内容和未改变类型的行为变更不在检查范围内。
| 根类型 | 类别 | SHA-256 | 已解析类型 |
|---|---|---|---|
+2 -2
View File
@@ -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 docs/persistence-changes/README.md
README.md: 3c05a4ce40e8b59c099f6054ab6049242b5e7fff
README.zh.md: 0f90b74fd3196f39c8d3f8928abae053f3b29fc7
README.md: 7eb46efe69e139c294ad76e1b584e2d7384151b5
README.zh.md: d91cfff9e5010bfff92b905ce0f075e7e419eb52
+1 -1
View File
@@ -12,7 +12,7 @@ Use this reference to inspect an acknowledged Session persistence-type change an
For older tagged versions, use the [prerelease archive](releases/README.md). It reconstructs alpha/RC type differences for historical reading and format validation; its observations do not serve as current compatibility acknowledgements.
For complete schemas grouped by Session format, use the [format references](../persistence-formats/README.md). Their coverage follows the writer constant, including intermediate formats without a release tag.
For complete schemas grouped by Session format, use the [format references](historical-formats/README.md). Their coverage follows the writer constant, including intermediate formats without a release tag.
## Table of Contents
+1 -1
View File
@@ -12,7 +12,7 @@ description: "审阅和维护已记录的会话持久化类型变更、对应 sc
较早的 tag 参见[预发行版本归档](releases/README.zh.md)。它重建 alpha/RC 类型差异,供历史阅读和格式校验使用;这些观察记录不作为当前兼容性确认。
按 Session 格式查看完整 schema 时,使用[格式参考](../persistence-formats/README.zh.md)。其覆盖范围由写入器常量决定,包括没有发布 tag 的中间格式。
按 Session 格式查看完整 schema 时,使用[格式参考](historical-formats/README.zh.md)。其覆盖范围由写入器常量决定,包括没有发布 tag 的中间格式。
## 目录
@@ -1,6 +1,6 @@
# Bilingual-pair consistency record (docs/i18n/README.md): the git blob hash of each
# 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 docs/persistence-formats/README.md
README.md: 04f03d454b3cab62786554660fc803869e3be0e8
README.zh.md: b58a4a68aadca8e049e77498fbf9410b6a13cf0a
# pnpm run verify-translation-pairing --write docs/persistence-changes/historical-formats/README.md
README.md: 8dc2caf4409e693c8b68c4e49db67b6c3234c238
README.zh.md: 36095f9872ed6a2bd72d348be280f6d4534aeea7
@@ -8,7 +8,7 @@ English | [中文](README.zh.md)
## Summary
Use this reference to inspect a stored Session generation’s headers, event envelopes, and payload types. Every format below the checkout writer has a bilingual document and a complete schema snapshot. The current format uses the generated persistence catalog. The [version authority](../session-format-status.md) owns the writer constant and release status.
Use this reference to inspect a stored Session generation’s headers, event envelopes, and payload types. Every format below the checkout writer has a bilingual document and a complete schema snapshot. The current format uses the generated persistence catalog. The [version authority](../../session-format-status.md) owns the writer constant and release status.
## Table of Contents
@@ -31,18 +31,18 @@ The index is generated from validated snapshots and the current writer constant.
| 0 | `dsh-v0.1.2-rc.1` | [V0](v0.md) | [JSON](v0.schema.json) | 54 / 415 |
| 1 | PR #3349 | [V1](v1.md) | [JSON](v1.schema.json) | 54 / 415 |
| 2 | `dsh-v0.1.3-alpha.2` | [V2](v2.md) | [JSON](v2.schema.json) | 56 / 435 |
| 3 | Current checkout | [Current catalog](../persistence-catalog.md) | [JSON](../persistence-schema.json) | 59 / 463 |
| 3 | Current checkout | [Current catalog](../../persistence-catalog.md) | [JSON](../../persistence-schema.json) | 59 / 463 |
<!-- persistence-format-index:end -->
<a id="scope"></a>
## Scope and evidence
Each `vN.md` / `vN.zh.md` pair has `kind: persistence-format`, an identical `yaml persistence-format` declaration binding every root key to its captured digest, a pairing sidecar, and a complete `vN.schema.json`. The [template](../../.agents/skills/dsh-doc/templates/persistence-format.md) defines these records. Roots cover the logical Session header, physical JSONL header, event envelope, and all first-party events at the selected checkpoint; each root includes every reachable declared type. Packed physical body records have separate codec owners linked from each page.
Each `vN.md` / `vN.zh.md` pair has `kind: persistence-format`, an identical `yaml persistence-format` declaration binding every root key to its captured digest, a pairing sidecar, and a complete `vN.schema.json`. The [template](../../../.agents/skills/dsh-doc/templates/persistence-format.md) defines these records. Roots cover the logical Session header, physical JSONL header, event envelope, and all first-party events at the selected checkpoint; each root includes every reachable declared type. Packed physical body records have separate codec owners linked from each page.
V0 and V2 use the latest matching tags in the [captured prerelease archive](../persistence-changes/releases/README.md). V1 uses an intermediate source tree identified in its reference because the captured tags contain no V1 writer. Historical sources retain file paths without line numbers. Snapshots preserve historical optional fields and opaque values; they do not substitute current types into older formats. Historical identifiers remain intact only in schema JSON and the verified generated schema regions; authored prose follows current terminology rules.
V0 and V2 use the latest matching tags in the [captured prerelease archive](../releases/README.md). V1 uses an intermediate source tree identified in its reference because the captured tags contain no V1 writer. Historical sources retain file paths without line numbers. Snapshots preserve historical optional fields and opaque values; they do not substitute current types into older formats. Historical identifiers remain intact only in schema JSON and the verified generated schema regions; authored prose follows current terminology rules.
These references describe selected schemas, not historical application replay or migration safety. Same-version event additions and optional payload changes can produce other valid inventories. The prerelease archive retains tag-by-tag differences; [change records](../persistence-changes/README.md) retain current compatibility acknowledgements. Neither history is replaced by these format snapshots.
These references describe selected schemas, not historical application replay or migration safety. Same-version event additions and optional payload changes can produce other valid inventories. The prerelease archive retains tag-by-tag differences; [change records](../README.md) retain current compatibility acknowledgements. Neither history is replaced by these format snapshots.
<a id="maintenance"></a>
## Maintain coverage
@@ -56,7 +56,7 @@ pnpm run verify-persistence-catalog
pnpm run verify-persistence-formats --archive 3
```
Replace `3` with the outgoing writer version for other transitions. `--archive N` creates `vN.schema.json` from the current inventory, retains exactly the types reachable from its roots, and removes source line numbers. It refuses an existing destination, a version other than the current writer, invalid or incomplete schemas, and combination with `--write`. Add the snapshot’s bilingual record and source evidence using the [template](../../.agents/skills/dsh-doc/templates/persistence-format.md). Retain all earlier records. The successor uses the current catalog; copying a new current catalog cannot satisfy the archived predecessor requirement. Follow the [format-version cookbook](../cookbook/adding-a-session-format-version.md) for runtime changes.
Replace `3` with the outgoing writer version for other transitions. `--archive N` creates `vN.schema.json` from the current inventory, retains exactly the types reachable from its roots, and removes source line numbers. It refuses an existing destination, a version other than the current writer, invalid or incomplete schemas, and combination with `--write`. Add the snapshot’s bilingual record and source evidence using the [template](../../../.agents/skills/dsh-doc/templates/persistence-format.md). Retain all earlier records. The successor uses the current catalog; copying a new current catalog cannot satisfy the archived predecessor requirement. Follow the [format-version cookbook](../../cookbook/adding-a-session-format-version.md) for runtime changes.
The schema definitions and index inside comment markers are generated. After advancing the writer and completing the machine data and authored evidence, refresh their tables and pairing records, then verify:
@@ -8,7 +8,7 @@ description: "查找从 V0 到工作区写入器版本的每个 Session 格式
## 概述
本参考用于查阅已存储 Session 代际的 header、事件信封与载荷类型。低于工作区写入器版本的每个格式都有双语文档与完整 schema 快照。当前格式使用生成的持久化目录。[版本真源](../session-format-status.zh.md)负责写入器常量与发布状态。
本参考用于查阅已存储 Session 代际的 header、事件信封与载荷类型。低于工作区写入器版本的每个格式都有双语文档与完整 schema 快照。当前格式使用生成的持久化目录。[版本真源](../../session-format-status.zh.md)负责写入器常量与发布状态。
## 目录
@@ -31,18 +31,18 @@ description: "查找从 V0 到工作区写入器版本的每个 Session 格式
| 0 | `dsh-v0.1.2-rc.1` | [V0](v0.zh.md) | [JSON](v0.schema.json) | 54 / 415 |
| 1 | PR #3349 | [V1](v1.zh.md) | [JSON](v1.schema.json) | 54 / 415 |
| 2 | `dsh-v0.1.3-alpha.2` | [V2](v2.zh.md) | [JSON](v2.schema.json) | 56 / 435 |
| 3 | 当前工作树 | [当前目录](../persistence-catalog.zh.md) | [JSON](../persistence-schema.json) | 59 / 463 |
| 3 | 当前工作树 | [当前目录](../../persistence-catalog.zh.md) | [JSON](../../persistence-schema.json) | 59 / 463 |
<!-- persistence-format-index:end -->
<a id="scope"></a>
## 范围与证据
每组 `vN.md` / `vN.zh.md` 文档包含 `kind: persistence-format`、将每个根类型键绑定到已记录摘要的相同 `yaml persistence-format` 声明、配对记录及完整的 `vN.schema.json`。[模板](../../.agents/skills/dsh-doc/templates/persistence-format.md)定义这些记录。根类型覆盖所选检查点的逻辑 Session header、物理 JSONL header、事件信封与所有第一方事件;每个根包含所有可达的声明类型。打包的物理 body 记录由每页链接的独立 codec 负责。
每组 `vN.md` / `vN.zh.md` 文档包含 `kind: persistence-format`、将每个根类型键绑定到已记录摘要的相同 `yaml persistence-format` 声明、配对记录及完整的 `vN.schema.json`。[模板](../../../.agents/skills/dsh-doc/templates/persistence-format.md)定义这些记录。根类型覆盖所选检查点的逻辑 Session header、物理 JSONL header、事件信封与所有第一方事件;每个根包含所有可达的声明类型。打包的物理 body 记录由每页链接的独立 codec 负责。
V0 与 V2 使用[已收录预发布归档](../persistence-changes/releases/README.zh.md)中最后一个对应 tag。由于收录的 tag 没有 V1 写入器,V1 使用其参考文档中标识的中间源码树。历史来源保留文件路径,不包含行号。快照保留历史可选字段与不透明值,不会将当前类型替换到旧格式中。历史标识符仅在 schema JSON 与已验证的生成 schema 区间中原样保留;人工说明遵循当前术语规则。
V0 与 V2 使用[已收录预发布归档](../releases/README.zh.md)中最后一个对应 tag。由于收录的 tag 没有 V1 写入器,V1 使用其参考文档中标识的中间源码树。历史来源保留文件路径,不包含行号。快照保留历史可选字段与不透明值,不会将当前类型替换到旧格式中。历史标识符仅在 schema JSON 与已验证的生成 schema 区间中原样保留;人工说明遵循当前术语规则。
这些参考描述选定的 schema,不表示历史应用回放结果或迁移安全性。同版本内新增事件与可选载荷变更可以产生其他有效目录。预发布归档保留逐 tag 差异;[变更记录](../persistence-changes/README.zh.md)保留当前兼容性确认。这些格式快照不替代任何一种历史。
这些参考描述选定的 schema,不表示历史应用回放结果或迁移安全性。同版本内新增事件与可选载荷变更可以产生其他有效目录。预发布归档保留逐 tag 差异;[变更记录](../README.zh.md)保留当前兼容性确认。这些格式快照不替代任何一种历史。
<a id="maintenance"></a>
## 维护覆盖
@@ -56,7 +56,7 @@ pnpm run verify-persistence-catalog
pnpm run verify-persistence-formats --archive 3
```
对于其他转换,将 `3` 替换为原写入器版本。`--archive N` 从当前目录创建 `vN.schema.json`,只保留从根类型可达的类型,并移除源码行号。它拒绝已存在的目标、与当前写入器不同的版本、无效或不完整的 schema,以及与 `--write` 的组合。使用[模板](../../.agents/skills/dsh-doc/templates/persistence-format.md)添加该快照的双语记录与来源证据。保留所有更早的记录。后继格式使用当前目录;复制新的当前目录不能满足归档前驱的要求。运行时变更遵循[格式版本实操手册](../cookbook/adding-a-session-format-version.zh.md)。
对于其他转换,将 `3` 替换为原写入器版本。`--archive N` 从当前目录创建 `vN.schema.json`,只保留从根类型可达的类型,并移除源码行号。它拒绝已存在的目标、与当前写入器不同的版本、无效或不完整的 schema,以及与 `--write` 的组合。使用[模板](../../../.agents/skills/dsh-doc/templates/persistence-format.md)添加该快照的双语记录与来源证据。保留所有更早的记录。后继格式使用当前目录;复制新的当前目录不能满足归档前驱的要求。运行时变更遵循[格式版本实操手册](../../cookbook/adding-a-session-format-version.zh.md)。
注释标记内的 schema 定义与索引由工具生成。推进写入器并完成机器数据与人工证据后,刷新其表格和配对记录,再执行验证:
@@ -1,6 +1,6 @@
# Bilingual-pair consistency record (docs/i18n/README.md): the git blob hash of each
# 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 docs/persistence-formats/v0.md
v0.md: c1fad8caaf3ffd02f2960e87414382d6b9fbfe1e
v0.zh.md: 6b088e725f20316a96ac0ea29b57ce4233237c08
# pnpm run verify-translation-pairing --write docs/persistence-changes/historical-formats/v0.md
v0.md: 91b8c239307c2ac721aa4879a4cb9857cd0396ce
v0.zh.md: 735ac81c461e7dcb89aac05cf1ae3997e5e0fd25
@@ -25,7 +25,7 @@ Inspect the V0 persistence types captured at `dsh-v0.1.2-rc.1`. Assistant chunks
<a id="evidence"></a>
## Source evidence
The source is the published tag `dsh-v0.1.2-rc.1`, whose writer constant is 0. This is the last V0 tag in the [captured prerelease archive](../persistence-changes/releases/README.md); the [tag record](../persistence-changes/releases/dsh-v0.1.2-rc.1.md) preserves its release context. Earlier V0 tags have additional type differences and are not replaced by this snapshot.
The source is the published tag `dsh-v0.1.2-rc.1`, whose writer constant is 0. This is the last V0 tag in the [captured prerelease archive](../releases/README.md); the [tag record](../releases/dsh-v0.1.2-rc.1.md) preserves its release context. Earlier V0 tags have additional type differences and are not replaced by this snapshot.
The extracted declarations come from `packages/core/session/src/types.ts`, `packages/session/session-persistence-jsonl/src/format.ts`, and every first-party `SessionEventMap` merge and its reachable types. Source locations below refer to that historical tree, not to current files.
@@ -38,7 +38,7 @@ These facts describe the selected checkpoint. The generated tables below preserv
- The logical header requires `isSeeded`. The physical header instead stores optional `seedLength`, with required `delegationDepth` and `type: "session"`.
- `assistant/chunk.data` contains `chunk`, `turn`, and `step`. `assistant/message.data` has no embedded `stream`, and `assistant/attempt` is absent.
The [V0/V1 codec](../../packages/session/session-format-v0-to-v1/src/codec.ts) owns packed Assistant rows and range-encoded source references. These physical body encodings are not additional roots in this declared-type inventory. The [adjacent migration reference](../../packages/session/session-format-v0-to-v1/README.md) owns accepted legacy normalizations and refusal rules.
The [V0/V1 codec](../../../packages/session/session-format-v0-to-v1/src/codec.ts) owns packed Assistant rows and range-encoded source references. These physical body encodings are not additional roots in this declared-type inventory. The [adjacent migration reference](../../../packages/session/session-format-v0-to-v1/README.md) owns accepted legacy normalizations and refusal rules.
Packed physical rows use `text-chunks`, `reasoning-chunks`, or `tool-call-chunks`, with `seq0`, `time0`, and `data.turn` / `step` / `index` / `dt`. Text and reasoning rows carry `texts`; tool-call rows carry `id`, optional `name`, and `args`. For N members, `dt` has N−1 time gaps, which may be negative. Member k has sequence `seq0 + k` and time `time0` plus the first k gaps. Physical `sourceEventSeqs` may use encoded ranges; the logical schemas below show expanded number arrays.
@@ -5089,7 +5089,7 @@ SHA-256: `a3edd1efd1fef21b72c55e213c8a8b24196b091abde2b89db52be0b2aaae3cfa`
<a id="verification"></a>
## Verification and limitations
The [source-only extractor](../../scripts/persistence-schema.ts) ran with TypeScript 6.0.3. Reconstruction omits redundant `object` intersection members and discards normalization-unreachable type definitions; it preserves existing `any` / `unknown` and does not substitute opaque types for unresolved imports. V0 and V2 root digests match the corresponding complete states reconstructed from the prerelease archive.
The [source-only extractor](../../../scripts/persistence-schema.ts) ran with TypeScript 6.0.3. Reconstruction omits redundant `object` intersection members and discards normalization-unreachable type definitions; it preserves existing `any` / `unknown` and does not substitute opaque types for unresolved imports. V0 and V2 root digests match the corresponding complete states reconstructed from the prerelease archive.
Historical surface-event declarations permit optional `surfaceOp`. This is retained evidence of declared types, not permission to omit fields required by a codec or migration validator. Opaque values, external plugin events, packed body encoding, and runtime relationships are outside the type inventory’s coverage.
@@ -5097,7 +5097,7 @@ Historical surface-event declarations permit optional `surfaceOp`. This is retai
pnpm run verify-persistence-formats
```
The check validates every format through the current writer, schema completeness, bilingual declarations, and generated tables using only this checkout. It does not replay historical applications or establish migration safety. See the [format index](README.md) for maintenance rules and the [current catalog](../persistence-catalog.md) for today’s declarations.
The check validates every format through the current writer, schema completeness, bilingual declarations, and generated tables using only this checkout. It does not replay historical applications or establish migration safety. See the [format index](README.md) for maintenance rules and the [current catalog](../../persistence-catalog.md) for today’s declarations.
<a id="dev-note"></a>
## Dev Note
@@ -25,7 +25,7 @@ kind: persistence-format
<a id="evidence"></a>
## 来源证据
来源为已发布 tag `dsh-v0.1.2-rc.1`,其写入器常量为 0。这是[已收录预发布归档](../persistence-changes/releases/README.zh.md)中最后一个 V0 tag;[tag 记录](../persistence-changes/releases/dsh-v0.1.2-rc.1.zh.md)保留其发布背景。更早的 V0 tag 还存在其他类型差异,本快照不替代那些记录。
来源为已发布 tag `dsh-v0.1.2-rc.1`,其写入器常量为 0。这是[已收录预发布归档](../releases/README.zh.md)中最后一个 V0 tag;[tag 记录](../releases/dsh-v0.1.2-rc.1.zh.md)保留其发布背景。更早的 V0 tag 还存在其他类型差异,本快照不替代那些记录。
提取的声明来自 `packages/core/session/src/types.ts`、`packages/session/session-persistence-jsonl/src/format.ts`,以及所有第一方 `SessionEventMap` 声明合并及其可达类型。下方源码位置指向该历史源码树,而非当前文件。
@@ -38,7 +38,7 @@ kind: persistence-format
- 逻辑 header 要求 `isSeeded`。物理 header 则存储可选的 `seedLength`,并要求 `delegationDepth` 和 `type: "session"`。
- `assistant/chunk.data` 包含 `chunk`、`turn` 和 `step`。`assistant/message.data` 没有内嵌的 `stream`,且不存在 `assistant/attempt`。
[V0/V1 codec](../../packages/session/session-format-v0-to-v1/src/codec.ts)负责打包的 Assistant 行与范围编码的来源引用。这些物理 body 编码不是本声明类型目录中的额外根类型。[相邻迁移参考](../../packages/session/session-format-v0-to-v1/README.zh.md)负责已接受的历史规范化与拒绝规则。
[V0/V1 codec](../../../packages/session/session-format-v0-to-v1/src/codec.ts)负责打包的 Assistant 行与范围编码的来源引用。这些物理 body 编码不是本声明类型目录中的额外根类型。[相邻迁移参考](../../../packages/session/session-format-v0-to-v1/README.zh.md)负责已接受的历史规范化与拒绝规则。
打包的物理行使用 `text-chunks`、`reasoning-chunks` 或 `tool-call-chunks`,包含 `seq0`、`time0` 和 `data.turn` / `step` / `index` / `dt`。文本与推理行携带 `texts`;工具调用行携带 `id`、可选的 `name` 和 `args`。N 个成员对应 N−1 个 `dt` 时间间隔,间隔可以为负数。第 k 个成员的序号为 `seq0 + k`,时间为 `time0` 加上前 k 个间隔。物理 `sourceEventSeqs` 可以使用范围编码;下方逻辑 schema 展示展开后的数值数组。
@@ -5089,7 +5089,7 @@ SHA-256: `a3edd1efd1fef21b72c55e213c8a8b24196b091abde2b89db52be0b2aaae3cfa`
<a id="verification"></a>
## 验证与限制
[仅使用源码的提取器](../../scripts/persistence-schema.ts)以 TypeScript 6.0.3 运行。重建时省略冗余的 `object` 交叉类型成员,并丢弃规范化后不可达的类型定义;保留已有的 `any` / `unknown`,不以不透明类型替代未解析的导入。V0 与 V2 根类型摘要与从预发布归档重建的对应完整状态一致。
[仅使用源码的提取器](../../../scripts/persistence-schema.ts)以 TypeScript 6.0.3 运行。重建时省略冗余的 `object` 交叉类型成员,并丢弃规范化后不可达的类型定义;保留已有的 `any` / `unknown`,不以不透明类型替代未解析的导入。V0 与 V2 根类型摘要与从预发布归档重建的对应完整状态一致。
历史 surface 事件声明允许可选的 `surfaceOp`。这保留了类型声明证据,并不允许省略 codec 或迁移验证器要求的字段。不透明值、外部插件事件、打包的 body 编码与运行时关系均不在类型目录覆盖范围内。
@@ -5097,7 +5097,7 @@ SHA-256: `a3edd1efd1fef21b72c55e213c8a8b24196b091abde2b89db52be0b2aaae3cfa`
pnpm run verify-persistence-formats
```
该检查仅使用本工作树验证直到当前写入器的每个格式、schema 完整性、双语声明与生成表格。它不会回放历史应用,也不确立迁移安全性。维护规则参见[格式索引](README.zh.md),当前声明参见[当前目录](../persistence-catalog.zh.md)。
该检查仅使用本工作树验证直到当前写入器的每个格式、schema 完整性、双语声明与生成表格。它不会回放历史应用,也不确立迁移安全性。维护规则参见[格式索引](README.zh.md),当前声明参见[当前目录](../../persistence-catalog.zh.md)。
<a id="dev-note"></a>
## 开发备注
@@ -1,6 +1,6 @@
# Bilingual-pair consistency record (docs/i18n/README.md): the git blob hash of each
# 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 docs/persistence-formats/v1.md
v1.md: 27d1ac65b8f3eb4474ae9814c43d234e731707ae
v1.zh.md: f9ee57d0b5762ef2cfe4bd0c0847582518c9cc4d
# pnpm run verify-translation-pairing --write docs/persistence-changes/historical-formats/v1.md
v1.md: 2cac9741d03659ef7afa80de4dae4e609b1b730e
v1.zh.md: 6eb7a5bc6fef32df2b6182f35272a9d237ffde16
@@ -25,7 +25,7 @@ Inspect the V1 persistence types immediately before the V2 writer change. V1 use
<a id="evidence"></a>
## Source evidence
The source is the last V1 tree before the first V2 writer change in the ancestry of `dsh-v0.1.3-alpha.1`, associated with PR #3349 (branch `worktree/session-format-03-v0-v1-migration`). Its source change is dated 2026-09-02 and titled `fix(session): close live assistant framing races`. This identifies the intermediate tree, not the final merged PR tree. The [prerelease archive](../persistence-changes/releases/README.md) records no V1 tag, so no product release is invented for this record.
The source is the last V1 tree before the first V2 writer change in the ancestry of `dsh-v0.1.3-alpha.1`, associated with PR #3349 (branch `worktree/session-format-03-v0-v1-migration`). Its source change is dated 2026-09-02 and titled `fix(session): close live assistant framing races`. This identifies the intermediate tree, not the final merged PR tree. The [prerelease archive](../releases/README.md) records no V1 tag, so no product release is invented for this record.
The extracted declarations come from `packages/core/session/src/types.ts`, `packages/session/session-persistence-jsonl/src/format.ts`, and every first-party `SessionEventMap` merge and its reachable types. Source locations below refer to that historical tree, not to current files.
@@ -38,7 +38,7 @@ These facts describe the selected checkpoint. The generated tables below preserv
- The logical header requires `isSeeded`, while the physical header still uses optional `seedLength`. This distinction must survive reconstruction of historical headers.
- `assistant/chunk` remains a top-level event. `assistant/message` has no embedded `stream`, and the V2 `assistant/attempt` event is absent.
The [V0/V1 codec](../../packages/session/session-format-v0-to-v1/src/codec.ts) owns the shared physical layout, including packed chunks and numeric inherited cuts. The [V1-to-V2 migration reference](../../packages/session/session-format-v1-to-v2/README.md) owns chunk consumption, embedded streams, and sequence remapping; those transformations are not inferred from the type digests.
The [V0/V1 codec](../../../packages/session/session-format-v0-to-v1/src/codec.ts) owns the shared physical layout, including packed chunks and numeric inherited cuts. The [V1-to-V2 migration reference](../../../packages/session/session-format-v1-to-v2/README.md) owns chunk consumption, embedded streams, and sequence remapping; those transformations are not inferred from the type digests.
Packed physical rows use `text-chunks`, `reasoning-chunks`, or `tool-call-chunks`, with `seq0`, `time0`, and `data.turn` / `step` / `index` / `dt`. Text and reasoning rows carry `texts`; tool-call rows carry `id`, optional `name`, and `args`. For N members, `dt` has N−1 time gaps, which may be negative. Member k has sequence `seq0 + k` and time `time0` plus the first k gaps. Physical `sourceEventSeqs` may use encoded ranges; the logical schemas below show expanded number arrays.
@@ -5091,7 +5091,7 @@ SHA-256: `a3edd1efd1fef21b72c55e213c8a8b24196b091abde2b89db52be0b2aaae3cfa`
<a id="verification"></a>
## Verification and limitations
The [source-only extractor](../../scripts/persistence-schema.ts) ran with TypeScript 6.0.3. Reconstruction omits redundant `object` intersection members and discards normalization-unreachable type definitions; it preserves existing `any` / `unknown` and does not substitute opaque types for unresolved imports.
The [source-only extractor](../../../scripts/persistence-schema.ts) ran with TypeScript 6.0.3. Reconstruction omits redundant `object` intersection members and discards normalization-unreachable type definitions; it preserves existing `any` / `unknown` and does not substitute opaque types for unresolved imports.
Historical surface-event declarations permit optional `surfaceOp`. This is retained evidence of declared types, not permission to omit fields required by a codec or migration validator. Opaque values, external plugin events, packed body encoding, and runtime relationships are outside the type inventory’s coverage.
@@ -5099,7 +5099,7 @@ Historical surface-event declarations permit optional `surfaceOp`. This is retai
pnpm run verify-persistence-formats
```
The check validates every format through the current writer, schema completeness, bilingual declarations, and generated tables using only this checkout. It does not replay historical applications or establish migration safety. See the [format index](README.md) for maintenance rules and the [current catalog](../persistence-catalog.md) for today’s declarations.
The check validates every format through the current writer, schema completeness, bilingual declarations, and generated tables using only this checkout. It does not replay historical applications or establish migration safety. See the [format index](README.md) for maintenance rules and the [current catalog](../../persistence-catalog.md) for today’s declarations.
<a id="dev-note"></a>
## Dev Note
@@ -25,7 +25,7 @@ kind: persistence-format
<a id="evidence"></a>
## 来源证据
来源为 `dsh-v0.1.3-alpha.1` 祖先历史中首次 V2 写入器变更之前的最后一个 V1 源码树,关联于 PR #3349(分支 `worktree/session-format-03-v0-v1-migration`)。其源码变更日期为 2026-09-02,标题为 `fix(session): close live assistant framing races`。这标识中间源码树,而非 PR 最终合并的源码树。[预发布归档](../persistence-changes/releases/README.zh.md)没有 V1 tag,本记录不会为此虚构产品发布。
来源为 `dsh-v0.1.3-alpha.1` 祖先历史中首次 V2 写入器变更之前的最后一个 V1 源码树,关联于 PR #3349(分支 `worktree/session-format-03-v0-v1-migration`)。其源码变更日期为 2026-09-02,标题为 `fix(session): close live assistant framing races`。这标识中间源码树,而非 PR 最终合并的源码树。[预发布归档](../releases/README.zh.md)没有 V1 tag,本记录不会为此虚构产品发布。
提取的声明来自 `packages/core/session/src/types.ts`、`packages/session/session-persistence-jsonl/src/format.ts`,以及所有第一方 `SessionEventMap` 声明合并及其可达类型。下方源码位置指向该历史源码树,而非当前文件。
@@ -38,7 +38,7 @@ kind: persistence-format
- 逻辑 header 要求 `isSeeded`,物理 header 仍使用可选的 `seedLength`。重建历史 header 时必须保留这一区别。
- `assistant/chunk` 仍为顶层事件。`assistant/message` 没有内嵌的 `stream`,且不存在 V2 的 `assistant/attempt` 事件。
[V0/V1 codec](../../packages/session/session-format-v0-to-v1/src/codec.ts)负责共享物理布局,包括打包的 chunk 与数值继承切点。[V1 到 V2 迁移参考](../../packages/session/session-format-v1-to-v2/README.zh.md)负责 chunk 消耗、内嵌流与序号重映射;这些转换不能从类型摘要中推断。
[V0/V1 codec](../../../packages/session/session-format-v0-to-v1/src/codec.ts)负责共享物理布局,包括打包的 chunk 与数值继承切点。[V1 到 V2 迁移参考](../../../packages/session/session-format-v1-to-v2/README.zh.md)负责 chunk 消耗、内嵌流与序号重映射;这些转换不能从类型摘要中推断。
打包的物理行使用 `text-chunks`、`reasoning-chunks` 或 `tool-call-chunks`,包含 `seq0`、`time0` 和 `data.turn` / `step` / `index` / `dt`。文本与推理行携带 `texts`;工具调用行携带 `id`、可选的 `name` 和 `args`。N 个成员对应 N−1 个 `dt` 时间间隔,间隔可以为负数。第 k 个成员的序号为 `seq0 + k`,时间为 `time0` 加上前 k 个间隔。物理 `sourceEventSeqs` 可以使用范围编码;下方逻辑 schema 展示展开后的数值数组。
@@ -5091,7 +5091,7 @@ SHA-256: `a3edd1efd1fef21b72c55e213c8a8b24196b091abde2b89db52be0b2aaae3cfa`
<a id="verification"></a>
## 验证与限制
[仅使用源码的提取器](../../scripts/persistence-schema.ts)以 TypeScript 6.0.3 运行。重建时省略冗余的 `object` 交叉类型成员,并丢弃规范化后不可达的类型定义;保留已有的 `any` / `unknown`,不以不透明类型替代未解析的导入。
[仅使用源码的提取器](../../../scripts/persistence-schema.ts)以 TypeScript 6.0.3 运行。重建时省略冗余的 `object` 交叉类型成员,并丢弃规范化后不可达的类型定义;保留已有的 `any` / `unknown`,不以不透明类型替代未解析的导入。
历史 surface 事件声明允许可选的 `surfaceOp`。这保留了类型声明证据,并不允许省略 codec 或迁移验证器要求的字段。不透明值、外部插件事件、打包的 body 编码与运行时关系均不在类型目录覆盖范围内。
@@ -5099,7 +5099,7 @@ SHA-256: `a3edd1efd1fef21b72c55e213c8a8b24196b091abde2b89db52be0b2aaae3cfa`
pnpm run verify-persistence-formats
```
该检查仅使用本工作树验证直到当前写入器的每个格式、schema 完整性、双语声明与生成表格。它不会回放历史应用,也不确立迁移安全性。维护规则参见[格式索引](README.zh.md),当前声明参见[当前目录](../persistence-catalog.zh.md)。
该检查仅使用本工作树验证直到当前写入器的每个格式、schema 完整性、双语声明与生成表格。它不会回放历史应用,也不确立迁移安全性。维护规则参见[格式索引](README.zh.md),当前声明参见[当前目录](../../persistence-catalog.zh.md)。
<a id="dev-note"></a>
## 开发备注
@@ -1,6 +1,6 @@
# Bilingual-pair consistency record (docs/i18n/README.md): the git blob hash of each
# 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 docs/persistence-formats/v2.md
v2.md: a40d42b38fa0f928365962e44ebbe3c0fe065b1a
v2.zh.md: 94294395d7d8ca217b4597048d2dbe0b62c957f9
# pnpm run verify-translation-pairing --write docs/persistence-changes/historical-formats/v2.md
v2.md: e37a81da6c34dd5936898d9c87c7f1d31e0b03b9
v2.zh.md: c54170ff050f64c998a4ad3eeeeac216b0dfa913
@@ -25,7 +25,7 @@ Inspect the V2 persistence types captured at `dsh-v0.1.3-alpha.2`. Assistant str
<a id="evidence"></a>
## Source evidence
The source is the published tag `dsh-v0.1.3-alpha.2`, whose writer constant is 2. The [tag record](../persistence-changes/releases/dsh-v0.1.3-alpha.2.md) describes the adjacent release changes. This complete inventory includes unchanged roots as well as that tag’s changed roots.
The source is the published tag `dsh-v0.1.3-alpha.2`, whose writer constant is 2. The [tag record](../releases/dsh-v0.1.3-alpha.2.md) describes the adjacent release changes. This complete inventory includes unchanged roots as well as that tag’s changed roots.
The extracted declarations come from `packages/core/session/src/types.ts`, `packages/session/session-persistence-jsonl/src/format.ts`, and every first-party `SessionEventMap` merge and its reachable types. Source locations below refer to that historical tree, not to current files.
@@ -38,7 +38,7 @@ These facts describe the selected checkpoint. The generated tables below preserv
- `assistant/chunk` is absent. Both `assistant/message.data` and log-only `assistant/attempt.data` require an embedded `stream`.
- Surface replacement declarations retain `start` / `end`; system prompt text remains in `request/header.data.header.system`. The V3 `system/message` event is absent.
The [V2 codec](../../packages/session/session-format-v1-to-v2/src/codec.ts) owns one-event-per-row framing, range-encoded source references, and inherited cuts derived from `session/end-seed` markers. The [V2-to-V3 specification](../../packages/session/session-format-v2-to-v3/README.md#v2-to-v3-specification) owns system-message insertion, PTC event names, and canonical envelope conversion.
The [V2 codec](../../../packages/session/session-format-v1-to-v2/src/codec.ts) owns one-event-per-row framing, range-encoded source references, and inherited cuts derived from `session/end-seed` markers. The [V2-to-V3 specification](../../../packages/session/session-format-v2-to-v3/README.md#v2-to-v3-specification) owns system-message insertion, PTC event names, and canonical envelope conversion.
For a seeded V2 Session, the last `session/end-seed` event carrying `data.inherited: true` identifies the inherited cut by its sequence position. Physical `sourceEventSeqs` may use encoded ranges; the logical schemas retain expanded number arrays.
@@ -5346,7 +5346,7 @@ SHA-256: `a3edd1efd1fef21b72c55e213c8a8b24196b091abde2b89db52be0b2aaae3cfa`
<a id="verification"></a>
## Verification and limitations
The [source-only extractor](../../scripts/persistence-schema.ts) ran with TypeScript 6.0.3. Reconstruction omits redundant `object` intersection members and discards normalization-unreachable type definitions; it preserves existing `any` / `unknown` and does not substitute opaque types for unresolved imports. V0 and V2 root digests match the corresponding complete states reconstructed from the prerelease archive.
The [source-only extractor](../../../scripts/persistence-schema.ts) ran with TypeScript 6.0.3. Reconstruction omits redundant `object` intersection members and discards normalization-unreachable type definitions; it preserves existing `any` / `unknown` and does not substitute opaque types for unresolved imports. V0 and V2 root digests match the corresponding complete states reconstructed from the prerelease archive.
Historical surface-event declarations permit optional `surfaceOp`. This is retained evidence of declared types, not permission to omit fields required by a codec or migration validator. Opaque values, external plugin events, packed body encoding, and runtime relationships are outside the type inventory’s coverage.
@@ -5354,7 +5354,7 @@ Historical surface-event declarations permit optional `surfaceOp`. This is retai
pnpm run verify-persistence-formats
```
The check validates every format through the current writer, schema completeness, bilingual declarations, and generated tables using only this checkout. It does not replay historical applications or establish migration safety. See the [format index](README.md) for maintenance rules and the [current catalog](../persistence-catalog.md) for today’s declarations.
The check validates every format through the current writer, schema completeness, bilingual declarations, and generated tables using only this checkout. It does not replay historical applications or establish migration safety. See the [format index](README.md) for maintenance rules and the [current catalog](../../persistence-catalog.md) for today’s declarations.
<a id="dev-note"></a>
## Dev Note
@@ -25,7 +25,7 @@ kind: persistence-format
<a id="evidence"></a>
## 来源证据
来源为已发布 tag `dsh-v0.1.3-alpha.2`,其写入器常量为 2。[tag 记录](../persistence-changes/releases/dsh-v0.1.3-alpha.2.zh.md)说明相邻发布之间的变化。本完整目录同时包含未变化的根类型和该 tag 变化的根类型。
来源为已发布 tag `dsh-v0.1.3-alpha.2`,其写入器常量为 2。[tag 记录](../releases/dsh-v0.1.3-alpha.2.zh.md)说明相邻发布之间的变化。本完整目录同时包含未变化的根类型和该 tag 变化的根类型。
提取的声明来自 `packages/core/session/src/types.ts`、`packages/session/session-persistence-jsonl/src/format.ts`,以及所有第一方 `SessionEventMap` 声明合并及其可达类型。下方源码位置指向该历史源码树,而非当前文件。
@@ -38,7 +38,7 @@ kind: persistence-format
- 不存在 `assistant/chunk`。`assistant/message.data` 与仅日志用途的 `assistant/attempt.data` 都要求内嵌的 `stream`。
- surface 替换声明保留 `start` / `end`;系统提示词文本仍位于 `request/header.data.header.system`。不存在 V3 的 `system/message` 事件。
[V2 codec](../../packages/session/session-format-v1-to-v2/src/codec.ts)负责每行一个事件的编码、范围编码的来源引用,以及从 `session/end-seed` 标记推导的继承切点。[V2 到 V3 规范](../../packages/session/session-format-v2-to-v3/README.zh.md#v2-to-v3-specification)负责系统消息插入、PTC 事件名称与规范信封转换。
[V2 codec](../../../packages/session/session-format-v1-to-v2/src/codec.ts)负责每行一个事件的编码、范围编码的来源引用,以及从 `session/end-seed` 标记推导的继承切点。[V2 到 V3 规范](../../../packages/session/session-format-v2-to-v3/README.zh.md#v2-to-v3-specification)负责系统消息插入、PTC 事件名称与规范信封转换。
对于已继承的 V2 Session,最后一个携带 `data.inherited: true` 的 `session/end-seed` 事件以其序号位置标识继承切点。物理 `sourceEventSeqs` 可以使用范围编码;逻辑 schema 保留展开后的数值数组。
@@ -5346,7 +5346,7 @@ SHA-256: `a3edd1efd1fef21b72c55e213c8a8b24196b091abde2b89db52be0b2aaae3cfa`
<a id="verification"></a>
## 验证与限制
[仅使用源码的提取器](../../scripts/persistence-schema.ts)以 TypeScript 6.0.3 运行。重建时省略冗余的 `object` 交叉类型成员,并丢弃规范化后不可达的类型定义;保留已有的 `any` / `unknown`,不以不透明类型替代未解析的导入。V0 与 V2 根类型摘要与从预发布归档重建的对应完整状态一致。
[仅使用源码的提取器](../../../scripts/persistence-schema.ts)以 TypeScript 6.0.3 运行。重建时省略冗余的 `object` 交叉类型成员,并丢弃规范化后不可达的类型定义;保留已有的 `any` / `unknown`,不以不透明类型替代未解析的导入。V0 与 V2 根类型摘要与从预发布归档重建的对应完整状态一致。
历史 surface 事件声明允许可选的 `surfaceOp`。这保留了类型声明证据,并不允许省略 codec 或迁移验证器要求的字段。不透明值、外部插件事件、打包的 body 编码与运行时关系均不在类型目录覆盖范围内。
@@ -5354,7 +5354,7 @@ SHA-256: `a3edd1efd1fef21b72c55e213c8a8b24196b091abde2b89db52be0b2aaae3cfa`
pnpm run verify-persistence-formats
```
该检查仅使用本工作树验证直到当前写入器的每个格式、schema 完整性、双语声明与生成表格。它不会回放历史应用,也不确立迁移安全性。维护规则参见[格式索引](README.zh.md),当前声明参见[当前目录](../persistence-catalog.zh.md)。
该检查仅使用本工作树验证直到当前写入器的每个格式、schema 完整性、双语声明与生成表格。它不会回放历史应用,也不确立迁移安全性。维护规则参见[格式索引](README.zh.md),当前声明参见[当前目录](../../persistence-catalog.zh.md)。
<a id="dev-note"></a>
## 开发备注
+2 -2
View File
@@ -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 docs/session-format-status.md
session-format-status.md: b13c82de24028f9d681d23207a0c01852c01021a
session-format-status.zh.md: e7726d3e71e45da3c3dd83942f3733e220328baa
session-format-status.md: eb48234963b866d1bc95bb7c3c72753d54dd93aa
session-format-status.zh.md: a54e98b32b37a723ef44c90d2f4983ea3615fc03
+1 -1
View File
@@ -22,7 +22,7 @@ Use this reference to distinguish the checkout’s Session writer version from t
An alpha, beta, or release-candidate product publication establishes released Session-format obligations. GitHub’s prerelease flag does not make persisted user data disposable. A missing release record is not evidence of non-publication. The [versioning and authority decision](../.agents/notes/implemented/architecture/2026-08-10-session-log-version-mechanism.md) owns compatibility decisions; [released-format migration](../.agents/notes/implemented/architecture/2026-08-31-released-session-format-migrations.md) owns immutable generations and adjacent conversion.
The [format references](persistence-formats/README.md) document every integer from zero through the checkout writer, with historical schemas and the existing current catalog.
The [format references](persistence-changes/historical-formats/README.md) document every integer from zero through the checkout writer, with historical schemas and the existing current catalog.
<a id="release-record"></a>
## Release record
+1 -1
View File
@@ -22,7 +22,7 @@
产品的 alpha、beta 或 release-candidate 发布都会确立已发布 Session 格式的义务。GitHub 的 prerelease 标记不会让持久化用户数据成为可丢弃数据。缺少发布记录不代表尚未发布。[版本与真源决策](../.agents/notes/implemented/architecture/2026-08-10-session-log-version-mechanism.zh.md)拥有兼容性决策;[已发布格式迁移](../.agents/notes/implemented/architecture/2026-08-31-released-session-format-migrations.zh.md)拥有不可变代际与相邻转换规则。
[格式参考](persistence-formats/README.zh.md)记录从零到工作区写入器的每个整数版本,提供历史 schema 并引用现有的当前目录。
[格式参考](persistence-changes/historical-formats/README.zh.md)记录从零到工作区写入器的每个整数版本,提供历史 schema 并引用现有的当前目录。
<a id="release-record"></a>
## 发布记录
+1 -1
View File
@@ -370,7 +370,7 @@ describe('dsh-doc skill consolidation', () => {
})
it('maps historical Session format references to their dedicated document kind', () => {
const files = globSync('docs/persistence-formats/v*.md', { cwd: root })
const files = globSync('docs/persistence-changes/historical-formats/v*.md', { cwd: root })
expect(files.length).toBe(readCurrentSessionFormatVersion(root) * 2)
for (const file of files) {
const metadata = readFrontmatter(file)
+2 -2
View File
@@ -11,7 +11,7 @@ const english = {
envelope: 'Event envelope', events: 'Events', sources: 'Sources: ', source: 'Source: ', types: 'Types: ',
fingerprints: 'Persistence type fingerprints',
fingerprintsIntro: 'The [machine inventory](persistence-schema.json) contains every reachable normalized type and its SHA-256 digest. Root digests include referenced types. Comments, source locations, alias names, erased brands, readonly markers, and harmless field, union, or intersection reordering do not affect these fingerprints. Tuple order, property names, value types, and optionality do. Catalog text and source locations can still produce a diff when digests stay unchanged.',
historyIntro: 'The [format references](persistence-formats/README.md) cover every historical Session format. The [change records](persistence-changes/README.md) acknowledge exact transitions using snapshots kept in this tree. Follow the [review workflow](cookbook/reviewing-persistence-type-changes.md) to classify a change and record it. These checks cover declared type structure; opaque payload contents and behavior without type changes are outside their scope.',
historyIntro: 'The [format references](persistence-changes/historical-formats/README.md) cover every historical Session format. The [change records](persistence-changes/README.md) acknowledge exact transitions using snapshots kept in this tree. Follow the [review workflow](cookbook/reviewing-persistence-type-changes.md) to classify a change and record it. These checks cover declared type structure; opaque payload contents and behavior without type changes are outside their scope.',
rootColumns: '| Root | Kind | SHA-256 | Resolved type |',
definitions: 'Resolved persistence types',
definitionsIntro: 'Each definition appears once. References preserve sharing and recursion; the digest beside a definition includes its complete reachable structure. Source names and locations identify its declarations but are excluded from its digest.',
@@ -29,7 +29,7 @@ const chinese: Record<keyof typeof english, string> = {
envelope: '事件信封', events: '事件', sources: '来源:', source: '来源:', types: '类型:',
fingerprints: '持久化类型指纹',
fingerprintsIntro: '[机器可读目录](persistence-schema.json)包含所有可达的规范化类型及其 SHA-256 摘要。根类型的摘要涵盖引用类型。注释、源码位置、别名、擦除的品牌标记、readonly 标记以及无语义变化的字段、联合类型或交叉类型重排不影响摘要;元组顺序、属性名称、值类型和可选性会影响摘要。摘要不变时,目录文本和源码位置仍可能产生 diff。',
historyIntro: '[格式参考](persistence-formats/README.zh.md)覆盖每个历史 Session 格式。[变更记录](persistence-changes/README.zh.md)通过保存在本源码树中的快照确认精确的类型转换。按照[评审流程](cookbook/reviewing-persistence-type-changes.zh.md)分类并记录变更。这些检查覆盖已声明的类型结构;不透明载荷的内部内容和未改变类型的行为变更不在检查范围内。',
historyIntro: '[格式参考](persistence-changes/historical-formats/README.zh.md)覆盖每个历史 Session 格式。[变更记录](persistence-changes/README.zh.md)通过保存在本源码树中的快照确认精确的类型转换。按照[评审流程](cookbook/reviewing-persistence-type-changes.zh.md)分类并记录变更。这些检查覆盖已声明的类型结构;不透明载荷的内部内容和未改变类型的行为变更不在检查范围内。',
rootColumns: '| 根类型 | 类别 | SHA-256 | 已解析类型 |',
definitions: '已解析的持久化类型',
definitionsIntro: '每个类型定义仅列出一次。引用保留共享和递归关系;定义旁的摘要涵盖其完整可达结构。源码名称和位置标识声明来源,但不参与摘要计算。',
+10
View File
@@ -410,6 +410,16 @@ describe('persistence changes current-tree commands', () => {
expect(() => loadPersistenceHistory(root)).toThrow('unreferenced')
})
it('excludes nested historical format documents and schemas from acknowledgements', () => {
const root = fixture()
baseline(root)
const directory = join(root, 'docs/persistence-changes/historical-formats')
mkdirSync(directory)
writeFileSync(join(directory, 'v0.md'), '---\nkind: persistence-format\n---\n')
writeFileSync(join(directory, 'v0.schema.json'), '{}\n')
expect(loadPersistenceHistory(root).entries.map(entry => entry.record.id)).toEqual([BASE_ID])
})
it.each([
['optional field', inventory({ value: 'string', 'label?': 'string' })],
['required-to-optional field', inventory({ 'value?': 'string' })],
+2 -2
View File
@@ -39,7 +39,7 @@ function historicalSchema(entry: PersistenceFormatEntry, language: Language): st
}
function formatIndex(formats: PersistenceFormats, language: Language): string {
const path = (target: string): string => posix.relative('docs/persistence-formats',
const path = (target: string): string => posix.relative('docs/persistence-changes/historical-formats',
language === 'zh' && target.endsWith('.md') ? target.replace(/\.md$/u, '.zh.md') : target)
return [
language === 'en' ? '| Format | Source | Reference | Machine schema | Roots / types |' : '| 格式 | 来源 | 参考文档 | 机器 schema | 根类型 / 类型 |',
@@ -68,7 +68,7 @@ export function persistenceFormatFactArtifacts(root: string, formats: Persistenc
}
artifacts.push(...renderPersistencePair(root, entry.document, render('en'), render('zh')))
}
const index = 'docs/persistence-formats/README.md'
const index = 'docs/persistence-changes/historical-formats/README.md'
const renderIndex = (language: Language): string => {
const path = language === 'en' ? index : index.replace(/\.md$/u, '.zh.md')
return replaceRegion(readFileSync(join(root, path), 'utf8'), 'index', formatIndex(formats, language), path)
+42 -36
View File
@@ -53,7 +53,7 @@ function write(root: string, path: string, content: string): void {
}
function schemaPath(version: number, currentVersion: number): string {
return version === currentVersion ? 'docs/persistence-schema.json' : `docs/persistence-formats/v${version}.schema.json`
return version === currentVersion ? 'docs/persistence-schema.json' : `docs/persistence-changes/historical-formats/v${version}.schema.json`
}
function replaceSnapshot(root: string, version: number, currentVersion: number, snapshot: PersistenceSchemaInventory): void {
@@ -62,7 +62,7 @@ function replaceSnapshot(root: string, version: number, currentVersion: number,
function saveReference(root: string, version: number, currentVersion: number, snapshot = inventory(version)): void {
const current = version === currentVersion
const filename = current ? 'persistence-catalog' : `persistence-formats/v${version}`
const filename = current ? 'persistence-catalog' : `persistence-changes/historical-formats/v${version}`
const schemaName = current ? 'persistence-schema.json' : `v${version}.schema.json`
const record = {
schemaVersion: 1, sessionFormatVersion: version,
@@ -82,7 +82,7 @@ function fixture(currentVersion = 3): string {
const root = mkdtempSync(join(tmpdir(), 'dsh-persistence-formats-'))
temporary.push(root)
mkdirSync(join(root, 'packages/core/session/src'), { recursive: true })
mkdirSync(join(root, 'docs/persistence-formats'), { recursive: true })
mkdirSync(join(root, 'docs/persistence-changes/historical-formats'), { recursive: true })
write(root, 'packages/core/session/src/types.ts', `export const SESSION_FORMAT_VERSION = ${currentVersion} as const\n`)
for (let version = 0; version <= currentVersion; version += 1) saveReference(root, version, currentVersion)
return root
@@ -95,18 +95,18 @@ function edit(root: string, path: string, from: string, to: string): void {
}
function editPair(root: string, version: number, from: string, to: string): void {
for (const suffix of ['.md', '.zh.md']) edit(root, `docs/persistence-formats/v${version}${suffix}`, from, to)
for (const suffix of ['.md', '.zh.md']) edit(root, `docs/persistence-changes/historical-formats/v${version}${suffix}`, from, to)
}
function prepareFacts(root: string, currentVersion = 3): void {
for (const suffix of ['.md', '.zh.md']) {
for (let version = 0; version < currentVersion; version += 1) {
const path = `docs/persistence-formats/v${version}${suffix}`
const path = `docs/persistence-changes/historical-formats/v${version}${suffix}`
const switcher = suffix === '.md' ? `English | [中文](v${version}.zh.md)` : `[English](v${version}.md) | 中文`
write(root, path, readFileSync(join(root, path), 'utf8') + `\n${switcher}\n\n<!-- persistence-format-schema:start -->\n\nPending schemas.\n\n<!-- persistence-format-schema:end -->\n`)
}
const switcher = suffix === '.md' ? 'English | [中文](README.zh.md)' : '[English](README.md) | 中文'
write(root, `docs/persistence-formats/README${suffix}`, `# Session formats\n\n${switcher}\n\nAuthored context.\n\n<!-- persistence-format-index:start -->\n\nPending formats.\n\n<!-- persistence-format-index:end -->\n`)
write(root, `docs/persistence-changes/historical-formats/README${suffix}`, `# Session formats\n\n${switcher}\n\nAuthored context.\n\n<!-- persistence-format-index:start -->\n\nPending formats.\n\n<!-- persistence-format-index:end -->\n`)
}
}
@@ -123,8 +123,8 @@ describe('archive current persistence format', () => {
replaceSnapshot(root, 3, 3, { ...original, types: [...types, { schema, digest: schemaDigest(schema), names: [], sources: [] }] })
const before = readFileSync(join(root, 'docs/persistence-schema.json'), 'utf8')
expect(runPersistenceFormats(['--archive', '3'], root)).toBe('Archived Session format v3 to docs/persistence-formats/v3.schema.json.')
const serialized = readFileSync(join(root, 'docs/persistence-formats/v3.schema.json'), 'utf8')
expect(runPersistenceFormats(['--archive', '3'], root)).toBe('Archived Session format v3 to docs/persistence-changes/historical-formats/v3.schema.json.')
const serialized = readFileSync(join(root, 'docs/persistence-changes/historical-formats/v3.schema.json'), 'utf8')
const archived = parseHistoricalPersistenceSnapshot(JSON.parse(serialized))
expect(archived).toEqual({
...original,
@@ -134,7 +134,7 @@ describe('archive current persistence format', () => {
})
expect(serialized).toBe(JSON.stringify(archived, null, 2) + '\n')
expect(readFileSync(join(root, 'docs/persistence-schema.json'), 'utf8')).toBe(before)
expect(existsSync(join(root, 'docs/persistence-formats/v3.md'))).toBe(false)
expect(existsSync(join(root, 'docs/persistence-changes/historical-formats/v3.md'))).toBe(false)
saveReference(root, 3, 4, archived)
write(root, 'packages/core/session/src/types.ts', 'export const SESSION_FORMAT_VERSION = 4 as const\n')
@@ -144,34 +144,34 @@ describe('archive current persistence format', () => {
it('creates the first archive directory and honors --root', () => {
const root = fixture(0)
rmSync(join(root, 'docs/persistence-formats'), { recursive: true })
expect(runPersistenceFormats(['--root', root, '--archive', '0'])).toBe('Archived Session format v0 to docs/persistence-formats/v0.schema.json.')
expect(JSON.parse(readFileSync(join(root, 'docs/persistence-formats/v0.schema.json'), 'utf8'))).toEqual(inventory(0))
rmSync(join(root, 'docs/persistence-changes/historical-formats'), { recursive: true })
expect(runPersistenceFormats(['--root', root, '--archive', '0'])).toBe('Archived Session format v0 to docs/persistence-changes/historical-formats/v0.schema.json.')
expect(JSON.parse(readFileSync(join(root, 'docs/persistence-changes/historical-formats/v0.schema.json'), 'utf8'))).toEqual(inventory(0))
})
it.each(['-1', '1.5', '01', '3junk', '9007199254740992'])('rejects an invalid archive version %s', (version) => {
const root = fixture()
expect(() => runPersistenceFormats([`--archive=${version}`], root)).toThrow('--archive must be a non-negative safe integer')
expect(existsSync(join(root, 'docs/persistence-formats/v3.schema.json'))).toBe(false)
expect(existsSync(join(root, 'docs/persistence-changes/historical-formats/v3.schema.json'))).toBe(false)
})
it.each(['2', '4'])('rejects archive v%s when the current writer is v3', (version) => {
const root = fixture()
expect(() => runPersistenceFormats(['--archive', version], root)).toThrow(`Cannot archive v${version}: current writer is v3`)
expect(existsSync(join(root, 'docs/persistence-formats/v3.schema.json'))).toBe(false)
expect(existsSync(join(root, 'docs/persistence-changes/historical-formats/v3.schema.json'))).toBe(false)
})
it('refuses to overwrite an existing archive', () => {
const root = fixture()
write(root, 'docs/persistence-formats/v3.schema.json', 'Retained history.\n')
expect(() => runPersistenceFormats(['--archive', '3'], root)).toThrow('docs/persistence-formats/v3.schema.json already exists')
expect(readFileSync(join(root, 'docs/persistence-formats/v3.schema.json'), 'utf8')).toBe('Retained history.\n')
write(root, 'docs/persistence-changes/historical-formats/v3.schema.json', 'Retained history.\n')
expect(() => runPersistenceFormats(['--archive', '3'], root)).toThrow('docs/persistence-changes/historical-formats/v3.schema.json already exists')
expect(readFileSync(join(root, 'docs/persistence-changes/historical-formats/v3.schema.json'), 'utf8')).toBe('Retained history.\n')
})
it('rejects --archive with --write before writing', () => {
const root = fixture()
expect(() => runPersistenceFormats(['--archive', '3', '--write'], root)).toThrow('--archive and --write cannot be combined')
expect(existsSync(join(root, 'docs/persistence-formats/v3.schema.json'))).toBe(false)
expect(existsSync(join(root, 'docs/persistence-changes/historical-formats/v3.schema.json'))).toBe(false)
})
it.each(['invalid-json', 'digest-drift', 'missing-type', 'missing-source-metadata', 'outdated-header'])('rejects %s before creating an archive', (variant) => {
@@ -185,14 +185,14 @@ describe('archive current persistence format', () => {
expect(() => runPersistenceFormats(['--archive', '3'], root)).toThrow(variant === 'invalid-json' ? SyntaxError
: variant === 'digest-drift' ? 'schema digest mismatch' : variant === 'missing-type' ? 'cover every reachable type'
: variant === 'missing-source-metadata' ? 'missing field sources' : 'SessionHeader.version must match')
expect(existsSync(join(root, 'docs/persistence-formats/v3.schema.json'))).toBe(false)
expect(existsSync(join(root, 'docs/persistence-changes/historical-formats/v3.schema.json'))).toBe(false)
})
})
describe('complete persistence format references', () => {
it.each(['.md', '.zh.md'])('rejects source coordinates in authored format evidence in %s', (suffix) => {
const root = fixture()
const path = `docs/persistence-formats/v1${suffix}`
const path = `docs/persistence-changes/historical-formats/v1${suffix}`
const original = readFileSync(join(root, path), 'utf8')
for (const position of [':36', ':36:2', '#L36', '#L36-L38']) {
write(root, path, original + `\nSource: \`packages/core/session/src/types.ts${position}\`.\n`)
@@ -214,19 +214,19 @@ describe('complete persistence format references', () => {
it('accepts a version-zero writer without a historical directory', () => {
const root = fixture(0)
rmSync(join(root, 'docs/persistence-formats'), { recursive: true })
rmSync(join(root, 'docs/persistence-changes/historical-formats'), { recursive: true })
expect(loadPersistenceFormats(root).entries).toHaveLength(1)
})
it.each(['.md', '.zh.md', '.schema.json'])('requires every historical companion %s', (suffix) => {
const root = fixture()
rmSync(join(root, `docs/persistence-formats/v1${suffix}`))
rmSync(join(root, `docs/persistence-changes/historical-formats/v1${suffix}`))
expect(() => loadPersistenceFormats(root)).toThrow(`missing persistence format artifact v1${suffix}`)
})
it('rejects a gap even when all remaining versions have complete companions', () => {
const root = fixture()
for (const suffix of ['.md', '.zh.md', '.schema.json']) rmSync(join(root, `docs/persistence-formats/v1${suffix}`))
for (const suffix of ['.md', '.zh.md', '.schema.json']) rmSync(join(root, `docs/persistence-changes/historical-formats/v1${suffix}`))
expect(() => loadPersistenceFormats(root)).toThrow('v1: missing persistence format artifact')
})
@@ -247,13 +247,13 @@ describe('complete persistence format references', () => {
it.each(['v3.md', 'v9.schema.json', 'v01.md', 'v-1.md', 'v2.schema.yaml', 'v3.i18n.yaml'])('rejects unexpected or duplicate format filename %s', (filename) => {
const root = fixture()
write(root, `docs/persistence-formats/${filename}`, '{}')
write(root, `docs/persistence-changes/historical-formats/${filename}`, '{}')
expect(() => loadPersistenceFormats(root)).toThrow(`unexpected persistence format artifact ${filename}`)
})
it('rejects bilingual declarations that differ', () => {
const root = fixture()
edit(root, 'docs/persistence-formats/v1.zh.md', 'pullRequest: 3349', 'pullRequest: 3397')
edit(root, 'docs/persistence-changes/historical-formats/v1.zh.md', 'pullRequest: 3349', 'pullRequest: 3397')
expect(() => loadPersistenceFormats(root)).toThrow('bilingual machine records differ')
})
@@ -304,7 +304,7 @@ describe('complete persistence format references', () => {
it.each(['missing', 'duplicate'])('rejects a %s machine block', (variant) => {
const root = fixture()
for (const suffix of ['.md', '.zh.md']) {
const path = `docs/persistence-formats/v1${suffix}`
const path = `docs/persistence-changes/historical-formats/v1${suffix}`
const text = readFileSync(join(root, path), 'utf8')
write(root, path, variant === 'missing' ? text.replace('```yaml persistence-format', '```yaml') : text + '\n```yaml persistence-format\n{}\n```\n')
}
@@ -386,9 +386,9 @@ describe('complete persistence format references', () => {
expect(() => loadPersistenceFormats(root)).toThrow(variant === 'duplicate-root' ? 'duplicate schema root' : variant === 'invalid-digest' ? 'schema digest mismatch' : variant === 'invalid-graph' ? 'unknown schema node' : 'unknown field extra')
})
it.each(['persistence-formats/v1.md', 'persistence-formats/v1.zh.md', 'persistence-catalog.md', 'persistence-catalog.zh.md'])('requires the matching schema link in %s', (document) => {
it.each(['persistence-changes/historical-formats/v1.md', 'persistence-changes/historical-formats/v1.zh.md', 'persistence-catalog.md', 'persistence-catalog.zh.md'])('requires the matching schema link in %s', (document) => {
const root = fixture()
const schema = document.startsWith('persistence-formats/') ? 'v1.schema.json' : 'persistence-schema.json'
const schema = document.startsWith('persistence-changes/historical-formats/') ? 'v1.schema.json' : 'persistence-schema.json'
edit(root, `docs/${document}`, `](${schema})`, '](wrong.schema.json)')
expect(() => loadPersistenceFormats(root)).toThrow(`missing link to ${schema}`)
})
@@ -408,8 +408,14 @@ describe('complete persistence format references', () => {
expect(runPersistenceFormats(['--write'], root)).toContain('Refreshed')
expect(runPersistenceFormats([], root)).toBe('Persistence formats: v0 through v3 verified (4 complete references).')
expect(paths.map(path => readFileSync(join(root, path), 'utf8'))).toEqual(before)
expect(readFileSync(join(root, 'docs/persistence-formats/README.md'), 'utf8')).toContain('Authored context.')
const english = readFileSync(join(root, 'docs/persistence-formats/v1.md'), 'utf8')
const index = readFileSync(join(root, 'docs/persistence-changes/historical-formats/README.md'), 'utf8')
expect(index).toContain('Authored context.')
expect(index).toContain('[V1](v1.md) | [JSON](v1.schema.json)')
expect(index).toContain('[Current catalog](../../persistence-catalog.md) | [JSON](../../persistence-schema.json)')
const translatedIndex = readFileSync(join(root, 'docs/persistence-changes/historical-formats/README.zh.md'), 'utf8')
expect(translatedIndex).toContain('[V1](v1.zh.md) | [JSON](v1.schema.json)')
expect(translatedIndex).toContain('[当前目录](../../persistence-catalog.zh.md) | [JSON](../../persistence-schema.json)')
const english = readFileSync(join(root, 'docs/persistence-changes/historical-formats/v1.md'), 'utf8')
expect(english).toContain('pullRequest: 3349')
const [visible, collapsed] = english.split('<details>')
expect(visible).toContain('\n## Complete schemas\n')
@@ -419,7 +425,7 @@ describe('complete persistence format references', () => {
expect(english.match(/^## /gmu)).toHaveLength(1)
expect(english.match(/^### /gmu)).toHaveLength(2)
expect(english.match(/^#### /gmu)).toHaveLength(inventory(1).types.length)
edit(root, 'docs/persistence-formats/v1.md', '<summary>Complete resolved types</summary>', '<summary>Missing definitions</summary>')
edit(root, 'docs/persistence-changes/historical-formats/v1.md', '<summary>Complete resolved types</summary>', '<summary>Missing definitions</summary>')
expect(() => runPersistenceFormats([], root)).toThrow('Stale persistence format facts')
})
@@ -434,7 +440,7 @@ describe('complete persistence format references', () => {
const root = fixture()
prepareFacts(root)
runPersistenceFormats(['--write'], root)
const sidecar = 'docs/persistence-formats/v1.i18n.yaml'
const sidecar = 'docs/persistence-changes/historical-formats/v1.i18n.yaml'
if (variant === 'missing') rmSync(join(root, sidecar))
else write(root, sidecar, 'schemaVersion: 1\n')
expect(() => runPersistenceFormats([], root)).toThrow(`Stale persistence format facts: ${sidecar}`)
@@ -445,10 +451,10 @@ describe('complete persistence format references', () => {
it('refuses to refresh facts until every format reference passes validation', () => {
const root = fixture()
prepareFacts(root)
const index = readFileSync(join(root, 'docs/persistence-formats/README.md'), 'utf8')
rmSync(join(root, 'docs/persistence-formats/v1.schema.json'))
const index = readFileSync(join(root, 'docs/persistence-changes/historical-formats/README.md'), 'utf8')
rmSync(join(root, 'docs/persistence-changes/historical-formats/v1.schema.json'))
expect(() => runPersistenceFormats(['--write'], root)).toThrow('missing persistence format artifact')
expect(readFileSync(join(root, 'docs/persistence-formats/README.md'), 'utf8')).toBe(index)
expect(readFileSync(join(root, 'docs/persistence-changes/historical-formats/README.md'), 'utf8')).toBe(index)
})
it('refuses to refresh an archive after an event and all its types are removed from its schema', () => {
@@ -461,7 +467,7 @@ describe('complete persistence format references', () => {
saveReference(root, 1, 3, complete)
prepareFacts(root)
runPersistenceFormats(['--write'], root)
const document = 'docs/persistence-formats/v1.md'
const document = 'docs/persistence-changes/historical-formats/v1.md'
const before = readFileSync(join(root, document), 'utf8')
replaceSnapshot(root, 1, 3, completeInventory(complete.roots.filter(root => root.key !== 'event:example/value')))
expect(() => runPersistenceFormats(['--write'], root)).toThrow('recorded roots do not match the complete schema inventory')
+1 -1
View File
@@ -11,7 +11,7 @@ import { canonicalizeSchema, schemaDigest } from './persistence-schema-model.ts'
import type { PersistenceSchemaInventory } from './persistence-schema-model.ts'
import { withoutPersistenceSourceLines } from './persistence-source-metadata.ts'
const DIRECTORY = 'docs/persistence-formats'
const DIRECTORY = 'docs/persistence-changes/historical-formats'
const CURRENT_DOCUMENT = 'docs/persistence-catalog.md'
const CURRENT_SCHEMA = 'docs/persistence-schema.json'
+22 -22
View File
@@ -86,26 +86,26 @@ describe('concrete terminology policy', () => {
})
it.each(['0', '1', '2', '10'])('preserves historical identifiers in the canonical v%s machine schema', (version) => {
expect(findConcreteTermViolations(`docs/persistence-formats/v${version}.schema.json`, blockedTerm)).toEqual([])
expect(findConcreteTermViolations(`docs/persistence-changes/historical-formats/v${version}.schema.json`, blockedTerm)).toEqual([])
})
it.each([
'docs/persistence-formats/v00.schema.json',
'docs/persistence-formats/v01.schema.json',
'docs/persistence-formats/v-1.schema.json',
'docs/persistence-formats/v1.zh.schema.json',
'docs/persistence-formats/v1.schema.json.backup',
'docs/persistence-formats/V1.schema.json',
'docs/persistence-formats/versions/v1.schema.json',
'docs/persistence-formats-extra/v1.schema.json',
'docs/other/persistence-formats/v1.schema.json',
'docs/persistence-changes/historical-formats/v00.schema.json',
'docs/persistence-changes/historical-formats/v01.schema.json',
'docs/persistence-changes/historical-formats/v-1.schema.json',
'docs/persistence-changes/historical-formats/v1.zh.schema.json',
'docs/persistence-changes/historical-formats/v1.schema.json.backup',
'docs/persistence-changes/historical-formats/V1.schema.json',
'docs/persistence-changes/historical-formats/versions/v1.schema.json',
'docs/persistence-changes/historical-formats-extra/v1.schema.json',
'docs/other/persistence-changes/historical-formats/v1.schema.json',
'docs/persistence-schema.json',
])('keeps machine content strict outside canonical historical schema path %s', (file) => {
expect(findConcreteTermViolations(file, blockedTerm)).toEqual([{ file, line: 1 }])
})
it.each(['md', 'zh.md'])('exempts only generated historical schema lines in %s references', (suffix) => {
const file = `docs/persistence-formats/v2.${suffix}`
const file = `docs/persistence-changes/historical-formats/v2.${suffix}`
for (const newline of ['\n', '\r\n']) {
expect(findConcreteTermViolations(file, [
`Authored ${blockedTerm}.`,
@@ -119,15 +119,15 @@ describe('concrete terminology policy', () => {
})
it.each([
'docs/persistence-formats/v00.md',
'docs/persistence-formats/v01.zh.md',
'docs/persistence-formats/v-1.md',
'docs/persistence-formats/v1.zh.zh.md',
'docs/persistence-formats/v1.md.backup',
'docs/persistence-formats/V1.md',
'docs/persistence-formats/README.md',
'docs/persistence-formats-extra/v1.md',
'docs/other/persistence-formats/v1.zh.md',
'docs/persistence-changes/historical-formats/v00.md',
'docs/persistence-changes/historical-formats/v01.zh.md',
'docs/persistence-changes/historical-formats/v-1.md',
'docs/persistence-changes/historical-formats/v1.zh.zh.md',
'docs/persistence-changes/historical-formats/v1.md.backup',
'docs/persistence-changes/historical-formats/V1.md',
'docs/persistence-changes/historical-formats/README.md',
'docs/persistence-changes/historical-formats-extra/v1.md',
'docs/other/persistence-changes/historical-formats/v1.zh.md',
'docs/persistence-catalog.md',
'docs/persistence-catalog.zh.md',
'docs/persistence-schema.json',
@@ -157,14 +157,14 @@ describe('concrete terminology policy', () => {
['other marker prefix', ['<!-- persistence-release-schema:start -->', '<!-- persistence-release-schema:end -->']],
['index marker', ['<!-- persistence-format-index:start -->', '<!-- persistence-format-index:end -->']],
])('exempts no lines with %s', (_label, markers) => {
const file = 'docs/persistence-formats/v1.md'
const file = 'docs/persistence-changes/historical-formats/v1.md'
const lines = [blockedTerm, ...markers.flatMap(marker => [marker, blockedTerm])].join('\n').split('\n')
const expected = lines.flatMap((line, index) => line === blockedTerm ? [{ file, line: index + 1 }] : [])
expect(findConcreteTermViolations(file, lines.join('\n'))).toEqual(expected)
})
it('keeps marker lines strict when they contain authored text', () => {
const file = 'docs/persistence-formats/v1.md'
const file = 'docs/persistence-changes/historical-formats/v1.md'
expect(findConcreteTermViolations(file, [
`<!-- persistence-format-schema:start --> ${blockedTerm}`,
blockedTerm,
+2 -2
View File
@@ -21,7 +21,7 @@ function isExcluded(file: string): boolean {
return excludedPrefixes.some(prefix => file.startsWith(prefix))
// Release snapshots retain the identifiers present in their pinned source.
|| /^docs\/persistence-changes\/releases\/dsh-v\d+\.\d+\.\d+-(?:alpha|rc)\.\d+\.schema\.json$/u.test(file)
|| /^docs\/persistence-formats\/v(?:0|[1-9]\d*)\.schema\.json$/u.test(file)
|| /^docs\/persistence-changes\/historical-formats\/v(?:0|[1-9]\d*)\.schema\.json$/u.test(file)
}
function containsBlockedTerm(value: string): boolean {
@@ -29,7 +29,7 @@ function containsBlockedTerm(value: string): boolean {
}
function historicalSchemaRegion(file: string, source: string, lines: readonly string[]): readonly [number, number] | undefined {
if (!/^docs\/persistence-formats\/v(?:0|[1-9]\d*)(?:\.zh)?\.md$/u.test(file)) return undefined
if (!/^docs\/persistence-changes\/historical-formats\/v(?:0|[1-9]\d*)(?:\.zh)?\.md$/u.test(file)) return undefined
if (source.match(/<!--\s*persistence-format-schema\b/giu)?.length !== 2) return undefined
const start = lines.indexOf('<!-- persistence-format-schema:start -->')
const end = lines.indexOf('<!-- persistence-format-schema:end -->')