mirror of
https://github.com/deepseek-ai/deepseek-harness.git
synced 2026-10-26 16:55:08 +00:00
Allow reviewed same-version Session schema changes
This commit is contained in:
1 parent
fd78c948a5
commit
5c5b51257a
80 files changed
+981
-713
No files matched your search
+2
-2
@@ -9,8 +9,8 @@
|
||||
en: c11f54e24be6cc3b
|
||||
zh: c1c274fbb161a57b
|
||||
/agent-note-session-log-versioning-one-integer-an-upgrade-chain-and-a-per-event-ignorable-marker/decision:
|
||||
en: 084bcb4de484ef32
|
||||
zh: 1edf1445913ac7b1
|
||||
en: b4b70104be3ff136
|
||||
zh: 5c12e062343cd40f
|
||||
/agent-note-session-log-versioning-one-integer-an-upgrade-chain-and-a-per-event-ignorable-marker/decision/writer-and-publication-authority:
|
||||
en: 461eaa03c6a4f441
|
||||
zh: 7e5b131326dc702c
|
||||
|
||||
@@ -10,15 +10,17 @@ Session logs must be upgradable after release, and the runtime that ships first
|
||||
|
||||
## Decision
|
||||
|
||||
The [reader-compatibility review decision](../process/2026-10-08-session-reader-compatibility-review.md) supersedes this note's original structural bump criterion below. The integer, unknown-event marker, direction-aware refusal, and generation-preservation decisions remain active.
|
||||
|
||||
**One monotonic integer, no major/minor split.** Whether a version step is auto-upgradable is a property of that step — expressed by whether its upgrader exists — not something a two-level numbering scheme should promise in advance; design time rarely reveals whether the next change will turn out "major".
|
||||
|
||||
**The writer decides bumps, not the reader.** A bump is required exactly when an old runtime could no longer handle a new log with full semantic correctness. "Parses without error" is not the bar: silently skipping content that shapes reconstruction is a wrong read. Only structural changes qualify — header shape, event envelope, core event semantics, the surface mechanism (`SurfaceEventType` set, `SurfaceOp` variants). When unsure, bump: a near-identity upgrader is almost free, a missed bump silently corrupts old readers.
|
||||
**Original bump criterion, superseded.** A bump is required exactly when an old runtime could no longer handle a new log with full semantic correctness. "Parses without error" is not the bar: silently skipping content that shapes reconstruction is a wrong read. Only structural changes qualify — header shape, event envelope, core event semantics, the surface mechanism (`SurfaceEventType` set, `SurfaceOp` variants). When unsure, bump: a near-identity upgrader is almost free, a missed bump silently corrupts old readers.
|
||||
|
||||
**Read rules by direction.** Equal version: read normally. Newer than the reader: refuse, name the direction ("written by a newer harness — upgrade"), and point at the raw log artifact so the user can still see the text (`SessionFormatUnsupportedError`, distinct from `SessionPersistenceCorruptionError` because nothing is damaged). Older than the reader: every event-body operation first runs the complete adjacent chain in memory and leaves the source path, bytes, and inode unchanged. Read handles may consume that current logical result directly; a write open exclusively publishes the final current generation under its canonical versioned filename before append. Header-only listing remains non-mutating and reports the numerically highest canonical generation. Catalog generation and module initialization reject a missing adjacent step, so a published first-party build never exposes a partial historical chain. Retained lower generations are not automatic fallback or a downgrade compatibility promise.
|
||||
|
||||
**A per-event `ignorable` marker covers vocabulary growth, so ordinary event additions never bump the version.** The event vocabulary is decided by which plugins are mounted, which a single version integer cannot describe. A reader meeting an unrecognized event type refuses to interpret the log unless the event carries `ignorable: true` in its envelope. The default is *required*: forgetting the marker over-refuses a resumable session (an inconvenience), while a default of ignorable would make the same mistake silently resume a gutted one (a safety failure). The architecture makes this sound: model-visible content flows only through the four `surfaceOp`-marked surface event types plus the `request/header`/`request/context` folds, so the dangerous unknowns are exactly the non-surface events that change how the rest of the log is read (`session/end-seed` is the existing example).
|
||||
|
||||
The [accepted compatibility checkpoint](../../../../docs/session-format-status.md#finalization-record) prevents an accepted transition from reusing its header increase for later breaking changes. Backward-compatible evolution uses new acknowledgement records at the same Session version; breaking changes require a higher writer. The checkpoint does not change the unknown-event protocol.
|
||||
**Original checkpoint criterion, superseded as to bump requirements.** The [accepted compatibility checkpoint](../../../../docs/session-format-status.md#finalization-record) prevents an accepted transition from reusing its header increase for later breaking changes. Backward-compatible evolution uses new acknowledgement records at the same Session version; breaking changes require a higher writer. The checkpoint does not change the unknown-event protocol.
|
||||
|
||||
### Writer and publication authority
|
||||
|
||||
|
||||
+4
-2
@@ -10,15 +10,17 @@ Session log 在发布后必须能升级格式,而最先发布的运行时决
|
||||
|
||||
## 决定
|
||||
|
||||
[读取器兼容性评审决策](../process/2026-10-08-session-reader-compatibility-review.zh.md)取代下文原有的结构性升版本标准。单调整数、未知事件标记、分方向拒绝与代际保留决策仍然有效。
|
||||
|
||||
**一个单调递增的整数,不分大小版本。**某一步能不能自动升级是那一步自己的属性,由它的升级器存在与否表达,不该由两级编号方案提前承诺;设计时很少能预知下一个变更算不算"大"。
|
||||
|
||||
**升不升版本由写入方决定,与读取方能力无关。**当且仅当老运行时无法在语义上完全正确地处理新日志时才必须升版本。"解析不报错"不是标准:静默跳过影响重建的内容就是读错。只有结构性变更够得上这条线:header 形状、事件信封、核心事件语义、surface 机制(`SurfaceEventType` 集合、`SurfaceOp` 变体)。拿不准就升:近似恒等的升级器几乎没有成本,漏升一次会让老读取器静默读坏。
|
||||
**原有升版本标准,已被取代。**当且仅当老运行时无法在语义上完全正确地处理新日志时才必须升版本。"解析不报错"不是标准:静默跳过影响重建的内容就是读错。只有结构性变更够得上这条线:header 形状、事件信封、核心事件语义、surface 机制(`SurfaceEventType` 集合、`SurfaceOp` 变体)。拿不准就升:近似恒等的升级器几乎没有成本,漏升一次会让老读取器静默读坏。
|
||||
|
||||
**读取规则按方向区分。**版本相等:正常读。比读取器新:拒绝,说明方向("由更新的 harness 写入,请升级"),并给出原始日志文件的路径,用户仍能看到文本(`SessionFormatUnsupportedError`,与 `SessionPersistenceCorruptionError` 区分,因为数据没有损坏)。比读取器旧:每个事件正文操作先在内存中运行完整相邻链,并保持源路径、字节与 inode 不变。读句柄可以直接使用该 current 逻辑结果;写 open 则在 append 前把最终 current generation 排他发布到其规范版本文件名。仅 header 的列表保持不变更,并报告数值最高的规范 generation。catalog 生成与模块初始化会拒绝缺失的相邻步骤,因此已发布第一方 build 绝不会暴露不完整历史链。保留的低 generation 不是自动 fallback,也不构成 downgrade compatibility 承诺。
|
||||
|
||||
**逐事件的 `ignorable` 标记吸收词汇表增长,普通的新增事件永远不用升版本。**事件词汇表由挂载了哪些插件决定,单个版本整数描述不了它。读取器遇到不认识的事件类型时拒绝解读日志,除非该事件的信封带 `ignorable: true`。默认为必需:忘写标记的后果是把一个本可恢复的会话拒绝过头(体验问题),而默认可忽略会让同样的疏忽静默恢复出残缺会话(安全事故)。架构保证了这条规则成立:模型可见内容只经四种带 `surfaceOp` 标记的 surface 事件加 `request/header`、`request/context` 折叠进入重建,危险的未知事件恰好是那些不进 surface 但改变日志其余部分解读方式的事件(`session/end-seed` 是现存例子)。
|
||||
|
||||
[已接受兼容性检查点](../../../../docs/session-format-status.zh.md#finalization-record)阻止已接受转换复用原来的头版本递增来授权后续破坏性变更。向后兼容的演进在相同 Session 版本上使用新的确认记录;破坏性变更要求更高的写入器版本。检查点不改变未知事件协议。
|
||||
**原有检查点标准,其升版本要求已被取代。**[已接受兼容性检查点](../../../../docs/session-format-status.zh.md#finalization-record)阻止已接受转换复用原来的头版本递增来授权后续破坏性变更。向后兼容的演进在相同 Session 版本上使用新的确认记录;破坏性变更要求更高的写入器版本。检查点不改变未知事件协议。
|
||||
|
||||
### 写入器与发布真源
|
||||
|
||||
|
||||
@@ -9,14 +9,14 @@
|
||||
en: c8e926bfc65d7195
|
||||
zh: e0e007960b68fe80
|
||||
/agent-note-in-tree-persistence-type-history/decision:
|
||||
en: 0d6257a57763ad48
|
||||
zh: 6ce3cb7228bba27a
|
||||
en: a2a78ce5c8ae8eea
|
||||
zh: ec454d682b11bb24
|
||||
/agent-note-in-tree-persistence-type-history/decision/finalized-checkpoints:
|
||||
en: 7dd42139850de3bf
|
||||
zh: 0783bff2f0084f71
|
||||
en: 83f60900b90f8a3b
|
||||
zh: a00d98fa224a6764
|
||||
/agent-note-in-tree-persistence-type-history/alternatives-considered:
|
||||
en: 5cdac1b3f15f89f6
|
||||
zh: 36cda3b92d31c916
|
||||
en: 3a7eb7c2fb98936b
|
||||
zh: 99c865ac63433424
|
||||
/agent-note-in-tree-persistence-type-history/consequences:
|
||||
en: 672c8cb59d291dc2
|
||||
zh: cbec90fbca92320a
|
||||
@@ -10,11 +10,13 @@ A persisted event can retain the same payload type expression while a referenced
|
||||
|
||||
## Decision
|
||||
|
||||
The [reader-compatibility review decision](2026-10-08-session-reader-compatibility-review.md) supersedes the automatic version floor described below. The normalized type history, per-root acknowledgements, and accepted checkpoint preservation remain active.
|
||||
|
||||
One normalized type model supplies the readable persistence catalog, the complete schema inventory, and transitive per-root digests. Roots identify the logical Session header, the physical JSONL header line, the event envelope, and every repository-declared event. Source locations, alias names, ordinary comments, and property order are presentation details outside the digest. Explicit compatibility annotations carry fingerprinted policy data under the [source-attribution policy](2026-09-17-persistence-schema-review.md). Historical records retain source file paths without line numbers because their selected source trees differ from the current checkout. Referenced and recursive types participate in structural comparison; opaque values retain explicit coverage limits.
|
||||
|
||||
The dedicated [persistence-change records](../../../../docs/persistence-changes/README.md) bind acknowledgement to the after digest of each affected root. A generated companion stores complete after schemas. The initial record covers all roots; later records name each root's predecessor. A predecessor's after schema supplies the next before schema. Verification rejects ambiguous history and requires current source to match the terminal recorded state without consulting Git history or remote services.
|
||||
|
||||
Automatic classification permits optional body additions, required-to-optional body changes, and ordinary event additions within one version. The scoped [source-attribution policy](2026-09-17-persistence-schema-review.md) also permits qualified new producer kinds when both saved schemas opt in. The [event-payload version rule](../../../../docs/persistence-changes/README.md#compatibility-rules) admits higher numeric payload versions when every old alternative remains unchanged and records the older-reader limitation. Other structural changes require a version-bump decision that includes an increasing header version in that record. The [format-version procedure](../../../../docs/cookbook/adding-a-session-format-version.md) continues to own adjacent migration work. Every structural difference requires an explicit record, including additions allowed within the same version.
|
||||
The original automatic classification permitted optional body additions, required-to-optional body changes, and ordinary event additions within one version. The scoped [source-attribution policy](2026-09-17-persistence-schema-review.md) also permits qualified new producer kinds when both saved schemas opt in. The [event-payload version rule](../../../../docs/persistence-changes/README.md#compatibility-rules) admits higher numeric payload versions when every old alternative remains unchanged and records the older-reader limitation. Other structural changes required a version-bump decision that included an increasing header version in that record. The [format-version procedure](../../../../docs/cookbook/adding-a-session-format-version.md) continues to own adjacent migration work. Every structural difference requires an explicit record, including additions allowed within the same version.
|
||||
|
||||
The new document kind preserves the compatibility reasoning and evidence for a historical type transition. Agent Notes retain mechanism-level decisions; they do not become a growing inventory of individual acknowledgements. Machine declarations remain identical across the bilingual pair and are parsed once.
|
||||
|
||||
@@ -28,7 +30,7 @@ Explicit update refreshes an unaccepted terminal record without deleting its aut
|
||||
|
||||
### Finalized checkpoints
|
||||
|
||||
Finalization records acceptance independently of publication. A terminal record must not reuse its original header increase to authorize another breaking change to already-written data. The [status record](../../../../docs/session-format-status.md#finalization-record) therefore requires a complete compatibility checkpoint: accepted record semantics remain locked, while compatible current-source changes proceed through new records and breaking changes require a higher writer. The existing classifier and unknown-event protocol are unchanged. This adds no runtime field and cannot detect behavior changes hidden behind unchanged declarations. Coordinated edits to the in-tree authority and checkpoint still require review.
|
||||
The original checkpoint policy below retains its history-preservation requirements; the successor decision owns the version criterion. Finalization records acceptance independently of publication. A terminal record must not reuse its original header increase to authorize another breaking change to already-written data. The [status record](../../../../docs/session-format-status.md#finalization-record) therefore requires a complete compatibility checkpoint: accepted record semantics remain locked, while compatible current-source changes proceed through new records and breaking changes require a higher writer. The existing classifier and unknown-event protocol are unchanged. This adds no runtime field and cannot detect behavior changes hidden behind unchanged declarations. Coordinated edits to the in-tree authority and checkpoint still require review.
|
||||
|
||||
## Alternatives considered
|
||||
|
||||
@@ -40,7 +42,7 @@ Finalization records acceptance independently of publication. A terminal record
|
||||
|
||||
**Treat every changed digest as a version bump.** Optional body additions, required-to-optional changes, and ordinary event additions do not always require a new persistence version. Conservative classification distinguishes these cases and leaves the compatibility explanation to review.
|
||||
|
||||
**Infer behavior from types or permit arbitrary compatibility overrides.** A type graph cannot prove replay semantics. The checker confines itself to detectable structure and rejects decisions below the mechanical classification; it does not claim to detect behavior-only changes.
|
||||
**Infer behavior from types or permit arbitrary compatibility overrides.** A type graph cannot prove replay semantics. The original checker confined itself to detectable structure and rejected decisions below the mechanical classification; it did not claim to detect behavior-only changes. The successor decision replaces that automatic floor with explicit reader evidence.
|
||||
|
||||
## Consequences
|
||||
|
||||
|
||||
@@ -10,11 +10,13 @@ Status: implemented
|
||||
|
||||
## 决策
|
||||
|
||||
[读取器兼容性评审决策](2026-10-08-session-reader-compatibility-review.zh.md)取代下文的自动最低版本要求。规范化类型历史、逐根确认与已接受检查点保留规则仍然有效。
|
||||
|
||||
一个规范化类型模型提供可读持久化目录、完整 schema 清单和包含传递引用的逐根摘要。根标识逻辑会话头、物理 JSONL 头行、事件封装以及每个仓库内声明的事件。源码位置、别名名称、普通注释和属性顺序属于摘要之外的展示细节。历史记录保留源码文件路径而不包含行号,因为其选定的源码树与当前工作树不同。被引用类型和递归类型参与结构比较;不透明值保留明确的覆盖限制。显式兼容性标记依照[消息源归属策略](2026-09-17-persistence-schema-review.zh.md)携带纳入指纹的策略数据。
|
||||
|
||||
专用的[持久化变更记录](../../../../docs/persistence-changes/README.zh.md)将确认绑定到每个受影响根的变更后摘要。生成的伴随文件保存完整的变更后 schema。初始记录覆盖所有根;后续记录为每个根指定前驱。前驱的变更后 schema 提供下一次的变更前 schema。验证拒绝有歧义的历史,并要求当前源码匹配最终记录状态,无需查询 Git 历史或远端服务。
|
||||
|
||||
自动分类允许可选事件体新增、事件体属性从必选改为可选,以及普通事件新增保持同一版本。当双方保存的 schema 均声明支持时,受限的[消息源归属策略](2026-09-17-persistence-schema-review.zh.md)也允许添加符合条件的生产者 kind。[事件载荷版本规则](../../../../docs/persistence-changes/README.zh.md#compatibility-rules) 允许在原样保留所有旧分支时增加更高的数字载荷版本,并记录旧读取器限制。其他结构变更需要升版本决策,并在该记录中包含递增的头部版本。[格式版本流程](../../../../docs/cookbook/adding-a-session-format-version.zh.md)继续负责相邻迁移工作。每个结构差异都需要显式记录,包括允许保持同一版本的新增。
|
||||
原有自动分类允许可选事件体新增、事件体属性从必选改为可选,以及普通事件新增保持同一版本。当双方保存的 schema 均声明支持时,受限的[消息源归属策略](2026-09-17-persistence-schema-review.zh.md)也允许添加符合条件的生产者 kind。[事件载荷版本规则](../../../../docs/persistence-changes/README.zh.md#compatibility-rules) 允许在原样保留所有旧分支时增加更高的数字载荷版本,并记录旧读取器限制。其他结构变更需要升版本决策,并在该记录中包含递增的头部版本。[格式版本流程](../../../../docs/cookbook/adding-a-session-format-version.zh.md)继续负责相邻迁移工作。每个结构差异都需要显式记录,包括允许保持同一版本的新增。
|
||||
|
||||
新文档类型保留历史类型转换的兼容性说明和证据。Agent Note 保留机制层面的决策,不积累单次确认清单。双语对中的机器声明保持相同,且只解析一次。
|
||||
|
||||
@@ -28,7 +30,7 @@ Status: implemented
|
||||
|
||||
### 定稿检查点
|
||||
|
||||
定稿独立于发布事实记录接受状态。末端记录不能复用原有头版本递增,继续授权对已写入数据进行破坏性变更。因此,[状态记录](../../../../docs/session-format-status.zh.md#finalization-record)要求完整兼容性检查点:已接受记录的语义保持锁定,当前源码的兼容变更通过新记录继续,破坏性变更要求更高的写入器版本。现有分类器与未知事件协议保持不变。这不会添加运行时字段,也不能检测未变声明后隐藏的行为变化。对目录内真源与检查点的协同修改仍须审阅。
|
||||
下文原有检查点政策保留其历史保存要求;后继决策负责版本标准。定稿独立于发布事实记录接受状态。末端记录不能复用原有头版本递增,继续授权对已写入数据进行破坏性变更。因此,[状态记录](../../../../docs/session-format-status.zh.md#finalization-record)要求完整兼容性检查点:已接受记录的语义保持锁定,当前源码的兼容变更通过新记录继续,破坏性变更要求更高的写入器版本。现有分类器与未知事件协议保持不变。这不会添加运行时字段,也不能检测未变声明后隐藏的行为变化。对目录内真源与检查点的协同修改仍须审阅。
|
||||
|
||||
## 考虑过的替代方案
|
||||
|
||||
@@ -40,7 +42,7 @@ Status: implemented
|
||||
|
||||
**把每次摘要变化都视为升版本。** 可选事件体新增、必选改可选,以及普通事件新增不一定需要新的持久化版本。保守分类区分这些情况,并将兼容性说明交给审阅。
|
||||
|
||||
**从类型推断行为,或允许任意兼容性豁免。** 类型图无法证明回放语义。检查器仅覆盖可检测结构,并拒绝低于机械分类要求的决策;它不声称能够检测纯行为变更。
|
||||
**从类型推断行为,或允许任意兼容性豁免。** 类型图无法证明回放语义。原有检查器仅覆盖可检测结构,并拒绝低于机械分类要求的决策;它不声称能够检测纯行为变更。后继决策以明确的读取器证据取代该自动最低要求。
|
||||
|
||||
## 影响
|
||||
|
||||
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
# Bilingual-pair consistency record for 2026-10-08-session-reader-compatibility-review.md (docs/i18n/README.md): per heading
|
||||
# section, a hash of its English and Chinese blocks outside code blocks and generated regions.
|
||||
# After editing either side, bring the other along and re-record with:
|
||||
# pnpm run verify-translation-pairing --write .agents/notes/implemented/process/2026-10-08-session-reader-compatibility-review.md
|
||||
/agent-note-session-versions-follow-reader-compatibility:
|
||||
en: b27e6ad9824a02e4
|
||||
zh: 034a5dc50838db3d
|
||||
/agent-note-session-versions-follow-reader-compatibility/problem:
|
||||
en: fa4f3e56f3b22e07
|
||||
zh: a8010ab822474c2c
|
||||
/agent-note-session-versions-follow-reader-compatibility/decision:
|
||||
en: a25a5cc9c567cfe2
|
||||
zh: e4062cbd1dfe7fb6
|
||||
/agent-note-session-versions-follow-reader-compatibility/migration-obligations:
|
||||
en: b218b28f19c41f33
|
||||
zh: 7e99314983707984
|
||||
/agent-note-session-versions-follow-reader-compatibility/alternatives-considered:
|
||||
en: b474904a53ab69ad
|
||||
zh: 838a6e14d1b1288e
|
||||
/agent-note-session-versions-follow-reader-compatibility/consequences:
|
||||
en: 0ee67db581e8e03d
|
||||
zh: ece4f775c8008f8a
|
||||
@@ -0,0 +1,35 @@
|
||||
# Agent Note: Session versions follow reader compatibility
|
||||
|
||||
Status: implemented
|
||||
|
||||
English | [中文](2026-10-08-session-reader-compatibility-review.zh.md)
|
||||
|
||||
## Problem
|
||||
|
||||
A structural schema difference does not establish that a Session needs a new integer version. An optional environment header field illustrates the cost: sessions that omit the feature can retain identical logs, while sessions that require it can be refused by older readers. A global bump also excludes feature-free sessions and requires a new generation and adjacent migration. Conversely, an unknown field or value does not establish safe refusal: permissive readers can ignore required meaning, and recovery can mistake unsupported records for damaged tails.
|
||||
|
||||
## Decision
|
||||
|
||||
The user chose reader behavior as the version criterion. A same-version change is permitted when older readers either resume correctly, safely ignore optional features while preserving their records, or refuse required features before execution, mutation, or tail repair. Feature absence must preserve existing behavior. Required-feature refusal needs an upgrade diagnostic; already released binaries retain their existing diagnostics, so review identifies any limitation explicitly.
|
||||
|
||||
New readers preserve the meaning of supported historical records and accept valid mixtures of old and new representations within one Session. Removing a required field or changing its representation is not automatically a bump: the replacement must remain distinguishable, and released-reader behavior must satisfy the same admission requirements. A version increase is required when no effective existing discriminator prevents unsafe interpretation. Reusing a logged value with a different meaning is one example; missing admission checks are another.
|
||||
|
||||
The schema classifier identifies structural changes requiring compatibility review instead of declaring them inherently version-breaking. A same-version acknowledgement for such a change includes machine-record `review` evidence for `oldReaders`, `newReaders`, and `verification`. The checker requires authored evidence; it cannot prove that the explanation matches runtime behavior. An explicit version-bump decision still requires its own increasing `SessionHeader.version`. Accepted records and checkpoints remain immutable; new review evidence participates in subsequent checkpoint hashes without rewriting legacy record hashes.
|
||||
|
||||
This decision partially supersedes the structural bump criterion in the [version-mechanism note](../architecture/2026-08-10-session-log-version-mechanism.md) and the automatic version floor in the [persistence-type-history note](2026-09-11-persistence-type-history.md). Their monotonic integer, required unknown-event default, type history, and immutable generation decisions remain active. The [review procedure](../../../../docs/cookbook/reviewing-persistence-type-changes.md) owns authoring and validation details.
|
||||
|
||||
## Migration obligations
|
||||
|
||||
One codec per integer version and one migration per adjacent pair remain sufficient. A version's reader supports its released representations without changing established meanings; an eventual successor migration accepts that same historical range, including mixed records. Historical conversion can keep producing a valid older representation within the target version. Same-version reads do not run an adjacent migration, so a compatibility claim cannot depend on an unimplemented normalization step. Header and payload extensions still need feature-specific admission and preservation through their actual consumers.
|
||||
|
||||
## Alternatives considered
|
||||
|
||||
**Require bumps for header changes and other structural edits.** This mechanically excludes safe refusal and optional features, imposing migration work even when existing discriminators already protect readers.
|
||||
|
||||
**Treat every unknown field or value as a refusal mechanism.** Released readers do not uniformly reject unknown values before recovery. Compatibility evidence must inspect the actual admission and mutation paths; a changed schema alone supplies no guarantee.
|
||||
|
||||
**Permit same-version changes without recorded reader evidence.** A schema graph cannot establish safe execution, lossless retention, historical interpretation, or mixed-record behavior. Explicit review preserves these obligations without making the classifier pretend to prove them.
|
||||
|
||||
## Consequences
|
||||
|
||||
The rule reduces unnecessary version transitions while making old-reader and new-reader evidence explicit. It changes review and acknowledgement enforcement, not runtime admission, diagnostics, codecs, or migration behavior. The environment example motivates the policy; it does not add an `env` field. Unsupported-feature detection, payload readers, and any necessary migration work remain obligations of each feature change. Focused verification must cover feature absence, optional retention or required refusal, historical input, and mixed representations as applicable; a written review is not a substitute for those results.
|
||||
+35
@@ -0,0 +1,35 @@
|
||||
# Agent Note: Session 版本取决于读取器兼容性
|
||||
|
||||
Status: implemented
|
||||
|
||||
[English](2026-10-08-session-reader-compatibility-review.md) | 中文
|
||||
|
||||
## 问题
|
||||
|
||||
schema 的结构差异不能证明 Session 需要新的整数版本。可选的环境头字段体现了这一成本:未使用该特性的会话可以保留完全相同的日志,需要该特性的会话则可以被旧读取器拒绝。全局升版本也会排除未使用特性的会话,并要求新的代际和相邻迁移。反过来,未知字段或值不能证明拒绝是安全的:宽松读取器可能忽略必需的含义,而恢复流程可能把不支持的记录误判为损坏的尾部。
|
||||
|
||||
## 决策
|
||||
|
||||
用户选择以读取器行为作为版本标准。当旧读取器能够正确恢复、安全忽略可选特性并保留其记录,或在执行、修改及尾部修复之前拒绝必需特性时,允许保持同一版本。未使用特性时必须保留现有行为。拒绝必需特性需要升级提示;已发布二进制保留其现有诊断,因此评审明确标出相关限制。
|
||||
|
||||
新读取器保留受支持历史记录的含义,并接受同一 Session 中新旧表示的有效混合。删除必需字段或改变其表示不自动要求升版本:替代表示必须可区分,已发布读取器的行为必须满足相同的准入要求。当不存在能够阻止不安全解释的有效既有判别标记时,必须升版本。将日志值复用于不同含义是一例;缺少准入检查是另一例。
|
||||
|
||||
schema 分类器标识需要兼容性评审的结构变更,而不将其直接判定为版本破坏。同版本确认此类变更时,机器记录中的 `review` 包含 `oldReaders`、`newReaders` 和 `verification` 证据。检查器要求作者提供证据,但无法证明说明与运行时行为一致。显式升版本决策仍须包含其自身递增的 `SessionHeader.version`。已接受记录与检查点保持不可变;新评审证据参与后续检查点哈希,不改写旧记录哈希。
|
||||
|
||||
本决策部分取代[版本机制说明](../architecture/2026-08-10-session-log-version-mechanism.zh.md)中的结构性升版本标准,以及[持久化类型历史说明](2026-09-11-persistence-type-history.zh.md)中的自动最低版本要求。它们的单调整数、未知事件默认必需、类型历史与不可变代际决策仍然有效。[评审流程](../../../../docs/cookbook/reviewing-persistence-type-changes.zh.md)负责记录编写与验证细节。
|
||||
|
||||
## 迁移义务
|
||||
|
||||
每个整数版本一个 codec、每组相邻版本一个迁移仍然足够。一个版本的读取器支持其已发布表示,且不改变已确立的含义;后继迁移接受相同的历史范围,包括混合记录。历史转换可以继续产生目标版本内有效的旧表示。同版本读取不运行相邻迁移,因此兼容性声明不能依赖尚未实现的规范化步骤。头部与载荷扩展仍须在实际消费方中实现各自的准入与保留。
|
||||
|
||||
## 考虑过的替代方案
|
||||
|
||||
**对头部变更及其他结构编辑要求升版本。** 这会机械地排除安全拒绝和可选特性,即使既有判别标记已能保护读取器,也强加迁移工作。
|
||||
|
||||
**把每个未知字段或值都当作拒绝机制。** 已发布读取器并不统一在恢复前拒绝未知值。兼容性证据必须检查实际准入与修改路径;schema 变化本身不提供保证。
|
||||
|
||||
**允许同版本变更而不记录读取器证据。** schema 图无法证明安全执行、无损保留、历史解释或混合记录行为。明确评审保留这些义务,同时不让分类器假装已证明它们。
|
||||
|
||||
## 影响
|
||||
|
||||
该规则减少不必要的版本转换,同时明确要求旧读取器与新读取器的证据。它改变评审与确认校验,不改变运行时准入、诊断、codec 或迁移行为。环境示例说明政策动机,并未添加 `env` 字段。不支持特性的检测、载荷读取器及必要迁移仍由各项特性变更负责。针对性验证必须按需覆盖特性未使用、可选记录保留或必需特性拒绝、历史输入与混合表示;书面评审不能替代这些结果。
|
||||
@@ -13,7 +13,7 @@ kind: persistence-change
|
||||
|
||||
## Skeleton
|
||||
|
||||
The generator fills the machine declaration, schema companion, and consistency records, inferring the minimum version decision from the fixed rules. Supply `--prose` JSON with `en` and `zh` objects, each containing authored `summary`, `compatibility`, and `verification` strings; the [cookbook](../../../../docs/cookbook/reviewing-persistence-type-changes.md) shows the complete operation. The following values illustrate the document fields; use the actual generated root names, record ids, and digests.
|
||||
The generator fills the machine declaration, schema companion, and consistency records. Structural changes outside the established same-version cases require an authored compatibility review or an increasing header version. Supply `--prose` JSON with `en` and `zh` objects, each containing authored `summary`, `compatibility`, and `verification` strings; supply `--review` JSON with `oldReaders`, `newReaders`, and `verification` strings when required. The [cookbook](../../../../docs/cookbook/reviewing-persistence-type-changes.md) shows the operation. Use the actual generated root names, record ids, and digests in the following fields.
|
||||
|
||||
````markdown
|
||||
# Persistence change: <specific persisted change>
|
||||
@@ -48,7 +48,7 @@ changes:
|
||||
<a id="compatibility"></a>
|
||||
## Compatibility
|
||||
|
||||
Explain the declared decision for the detected paths. For an optional addition, explain why existing records can omit it and older readers can ignore it without changing replay. For required-to-optional changes, explain how readers handle absence. For a version bump, link the adjacent migration and its verification owner.
|
||||
Explain the declared decision for every detected path: how older readers preserve meaning, safely ignore optional data, or refuse required features before execution or recovery; and how new readers preserve historical meaning and mixed representations. Feature absence retains existing behavior. For a version bump, link the adjacent migration and its verification owner.
|
||||
|
||||
<a id="verification"></a>
|
||||
## Verification
|
||||
@@ -66,8 +66,8 @@ None.
|
||||
- Keep exactly one `yaml persistence-change` block. The English and Chinese blocks are byte-identical; the verifier parses the English record once and checks its counterpart.
|
||||
- The filename stem and `id` agree. A root's `previous` identifies its predecessor record; `null` introduces a root. Its `after` matches the generated companion schema digest; `null` records deletion.
|
||||
- One initial `baseline: true` record covers the complete inventory. Routine changes use `baseline: false` and preserve accepted predecessors. A version-bump record carries its own increasing `SessionHeader.version` transition.
|
||||
- Omit `--decision` in the ordinary authoring flow. An explicit value asserts the decision and cannot bypass classification or the required header increase. The command supplies no source changes or validation claims.
|
||||
- Structural differences alone do not infer a bump. A same-version change requiring review needs `--review`; a changed header version infers `version-bump`. An explicit decision cannot waive review or the required header increase. The command supplies no source changes or validation claims.
|
||||
- Explain only the detected persistence-type changes. This kind permits the before/after facts and historical evidence needed for that transition; behavior-only changes remain out of scope.
|
||||
- Record creation and terminal-record update generate the catalog pair and consistency sidecars. Review both languages and retain the generated companions. Direct prose edits still require re-recording pairing; do not hand-maintain schema nodes or digests.
|
||||
- Use explicit `--update` only for an unaccepted terminal record. It rejects baselines and depended-on records; `--prose` replaces its authored explanations, while omission preserves them. Accepted records receive successors.
|
||||
- Use explicit `--update` only for an unaccepted terminal record. It rejects baselines and depended-on records; `--prose` replaces its authored explanations, while omission preserves them. Changed reviewed schemas require renewed `--review` evidence; unchanged transitions retain their review. Accepted records receive successors.
|
||||
- The [folder reference](../../../../docs/persistence-changes/README.md) owns compatibility rules; the [cookbook](../../../../docs/cookbook/reviewing-persistence-type-changes.md) owns commands and recovery.
|
||||
@@ -128,7 +128,7 @@ Real-API tests/demos read `DEEPSEEK_API_KEY`, optional `DEEPSEEK_BASE_URL`, and
|
||||
- Packages use `@deepseek-ai/dsh-<name>`; vendor is [rescoped](docs/rescope.md) and `private: true`. Harness packages declare `@deepseek-ai/cordis` in `peerDependencies`/`devDependencies`. Workspace dependency sections use DSH `workspace:*`, vendor/native `workspace:~` ([rules](.agents/notes/implemented/process/2026-08-10-npm-release-sequences.md)).
|
||||
- ESM everywhere (`"type": "module"`). Use package names across packages and `.ts` in local relative imports. Config subprocesses run built `lib/` under plain Node; source regressions use their declared launcher ([testing policy](docs/testing.md#test-subprocess-launch-modes)). The `dsh` CLI source launch runs through tsx's ESM-only hook (`node --import tsx/esm`); modules it reaches must stay ESM (no CJS-only exports) — Node's native TypeScript modes are unavailable across the engines range ([source-launch contract](.agents/notes/implemented/architecture/2026-07-29-dsh-source-launch-tsx-esm.md)). Raw/Web `cordis.yml` bare plugins must appear in their resolver manifest's `dependencies`; `verify-cordis-config` enforces it.
|
||||
- **Registrations are effects**: every contribution goes through `ctx.effect()` / `ctx.on()`; a registry's `register()` returns the disposer.
|
||||
- **Typed events use declaration merging** and merge-extensible maps. Event JSDoc needs `@mode` and payload `@param`. Public service methods document parameters and non-void returns. `SessionEventMap` members are required-on-read by default — builds that do not know a type refuse the log unless the event carries the envelope's `ignorable: true`; only structural format changes bump `SESSION_FORMAT_VERSION` ([mechanism](.agents/notes/implemented/architecture/2026-08-10-session-log-version-mechanism.md)).
|
||||
- **Typed events use declaration merging** and merge-extensible maps. Event JSDoc needs `@mode` and payload `@param`. Public service methods document parameters and non-void returns. `SessionEventMap` members are required-on-read unless the envelope carries `ignorable: true`. Bump `SESSION_FORMAT_VERSION` only when existing discriminators cannot prevent unsafe interpretation; same-version changes require safe reader behavior ([review](.agents/notes/implemented/process/2026-10-08-session-reader-compatibility-review.md)).
|
||||
- **Switch on discriminant tags.** Closed unions end in `assertNever`; merge-extensible unions fall through a documented default.
|
||||
- **Waterfall listeners MUST call `next()`** to delegate; returning without it short-circuits the chain ([semantics](docs/cordis-primer.md#cordis-waterfall-semantics)).
|
||||
- **Model-visible ⟺ logged**: anything that reaches a model request must be reconstructable from the session log; a new model-visible input requires a session event.
|
||||
|
||||
@@ -6,14 +6,14 @@
|
||||
en: 71132d61e41cb40b
|
||||
zh: 0b56ed8893bf8d0a
|
||||
/cookbook-adding-a-session-log-format-version/summary:
|
||||
en: 76b76c19e0889ab3
|
||||
zh: 1ddc1afa5c2333f0
|
||||
en: 3dc6121d1fcef4f8
|
||||
zh: 178bcf02b63c4fbf
|
||||
/cookbook-adding-a-session-log-format-version/table-of-contents:
|
||||
en: 6977fff29f49e4a4
|
||||
zh: 7ffb6f5490831dce
|
||||
/cookbook-adding-a-session-log-format-version/1-choose-the-version-and-release-base:
|
||||
en: 3205409b918b891d
|
||||
zh: 98f17c19045954d5
|
||||
en: 3e763cfbe8cd11d5
|
||||
zh: 2d4998217cbe8030
|
||||
/cookbook-adding-a-session-log-format-version/2-add-an-identity-edge:
|
||||
en: 77c9c26d94fb33ca
|
||||
zh: 313c54c45cfc7dbc
|
||||
|
||||
@@ -4,7 +4,7 @@ English | [中文](adding-a-session-format-version.zh.md)
|
||||
|
||||
## Summary
|
||||
|
||||
Use this tutorial to introduce the next structural Session log version without rewriting released data. Read the [version and release-status authority](../session-format-status.md) to identify the checkout writer and the latest released format. Let N denote that verified released format and N+1 the target; substitute numeric values for these placeholders in names and metadata. Start with a working contributor checkout and read the [package checklist](adding-a-package.md), [format library](../../packages/session/session-format/README.md), and [released-format decision](../../.agents/notes/implemented/architecture/2026-08-31-released-session-format-migrations.md).
|
||||
Use this tutorial to introduce the next Session log format version without rewriting released data. Read the [version and release-status authority](../session-format-status.md) to identify the checkout writer and the latest released format. Let N denote that verified released format and N+1 the target; substitute numeric values for these placeholders in names and metadata. Start with a working contributor checkout and read the [package checklist](adding-a-package.md), [format library](../../packages/session/session-format/README.md), and [released-format decision](../../.agents/notes/implemented/architecture/2026-08-31-released-session-format-migrations.md).
|
||||
|
||||
## Table of Contents
|
||||
|
||||
@@ -20,15 +20,15 @@ Use this tutorial to introduce the next structural Session log version without r
|
||||
<a id="choose-the-version"></a>
|
||||
## 1. Choose the version and release base
|
||||
|
||||
Bump the format for a structural change to headers, event envelopes, core event semantics, or surface reconstruction. Backward-compatible changes can remain at the current version through new acknowledgements; follow the [versioning rule](../../.agents/notes/implemented/architecture/2026-08-10-session-log-version-mechanism.md) and [type-change rules](../persistence-changes/README.md#compatibility-rules). Distinguish the Session format integer from package release versions, SQLite schema versions, projection-unit versions, and protocol-wrapper versions.
|
||||
Bump the format only when an effective discriminator cannot prevent unsafe interpretation. A structural change to headers, event envelopes, event semantics, or reconstruction requires assessment under the [reader-compatibility rule](../../.agents/notes/implemented/process/2026-10-08-session-reader-compatibility-review.md), not an automatic bump. Record safe same-version evolution through the [type-change procedure](reviewing-persistence-type-changes.md). Distinguish the Session format integer from package release versions, SQLite schema versions, projection-unit versions, and protocol-wrapper versions.
|
||||
|
||||
Let N be the accepted or published version in [version/status](../session-format-status.md) when planning the next breaking change. A breaking change after accepted V4 therefore needs V5 even before V4 publication is recorded; compatible additions do not require that step.
|
||||
Let N be the accepted or published version in [version/status](../session-format-status.md) when a bump is required. After accepted V4, that bump needs V5 even before V4 publication is recorded; reviewed same-version evolution does not require that step.
|
||||
|
||||
Use a shared `release/*` integration base for N+1. The base change adds the writer, codec, catalog wiring, identity migration, and verification. Create each independent child branch from that base and target its PR at the release branch, not another independent child’s branch. Each child adds its structural transformation, validators, consumers, and tests to the same adjacent migration package. Do not allocate extra versions just to represent review order. Merge reviewed children into the release branch through PRs, then validate the combined result before release. Honor release-branch force-push and deletion protections; do not force-sync it.
|
||||
|
||||
Released codecs and accepted target-format meanings remain stable. The N→N+1 converter may receive fixes or support additional historical cases after N+1 ships, without a version bump, if its output remains compatible with N+1. A converter fix affects subsequent conversions, not existing N+1 successors. Document changes to previously accepted mappings and how existing outputs remain supported. Changing the established target representation or interpretation incompatibly requires the next version.
|
||||
Released codecs preserve accepted interpretations. A current-version codec can admit explicitly discriminated variants while retaining historical readability; do not broaden an older codec to admit a current feature. The N→N+1 converter may receive fixes or support additional historical cases after N+1 ships if its output remains compatible with N+1. A fix affects subsequent conversions, not existing N+1 successors. Document changed mappings and continued support for existing output; apply the reader-compatibility rule to any new target representation.
|
||||
|
||||
Use disposable, isolated Harness homes while N+1 remains open for integration. 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.
|
||||
Same-version logs bypass adjacent migrations and can mix historical and newer record variants after resume. Each integer therefore identifies a family of supported representations, not one fixed schema. Owning readers must retain and distinguish those variants; the next outgoing migration must handle them. Do not add same-version migrations or rely on `restoreCurrent` alone for normalization: current-version JSONL reads using transformed validation bypass that hook. Use disposable, isolated Harness homes for interim integration formats; rerun changed adjacent conversions from unchanged historical input rather than rewriting a committed generation or using a real user's home.
|
||||
|
||||
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.
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
## 概述
|
||||
|
||||
本教程介绍如何添加下一个结构性 Session 日志版本,同时不改写已发布数据。阅读[版本与发布状态真源](../session-format-status.zh.md),确定工作区写入器与最新已发布格式。令 N 表示经核实的已发布格式,N+1 表示目标版本;名称与元数据中的这些占位符须替换为数字。开始前,请准备可用的贡献者工作区,并阅读[包检查清单](adding-a-package.zh.md)、[格式库](../../packages/session/session-format/README.zh.md)和[已发布格式决策](../../.agents/notes/implemented/architecture/2026-08-31-released-session-format-migrations.zh.md)。
|
||||
本教程介绍如何添加下一个 Session 日志格式版本,同时不改写已发布数据。阅读[版本与发布状态真源](../session-format-status.zh.md),确定工作区写入器与最新已发布格式。令 N 表示经核实的已发布格式,N+1 表示目标版本;名称与元数据中的这些占位符须替换为数字。开始前,请准备可用的贡献者工作区,并阅读[包检查清单](adding-a-package.zh.md)、[格式库](../../packages/session/session-format/README.zh.md)和[已发布格式决策](../../.agents/notes/implemented/architecture/2026-08-31-released-session-format-migrations.zh.md)。
|
||||
|
||||
## 目录
|
||||
|
||||
@@ -20,15 +20,15 @@
|
||||
<a id="choose-the-version"></a>
|
||||
## 1. 选择版本与发布基线
|
||||
|
||||
当 header、事件信封、核心事件语义或表面重建发生结构性变更时,提升格式版本。向后兼容的变更可以通过新的确认记录保留当前版本;遵循[版本规则](../../.agents/notes/implemented/architecture/2026-08-10-session-log-version-mechanism.zh.md)与[类型变更规则](../persistence-changes/README.zh.md#compatibility-rules)。区分 Session 格式整数与包发布版本、SQLite schema 版本、投影单元版本及协议包装层版本。
|
||||
只有有效判别信息无法阻止不安全解释时,才提升格式版本。头、事件封装、事件语义或重建的结构变更需要按[读取器兼容性规则](../../.agents/notes/implemented/process/2026-10-08-session-reader-compatibility-review.zh.md)评估,不会自动升版本。通过[类型变更流程](reviewing-persistence-type-changes.zh.md)记录安全的同版本演进。区分 Session 格式整数与包发布版本、SQLite schema 版本、投影单元版本及协议包装层版本。
|
||||
|
||||
规划下一次破坏性变更时,N 取[版本/状态](../session-format-status.zh.md)中的已接受或已发布版本。因此,即使尚未记录 V4 发布,已接受 V4 之后的破坏性变更也需要 V5;兼容新增不需要这一步。
|
||||
确定需要升版本时,N 取[版本/状态](../session-format-status.zh.md)中的已接受或已发布版本。已接受 V4 之后,即使尚未记录 V4 发布,此次升版本也需要 V5;经评审的同版本演进不需要这一步。
|
||||
|
||||
为 N+1 使用共享的 `release/*` 集成基线。基线变更添加写入器、codec、catalog 接线、恒等迁移与验证。从该基线创建各个独立子分支,并将其 PR(Pull Request)的目标设为发布分支,而非另一个独立子分支。每个子分支在同一个相邻迁移包内添加自身的结构变换、校验器、消费方和测试。不要只为表示评审顺序而分配额外版本。通过 PR 将评审后的子分支合入发布分支,并在发布前验证组合结果。遵守发布分支的强制推送与删除保护;不要强制同步该分支。
|
||||
|
||||
已发布 codec 和已接受的目标格式含义保持稳定。N+1 发布后,N→N+1 转换器仍可修复或增加历史场景支持;只要输出仍兼容 N+1,就不必提高版本。转换器修复只影响之后的转换,不会重新处理已有 N+1 后继文件。记录对原先已接纳映射的修改,以及已有输出如何继续得到支持。不兼容地改变既定目标表示或解释语义需要下一版本。
|
||||
已发布 codec 保留已接受的解释。当前版本 codec 可以接纳有明确判别信息的分支,同时保留历史可读性;不要为了接纳当前功能而放宽旧 codec。N+1 发布后,只要输出仍兼容 N+1,N→N+1 转换器就可以修复或增加历史场景支持。修复只影响之后的转换,不影响已有 N+1 后继文件。记录映射变化及已有输出如何继续得到支持;任何新的目标表示都须应用读取器兼容性规则。
|
||||
|
||||
N+1 尚处于开放集成阶段时,应使用可丢弃、相互隔离的 Harness home。中间版本产生的 N+1 文件已标为目标写入器版本,因此后续对 N→N+1 的修改不会再次迁移该文件。请在全新测试 home 中从未变更的历史输入重新运行;绝不通过改写已提交代际或复用真实用户 home 来修复这个问题。
|
||||
同版本日志跳过相邻迁移,恢复后可以混合历史与较新的记录分支。因此,每个整数标识一组受支持表示,而非一个固定 schema。所属读取器必须保留并区分这些分支;下一条向外迁移必须处理它们。不要添加同版本迁移,也不要仅依赖 `restoreCurrent` 进行归一化:使用 transformed 校验的当前版本 JSONL 读取会跳过该 hook。中间集成格式使用可丢弃、相互隔离的 Harness home;相邻转换发生变化时,从未变更的历史输入重新运行,不得改写已提交代际或使用真实用户 home。
|
||||
|
||||
变更写入器之前,使用[归档命令](../persistence-changes/historical-formats/README.zh.md#maintenance)保留其完整持久化 schema,再在 `docs/persistence-changes/historical-formats/vN.*` 下添加双语格式参考。保留所有更早的记录。格式覆盖检查要求低于新写入器的每个整数版本都有独立文档;当前生成的目录只覆盖新写入器。
|
||||
|
||||
|
||||
@@ -15,17 +15,17 @@
|
||||
en: 5fca19ac2bb1ddac
|
||||
zh: 00d78fe7e80ba4f7
|
||||
/cookbook-reviewing-persistence-type-changes/optional-inspect-the-change:
|
||||
en: 3f85edb803afe870
|
||||
zh: 545c651b9e5ccb56
|
||||
en: 2b173ebeb41f37d3
|
||||
zh: 6b9543d6207dc98e
|
||||
/cookbook-reviewing-persistence-type-changes/1-record-the-change:
|
||||
en: 342bd49412db070e
|
||||
zh: a8d14958e9a637d9
|
||||
en: d4986ad9d62ce28a
|
||||
zh: 14db3bb53b5043b6
|
||||
/cookbook-reviewing-persistence-type-changes/2-check-commit-and-push:
|
||||
en: 91d39719ec40b0f7
|
||||
zh: 405116ae4c91b100
|
||||
/cookbook-reviewing-persistence-type-changes/update-an-unaccepted-record:
|
||||
en: 4fd3f48761f7909f
|
||||
zh: 69af35c41656fa17
|
||||
en: 2aa80b901961330b
|
||||
zh: b58240f92e0e4085
|
||||
/cookbook-reviewing-persistence-type-changes/dev-note:
|
||||
en: 5de1d6304e00c58f
|
||||
zh: 8d8225361cfa29e5
|
||||
@@ -31,7 +31,7 @@ pnpm --silent run verify-persistence-changes --json
|
||||
|
||||
Use `--silent` when consuming JSON: pnpm otherwise appends lifecycle failure text to stdout. Failed commands still exit with code 1.
|
||||
|
||||
Read the reported root, path, change kind, and version requirement. A referenced type can affect several event digests; inspect every affected root. Until the history covers the new schemas, verification fails. A stale generated inventory also fails verification; the recording command refreshes it. If `changes` is empty after reordering fields or union alternatives, run `pnpm run gen-persistence-catalog` and repeat the check. An unchanged digest needs no new acknowledgement even when copied declarations or source locations produce a catalog diff.
|
||||
Read the reported root, path, change kind, and compatibility-review requirement. A referenced type can affect several event digests; inspect every affected root. Until the history covers the new schemas, verification fails. A stale generated inventory also fails verification; the recording command refreshes it. If `changes` is empty after reordering fields or union alternatives, run `pnpm run gen-persistence-catalog` and repeat the check. An unchanged digest needs no new acknowledgement even when copied declarations or source locations produce a catalog diff.
|
||||
|
||||
To review a PR independently of its acknowledgement history, save the base and head inventories as local JSON files and run:
|
||||
|
||||
@@ -46,7 +46,7 @@ The current machine inventory stores complete graphs in `roots`. A `types` entry
|
||||
<a id="acknowledge"></a>
|
||||
## 1. Record the change
|
||||
|
||||
Check the [accepted baseline](../session-format-status.md#finalization-record) first. Preserve its locked records. Record backward-compatible evolution in a new same-version acknowledgement; implement a higher writer version before recording a breaking change.
|
||||
Check the [accepted baseline](../session-format-status.md#finalization-record) first and preserve its locked records. Assess both reader directions under the [compatibility rules](../persistence-changes/README.md#compatibility-rules). A structural difference can remain same-version with evidence of safe interpretation or refusal; it does not automatically require a higher writer version.
|
||||
|
||||
Write a local JSON file containing `en` and `zh`, each with `summary`, `compatibility`, and `verification` strings. The following input describes an exercised required-to-optional hook audit field change. Replace the explanation and test evidence with facts about your change; the CLI does not establish these claims.
|
||||
|
||||
@@ -73,9 +73,9 @@ Use a date and descriptive slug in place of this example id:
|
||||
pnpm --silent run persistence-changes --record 2026-09-11-poc-optional --prose .artifacts/persistence-change.prose.json --json
|
||||
```
|
||||
|
||||
The command validates the history and paired prose, infers the minimum version decision, and checks any required header increase before writing. It generates the record pair, complete after schemas, both catalogs, the machine inventory, and pairing records. Review the explanations and returned `changes`, `roots`, and `files` before committing. Omitting `--prose` creates unfinished drafts that verification rejects until their explanations are completed.
|
||||
The command validates the history and paired prose, selects an established same-version decision or checks the supplied compatibility review, and checks any explicit version-bump decision before writing. It generates the record pair, complete after schemas, both catalogs, the machine inventory, and pairing records. Review the explanations and returned `changes`, `roots`, and `files` before committing. Omitting `--prose` creates unfinished drafts that verification rejects until their explanations are completed.
|
||||
|
||||
Inference follows the [fixed compatibility rules](../persistence-changes/README.md#compatibility-rules); it never changes source or relaxes them. If a bump is required, first follow [adding a Session format version](adding-a-session-format-version.md). The record must include its own increasing `SessionHeader.version` transition; an unrelated historical bump cannot authorize it. Routine changes never create another baseline.
|
||||
For a change marked `requiresCompatibilityReview`, supply `--review FILE` with a JSON object containing authored, nonempty `oldReaders`, `newReaders`, and `verification` strings. Describe actual older-reader handling of new records, new-reader support for historical records, and executed checks. The generated machine declaration embeds this review identically in both languages. When the header version is unchanged, recording without review or explicit `--decision version-bump` fails with `compatibility-review-required`; structural differences alone do not infer a bump. An actual header version increase can infer `version-bump`. If an effective discriminator cannot prevent unsafe interpretation, follow [adding a Session format version](adding-a-session-format-version.md) and select `--decision version-bump`. That record must include its own increasing `SessionHeader.version` transition; review cannot waive it, and an unrelated historical bump cannot authorize it. Routine changes never create another baseline.
|
||||
|
||||
<a id="verify"></a>
|
||||
## 2. Check, commit, and push
|
||||
@@ -99,11 +99,11 @@ If source changes after recording, review the compatibility explanation and refr
|
||||
pnpm --silent run persistence-changes --update 2026-09-11-poc-optional --prose .artifacts/persistence-change.prose.json --json
|
||||
```
|
||||
|
||||
The command refreshes the machine declaration, schemas, catalogs, and pairing. Without `--prose`, it preserves the existing explanation. Update refuses the initial baseline, records that another record depends on, and finalized checkpoint records. Outside finalized checkpoints, the tree does not infer review acceptance: preserve accepted history and create a successor instead.
|
||||
The command refreshes the machine declaration, schemas, catalogs, and pairing. Without `--prose`, it preserves the existing explanation. A changed schema requiring compatibility review needs a fresh `--review FILE`; an earlier review cannot authorize the new transition. Update refuses the initial baseline, records that another record depends on, and finalized checkpoint records. Outside finalized checkpoints, the tree does not infer review acceptance: preserve accepted history and create a successor instead.
|
||||
|
||||
When integration creates competing terminal records, update the unaccepted record against the remaining history, then reassess the resulting diff. An unrelated root's acknowledgement does not need refreshing. The [mechanism decision](../../.agents/notes/implemented/process/2026-09-11-persistence-type-history.md) explains why complete snapshots and per-root predecessors are retained.
|
||||
|
||||
An explicit `--decision` remains a checked assertion. For an existing property's value-type change, the following deliberately wrong assertion fails before writing:
|
||||
An explicit `--decision same-version` does not replace review evidence. For an existing property value-type change requiring review, this command fails before writing unless a fresh `--review FILE` is supplied:
|
||||
|
||||
```sh
|
||||
pnpm --silent run persistence-changes --update 2026-09-11-poc-optional --decision same-version --json
|
||||
|
||||
@@ -31,7 +31,7 @@ pnpm --silent run verify-persistence-changes --json
|
||||
|
||||
消费 JSON 时使用 `--silent`:否则 pnpm 会把生命周期失败文本追加到标准输出。失败命令仍以退出码 1 结束。
|
||||
|
||||
阅读报告中的根、路径、变更种类和版本要求。被引用类型可能影响多个事件摘要;检查每个受影响的根。在历史覆盖新 schema 之前,验证会失败。陈旧生成清单也会导致验证失败;记录命令会刷新它。若重排字段或联合类型分支后 `changes` 为空,运行 `pnpm run gen-persistence-catalog` 并重新检查。即使复制的声明或源码位置产生目录 diff,未变的摘要也无需新增确认记录。
|
||||
阅读报告中的根、路径、变更种类和兼容性评审要求。被引用类型可能影响多个事件摘要;检查每个受影响的根。在历史覆盖新 schema 之前,验证会失败。陈旧生成清单也会导致验证失败;记录命令会刷新它。若重排字段或联合类型分支后 `changes` 为空,运行 `pnpm run gen-persistence-catalog` 并重新检查。即使复制的声明或源码位置产生目录 diff,未变的摘要也无需新增确认记录。
|
||||
|
||||
要独立于确认历史评审 PR,先将 base 和 head 的目录保存为本地 JSON 文件,再运行:
|
||||
|
||||
@@ -46,7 +46,7 @@ pnpm --silent run persistence-review --before .artifacts/base.schema.json --afte
|
||||
<a id="acknowledge"></a>
|
||||
## 1. 记录变更
|
||||
|
||||
先检查[已接受基线](../session-format-status.zh.md#finalization-record),保留其锁定记录。向后兼容的演进使用新的同版本确认记录;记录破坏性变更之前,先实现更高的写入器版本。
|
||||
先检查[已接受基线](../session-format-status.zh.md#finalization-record),保留其锁定记录。按[兼容性规则](../persistence-changes/README.zh.md#compatibility-rules)评估两个读取方向。若有安全解释或拒绝的证据,结构差异可以保留同一版本,不会自动要求更高的写入器版本。
|
||||
|
||||
编写包含 `en` 和 `zh` 的本地 JSON 文件,两者分别包含 `summary`、`compatibility` 和 `verification` 字符串。以下输入描述一个经过验证的钩子审计字段从必选改为可选的变更。用你所做变更的事实替换说明和测试证据;CLI(命令行界面)不会证明这些声明。
|
||||
|
||||
@@ -73,9 +73,9 @@ pnpm --silent run persistence-review --before .artifacts/base.schema.json --afte
|
||||
pnpm --silent run persistence-changes --record 2026-09-11-poc-optional --prose .artifacts/persistence-change.prose.json --json
|
||||
```
|
||||
|
||||
命令在写入前验证历史和双语说明、推断最低版本决策,并检查所需的头部版本递增。它生成记录对、完整的变更后 schema、两份目录、机器清单和配对记录。提交前审阅说明及返回的 `changes`、`roots` 和 `files`。省略 `--prose` 会创建未完成草稿,验证将拒绝它们,直到说明补齐。
|
||||
命令在写入前验证历史和双语说明,选择明确允许的同版本决策或检查所提供的兼容性评审,并检查显式升版本决策。它生成记录对、完整的变更后 schema、两份目录、机器清单和配对记录。提交前审阅说明及返回的 `changes`、`roots` 和 `files`。省略 `--prose` 会创建未完成草稿,验证将拒绝它们,直到说明补齐。
|
||||
|
||||
推断遵循[固定兼容性规则](../persistence-changes/README.zh.md#compatibility-rules),不会更改源码或放宽规则。需要升版本时,先遵循[添加会话格式版本](adding-a-session-format-version.zh.md)。记录必须包含其自身的 `SessionHeader.version` 递增转换;无关的历史升版本不能授权它。日常变更不创建另一条基线。
|
||||
对于标为 `requiresCompatibilityReview` 的变更,提供 `--review FILE`,文件为 JSON 对象,包含作者填写的非空 `oldReaders`、`newReaders` 和 `verification` 字符串。说明旧读取器如何实际处理新记录、新读取器如何支持历史记录,以及已执行的检查。生成的机器声明在两种语言中嵌入相同评审。头版本未变时,没有评审且未显式指定 `--decision version-bump` 的记录以 `compatibility-review-required` 失败;仅凭结构差异不会推断升版本。实际头版本递增可以推断出 `version-bump`。若有效判别信息无法阻止不安全解释,遵循[添加会话格式版本](adding-a-session-format-version.zh.md),并选择 `--decision version-bump`。该记录必须包含自身的 `SessionHeader.version` 递增转换;评审不能豁免它,无关的历史升版本也不能授权它。日常变更不创建另一条基线。
|
||||
|
||||
<a id="verify"></a>
|
||||
## 2. 检查、提交并推送
|
||||
@@ -99,11 +99,11 @@ pnpm run doc-sync
|
||||
pnpm --silent run persistence-changes --update 2026-09-11-poc-optional --prose .artifacts/persistence-change.prose.json --json
|
||||
```
|
||||
|
||||
命令刷新机器声明、schema、目录和配对。没有 `--prose` 时,它保留已有说明。更新会拒绝初始基线、其他记录所依赖的记录,以及已被定稿检查点锁定的记录。定稿检查点之外,目录不会推断审阅接受状态:保留已接受历史,并创建后继。
|
||||
命令刷新机器声明、schema、目录和配对。没有 `--prose` 时,它保留已有说明。需要兼容性评审的 schema 再次变化时,必须提供新的 `--review FILE`;之前的评审不能授权新转换。更新会拒绝初始基线、其他记录所依赖的记录,以及已被定稿检查点锁定的记录。定稿检查点之外,目录不会推断审阅接受状态:保留已接受历史,并创建后继。
|
||||
|
||||
集成产生竞争末端记录时,根据剩余历史更新尚未接受的记录,再重新评估最终差异。无关根的确认无需刷新。[机制决策](../../.agents/notes/implemented/process/2026-09-11-persistence-type-history.zh.md)解释为何保留完整快照和逐根前驱。
|
||||
|
||||
显式 `--decision` 仍是受检查的断言。若已有属性的值类型发生变化,下面这个故意错误的断言会在写入前失败:
|
||||
显式 `--decision same-version` 不能替代评审证据。若已有属性的值类型变化需要评审,以下命令在未提供新 `--review FILE` 时会在写入前失败:
|
||||
|
||||
```sh
|
||||
pnpm --silent run persistence-changes --update 2026-09-11-poc-optional --decision same-version --json
|
||||
|
||||
@@ -18,11 +18,11 @@
|
||||
en: 6cd0f79a9455bdf2
|
||||
zh: a5978b7834c0802b
|
||||
/persistence-type-change-records/compatibility-rules:
|
||||
en: 62ce78d5618572b6
|
||||
zh: 023021d431f8a8e6
|
||||
en: ef7e647c2e8cc40e
|
||||
zh: e2cd28593af89146
|
||||
/persistence-type-change-records/history-and-limitations:
|
||||
en: 10a426d36c41548b
|
||||
zh: ef03b693d9d9be26
|
||||
en: 705a215ce0dd5d82
|
||||
zh: a3b3e46231dae6ce
|
||||
/persistence-type-change-records/dev-note:
|
||||
en: 5de1d6304e00c58f
|
||||
zh: 8d8225361cfa29e5
|
||||
@@ -46,25 +46,25 @@ The [record template](../../.agents/skills/dsh-doc/templates/persistence-change.
|
||||
<a id="compatibility-rules"></a>
|
||||
## Compatibility rules
|
||||
|
||||
Every detected structural change requires an acknowledgement. Record creation and update infer the minimum decision from these fixed rules; an explicit `--decision` is a checked assertion. The rules apply to the complete change, so an allowed change cannot hide a simultaneous breaking change.
|
||||
Every detected structural change requires an acknowledgement. Keep the Session version when older readers preserve the required meaning of accepted data, safely ignore optional features while retaining opaque records, or refuse unsupported required data before execution, mutation, or recovery truncation. New readers must preserve historical meaning and readability. Bump only when unsafe interpretation cannot be prevented by an effective discriminator. Unknown fields or values count only when existing readers actually handle them safely; type differences alone cannot establish that behavior. See the [reader-compatibility decision](../../.agents/notes/implemented/process/2026-10-08-session-reader-compatibility-review.md).
|
||||
|
||||
| Detected change | Minimum decision |
|
||||
| Detected change | Recording requirement |
|
||||
|---|---|
|
||||
| Add an optional event-body property, including its complete subtree | `same-version` |
|
||||
| Make a required event-body property optional | `same-version` |
|
||||
| Add an ordinary event type | `same-version` |
|
||||
| Add a higher numeric `data.version` to an ordinary event while retaining every old payload alternative under the existing compatibility rules | `same-version` |
|
||||
| Add an explicitly qualified attribution kind to a user/developer source slot whose before and after schemas carry the same supported policy | `same-version` |
|
||||
| Make an optional property required, add a required property, change an existing type, or remove/rename a property or event | `version-bump` |
|
||||
| Change the Session header or event envelope | `version-bump` |
|
||||
| Make an optional property required, add a required property, change an existing type, or remove/rename a property or event | Compatibility review for `same-version`, or explicit `version-bump` |
|
||||
| Change the Session header or event envelope | Compatibility review for `same-version`; a header version change requires `version-bump` |
|
||||
|
||||
A finalized checkpoint protects the accepted baseline without replacing these compatibility rules. At V4, optional additions, ordinary events, and qualified attribution additions can receive new same-version records. Breaking differences require a higher writer version and an acknowledgement containing its own header increase. The accepted V4 record cannot be updated to reuse its original 3→4 transition.
|
||||
The classifier identifies established same-version cases and changes requiring compatibility review; it cannot decide semantic safety. A reviewed same-version record carries a machine `review` object with authored, nonempty `oldReaders`, `newReaders`, and `verification` strings. These identify actual reader behavior in both directions and the checks establishing it. The complete transition is reviewed, including any simultaneous changes. An explicit `version-bump` instead requires its own increasing `SessionHeader.version` transition; review cannot waive a header version change. Finalized records remain immutable, so a later record cannot reuse the accepted 3→4 transition.
|
||||
|
||||
An ordinary event may add payload alternatives with a required, nonnegative integer `data.version` greater than every existing payload version, provided every existing alternative remains readable under the compatibility rules above. Optional property additions and required-to-optional changes may accompany the new version; each retained alternative is compared separately. The reader must retain support for the old payloads; adding variants at an existing version, dropping old versions, and changing the Session header or envelope remain breaking. Older readers may reject the new payload version. The [catalog acknowledgement](2026-09-20-unknown-child-catalog.md) records one such transition.
|
||||
An ordinary event may add payload alternatives with a required, nonnegative integer `data.version` greater than every existing payload version, provided every existing alternative remains readable under the established same-version rules above. Optional property additions and required-to-optional changes may accompany the new version; each retained alternative is compared separately. Other payload changes require compatibility review. Older readers may reject the new payload version. The [catalog acknowledgement](2026-09-20-unknown-child-catalog.md) records one such transition.
|
||||
|
||||
The extractor accepts an explicit `@persistenceSource` binding for a core-owned source property on a literal user or developer role; it does not infer a binding from an unannotated type. A producer qualifies its `MessageSourceMap` entry with `@persistenceAttribution`. Qualification promises that an unknown kind and its JSON metadata survive reading without the producer, and that the kind imposes no validation, replay, or authority requirement. A producer may inspect its own kind to resume duplicate suppression; other readers must preserve and derive the recorded messages without that projection. The recorded schema retains the binding, policy version, literal `kind` discriminator, preservation promise, and qualified kind set. Inventory format 2 stores those promises; extraction without a bound policy retains format 1. Session format versions are independent. Both compared snapshots must carry compatible policy state. Existing kind groups still receive ordinary structural comparison; removals, unmarked additions, policy changes, and unrelated breaking changes remain strict. Multiple context-form alternatives with the same wire kind form one group.
|
||||
The extractor accepts an explicit `@persistenceSource` binding for a core-owned source property on a literal user or developer role; it does not infer a binding from an unannotated type. A producer qualifies its `MessageSourceMap` entry with `@persistenceAttribution`. Qualification promises that an unknown kind and its JSON metadata survive reading without the producer, and that the kind imposes no validation, replay, or authority requirement. A producer may inspect its own kind to resume duplicate suppression; other readers must preserve and derive the recorded messages without that projection. The recorded schema retains the binding, policy version, literal `kind` discriminator, preservation promise, and qualified kind set. Inventory format 2 stores those promises; extraction without a bound policy retains format 1. Session format versions are independent. Both compared snapshots must carry compatible policy state. Existing kind groups still receive ordinary structural comparison; removals, unmarked additions, policy changes, and unrelated structural changes require compatibility review. Multiple context-form alternatives with the same wire kind form one group.
|
||||
|
||||
A same-version explanation states how old records remain readable and how older readers handle new records. Optional additions explain why older readers can ignore them without changing replay; a new payload version records the older-reader rejection. For required-to-optional changes, it explains how readers handle an absent value. The checker validates the type classification; reviewers assess the explanation. A version-bump record includes the increasing header version in the same transition and follows the [Session-format procedure](../cookbook/adding-a-session-format-version.md).
|
||||
Every same-version explanation states how old records remain readable and how older readers handle new records, including established cases that need no machine review. A feature absent from a log must retain the old behavior; dispensable features must be safe to ignore; required features need an effective refusal signal. Unsupported-feature refusal must not become corruption recovery or tail truncation. For required-to-optional changes, explain how readers handle an absent value. Reviewers assess behavior and evidence; the checker validates the declaration and type classification. A version-bump record follows the [Session-format procedure](../cookbook/adding-a-session-format-version.md).
|
||||
|
||||
When a reader rejects a JSON property by name, declare the property as optional `never` and mark it with argument-free `@persistenceReserved`. The extractor retains the forbidden field, so allowing a JSON value later requires an existing-field type change. Required or JSON-valued properties cannot carry this marker. Unmarked optional `never` and `undefined` properties retain their existing omission behavior.
|
||||
|
||||
@@ -75,7 +75,7 @@ One baseline records the complete initial inventory. Later records use their pre
|
||||
|
||||
Accepted records describe historical transitions; preserve their machine declarations and schema snapshots when adding a successor. An unaccepted terminal record can be refreshed explicitly; the command rejects baselines, records with dependants, and checkpoint-locked records before producing artifacts. Verification checks retained checkpoint hashes; it does not prove that the checkpoints and their authority record were never edited together. No Git ref, remote service, or released checkout supplies the baseline.
|
||||
|
||||
Digests describe declared persistence types, not runtime validation or behavior. Ordinary comments, source locations, alias names, and harmless declaration reordering do not affect them. Compatibility annotations are recorded policy data and do affect digests. Unannotated version-1 snapshots retain their original normalization, fingerprints, and strict classification; policy-bearing graphs use a separate fingerprint domain. Object fields, union alternatives, intersection operands, and index signatures can be reordered when their resolved types stay the same; tuple positions and numeric enum values remain significant. Catalog text and source locations may still change, so regenerate stale artifacts without adding an acknowledgement for an unchanged digest. Opaque types such as `unknown` expose no hidden structure to compare. Behavior-only changes and structures hidden inside opaque values are outside this mechanism's scope. The [decision](../../.agents/notes/implemented/process/2026-09-11-persistence-type-history.md) records these trade-offs.
|
||||
Digests describe declared persistence types, not runtime validation or behavior. Ordinary comments, source locations, alias names, and harmless declaration reordering do not affect them. Compatibility annotations are recorded policy data and do affect digests. Unannotated version-1 snapshots retain their original normalization and fingerprints; policy-bearing graphs use a separate fingerprint domain. Object fields, union alternatives, intersection operands, and index signatures can be reordered when their resolved types stay the same; tuple positions and numeric enum values remain significant. Catalog text and source locations may still change, so regenerate stale artifacts without adding an acknowledgement for an unchanged digest. Opaque types such as `unknown` expose no hidden structure to compare. Behavior-only changes and structures hidden inside opaque values are outside this mechanism's scope. The [decision](../../.agents/notes/implemented/process/2026-09-11-persistence-type-history.md) records these trade-offs.
|
||||
|
||||
<a id="dev-note"></a>
|
||||
## Dev Note
|
||||
|
||||
@@ -46,25 +46,25 @@ description: "审阅和维护已记录的会话持久化类型变更、对应 sc
|
||||
<a id="compatibility-rules"></a>
|
||||
## 兼容性规则
|
||||
|
||||
每个检测到的结构变更都需要确认。创建和更新记录依据这些固定规则推断最低决策;显式 `--decision` 是受检查的断言。规则应用于整个变更,因此允许的变更不能掩盖同时发生的破坏性变更。
|
||||
每个检测到的结构变更都需要确认。当旧读取器保留所接受数据的必要含义、安全忽略可选功能并保留不透明记录,或在执行、修改及恢复截断前拒绝不受支持的必要数据时,可以保留 Session 版本。新读取器必须保留历史含义和可读性。只有有效判别信息无法阻止不安全解释时,才提升版本。未知字段或值只有在已有读取器实际安全处理它们时才有效;类型差异本身无法证明这种行为。参见[读取器兼容性决策](../../.agents/notes/implemented/process/2026-10-08-session-reader-compatibility-review.zh.md)。
|
||||
|
||||
| 检测到的变更 | 最低决策要求 |
|
||||
| 检测到的变更 | 记录要求 |
|
||||
|---|---|
|
||||
| 添加可选事件体属性,包括其完整子树 | `same-version` |
|
||||
| 将必选事件体属性改为可选 | `same-version` |
|
||||
| 添加普通事件类型 | `same-version` |
|
||||
| 为普通事件增加更高的数字 `data.version`,并按现有兼容性规则保留所有旧载荷分支 | `same-version` |
|
||||
| 在前后 schema 均带有相同受支持策略的 user/developer 消息源字段中,添加显式声明为归属信息的 kind | `same-version` |
|
||||
| 将可选属性改为必选、添加必选属性、更改已有类型,或删除/重命名属性或事件 | `version-bump` |
|
||||
| 更改会话头或事件封装 | `version-bump` |
|
||||
| 将可选属性改为必选、添加必选属性、更改已有类型,或删除/重命名属性或事件 | `same-version` 需兼容性评审,或显式 `version-bump` |
|
||||
| 更改会话头或事件封装 | `same-version` 需兼容性评审;头版本变化要求 `version-bump` |
|
||||
|
||||
定稿检查点保护已接受基线,不替换这些兼容性规则。在 V4 中,可选新增、普通事件及符合条件的归属 kind 新增可以使用新的同版本记录。破坏性差异要求更高的写入器版本,以及包含自身头版本递增的确认记录。不能更新已接受 V4 记录以复用其原有 3→4 转换。
|
||||
分类器识别已明确允许的同版本情形及需要兼容性评审的变更,不能判定语义安全性。经评审的同版本记录携带机器 `review` 对象,其 `oldReaders`、`newReaders` 和 `verification` 字符串必须由作者填写且非空,用于标识两个方向上的实际读取器行为及其验证。评审覆盖整个转换,包括同时发生的其他变更。显式 `version-bump` 则要求自身包含 `SessionHeader.version` 递增转换;评审不能豁免头版本变化。定稿记录保持不可变,因此后续记录不能复用已接受的 3→4 转换。
|
||||
|
||||
普通事件可增加具有必选、非负整数 `data.version` 的载荷分支,新版本必须高于所有已有载荷版本,且所有已有分支按上述兼容性规则保持可读。可选属性新增和必选改可选可以与新版本一同引入;每个保留分支均独立比较。读取器必须保留旧载荷支持;在已有版本中增加分支、移除旧版本,以及修改 Session header 或事件封装仍属于破坏性变更。旧读取器可能拒绝新载荷版本。[Catalog 确认记录](2026-09-20-unknown-child-catalog.zh.md) 记录了此类演进。
|
||||
普通事件可增加具有必选、非负整数 `data.version` 的载荷分支,新版本必须高于所有已有载荷版本,且所有已有分支按上述明确允许的同版本规则保持可读。可选属性新增和必选改可选可以与新版本一同引入;每个保留分支均独立比较。其他载荷变更需要兼容性评审。旧读取器可能拒绝新载荷版本。[Catalog 确认记录](2026-09-20-unknown-child-catalog.zh.md)记录了此类演进。
|
||||
|
||||
提取器接受核心拥有的 source 属性上针对 user 或 developer 字面量角色的显式 `@persistenceSource` 绑定,不会从未标记类型中推断绑定。生产者用 `@persistenceAttribution` 标记其 `MessageSourceMap` 成员。该标记承诺:读取器无需生产者即可保留未知 kind 及其 JSON 元数据,且该 kind 不引入校验、回放或权限要求。生产者可以检查自身 kind 来恢复去重状态;其他读取器必须无需该投影也能保留并派生已记录的消息。记录的 schema 保存绑定、策略版本、字面量 `kind` 判别字段、保留承诺及符合条件的 kind 集合。Inventory format 2 保存这些承诺;没有绑定策略的提取仍使用 format 1。Session 格式版本独立于此。比较双方的快照必须带有兼容的策略状态。已有 kind 分组仍进行常规结构比较;删除、未标记的添加、策略更改及无关破坏性变更继续采用严格规则。同一 wire kind 的多个上下文形式分支归为一组。
|
||||
提取器接受核心拥有的 source 属性上针对 user 或 developer 字面量角色的显式 `@persistenceSource` 绑定,不会从未标记类型中推断绑定。生产者用 `@persistenceAttribution` 标记其 `MessageSourceMap` 成员。该标记承诺:读取器无需生产者即可保留未知 kind 及其 JSON 元数据,且该 kind 不引入校验、回放或权限要求。生产者可以检查自身 kind 来恢复去重状态;其他读取器必须无需该投影也能保留并派生已记录的消息。记录的 schema 保存绑定、策略版本、字面量 `kind` 判别字段、保留承诺及符合条件的 kind 集合。Inventory format 2 保存这些承诺;没有绑定策略的提取仍使用 format 1。Session 格式版本独立于此。比较双方的快照必须带有兼容的策略状态。已有 kind 分组仍进行常规结构比较;删除、未标记的添加、策略更改及无关结构变更需要兼容性评审。同一 wire kind 的多个上下文形式分支归为一组。
|
||||
|
||||
同版本说明须解释旧记录如何保持可读,以及旧读取器如何处理新记录。可选新增须说明旧读取器为何可以忽略它而不改变回放;新增载荷版本须记录旧读取器的拒绝行为。对于必选改可选的变更,说明须解释读取器如何处理缺失值。检查器验证类型分类;审阅者判断说明是否成立。升版本记录在同一转换中包含递增的头部版本,并遵循[会话格式流程](../cookbook/adding-a-session-format-version.zh.md)。
|
||||
每项同版本说明都须解释旧记录如何保持可读,以及旧读取器如何处理新记录,包括无需机器评审的明确允许情形。日志未使用某功能时必须保留旧行为;可舍弃功能必须能安全忽略;必要功能需要有效的拒绝信号。不受支持功能的拒绝不能变成损坏恢复或尾部截断。对于必选改可选的变更,说明读取器如何处理缺失值。审阅者判断行为和证据;检查器校验声明及类型分类。升版本记录遵循[会话格式流程](../cookbook/adding-a-session-format-version.zh.md)。
|
||||
|
||||
当读取器按属性名拒绝 JSON 字段时,将该属性声明为可选 `never`,并添加无参数的 `@persistenceReserved` 标记。提取器保留这一禁止字段,因此后续允许 JSON 值属于已有字段类型变更。必选属性或允许 JSON 值的属性不能携带该标记。未标记的可选 `never` 和 `undefined` 属性保留现有省略行为。
|
||||
|
||||
@@ -75,7 +75,7 @@ description: "审阅和维护已记录的会话持久化类型变更、对应 sc
|
||||
|
||||
已接受记录描述历史转换;添加后继时须保留其机器声明和 schema 快照。尚未接受的末端记录可以显式刷新;命令在生成产物前拒绝基线、被依赖的记录和被检查点锁定的记录。验证检查所保留检查点的哈希,但不证明检查点及其真源记录从未被一起修改。基线不依赖 Git 引用、远端服务或已发布版本的检出目录。
|
||||
|
||||
摘要描述声明的持久化类型,不描述运行时验证或行为。普通注释、源码位置、别名名称和无语义变化的声明重排不影响摘要。兼容性标记是记录的策略数据,会影响摘要。不带标记的版本 1 快照保留原有规范化、指纹和严格分类;包含策略的图使用独立的指纹域。对象字段、联合类型分支、交叉类型操作数和索引签名可以在解析类型不变时重排;元组位置和数字枚举值仍然影响摘要。目录文本和源码位置仍可能变化,因此应重新生成陈旧产物,无需为未变的摘要添加确认记录。`unknown` 等不透明类型不提供可比较的内部结构。纯行为变更以及不透明值中隐藏的结构不在本机制范围内。[决策记录](../../.agents/notes/implemented/process/2026-09-11-persistence-type-history.zh.md)说明这些取舍。
|
||||
摘要描述声明的持久化类型,不描述运行时验证或行为。普通注释、源码位置、别名名称和无语义变化的声明重排不影响摘要。兼容性标记是记录的策略数据,会影响摘要。不带标记的版本 1 快照保留原有规范化和指纹;包含策略的图使用独立的指纹域。对象字段、联合类型分支、交叉类型操作数和索引签名可以在解析类型不变时重排;元组位置和数字枚举值仍然影响摘要。目录文本和源码位置仍可能变化,因此应重新生成陈旧产物,无需为未变的摘要添加确认记录。`unknown` 等不透明类型不提供可比较的内部结构。纯行为变更以及不透明值中隐藏的结构不在本机制范围内。[决策记录](../../.agents/notes/implemented/process/2026-09-11-persistence-type-history.zh.md)说明这些取舍。
|
||||
|
||||
<a id="dev-note"></a>
|
||||
## 开发备注
|
||||
|
||||
@@ -21,8 +21,8 @@
|
||||
en: af9cea43334ee692
|
||||
zh: c6c8bd0d6a17718a
|
||||
/persistence-release-dsh-v0-0-1-rc-2/structural-changes:
|
||||
en: bdf6c95697ae5816
|
||||
zh: 0332dec60181ab4a
|
||||
en: 88698b759e85cd92
|
||||
zh: 585d2d49d32b631f
|
||||
/persistence-release-dsh-v0-0-1-rc-2/verification:
|
||||
en: 8ccca317964523b1
|
||||
zh: 2c8b3e70a3320778
|
||||
|
||||
@@ -191,58 +191,58 @@ changes:
|
||||
|
||||
<!-- persistence-release-changes:start -->
|
||||
|
||||
Detected 45 changed roots and 48 structural differences. The minimum below is calculated using current rules for comparison only; it does not assert historical compliance, migration correctness, or runtime compatibility.
|
||||
Detected 45 changed roots and 48 structural differences. The review requirement below is calculated using current rules for comparison only; it does not assert historical compliance, migration correctness, or runtime compatibility.
|
||||
|
||||
| Path | Change | Current minimum |
|
||||
| Path | Change | Review requirement |
|
||||
|---|---|---|
|
||||
| `SessionEventEnvelope` | `union-variants-changed` | `version-bump` |
|
||||
| `event:agent-preset/selected.ignorable` | `optional-property-added` | `version-bump` |
|
||||
| `event:agent/inbox/spliced.data.inserted[].source` | `union-variants-changed` | `version-bump` |
|
||||
| `event:agent/inbox/spliced.ignorable` | `optional-property-added` | `version-bump` |
|
||||
| `event:approval/asked.ignorable` | `optional-property-added` | `version-bump` |
|
||||
| `event:approval/decided.ignorable` | `optional-property-added` | `version-bump` |
|
||||
| `event:approval/policy.ignorable` | `optional-property-added` | `version-bump` |
|
||||
| `event:assistant/chunk.ignorable` | `optional-property-added` | `version-bump` |
|
||||
| `event:assistant/message.ignorable` | `optional-property-added` | `version-bump` |
|
||||
| `event:command/done.ignorable` | `optional-property-added` | `version-bump` |
|
||||
| `event:command/run.ignorable` | `optional-property-added` | `version-bump` |
|
||||
| `event:compact/end.ignorable` | `optional-property-added` | `version-bump` |
|
||||
| `event:compact/prune.ignorable` | `optional-property-added` | `version-bump` |
|
||||
| `event:compact/start.ignorable` | `optional-property-added` | `version-bump` |
|
||||
| `event:compact/summary.ignorable` | `optional-property-added` | `version-bump` |
|
||||
| `event:feedback/record.ignorable` | `optional-property-added` | `version-bump` |
|
||||
| `event:goal/change.ignorable` | `optional-property-added` | `version-bump` |
|
||||
| `event:hook/invoked.ignorable` | `optional-property-added` | `version-bump` |
|
||||
| `event:hook/result.ignorable` | `optional-property-added` | `version-bump` |
|
||||
| `event:llm/retry.ignorable` | `optional-property-added` | `version-bump` |
|
||||
| `event:llm/retry-started.ignorable` | `optional-property-added` | `version-bump` |
|
||||
| `event:permission/preset.ignorable` | `optional-property-added` | `version-bump` |
|
||||
| `event:plan/mode.ignorable` | `optional-property-added` | `version-bump` |
|
||||
| `event:request/context.ignorable` | `optional-property-added` | `version-bump` |
|
||||
| `event:request/header.ignorable` | `optional-property-added` | `version-bump` |
|
||||
| `event:sandbox/mode.ignorable` | `optional-property-added` | `version-bump` |
|
||||
| `SessionEventEnvelope` | `union-variants-changed` | `review-required` |
|
||||
| `event:agent-preset/selected.ignorable` | `optional-property-added` | `review-required` |
|
||||
| `event:agent/inbox/spliced.data.inserted[].source` | `union-variants-changed` | `review-required` |
|
||||
| `event:agent/inbox/spliced.ignorable` | `optional-property-added` | `review-required` |
|
||||
| `event:approval/asked.ignorable` | `optional-property-added` | `review-required` |
|
||||
| `event:approval/decided.ignorable` | `optional-property-added` | `review-required` |
|
||||
| `event:approval/policy.ignorable` | `optional-property-added` | `review-required` |
|
||||
| `event:assistant/chunk.ignorable` | `optional-property-added` | `review-required` |
|
||||
| `event:assistant/message.ignorable` | `optional-property-added` | `review-required` |
|
||||
| `event:command/done.ignorable` | `optional-property-added` | `review-required` |
|
||||
| `event:command/run.ignorable` | `optional-property-added` | `review-required` |
|
||||
| `event:compact/end.ignorable` | `optional-property-added` | `review-required` |
|
||||
| `event:compact/prune.ignorable` | `optional-property-added` | `review-required` |
|
||||
| `event:compact/start.ignorable` | `optional-property-added` | `review-required` |
|
||||
| `event:compact/summary.ignorable` | `optional-property-added` | `review-required` |
|
||||
| `event:feedback/record.ignorable` | `optional-property-added` | `review-required` |
|
||||
| `event:goal/change.ignorable` | `optional-property-added` | `review-required` |
|
||||
| `event:hook/invoked.ignorable` | `optional-property-added` | `review-required` |
|
||||
| `event:hook/result.ignorable` | `optional-property-added` | `review-required` |
|
||||
| `event:llm/retry.ignorable` | `optional-property-added` | `review-required` |
|
||||
| `event:llm/retry-started.ignorable` | `optional-property-added` | `review-required` |
|
||||
| `event:permission/preset.ignorable` | `optional-property-added` | `review-required` |
|
||||
| `event:plan/mode.ignorable` | `optional-property-added` | `review-required` |
|
||||
| `event:request/context.ignorable` | `optional-property-added` | `review-required` |
|
||||
| `event:request/header.ignorable` | `optional-property-added` | `review-required` |
|
||||
| `event:sandbox/mode.ignorable` | `optional-property-added` | `review-required` |
|
||||
| `event:schedule/change` | `root-added` | `same-version` |
|
||||
| `event:session/end-seed.ignorable` | `optional-property-added` | `version-bump` |
|
||||
| `event:session/title.ignorable` | `optional-property-added` | `version-bump` |
|
||||
| `event:session/title-llm-request.data.messages[].source` | `union-variants-changed` | `version-bump` |
|
||||
| `event:session/title-llm-request.ignorable` | `optional-property-added` | `version-bump` |
|
||||
| `event:step/end.ignorable` | `optional-property-added` | `version-bump` |
|
||||
| `event:step/start.ignorable` | `optional-property-added` | `version-bump` |
|
||||
| `event:subagent/descriptor.ignorable` | `optional-property-added` | `version-bump` |
|
||||
| `event:todo/write.ignorable` | `optional-property-added` | `version-bump` |
|
||||
| `event:session/end-seed.ignorable` | `optional-property-added` | `review-required` |
|
||||
| `event:session/title.ignorable` | `optional-property-added` | `review-required` |
|
||||
| `event:session/title-llm-request.data.messages[].source` | `union-variants-changed` | `review-required` |
|
||||
| `event:session/title-llm-request.ignorable` | `optional-property-added` | `review-required` |
|
||||
| `event:step/end.ignorable` | `optional-property-added` | `review-required` |
|
||||
| `event:step/start.ignorable` | `optional-property-added` | `review-required` |
|
||||
| `event:subagent/descriptor.ignorable` | `optional-property-added` | `review-required` |
|
||||
| `event:todo/write.ignorable` | `optional-property-added` | `review-required` |
|
||||
| `event:tool-workflow/agent-end` | `root-added` | `same-version` |
|
||||
| `event:tool-workflow/agent-start` | `root-added` | `same-version` |
|
||||
| `event:tool-workflow/run-end` | `root-added` | `same-version` |
|
||||
| `event:tool-workflow/run-start` | `root-added` | `same-version` |
|
||||
| `event:tool/call.ignorable` | `optional-property-added` | `version-bump` |
|
||||
| `event:tool/code-dispatch.ignorable` | `optional-property-added` | `version-bump` |
|
||||
| `event:tool/code-dispatch-start.ignorable` | `optional-property-added` | `version-bump` |
|
||||
| `event:tool/result.ignorable` | `optional-property-added` | `version-bump` |
|
||||
| `event:turn/end.ignorable` | `optional-property-added` | `version-bump` |
|
||||
| `event:turn/start.ignorable` | `optional-property-added` | `version-bump` |
|
||||
| `event:user/message.data.source` | `union-variants-changed` | `version-bump` |
|
||||
| `event:user/message.ignorable` | `optional-property-added` | `version-bump` |
|
||||
| `event:web/deepseek-search-llm-request.ignorable` | `optional-property-added` | `version-bump` |
|
||||
| `event:tool/call.ignorable` | `optional-property-added` | `review-required` |
|
||||
| `event:tool/code-dispatch.ignorable` | `optional-property-added` | `review-required` |
|
||||
| `event:tool/code-dispatch-start.ignorable` | `optional-property-added` | `review-required` |
|
||||
| `event:tool/result.ignorable` | `optional-property-added` | `review-required` |
|
||||
| `event:turn/end.ignorable` | `optional-property-added` | `review-required` |
|
||||
| `event:turn/start.ignorable` | `optional-property-added` | `review-required` |
|
||||
| `event:user/message.data.source` | `union-variants-changed` | `review-required` |
|
||||
| `event:user/message.ignorable` | `optional-property-added` | `review-required` |
|
||||
| `event:web/deepseek-search-llm-request.ignorable` | `optional-property-added` | `review-required` |
|
||||
|
||||
<!-- persistence-release-changes:end -->
|
||||
|
||||
|
||||
@@ -191,58 +191,58 @@ changes:
|
||||
|
||||
<!-- persistence-release-changes:start -->
|
||||
|
||||
检测到 45 个根类型变化、48 项结构差异。下表的最低要求按当前规则计算,只用于比较;不表示旧版本曾遵守这些规则,也不证明迁移或运行时兼容性。
|
||||
检测到 45 个根类型变化、48 项结构差异。下表的审查要求按当前规则计算,只用于比较;不表示旧版本曾遵守这些规则,也不证明迁移或运行时兼容性。
|
||||
|
||||
| 路径 | 变化 | 当前最低要求 |
|
||||
| 路径 | 变化 | 审查要求 |
|
||||
|---|---|---|
|
||||
| `SessionEventEnvelope` | `union-variants-changed` | `version-bump` |
|
||||
| `event:agent-preset/selected.ignorable` | `optional-property-added` | `version-bump` |
|
||||
| `event:agent/inbox/spliced.data.inserted[].source` | `union-variants-changed` | `version-bump` |
|
||||
| `event:agent/inbox/spliced.ignorable` | `optional-property-added` | `version-bump` |
|
||||
| `event:approval/asked.ignorable` | `optional-property-added` | `version-bump` |
|
||||
| `event:approval/decided.ignorable` | `optional-property-added` | `version-bump` |
|
||||
| `event:approval/policy.ignorable` | `optional-property-added` | `version-bump` |
|
||||
| `event:assistant/chunk.ignorable` | `optional-property-added` | `version-bump` |
|
||||
| `event:assistant/message.ignorable` | `optional-property-added` | `version-bump` |
|
||||
| `event:command/done.ignorable` | `optional-property-added` | `version-bump` |
|
||||
| `event:command/run.ignorable` | `optional-property-added` | `version-bump` |
|
||||
| `event:compact/end.ignorable` | `optional-property-added` | `version-bump` |
|
||||
| `event:compact/prune.ignorable` | `optional-property-added` | `version-bump` |
|
||||
| `event:compact/start.ignorable` | `optional-property-added` | `version-bump` |
|
||||
| `event:compact/summary.ignorable` | `optional-property-added` | `version-bump` |
|
||||
| `event:feedback/record.ignorable` | `optional-property-added` | `version-bump` |
|
||||
| `event:goal/change.ignorable` | `optional-property-added` | `version-bump` |
|
||||
| `event:hook/invoked.ignorable` | `optional-property-added` | `version-bump` |
|
||||
| `event:hook/result.ignorable` | `optional-property-added` | `version-bump` |
|
||||
| `event:llm/retry.ignorable` | `optional-property-added` | `version-bump` |
|
||||
| `event:llm/retry-started.ignorable` | `optional-property-added` | `version-bump` |
|
||||
| `event:permission/preset.ignorable` | `optional-property-added` | `version-bump` |
|
||||
| `event:plan/mode.ignorable` | `optional-property-added` | `version-bump` |
|
||||
| `event:request/context.ignorable` | `optional-property-added` | `version-bump` |
|
||||
| `event:request/header.ignorable` | `optional-property-added` | `version-bump` |
|
||||
| `event:sandbox/mode.ignorable` | `optional-property-added` | `version-bump` |
|
||||
| `SessionEventEnvelope` | `union-variants-changed` | `review-required` |
|
||||
| `event:agent-preset/selected.ignorable` | `optional-property-added` | `review-required` |
|
||||
| `event:agent/inbox/spliced.data.inserted[].source` | `union-variants-changed` | `review-required` |
|
||||
| `event:agent/inbox/spliced.ignorable` | `optional-property-added` | `review-required` |
|
||||
| `event:approval/asked.ignorable` | `optional-property-added` | `review-required` |
|
||||
| `event:approval/decided.ignorable` | `optional-property-added` | `review-required` |
|
||||
| `event:approval/policy.ignorable` | `optional-property-added` | `review-required` |
|
||||
| `event:assistant/chunk.ignorable` | `optional-property-added` | `review-required` |
|
||||
| `event:assistant/message.ignorable` | `optional-property-added` | `review-required` |
|
||||
| `event:command/done.ignorable` | `optional-property-added` | `review-required` |
|
||||
| `event:command/run.ignorable` | `optional-property-added` | `review-required` |
|
||||
| `event:compact/end.ignorable` | `optional-property-added` | `review-required` |
|
||||
| `event:compact/prune.ignorable` | `optional-property-added` | `review-required` |
|
||||
| `event:compact/start.ignorable` | `optional-property-added` | `review-required` |
|
||||
| `event:compact/summary.ignorable` | `optional-property-added` | `review-required` |
|
||||
| `event:feedback/record.ignorable` | `optional-property-added` | `review-required` |
|
||||
| `event:goal/change.ignorable` | `optional-property-added` | `review-required` |
|
||||
| `event:hook/invoked.ignorable` | `optional-property-added` | `review-required` |
|
||||
| `event:hook/result.ignorable` | `optional-property-added` | `review-required` |
|
||||
| `event:llm/retry.ignorable` | `optional-property-added` | `review-required` |
|
||||
| `event:llm/retry-started.ignorable` | `optional-property-added` | `review-required` |
|
||||
| `event:permission/preset.ignorable` | `optional-property-added` | `review-required` |
|
||||
| `event:plan/mode.ignorable` | `optional-property-added` | `review-required` |
|
||||
| `event:request/context.ignorable` | `optional-property-added` | `review-required` |
|
||||
| `event:request/header.ignorable` | `optional-property-added` | `review-required` |
|
||||
| `event:sandbox/mode.ignorable` | `optional-property-added` | `review-required` |
|
||||
| `event:schedule/change` | `root-added` | `same-version` |
|
||||
| `event:session/end-seed.ignorable` | `optional-property-added` | `version-bump` |
|
||||
| `event:session/title.ignorable` | `optional-property-added` | `version-bump` |
|
||||
| `event:session/title-llm-request.data.messages[].source` | `union-variants-changed` | `version-bump` |
|
||||
| `event:session/title-llm-request.ignorable` | `optional-property-added` | `version-bump` |
|
||||
| `event:step/end.ignorable` | `optional-property-added` | `version-bump` |
|
||||
| `event:step/start.ignorable` | `optional-property-added` | `version-bump` |
|
||||
| `event:subagent/descriptor.ignorable` | `optional-property-added` | `version-bump` |
|
||||
| `event:todo/write.ignorable` | `optional-property-added` | `version-bump` |
|
||||
| `event:session/end-seed.ignorable` | `optional-property-added` | `review-required` |
|
||||
| `event:session/title.ignorable` | `optional-property-added` | `review-required` |
|
||||
| `event:session/title-llm-request.data.messages[].source` | `union-variants-changed` | `review-required` |
|
||||
| `event:session/title-llm-request.ignorable` | `optional-property-added` | `review-required` |
|
||||
| `event:step/end.ignorable` | `optional-property-added` | `review-required` |
|
||||
| `event:step/start.ignorable` | `optional-property-added` | `review-required` |
|
||||
| `event:subagent/descriptor.ignorable` | `optional-property-added` | `review-required` |
|
||||
| `event:todo/write.ignorable` | `optional-property-added` | `review-required` |
|
||||
| `event:tool-workflow/agent-end` | `root-added` | `same-version` |
|
||||
| `event:tool-workflow/agent-start` | `root-added` | `same-version` |
|
||||
| `event:tool-workflow/run-end` | `root-added` | `same-version` |
|
||||
| `event:tool-workflow/run-start` | `root-added` | `same-version` |
|
||||
| `event:tool/call.ignorable` | `optional-property-added` | `version-bump` |
|
||||
| `event:tool/code-dispatch.ignorable` | `optional-property-added` | `version-bump` |
|
||||
| `event:tool/code-dispatch-start.ignorable` | `optional-property-added` | `version-bump` |
|
||||
| `event:tool/result.ignorable` | `optional-property-added` | `version-bump` |
|
||||
| `event:turn/end.ignorable` | `optional-property-added` | `version-bump` |
|
||||
| `event:turn/start.ignorable` | `optional-property-added` | `version-bump` |
|
||||
| `event:user/message.data.source` | `union-variants-changed` | `version-bump` |
|
||||
| `event:user/message.ignorable` | `optional-property-added` | `version-bump` |
|
||||
| `event:web/deepseek-search-llm-request.ignorable` | `optional-property-added` | `version-bump` |
|
||||
| `event:tool/call.ignorable` | `optional-property-added` | `review-required` |
|
||||
| `event:tool/code-dispatch.ignorable` | `optional-property-added` | `review-required` |
|
||||
| `event:tool/code-dispatch-start.ignorable` | `optional-property-added` | `review-required` |
|
||||
| `event:tool/result.ignorable` | `optional-property-added` | `review-required` |
|
||||
| `event:turn/end.ignorable` | `optional-property-added` | `review-required` |
|
||||
| `event:turn/start.ignorable` | `optional-property-added` | `review-required` |
|
||||
| `event:user/message.data.source` | `union-variants-changed` | `review-required` |
|
||||
| `event:user/message.ignorable` | `optional-property-added` | `review-required` |
|
||||
| `event:web/deepseek-search-llm-request.ignorable` | `optional-property-added` | `review-required` |
|
||||
|
||||
<!-- persistence-release-changes:end -->
|
||||
|
||||
|
||||
@@ -21,8 +21,8 @@
|
||||
en: af9cea43334ee692
|
||||
zh: c6c8bd0d6a17718a
|
||||
/persistence-release-dsh-v0-0-1-rc-3/structural-changes:
|
||||
en: b4ded7b41999584d
|
||||
zh: 49c3f355a5559cbd
|
||||
en: 6bdebcbbb335e840
|
||||
zh: 6d94a3b39685385e
|
||||
/persistence-release-dsh-v0-0-1-rc-3/verification:
|
||||
en: 8ccca317964523b1
|
||||
zh: 2c8b3e70a3320778
|
||||
|
||||
@@ -92,22 +92,22 @@ changes:
|
||||
|
||||
<!-- persistence-release-changes:start -->
|
||||
|
||||
Detected 12 changed roots and 12 structural differences. The minimum below is calculated using current rules for comparison only; it does not assert historical compliance, migration correctness, or runtime compatibility.
|
||||
Detected 12 changed roots and 12 structural differences. The review requirement below is calculated using current rules for comparison only; it does not assert historical compliance, migration correctness, or runtime compatibility.
|
||||
|
||||
| Path | Change | Current minimum |
|
||||
| Path | Change | Review requirement |
|
||||
|---|---|---|
|
||||
| `event:agent/inbox/spliced.data.inserted[].source.kind` | `type-changed` | `version-bump` |
|
||||
| `event:compact/end` | `root-removed` | `version-bump` |
|
||||
| `event:compact/prune` | `root-removed` | `version-bump` |
|
||||
| `event:compact/start` | `root-removed` | `version-bump` |
|
||||
| `event:compact/summary` | `root-removed` | `version-bump` |
|
||||
| `event:agent/inbox/spliced.data.inserted[].source.kind` | `type-changed` | `review-required` |
|
||||
| `event:compact/end` | `root-removed` | `review-required` |
|
||||
| `event:compact/prune` | `root-removed` | `review-required` |
|
||||
| `event:compact/start` | `root-removed` | `review-required` |
|
||||
| `event:compact/summary` | `root-removed` | `review-required` |
|
||||
| `event:compaction/end` | `root-added` | `same-version` |
|
||||
| `event:compaction/prune` | `root-added` | `same-version` |
|
||||
| `event:compaction/start` | `root-added` | `same-version` |
|
||||
| `event:compaction/summary` | `root-added` | `same-version` |
|
||||
| `event:hook/invoked.data.dialect` | `type-changed` | `version-bump` |
|
||||
| `event:session/title-llm-request.data.messages[].source.kind` | `type-changed` | `version-bump` |
|
||||
| `event:user/message.data.source.kind` | `type-changed` | `version-bump` |
|
||||
| `event:hook/invoked.data.dialect` | `type-changed` | `review-required` |
|
||||
| `event:session/title-llm-request.data.messages[].source.kind` | `type-changed` | `review-required` |
|
||||
| `event:user/message.data.source.kind` | `type-changed` | `review-required` |
|
||||
|
||||
<!-- persistence-release-changes:end -->
|
||||
|
||||
|
||||
@@ -92,22 +92,22 @@ changes:
|
||||
|
||||
<!-- persistence-release-changes:start -->
|
||||
|
||||
检测到 12 个根类型变化、12 项结构差异。下表的最低要求按当前规则计算,只用于比较;不表示旧版本曾遵守这些规则,也不证明迁移或运行时兼容性。
|
||||
检测到 12 个根类型变化、12 项结构差异。下表的审查要求按当前规则计算,只用于比较;不表示旧版本曾遵守这些规则,也不证明迁移或运行时兼容性。
|
||||
|
||||
| 路径 | 变化 | 当前最低要求 |
|
||||
| 路径 | 变化 | 审查要求 |
|
||||
|---|---|---|
|
||||
| `event:agent/inbox/spliced.data.inserted[].source.kind` | `type-changed` | `version-bump` |
|
||||
| `event:compact/end` | `root-removed` | `version-bump` |
|
||||
| `event:compact/prune` | `root-removed` | `version-bump` |
|
||||
| `event:compact/start` | `root-removed` | `version-bump` |
|
||||
| `event:compact/summary` | `root-removed` | `version-bump` |
|
||||
| `event:agent/inbox/spliced.data.inserted[].source.kind` | `type-changed` | `review-required` |
|
||||
| `event:compact/end` | `root-removed` | `review-required` |
|
||||
| `event:compact/prune` | `root-removed` | `review-required` |
|
||||
| `event:compact/start` | `root-removed` | `review-required` |
|
||||
| `event:compact/summary` | `root-removed` | `review-required` |
|
||||
| `event:compaction/end` | `root-added` | `same-version` |
|
||||
| `event:compaction/prune` | `root-added` | `same-version` |
|
||||
| `event:compaction/start` | `root-added` | `same-version` |
|
||||
| `event:compaction/summary` | `root-added` | `same-version` |
|
||||
| `event:hook/invoked.data.dialect` | `type-changed` | `version-bump` |
|
||||
| `event:session/title-llm-request.data.messages[].source.kind` | `type-changed` | `version-bump` |
|
||||
| `event:user/message.data.source.kind` | `type-changed` | `version-bump` |
|
||||
| `event:hook/invoked.data.dialect` | `type-changed` | `review-required` |
|
||||
| `event:session/title-llm-request.data.messages[].source.kind` | `type-changed` | `review-required` |
|
||||
| `event:user/message.data.source.kind` | `type-changed` | `review-required` |
|
||||
|
||||
<!-- persistence-release-changes:end -->
|
||||
|
||||
|
||||
@@ -21,8 +21,8 @@
|
||||
en: af9cea43334ee692
|
||||
zh: c6c8bd0d6a17718a
|
||||
/persistence-release-dsh-v0-1-0-rc-7/structural-changes:
|
||||
en: 5178d26722926711
|
||||
zh: bfe39111dd34ff48
|
||||
en: 1a08248deb5e841c
|
||||
zh: b5dd3c2ac161a100
|
||||
/persistence-release-dsh-v0-1-0-rc-7/verification:
|
||||
en: 8ccca317964523b1
|
||||
zh: 2c8b3e70a3320778
|
||||
|
||||
@@ -59,11 +59,11 @@ changes:
|
||||
|
||||
<!-- persistence-release-changes:start -->
|
||||
|
||||
Detected 1 changed root and 1 structural difference. The minimum below is calculated using current rules for comparison only; it does not assert historical compliance, migration correctness, or runtime compatibility.
|
||||
Detected 1 changed root and 1 structural difference. The review requirement below is calculated using current rules for comparison only; it does not assert historical compliance, migration correctness, or runtime compatibility.
|
||||
|
||||
| Path | Change | Current minimum |
|
||||
| Path | Change | Review requirement |
|
||||
|---|---|---|
|
||||
| `event:assistant/chunk.data.chunk.replayState` | `type-changed` | `version-bump` |
|
||||
| `event:assistant/chunk.data.chunk.replayState` | `type-changed` | `review-required` |
|
||||
|
||||
<!-- persistence-release-changes:end -->
|
||||
|
||||
|
||||
@@ -59,11 +59,11 @@ changes:
|
||||
|
||||
<!-- persistence-release-changes:start -->
|
||||
|
||||
检测到 1 个根类型变化、1 项结构差异。下表的最低要求按当前规则计算,只用于比较;不表示旧版本曾遵守这些规则,也不证明迁移或运行时兼容性。
|
||||
检测到 1 个根类型变化、1 项结构差异。下表的审查要求按当前规则计算,只用于比较;不表示旧版本曾遵守这些规则,也不证明迁移或运行时兼容性。
|
||||
|
||||
| 路径 | 变化 | 当前最低要求 |
|
||||
| 路径 | 变化 | 审查要求 |
|
||||
|---|---|---|
|
||||
| `event:assistant/chunk.data.chunk.replayState` | `type-changed` | `version-bump` |
|
||||
| `event:assistant/chunk.data.chunk.replayState` | `type-changed` | `review-required` |
|
||||
|
||||
<!-- persistence-release-changes:end -->
|
||||
|
||||
|
||||
@@ -21,8 +21,8 @@
|
||||
en: af9cea43334ee692
|
||||
zh: c6c8bd0d6a17718a
|
||||
/persistence-release-dsh-v0-1-0-rc-8/structural-changes:
|
||||
en: 6c7808df4fdcbf70
|
||||
zh: 8b6d83a8adec7f51
|
||||
en: 6fe06d95f9b394a9
|
||||
zh: a6d0517f2c542111
|
||||
/persistence-release-dsh-v0-1-0-rc-8/verification:
|
||||
en: 8ccca317964523b1
|
||||
zh: 2c8b3e70a3320778
|
||||
|
||||
@@ -80,18 +80,18 @@ changes:
|
||||
|
||||
<!-- persistence-release-changes:start -->
|
||||
|
||||
Detected 8 changed roots and 8 structural differences. The minimum below is calculated using current rules for comparison only; it does not assert historical compliance, migration correctness, or runtime compatibility.
|
||||
Detected 8 changed roots and 8 structural differences. The review requirement below is calculated using current rules for comparison only; it does not assert historical compliance, migration correctness, or runtime compatibility.
|
||||
|
||||
| Path | Change | Current minimum |
|
||||
| Path | Change | Review requirement |
|
||||
|---|---|---|
|
||||
| `event:agent/inbox/spliced.data.inserted[].source` | `union-variants-changed` | `version-bump` |
|
||||
| `event:agent/inbox/spliced.data.inserted[].source` | `union-variants-changed` | `review-required` |
|
||||
| `event:assistant/message.data.interrupted` | `optional-property-added` | `same-version` |
|
||||
| `event:session/title-llm-request.data.messages[].source` | `union-variants-changed` | `version-bump` |
|
||||
| `event:session/title-llm-request.data.messages[].source` | `union-variants-changed` | `review-required` |
|
||||
| `event:team/member` | `root-added` | `same-version` |
|
||||
| `event:team/message/delivered` | `root-added` | `same-version` |
|
||||
| `event:team/message/queued` | `root-added` | `same-version` |
|
||||
| `event:team/task` | `root-added` | `same-version` |
|
||||
| `event:user/message.data.source` | `union-variants-changed` | `version-bump` |
|
||||
| `event:user/message.data.source` | `union-variants-changed` | `review-required` |
|
||||
|
||||
<!-- persistence-release-changes:end -->
|
||||
|
||||
|
||||
@@ -80,18 +80,18 @@ changes:
|
||||
|
||||
<!-- persistence-release-changes:start -->
|
||||
|
||||
检测到 8 个根类型变化、8 项结构差异。下表的最低要求按当前规则计算,只用于比较;不表示旧版本曾遵守这些规则,也不证明迁移或运行时兼容性。
|
||||
检测到 8 个根类型变化、8 项结构差异。下表的审查要求按当前规则计算,只用于比较;不表示旧版本曾遵守这些规则,也不证明迁移或运行时兼容性。
|
||||
|
||||
| 路径 | 变化 | 当前最低要求 |
|
||||
| 路径 | 变化 | 审查要求 |
|
||||
|---|---|---|
|
||||
| `event:agent/inbox/spliced.data.inserted[].source` | `union-variants-changed` | `version-bump` |
|
||||
| `event:agent/inbox/spliced.data.inserted[].source` | `union-variants-changed` | `review-required` |
|
||||
| `event:assistant/message.data.interrupted` | `optional-property-added` | `same-version` |
|
||||
| `event:session/title-llm-request.data.messages[].source` | `union-variants-changed` | `version-bump` |
|
||||
| `event:session/title-llm-request.data.messages[].source` | `union-variants-changed` | `review-required` |
|
||||
| `event:team/member` | `root-added` | `same-version` |
|
||||
| `event:team/message/delivered` | `root-added` | `same-version` |
|
||||
| `event:team/message/queued` | `root-added` | `same-version` |
|
||||
| `event:team/task` | `root-added` | `same-version` |
|
||||
| `event:user/message.data.source` | `union-variants-changed` | `version-bump` |
|
||||
| `event:user/message.data.source` | `union-variants-changed` | `review-required` |
|
||||
|
||||
<!-- persistence-release-changes:end -->
|
||||
|
||||
|
||||
@@ -21,8 +21,8 @@
|
||||
en: af9cea43334ee692
|
||||
zh: c6c8bd0d6a17718a
|
||||
/persistence-release-dsh-v0-1-1-rc-1/structural-changes:
|
||||
en: 0be3fa84192c242d
|
||||
zh: 59dc45dd8e7f7594
|
||||
en: ccbbf46733caf0b0
|
||||
zh: 6dad1347c9f7c3e6
|
||||
/persistence-release-dsh-v0-1-1-rc-1/verification:
|
||||
en: 8ccca317964523b1
|
||||
zh: 2c8b3e70a3320778
|
||||
|
||||
@@ -59,9 +59,9 @@ changes:
|
||||
|
||||
<!-- persistence-release-changes:start -->
|
||||
|
||||
Detected 1 changed root and 1 structural difference. The minimum below is calculated using current rules for comparison only; it does not assert historical compliance, migration correctness, or runtime compatibility.
|
||||
Detected 1 changed root and 1 structural difference. The review requirement below is calculated using current rules for comparison only; it does not assert historical compliance, migration correctness, or runtime compatibility.
|
||||
|
||||
| Path | Change | Current minimum |
|
||||
| Path | Change | Review requirement |
|
||||
|---|---|---|
|
||||
| `event:permission/preset.data.origin` | `optional-property-added` | `same-version` |
|
||||
|
||||
|
||||
@@ -59,9 +59,9 @@ changes:
|
||||
|
||||
<!-- persistence-release-changes:start -->
|
||||
|
||||
检测到 1 个根类型变化、1 项结构差异。下表的最低要求按当前规则计算,只用于比较;不表示旧版本曾遵守这些规则,也不证明迁移或运行时兼容性。
|
||||
检测到 1 个根类型变化、1 项结构差异。下表的审查要求按当前规则计算,只用于比较;不表示旧版本曾遵守这些规则,也不证明迁移或运行时兼容性。
|
||||
|
||||
| 路径 | 变化 | 当前最低要求 |
|
||||
| 路径 | 变化 | 审查要求 |
|
||||
|---|---|---|
|
||||
| `event:permission/preset.data.origin` | `optional-property-added` | `same-version` |
|
||||
|
||||
|
||||
@@ -21,8 +21,8 @@
|
||||
en: af9cea43334ee692
|
||||
zh: c6c8bd0d6a17718a
|
||||
/persistence-release-dsh-v0-1-1-rc-2/structural-changes:
|
||||
en: c03cbb0f4bfb5d83
|
||||
zh: c23900507d4111aa
|
||||
en: 330d88a97c27b7d3
|
||||
zh: 4b9b2ddcd94e7a33
|
||||
/persistence-release-dsh-v0-1-1-rc-2/verification:
|
||||
en: 8ccca317964523b1
|
||||
zh: 2c8b3e70a3320778
|
||||
|
||||
@@ -86,16 +86,16 @@ changes:
|
||||
|
||||
<!-- persistence-release-changes:start -->
|
||||
|
||||
Detected 10 changed roots and 11 structural differences. The minimum below is calculated using current rules for comparison only; it does not assert historical compliance, migration correctness, or runtime compatibility.
|
||||
Detected 10 changed roots and 11 structural differences. The review requirement below is calculated using current rules for comparison only; it does not assert historical compliance, migration correctness, or runtime compatibility.
|
||||
|
||||
| Path | Change | Current minimum |
|
||||
| Path | Change | Review requirement |
|
||||
|---|---|---|
|
||||
| `event:agent/inbox/spliced.data.inserted[].content[].attachment.originalDimensions` | `optional-property-added` | `same-version` |
|
||||
| `event:assistant/chunk.data.chunk.block.attachment.originalDimensions` | `optional-property-added` | `same-version` |
|
||||
| `event:assistant/message.data.message.content[].attachment.originalDimensions` | `optional-property-added` | `same-version` |
|
||||
| `event:compaction/summary.data.rawOutput[].attachment.originalDimensions` | `optional-property-added` | `same-version` |
|
||||
| `event:compaction/summary.data.summary[].attachment.originalDimensions` | `optional-property-added` | `same-version` |
|
||||
| `event:permission/preset.data.origin` | `property-removed` | `version-bump` |
|
||||
| `event:permission/preset.data.origin` | `property-removed` | `review-required` |
|
||||
| `event:session/title-llm-request.data.messages[].content[].attachment.originalDimensions` | `optional-property-added` | `same-version` |
|
||||
| `event:team/message/queued.data.message.content[].attachment.originalDimensions` | `optional-property-added` | `same-version` |
|
||||
| `event:tool/code-dispatch.data.content[].attachment.originalDimensions` | `optional-property-added` | `same-version` |
|
||||
|
||||
@@ -86,16 +86,16 @@ changes:
|
||||
|
||||
<!-- persistence-release-changes:start -->
|
||||
|
||||
检测到 10 个根类型变化、11 项结构差异。下表的最低要求按当前规则计算,只用于比较;不表示旧版本曾遵守这些规则,也不证明迁移或运行时兼容性。
|
||||
检测到 10 个根类型变化、11 项结构差异。下表的审查要求按当前规则计算,只用于比较;不表示旧版本曾遵守这些规则,也不证明迁移或运行时兼容性。
|
||||
|
||||
| 路径 | 变化 | 当前最低要求 |
|
||||
| 路径 | 变化 | 审查要求 |
|
||||
|---|---|---|
|
||||
| `event:agent/inbox/spliced.data.inserted[].content[].attachment.originalDimensions` | `optional-property-added` | `same-version` |
|
||||
| `event:assistant/chunk.data.chunk.block.attachment.originalDimensions` | `optional-property-added` | `same-version` |
|
||||
| `event:assistant/message.data.message.content[].attachment.originalDimensions` | `optional-property-added` | `same-version` |
|
||||
| `event:compaction/summary.data.rawOutput[].attachment.originalDimensions` | `optional-property-added` | `same-version` |
|
||||
| `event:compaction/summary.data.summary[].attachment.originalDimensions` | `optional-property-added` | `same-version` |
|
||||
| `event:permission/preset.data.origin` | `property-removed` | `version-bump` |
|
||||
| `event:permission/preset.data.origin` | `property-removed` | `review-required` |
|
||||
| `event:session/title-llm-request.data.messages[].content[].attachment.originalDimensions` | `optional-property-added` | `same-version` |
|
||||
| `event:team/message/queued.data.message.content[].attachment.originalDimensions` | `optional-property-added` | `same-version` |
|
||||
| `event:tool/code-dispatch.data.content[].attachment.originalDimensions` | `optional-property-added` | `same-version` |
|
||||
|
||||
@@ -21,8 +21,8 @@
|
||||
en: af9cea43334ee692
|
||||
zh: c6c8bd0d6a17718a
|
||||
/persistence-release-dsh-v0-1-2-alpha-1/structural-changes:
|
||||
en: f8e5907da99d32b9
|
||||
zh: dcd8dbc9660a20b6
|
||||
en: 5c0c487e21c022cc
|
||||
zh: 4d4c97941321611c
|
||||
/persistence-release-dsh-v0-1-2-alpha-1/verification:
|
||||
en: 8ccca317964523b1
|
||||
zh: 2c8b3e70a3320778
|
||||
|
||||
@@ -212,71 +212,71 @@ changes:
|
||||
|
||||
<!-- persistence-release-changes:start -->
|
||||
|
||||
Detected 52 changed roots and 61 structural differences. The minimum below is calculated using current rules for comparison only; it does not assert historical compliance, migration correctness, or runtime compatibility.
|
||||
Detected 52 changed roots and 61 structural differences. The review requirement below is calculated using current rules for comparison only; it does not assert historical compliance, migration correctness, or runtime compatibility.
|
||||
|
||||
| Path | Change | Current minimum |
|
||||
| Path | Change | Review requirement |
|
||||
|---|---|---|
|
||||
| `SessionEventEnvelope` | `union-variants-changed` | `version-bump` |
|
||||
| `event:agent-preset/selected.ignorable` | `property-removed` | `version-bump` |
|
||||
| `event:agent/inbox/spliced.data.inserted[].source` | `union-variants-changed` | `version-bump` |
|
||||
| `event:agent/inbox/spliced.ignorable` | `property-removed` | `version-bump` |
|
||||
| `event:approval/asked.ignorable` | `property-removed` | `version-bump` |
|
||||
| `event:approval/decided.ignorable` | `property-removed` | `version-bump` |
|
||||
| `event:approval/policy.ignorable` | `property-removed` | `version-bump` |
|
||||
| `SessionEventEnvelope` | `union-variants-changed` | `review-required` |
|
||||
| `event:agent-preset/selected.ignorable` | `property-removed` | `review-required` |
|
||||
| `event:agent/inbox/spliced.data.inserted[].source` | `union-variants-changed` | `review-required` |
|
||||
| `event:agent/inbox/spliced.ignorable` | `property-removed` | `review-required` |
|
||||
| `event:approval/asked.ignorable` | `property-removed` | `review-required` |
|
||||
| `event:approval/decided.ignorable` | `property-removed` | `review-required` |
|
||||
| `event:approval/policy.ignorable` | `property-removed` | `review-required` |
|
||||
| `event:assistant/chunk.data.chunk.usage.totalTokens` | `optional-property-added` | `same-version` |
|
||||
| `event:assistant/chunk.ignorable` | `property-removed` | `version-bump` |
|
||||
| `event:assistant/chunk.ignorable` | `property-removed` | `review-required` |
|
||||
| `event:assistant/message.data.usage.totalTokens` | `optional-property-added` | `same-version` |
|
||||
| `event:assistant/message.ignorable` | `property-removed` | `version-bump` |
|
||||
| `event:command/done.ignorable` | `property-removed` | `version-bump` |
|
||||
| `event:command/run.ignorable` | `property-removed` | `version-bump` |
|
||||
| `event:compaction/end.ignorable` | `property-removed` | `version-bump` |
|
||||
| `event:compaction/prune.ignorable` | `property-removed` | `version-bump` |
|
||||
| `event:compaction/start.ignorable` | `property-removed` | `version-bump` |
|
||||
| `event:assistant/message.ignorable` | `property-removed` | `review-required` |
|
||||
| `event:command/done.ignorable` | `property-removed` | `review-required` |
|
||||
| `event:command/run.ignorable` | `property-removed` | `review-required` |
|
||||
| `event:compaction/end.ignorable` | `property-removed` | `review-required` |
|
||||
| `event:compaction/prune.ignorable` | `property-removed` | `review-required` |
|
||||
| `event:compaction/start.ignorable` | `property-removed` | `review-required` |
|
||||
| `event:compaction/summary.data.usage.totalTokens` | `optional-property-added` | `same-version` |
|
||||
| `event:compaction/summary.ignorable` | `property-removed` | `version-bump` |
|
||||
| `event:feedback/record.ignorable` | `property-removed` | `version-bump` |
|
||||
| `event:goal/change.ignorable` | `property-removed` | `version-bump` |
|
||||
| `event:hook/invoked.ignorable` | `property-removed` | `version-bump` |
|
||||
| `event:hook/result.ignorable` | `property-removed` | `version-bump` |
|
||||
| `event:llm/retry.ignorable` | `property-removed` | `version-bump` |
|
||||
| `event:llm/retry-started.ignorable` | `property-removed` | `version-bump` |
|
||||
| `event:compaction/summary.ignorable` | `property-removed` | `review-required` |
|
||||
| `event:feedback/record.ignorable` | `property-removed` | `review-required` |
|
||||
| `event:goal/change.ignorable` | `property-removed` | `review-required` |
|
||||
| `event:hook/invoked.ignorable` | `property-removed` | `review-required` |
|
||||
| `event:hook/result.ignorable` | `property-removed` | `review-required` |
|
||||
| `event:llm/retry.ignorable` | `property-removed` | `review-required` |
|
||||
| `event:llm/retry-started.ignorable` | `property-removed` | `review-required` |
|
||||
| `event:model/selection` | `root-added` | `same-version` |
|
||||
| `event:permission/preset.ignorable` | `property-removed` | `version-bump` |
|
||||
| `event:plan/mode.ignorable` | `property-removed` | `version-bump` |
|
||||
| `event:request/context.ignorable` | `property-removed` | `version-bump` |
|
||||
| `event:request/header.data.reason` | `union-variants-changed` | `version-bump` |
|
||||
| `event:permission/preset.ignorable` | `property-removed` | `review-required` |
|
||||
| `event:plan/mode.ignorable` | `property-removed` | `review-required` |
|
||||
| `event:request/context.ignorable` | `property-removed` | `review-required` |
|
||||
| `event:request/header.data.reason` | `union-variants-changed` | `review-required` |
|
||||
| `event:request/header.data.startsSeries` | `optional-property-added` | `same-version` |
|
||||
| `event:request/header.ignorable` | `property-removed` | `version-bump` |
|
||||
| `event:sandbox/mode.ignorable` | `property-removed` | `version-bump` |
|
||||
| `event:schedule/change.ignorable` | `property-removed` | `version-bump` |
|
||||
| `event:request/header.ignorable` | `property-removed` | `review-required` |
|
||||
| `event:sandbox/mode.ignorable` | `property-removed` | `review-required` |
|
||||
| `event:schedule/change.ignorable` | `property-removed` | `review-required` |
|
||||
| `event:session-log-deepseek/delivery-accepted` | `root-added` | `same-version` |
|
||||
| `event:session/end-seed.ignorable` | `property-removed` | `version-bump` |
|
||||
| `event:session/title.ignorable` | `property-removed` | `version-bump` |
|
||||
| `event:session/title-llm-request.data.messages[].source` | `union-variants-changed` | `version-bump` |
|
||||
| `event:session/title-llm-request.ignorable` | `property-removed` | `version-bump` |
|
||||
| `event:step/end.ignorable` | `property-removed` | `version-bump` |
|
||||
| `event:step/start.ignorable` | `property-removed` | `version-bump` |
|
||||
| `event:session/end-seed.ignorable` | `property-removed` | `review-required` |
|
||||
| `event:session/title.ignorable` | `property-removed` | `review-required` |
|
||||
| `event:session/title-llm-request.data.messages[].source` | `union-variants-changed` | `review-required` |
|
||||
| `event:session/title-llm-request.ignorable` | `property-removed` | `review-required` |
|
||||
| `event:step/end.ignorable` | `property-removed` | `review-required` |
|
||||
| `event:step/start.ignorable` | `property-removed` | `review-required` |
|
||||
| `event:subagent/descriptor.data.agentReasoningEffort` | `optional-property-added` | `same-version` |
|
||||
| `event:subagent/descriptor.ignorable` | `property-removed` | `version-bump` |
|
||||
| `event:subagent/descriptor.ignorable` | `property-removed` | `review-required` |
|
||||
| `event:subagent/model-selection-policy` | `root-added` | `same-version` |
|
||||
| `event:team/member.ignorable` | `property-removed` | `version-bump` |
|
||||
| `event:team/message/delivered.ignorable` | `property-removed` | `version-bump` |
|
||||
| `event:team/message/queued.ignorable` | `property-removed` | `version-bump` |
|
||||
| `event:team/task.ignorable` | `property-removed` | `version-bump` |
|
||||
| `event:todo/write.ignorable` | `property-removed` | `version-bump` |
|
||||
| `event:tool-workflow/agent-end.ignorable` | `property-removed` | `version-bump` |
|
||||
| `event:tool-workflow/agent-start.ignorable` | `property-removed` | `version-bump` |
|
||||
| `event:tool-workflow/run-end.ignorable` | `property-removed` | `version-bump` |
|
||||
| `event:tool-workflow/run-start.ignorable` | `property-removed` | `version-bump` |
|
||||
| `event:tool/call.ignorable` | `property-removed` | `version-bump` |
|
||||
| `event:tool/code-dispatch.ignorable` | `property-removed` | `version-bump` |
|
||||
| `event:tool/code-dispatch-start.ignorable` | `property-removed` | `version-bump` |
|
||||
| `event:tool/result.ignorable` | `property-removed` | `version-bump` |
|
||||
| `event:turn/end.ignorable` | `property-removed` | `version-bump` |
|
||||
| `event:turn/start.ignorable` | `property-removed` | `version-bump` |
|
||||
| `event:user/message.data.source` | `union-variants-changed` | `version-bump` |
|
||||
| `event:user/message.ignorable` | `property-removed` | `version-bump` |
|
||||
| `event:web/deepseek-search-llm-request.ignorable` | `property-removed` | `version-bump` |
|
||||
| `event:team/member.ignorable` | `property-removed` | `review-required` |
|
||||
| `event:team/message/delivered.ignorable` | `property-removed` | `review-required` |
|
||||
| `event:team/message/queued.ignorable` | `property-removed` | `review-required` |
|
||||
| `event:team/task.ignorable` | `property-removed` | `review-required` |
|
||||
| `event:todo/write.ignorable` | `property-removed` | `review-required` |
|
||||
| `event:tool-workflow/agent-end.ignorable` | `property-removed` | `review-required` |
|
||||
| `event:tool-workflow/agent-start.ignorable` | `property-removed` | `review-required` |
|
||||
| `event:tool-workflow/run-end.ignorable` | `property-removed` | `review-required` |
|
||||
| `event:tool-workflow/run-start.ignorable` | `property-removed` | `review-required` |
|
||||
| `event:tool/call.ignorable` | `property-removed` | `review-required` |
|
||||
| `event:tool/code-dispatch.ignorable` | `property-removed` | `review-required` |
|
||||
| `event:tool/code-dispatch-start.ignorable` | `property-removed` | `review-required` |
|
||||
| `event:tool/result.ignorable` | `property-removed` | `review-required` |
|
||||
| `event:turn/end.ignorable` | `property-removed` | `review-required` |
|
||||
| `event:turn/start.ignorable` | `property-removed` | `review-required` |
|
||||
| `event:user/message.data.source` | `union-variants-changed` | `review-required` |
|
||||
| `event:user/message.ignorable` | `property-removed` | `review-required` |
|
||||
| `event:web/deepseek-search-llm-request.ignorable` | `property-removed` | `review-required` |
|
||||
|
||||
<!-- persistence-release-changes:end -->
|
||||
|
||||
|
||||
@@ -212,71 +212,71 @@ changes:
|
||||
|
||||
<!-- persistence-release-changes:start -->
|
||||
|
||||
检测到 52 个根类型变化、61 项结构差异。下表的最低要求按当前规则计算,只用于比较;不表示旧版本曾遵守这些规则,也不证明迁移或运行时兼容性。
|
||||
检测到 52 个根类型变化、61 项结构差异。下表的审查要求按当前规则计算,只用于比较;不表示旧版本曾遵守这些规则,也不证明迁移或运行时兼容性。
|
||||
|
||||
| 路径 | 变化 | 当前最低要求 |
|
||||
| 路径 | 变化 | 审查要求 |
|
||||
|---|---|---|
|
||||
| `SessionEventEnvelope` | `union-variants-changed` | `version-bump` |
|
||||
| `event:agent-preset/selected.ignorable` | `property-removed` | `version-bump` |
|
||||
| `event:agent/inbox/spliced.data.inserted[].source` | `union-variants-changed` | `version-bump` |
|
||||
| `event:agent/inbox/spliced.ignorable` | `property-removed` | `version-bump` |
|
||||
| `event:approval/asked.ignorable` | `property-removed` | `version-bump` |
|
||||
| `event:approval/decided.ignorable` | `property-removed` | `version-bump` |
|
||||
| `event:approval/policy.ignorable` | `property-removed` | `version-bump` |
|
||||
| `SessionEventEnvelope` | `union-variants-changed` | `review-required` |
|
||||
| `event:agent-preset/selected.ignorable` | `property-removed` | `review-required` |
|
||||
| `event:agent/inbox/spliced.data.inserted[].source` | `union-variants-changed` | `review-required` |
|
||||
| `event:agent/inbox/spliced.ignorable` | `property-removed` | `review-required` |
|
||||
| `event:approval/asked.ignorable` | `property-removed` | `review-required` |
|
||||
| `event:approval/decided.ignorable` | `property-removed` | `review-required` |
|
||||
| `event:approval/policy.ignorable` | `property-removed` | `review-required` |
|
||||
| `event:assistant/chunk.data.chunk.usage.totalTokens` | `optional-property-added` | `same-version` |
|
||||
| `event:assistant/chunk.ignorable` | `property-removed` | `version-bump` |
|
||||
| `event:assistant/chunk.ignorable` | `property-removed` | `review-required` |
|
||||
| `event:assistant/message.data.usage.totalTokens` | `optional-property-added` | `same-version` |
|
||||
| `event:assistant/message.ignorable` | `property-removed` | `version-bump` |
|
||||
| `event:command/done.ignorable` | `property-removed` | `version-bump` |
|
||||
| `event:command/run.ignorable` | `property-removed` | `version-bump` |
|
||||
| `event:compaction/end.ignorable` | `property-removed` | `version-bump` |
|
||||
| `event:compaction/prune.ignorable` | `property-removed` | `version-bump` |
|
||||
| `event:compaction/start.ignorable` | `property-removed` | `version-bump` |
|
||||
| `event:assistant/message.ignorable` | `property-removed` | `review-required` |
|
||||
| `event:command/done.ignorable` | `property-removed` | `review-required` |
|
||||
| `event:command/run.ignorable` | `property-removed` | `review-required` |
|
||||
| `event:compaction/end.ignorable` | `property-removed` | `review-required` |
|
||||
| `event:compaction/prune.ignorable` | `property-removed` | `review-required` |
|
||||
| `event:compaction/start.ignorable` | `property-removed` | `review-required` |
|
||||
| `event:compaction/summary.data.usage.totalTokens` | `optional-property-added` | `same-version` |
|
||||
| `event:compaction/summary.ignorable` | `property-removed` | `version-bump` |
|
||||
| `event:feedback/record.ignorable` | `property-removed` | `version-bump` |
|
||||
| `event:goal/change.ignorable` | `property-removed` | `version-bump` |
|
||||
| `event:hook/invoked.ignorable` | `property-removed` | `version-bump` |
|
||||
| `event:hook/result.ignorable` | `property-removed` | `version-bump` |
|
||||
| `event:llm/retry.ignorable` | `property-removed` | `version-bump` |
|
||||
| `event:llm/retry-started.ignorable` | `property-removed` | `version-bump` |
|
||||
| `event:compaction/summary.ignorable` | `property-removed` | `review-required` |
|
||||
| `event:feedback/record.ignorable` | `property-removed` | `review-required` |
|
||||
| `event:goal/change.ignorable` | `property-removed` | `review-required` |
|
||||
| `event:hook/invoked.ignorable` | `property-removed` | `review-required` |
|
||||
| `event:hook/result.ignorable` | `property-removed` | `review-required` |
|
||||
| `event:llm/retry.ignorable` | `property-removed` | `review-required` |
|
||||
| `event:llm/retry-started.ignorable` | `property-removed` | `review-required` |
|
||||
| `event:model/selection` | `root-added` | `same-version` |
|
||||
| `event:permission/preset.ignorable` | `property-removed` | `version-bump` |
|
||||
| `event:plan/mode.ignorable` | `property-removed` | `version-bump` |
|
||||
| `event:request/context.ignorable` | `property-removed` | `version-bump` |
|
||||
| `event:request/header.data.reason` | `union-variants-changed` | `version-bump` |
|
||||
| `event:permission/preset.ignorable` | `property-removed` | `review-required` |
|
||||
| `event:plan/mode.ignorable` | `property-removed` | `review-required` |
|
||||
| `event:request/context.ignorable` | `property-removed` | `review-required` |
|
||||
| `event:request/header.data.reason` | `union-variants-changed` | `review-required` |
|
||||
| `event:request/header.data.startsSeries` | `optional-property-added` | `same-version` |
|
||||
| `event:request/header.ignorable` | `property-removed` | `version-bump` |
|
||||
| `event:sandbox/mode.ignorable` | `property-removed` | `version-bump` |
|
||||
| `event:schedule/change.ignorable` | `property-removed` | `version-bump` |
|
||||
| `event:request/header.ignorable` | `property-removed` | `review-required` |
|
||||
| `event:sandbox/mode.ignorable` | `property-removed` | `review-required` |
|
||||
| `event:schedule/change.ignorable` | `property-removed` | `review-required` |
|
||||
| `event:session-log-deepseek/delivery-accepted` | `root-added` | `same-version` |
|
||||
| `event:session/end-seed.ignorable` | `property-removed` | `version-bump` |
|
||||
| `event:session/title.ignorable` | `property-removed` | `version-bump` |
|
||||
| `event:session/title-llm-request.data.messages[].source` | `union-variants-changed` | `version-bump` |
|
||||
| `event:session/title-llm-request.ignorable` | `property-removed` | `version-bump` |
|
||||
| `event:step/end.ignorable` | `property-removed` | `version-bump` |
|
||||
| `event:step/start.ignorable` | `property-removed` | `version-bump` |
|
||||
| `event:session/end-seed.ignorable` | `property-removed` | `review-required` |
|
||||
| `event:session/title.ignorable` | `property-removed` | `review-required` |
|
||||
| `event:session/title-llm-request.data.messages[].source` | `union-variants-changed` | `review-required` |
|
||||
| `event:session/title-llm-request.ignorable` | `property-removed` | `review-required` |
|
||||
| `event:step/end.ignorable` | `property-removed` | `review-required` |
|
||||
| `event:step/start.ignorable` | `property-removed` | `review-required` |
|
||||
| `event:subagent/descriptor.data.agentReasoningEffort` | `optional-property-added` | `same-version` |
|
||||
| `event:subagent/descriptor.ignorable` | `property-removed` | `version-bump` |
|
||||
| `event:subagent/descriptor.ignorable` | `property-removed` | `review-required` |
|
||||
| `event:subagent/model-selection-policy` | `root-added` | `same-version` |
|
||||
| `event:team/member.ignorable` | `property-removed` | `version-bump` |
|
||||
| `event:team/message/delivered.ignorable` | `property-removed` | `version-bump` |
|
||||
| `event:team/message/queued.ignorable` | `property-removed` | `version-bump` |
|
||||
| `event:team/task.ignorable` | `property-removed` | `version-bump` |
|
||||
| `event:todo/write.ignorable` | `property-removed` | `version-bump` |
|
||||
| `event:tool-workflow/agent-end.ignorable` | `property-removed` | `version-bump` |
|
||||
| `event:tool-workflow/agent-start.ignorable` | `property-removed` | `version-bump` |
|
||||
| `event:tool-workflow/run-end.ignorable` | `property-removed` | `version-bump` |
|
||||
| `event:tool-workflow/run-start.ignorable` | `property-removed` | `version-bump` |
|
||||
| `event:tool/call.ignorable` | `property-removed` | `version-bump` |
|
||||
| `event:tool/code-dispatch.ignorable` | `property-removed` | `version-bump` |
|
||||
| `event:tool/code-dispatch-start.ignorable` | `property-removed` | `version-bump` |
|
||||
| `event:tool/result.ignorable` | `property-removed` | `version-bump` |
|
||||
| `event:turn/end.ignorable` | `property-removed` | `version-bump` |
|
||||
| `event:turn/start.ignorable` | `property-removed` | `version-bump` |
|
||||
| `event:user/message.data.source` | `union-variants-changed` | `version-bump` |
|
||||
| `event:user/message.ignorable` | `property-removed` | `version-bump` |
|
||||
| `event:web/deepseek-search-llm-request.ignorable` | `property-removed` | `version-bump` |
|
||||
| `event:team/member.ignorable` | `property-removed` | `review-required` |
|
||||
| `event:team/message/delivered.ignorable` | `property-removed` | `review-required` |
|
||||
| `event:team/message/queued.ignorable` | `property-removed` | `review-required` |
|
||||
| `event:team/task.ignorable` | `property-removed` | `review-required` |
|
||||
| `event:todo/write.ignorable` | `property-removed` | `review-required` |
|
||||
| `event:tool-workflow/agent-end.ignorable` | `property-removed` | `review-required` |
|
||||
| `event:tool-workflow/agent-start.ignorable` | `property-removed` | `review-required` |
|
||||
| `event:tool-workflow/run-end.ignorable` | `property-removed` | `review-required` |
|
||||
| `event:tool-workflow/run-start.ignorable` | `property-removed` | `review-required` |
|
||||
| `event:tool/call.ignorable` | `property-removed` | `review-required` |
|
||||
| `event:tool/code-dispatch.ignorable` | `property-removed` | `review-required` |
|
||||
| `event:tool/code-dispatch-start.ignorable` | `property-removed` | `review-required` |
|
||||
| `event:tool/result.ignorable` | `property-removed` | `review-required` |
|
||||
| `event:turn/end.ignorable` | `property-removed` | `review-required` |
|
||||
| `event:turn/start.ignorable` | `property-removed` | `review-required` |
|
||||
| `event:user/message.data.source` | `union-variants-changed` | `review-required` |
|
||||
| `event:user/message.ignorable` | `property-removed` | `review-required` |
|
||||
| `event:web/deepseek-search-llm-request.ignorable` | `property-removed` | `review-required` |
|
||||
|
||||
<!-- persistence-release-changes:end -->
|
||||
|
||||
|
||||
@@ -21,8 +21,8 @@
|
||||
en: af9cea43334ee692
|
||||
zh: c6c8bd0d6a17718a
|
||||
/persistence-release-dsh-v0-1-2-alpha-2/structural-changes:
|
||||
en: 28cb9fbbfbc761af
|
||||
zh: f3345b2dc10920b1
|
||||
en: aeca9db54d4a5ea4
|
||||
zh: fa2e354c039b8381
|
||||
/persistence-release-dsh-v0-1-2-alpha-2/verification:
|
||||
en: 8ccca317964523b1
|
||||
zh: 2c8b3e70a3320778
|
||||
|
||||
@@ -212,62 +212,62 @@ changes:
|
||||
|
||||
<!-- persistence-release-changes:start -->
|
||||
|
||||
Detected 52 changed roots and 52 structural differences. The minimum below is calculated using current rules for comparison only; it does not assert historical compliance, migration correctness, or runtime compatibility.
|
||||
Detected 52 changed roots and 52 structural differences. The review requirement below is calculated using current rules for comparison only; it does not assert historical compliance, migration correctness, or runtime compatibility.
|
||||
|
||||
| Path | Change | Current minimum |
|
||||
| Path | Change | Review requirement |
|
||||
|---|---|---|
|
||||
| `SessionEventEnvelope` | `union-variants-changed` | `version-bump` |
|
||||
| `event:agent-preset/selected.ignorable` | `optional-property-added` | `version-bump` |
|
||||
| `event:agent/inbox/spliced.ignorable` | `optional-property-added` | `version-bump` |
|
||||
| `event:approval/asked.ignorable` | `optional-property-added` | `version-bump` |
|
||||
| `event:approval/decided.ignorable` | `optional-property-added` | `version-bump` |
|
||||
| `event:approval/policy.ignorable` | `optional-property-added` | `version-bump` |
|
||||
| `event:assistant/chunk.ignorable` | `optional-property-added` | `version-bump` |
|
||||
| `event:assistant/message.ignorable` | `optional-property-added` | `version-bump` |
|
||||
| `event:command/done.ignorable` | `optional-property-added` | `version-bump` |
|
||||
| `event:command/run.ignorable` | `optional-property-added` | `version-bump` |
|
||||
| `event:compaction/end.ignorable` | `optional-property-added` | `version-bump` |
|
||||
| `event:compaction/prune.ignorable` | `optional-property-added` | `version-bump` |
|
||||
| `event:compaction/start.ignorable` | `optional-property-added` | `version-bump` |
|
||||
| `event:compaction/summary.ignorable` | `optional-property-added` | `version-bump` |
|
||||
| `event:feedback/record.ignorable` | `optional-property-added` | `version-bump` |
|
||||
| `event:goal/change.ignorable` | `optional-property-added` | `version-bump` |
|
||||
| `event:hook/invoked.ignorable` | `optional-property-added` | `version-bump` |
|
||||
| `event:hook/result.ignorable` | `optional-property-added` | `version-bump` |
|
||||
| `event:llm/retry.ignorable` | `optional-property-added` | `version-bump` |
|
||||
| `event:llm/retry-started.ignorable` | `optional-property-added` | `version-bump` |
|
||||
| `event:model/selection.ignorable` | `optional-property-added` | `version-bump` |
|
||||
| `event:permission/preset.ignorable` | `optional-property-added` | `version-bump` |
|
||||
| `event:plan/mode.ignorable` | `optional-property-added` | `version-bump` |
|
||||
| `event:request/context.ignorable` | `optional-property-added` | `version-bump` |
|
||||
| `event:request/header.ignorable` | `optional-property-added` | `version-bump` |
|
||||
| `event:sandbox/mode.ignorable` | `optional-property-added` | `version-bump` |
|
||||
| `event:schedule/change.ignorable` | `optional-property-added` | `version-bump` |
|
||||
| `event:session-log-deepseek/delivery-accepted.ignorable` | `optional-property-added` | `version-bump` |
|
||||
| `event:session/end-seed.ignorable` | `optional-property-added` | `version-bump` |
|
||||
| `event:session/title.ignorable` | `optional-property-added` | `version-bump` |
|
||||
| `event:session/title-llm-request.ignorable` | `optional-property-added` | `version-bump` |
|
||||
| `event:step/end.ignorable` | `optional-property-added` | `version-bump` |
|
||||
| `event:step/start.ignorable` | `optional-property-added` | `version-bump` |
|
||||
| `event:subagent/descriptor.ignorable` | `optional-property-added` | `version-bump` |
|
||||
| `event:subagent/model-selection-policy.ignorable` | `optional-property-added` | `version-bump` |
|
||||
| `event:team/member.ignorable` | `optional-property-added` | `version-bump` |
|
||||
| `event:team/message/delivered.ignorable` | `optional-property-added` | `version-bump` |
|
||||
| `event:team/message/queued.ignorable` | `optional-property-added` | `version-bump` |
|
||||
| `event:team/task.ignorable` | `optional-property-added` | `version-bump` |
|
||||
| `event:todo/write.ignorable` | `optional-property-added` | `version-bump` |
|
||||
| `event:tool-workflow/agent-end.ignorable` | `optional-property-added` | `version-bump` |
|
||||
| `event:tool-workflow/agent-start.ignorable` | `optional-property-added` | `version-bump` |
|
||||
| `event:tool-workflow/run-end.ignorable` | `optional-property-added` | `version-bump` |
|
||||
| `event:tool-workflow/run-start.ignorable` | `optional-property-added` | `version-bump` |
|
||||
| `event:tool/call.ignorable` | `optional-property-added` | `version-bump` |
|
||||
| `event:tool/code-dispatch.ignorable` | `optional-property-added` | `version-bump` |
|
||||
| `event:tool/code-dispatch-start.ignorable` | `optional-property-added` | `version-bump` |
|
||||
| `event:tool/result.ignorable` | `optional-property-added` | `version-bump` |
|
||||
| `event:turn/end.ignorable` | `optional-property-added` | `version-bump` |
|
||||
| `event:turn/start.ignorable` | `optional-property-added` | `version-bump` |
|
||||
| `event:user/message.ignorable` | `optional-property-added` | `version-bump` |
|
||||
| `event:web/deepseek-search-llm-request.ignorable` | `optional-property-added` | `version-bump` |
|
||||
| `SessionEventEnvelope` | `union-variants-changed` | `review-required` |
|
||||
| `event:agent-preset/selected.ignorable` | `optional-property-added` | `review-required` |
|
||||
| `event:agent/inbox/spliced.ignorable` | `optional-property-added` | `review-required` |
|
||||
| `event:approval/asked.ignorable` | `optional-property-added` | `review-required` |
|
||||
| `event:approval/decided.ignorable` | `optional-property-added` | `review-required` |
|
||||
| `event:approval/policy.ignorable` | `optional-property-added` | `review-required` |
|
||||
| `event:assistant/chunk.ignorable` | `optional-property-added` | `review-required` |
|
||||
| `event:assistant/message.ignorable` | `optional-property-added` | `review-required` |
|
||||
| `event:command/done.ignorable` | `optional-property-added` | `review-required` |
|
||||
| `event:command/run.ignorable` | `optional-property-added` | `review-required` |
|
||||
| `event:compaction/end.ignorable` | `optional-property-added` | `review-required` |
|
||||
| `event:compaction/prune.ignorable` | `optional-property-added` | `review-required` |
|
||||
| `event:compaction/start.ignorable` | `optional-property-added` | `review-required` |
|
||||
| `event:compaction/summary.ignorable` | `optional-property-added` | `review-required` |
|
||||
| `event:feedback/record.ignorable` | `optional-property-added` | `review-required` |
|
||||
| `event:goal/change.ignorable` | `optional-property-added` | `review-required` |
|
||||
| `event:hook/invoked.ignorable` | `optional-property-added` | `review-required` |
|
||||
| `event:hook/result.ignorable` | `optional-property-added` | `review-required` |
|
||||
| `event:llm/retry.ignorable` | `optional-property-added` | `review-required` |
|
||||
| `event:llm/retry-started.ignorable` | `optional-property-added` | `review-required` |
|
||||
| `event:model/selection.ignorable` | `optional-property-added` | `review-required` |
|
||||
| `event:permission/preset.ignorable` | `optional-property-added` | `review-required` |
|
||||
| `event:plan/mode.ignorable` | `optional-property-added` | `review-required` |
|
||||
| `event:request/context.ignorable` | `optional-property-added` | `review-required` |
|
||||
| `event:request/header.ignorable` | `optional-property-added` | `review-required` |
|
||||
| `event:sandbox/mode.ignorable` | `optional-property-added` | `review-required` |
|
||||
| `event:schedule/change.ignorable` | `optional-property-added` | `review-required` |
|
||||
| `event:session-log-deepseek/delivery-accepted.ignorable` | `optional-property-added` | `review-required` |
|
||||
| `event:session/end-seed.ignorable` | `optional-property-added` | `review-required` |
|
||||
| `event:session/title.ignorable` | `optional-property-added` | `review-required` |
|
||||
| `event:session/title-llm-request.ignorable` | `optional-property-added` | `review-required` |
|
||||
| `event:step/end.ignorable` | `optional-property-added` | `review-required` |
|
||||
| `event:step/start.ignorable` | `optional-property-added` | `review-required` |
|
||||
| `event:subagent/descriptor.ignorable` | `optional-property-added` | `review-required` |
|
||||
| `event:subagent/model-selection-policy.ignorable` | `optional-property-added` | `review-required` |
|
||||
| `event:team/member.ignorable` | `optional-property-added` | `review-required` |
|
||||
| `event:team/message/delivered.ignorable` | `optional-property-added` | `review-required` |
|
||||
| `event:team/message/queued.ignorable` | `optional-property-added` | `review-required` |
|
||||
| `event:team/task.ignorable` | `optional-property-added` | `review-required` |
|
||||
| `event:todo/write.ignorable` | `optional-property-added` | `review-required` |
|
||||
| `event:tool-workflow/agent-end.ignorable` | `optional-property-added` | `review-required` |
|
||||
| `event:tool-workflow/agent-start.ignorable` | `optional-property-added` | `review-required` |
|
||||
| `event:tool-workflow/run-end.ignorable` | `optional-property-added` | `review-required` |
|
||||
| `event:tool-workflow/run-start.ignorable` | `optional-property-added` | `review-required` |
|
||||
| `event:tool/call.ignorable` | `optional-property-added` | `review-required` |
|
||||
| `event:tool/code-dispatch.ignorable` | `optional-property-added` | `review-required` |
|
||||
| `event:tool/code-dispatch-start.ignorable` | `optional-property-added` | `review-required` |
|
||||
| `event:tool/result.ignorable` | `optional-property-added` | `review-required` |
|
||||
| `event:turn/end.ignorable` | `optional-property-added` | `review-required` |
|
||||
| `event:turn/start.ignorable` | `optional-property-added` | `review-required` |
|
||||
| `event:user/message.ignorable` | `optional-property-added` | `review-required` |
|
||||
| `event:web/deepseek-search-llm-request.ignorable` | `optional-property-added` | `review-required` |
|
||||
|
||||
<!-- persistence-release-changes:end -->
|
||||
|
||||
|
||||
@@ -212,62 +212,62 @@ changes:
|
||||
|
||||
<!-- persistence-release-changes:start -->
|
||||
|
||||
检测到 52 个根类型变化、52 项结构差异。下表的最低要求按当前规则计算,只用于比较;不表示旧版本曾遵守这些规则,也不证明迁移或运行时兼容性。
|
||||
检测到 52 个根类型变化、52 项结构差异。下表的审查要求按当前规则计算,只用于比较;不表示旧版本曾遵守这些规则,也不证明迁移或运行时兼容性。
|
||||
|
||||
| 路径 | 变化 | 当前最低要求 |
|
||||
| 路径 | 变化 | 审查要求 |
|
||||
|---|---|---|
|
||||
| `SessionEventEnvelope` | `union-variants-changed` | `version-bump` |
|
||||
| `event:agent-preset/selected.ignorable` | `optional-property-added` | `version-bump` |
|
||||
| `event:agent/inbox/spliced.ignorable` | `optional-property-added` | `version-bump` |
|
||||
| `event:approval/asked.ignorable` | `optional-property-added` | `version-bump` |
|
||||
| `event:approval/decided.ignorable` | `optional-property-added` | `version-bump` |
|
||||
| `event:approval/policy.ignorable` | `optional-property-added` | `version-bump` |
|
||||
| `event:assistant/chunk.ignorable` | `optional-property-added` | `version-bump` |
|
||||
| `event:assistant/message.ignorable` | `optional-property-added` | `version-bump` |
|
||||
| `event:command/done.ignorable` | `optional-property-added` | `version-bump` |
|
||||
| `event:command/run.ignorable` | `optional-property-added` | `version-bump` |
|
||||
| `event:compaction/end.ignorable` | `optional-property-added` | `version-bump` |
|
||||
| `event:compaction/prune.ignorable` | `optional-property-added` | `version-bump` |
|
||||
| `event:compaction/start.ignorable` | `optional-property-added` | `version-bump` |
|
||||
| `event:compaction/summary.ignorable` | `optional-property-added` | `version-bump` |
|
||||
| `event:feedback/record.ignorable` | `optional-property-added` | `version-bump` |
|
||||
| `event:goal/change.ignorable` | `optional-property-added` | `version-bump` |
|
||||
| `event:hook/invoked.ignorable` | `optional-property-added` | `version-bump` |
|
||||
| `event:hook/result.ignorable` | `optional-property-added` | `version-bump` |
|
||||
| `event:llm/retry.ignorable` | `optional-property-added` | `version-bump` |
|
||||
| `event:llm/retry-started.ignorable` | `optional-property-added` | `version-bump` |
|
||||
| `event:model/selection.ignorable` | `optional-property-added` | `version-bump` |
|
||||
| `event:permission/preset.ignorable` | `optional-property-added` | `version-bump` |
|
||||
| `event:plan/mode.ignorable` | `optional-property-added` | `version-bump` |
|
||||
| `event:request/context.ignorable` | `optional-property-added` | `version-bump` |
|
||||
| `event:request/header.ignorable` | `optional-property-added` | `version-bump` |
|
||||
| `event:sandbox/mode.ignorable` | `optional-property-added` | `version-bump` |
|
||||
| `event:schedule/change.ignorable` | `optional-property-added` | `version-bump` |
|
||||
| `event:session-log-deepseek/delivery-accepted.ignorable` | `optional-property-added` | `version-bump` |
|
||||
| `event:session/end-seed.ignorable` | `optional-property-added` | `version-bump` |
|
||||
| `event:session/title.ignorable` | `optional-property-added` | `version-bump` |
|
||||
| `event:session/title-llm-request.ignorable` | `optional-property-added` | `version-bump` |
|
||||
| `event:step/end.ignorable` | `optional-property-added` | `version-bump` |
|
||||
| `event:step/start.ignorable` | `optional-property-added` | `version-bump` |
|
||||
| `event:subagent/descriptor.ignorable` | `optional-property-added` | `version-bump` |
|
||||
| `event:subagent/model-selection-policy.ignorable` | `optional-property-added` | `version-bump` |
|
||||
| `event:team/member.ignorable` | `optional-property-added` | `version-bump` |
|
||||
| `event:team/message/delivered.ignorable` | `optional-property-added` | `version-bump` |
|
||||
| `event:team/message/queued.ignorable` | `optional-property-added` | `version-bump` |
|
||||
| `event:team/task.ignorable` | `optional-property-added` | `version-bump` |
|
||||
| `event:todo/write.ignorable` | `optional-property-added` | `version-bump` |
|
||||
| `event:tool-workflow/agent-end.ignorable` | `optional-property-added` | `version-bump` |
|
||||
| `event:tool-workflow/agent-start.ignorable` | `optional-property-added` | `version-bump` |
|
||||
| `event:tool-workflow/run-end.ignorable` | `optional-property-added` | `version-bump` |
|
||||
| `event:tool-workflow/run-start.ignorable` | `optional-property-added` | `version-bump` |
|
||||
| `event:tool/call.ignorable` | `optional-property-added` | `version-bump` |
|
||||
| `event:tool/code-dispatch.ignorable` | `optional-property-added` | `version-bump` |
|
||||
| `event:tool/code-dispatch-start.ignorable` | `optional-property-added` | `version-bump` |
|
||||
| `event:tool/result.ignorable` | `optional-property-added` | `version-bump` |
|
||||
| `event:turn/end.ignorable` | `optional-property-added` | `version-bump` |
|
||||
| `event:turn/start.ignorable` | `optional-property-added` | `version-bump` |
|
||||
| `event:user/message.ignorable` | `optional-property-added` | `version-bump` |
|
||||
| `event:web/deepseek-search-llm-request.ignorable` | `optional-property-added` | `version-bump` |
|
||||
| `SessionEventEnvelope` | `union-variants-changed` | `review-required` |
|
||||
| `event:agent-preset/selected.ignorable` | `optional-property-added` | `review-required` |
|
||||
| `event:agent/inbox/spliced.ignorable` | `optional-property-added` | `review-required` |
|
||||
| `event:approval/asked.ignorable` | `optional-property-added` | `review-required` |
|
||||
| `event:approval/decided.ignorable` | `optional-property-added` | `review-required` |
|
||||
| `event:approval/policy.ignorable` | `optional-property-added` | `review-required` |
|
||||
| `event:assistant/chunk.ignorable` | `optional-property-added` | `review-required` |
|
||||
| `event:assistant/message.ignorable` | `optional-property-added` | `review-required` |
|
||||
| `event:command/done.ignorable` | `optional-property-added` | `review-required` |
|
||||
| `event:command/run.ignorable` | `optional-property-added` | `review-required` |
|
||||
| `event:compaction/end.ignorable` | `optional-property-added` | `review-required` |
|
||||
| `event:compaction/prune.ignorable` | `optional-property-added` | `review-required` |
|
||||
| `event:compaction/start.ignorable` | `optional-property-added` | `review-required` |
|
||||
| `event:compaction/summary.ignorable` | `optional-property-added` | `review-required` |
|
||||
| `event:feedback/record.ignorable` | `optional-property-added` | `review-required` |
|
||||
| `event:goal/change.ignorable` | `optional-property-added` | `review-required` |
|
||||
| `event:hook/invoked.ignorable` | `optional-property-added` | `review-required` |
|
||||
| `event:hook/result.ignorable` | `optional-property-added` | `review-required` |
|
||||
| `event:llm/retry.ignorable` | `optional-property-added` | `review-required` |
|
||||
| `event:llm/retry-started.ignorable` | `optional-property-added` | `review-required` |
|
||||
| `event:model/selection.ignorable` | `optional-property-added` | `review-required` |
|
||||
| `event:permission/preset.ignorable` | `optional-property-added` | `review-required` |
|
||||
| `event:plan/mode.ignorable` | `optional-property-added` | `review-required` |
|
||||
| `event:request/context.ignorable` | `optional-property-added` | `review-required` |
|
||||
| `event:request/header.ignorable` | `optional-property-added` | `review-required` |
|
||||
| `event:sandbox/mode.ignorable` | `optional-property-added` | `review-required` |
|
||||
| `event:schedule/change.ignorable` | `optional-property-added` | `review-required` |
|
||||
| `event:session-log-deepseek/delivery-accepted.ignorable` | `optional-property-added` | `review-required` |
|
||||
| `event:session/end-seed.ignorable` | `optional-property-added` | `review-required` |
|
||||
| `event:session/title.ignorable` | `optional-property-added` | `review-required` |
|
||||
| `event:session/title-llm-request.ignorable` | `optional-property-added` | `review-required` |
|
||||
| `event:step/end.ignorable` | `optional-property-added` | `review-required` |
|
||||
| `event:step/start.ignorable` | `optional-property-added` | `review-required` |
|
||||
| `event:subagent/descriptor.ignorable` | `optional-property-added` | `review-required` |
|
||||
| `event:subagent/model-selection-policy.ignorable` | `optional-property-added` | `review-required` |
|
||||
| `event:team/member.ignorable` | `optional-property-added` | `review-required` |
|
||||
| `event:team/message/delivered.ignorable` | `optional-property-added` | `review-required` |
|
||||
| `event:team/message/queued.ignorable` | `optional-property-added` | `review-required` |
|
||||
| `event:team/task.ignorable` | `optional-property-added` | `review-required` |
|
||||
| `event:todo/write.ignorable` | `optional-property-added` | `review-required` |
|
||||
| `event:tool-workflow/agent-end.ignorable` | `optional-property-added` | `review-required` |
|
||||
| `event:tool-workflow/agent-start.ignorable` | `optional-property-added` | `review-required` |
|
||||
| `event:tool-workflow/run-end.ignorable` | `optional-property-added` | `review-required` |
|
||||
| `event:tool-workflow/run-start.ignorable` | `optional-property-added` | `review-required` |
|
||||
| `event:tool/call.ignorable` | `optional-property-added` | `review-required` |
|
||||
| `event:tool/code-dispatch.ignorable` | `optional-property-added` | `review-required` |
|
||||
| `event:tool/code-dispatch-start.ignorable` | `optional-property-added` | `review-required` |
|
||||
| `event:tool/result.ignorable` | `optional-property-added` | `review-required` |
|
||||
| `event:turn/end.ignorable` | `optional-property-added` | `review-required` |
|
||||
| `event:turn/start.ignorable` | `optional-property-added` | `review-required` |
|
||||
| `event:user/message.ignorable` | `optional-property-added` | `review-required` |
|
||||
| `event:web/deepseek-search-llm-request.ignorable` | `optional-property-added` | `review-required` |
|
||||
|
||||
<!-- persistence-release-changes:end -->
|
||||
|
||||
|
||||
@@ -21,8 +21,8 @@
|
||||
en: af9cea43334ee692
|
||||
zh: c6c8bd0d6a17718a
|
||||
/persistence-release-dsh-v0-1-2-alpha-4/structural-changes:
|
||||
en: 19dc565f0446afc5
|
||||
zh: 16309465db21bd8b
|
||||
en: eec8e831ff143edc
|
||||
zh: 253dcaaf41ecac6f
|
||||
/persistence-release-dsh-v0-1-2-alpha-4/verification:
|
||||
en: 8ccca317964523b1
|
||||
zh: 2c8b3e70a3320778
|
||||
|
||||
@@ -68,15 +68,15 @@ changes:
|
||||
|
||||
<!-- persistence-release-changes:start -->
|
||||
|
||||
Detected 4 changed roots and 5 structural differences. The minimum below is calculated using current rules for comparison only; it does not assert historical compliance, migration correctness, or runtime compatibility.
|
||||
Detected 4 changed roots and 5 structural differences. The review requirement below is calculated using current rules for comparison only; it does not assert historical compliance, migration correctness, or runtime compatibility.
|
||||
|
||||
| Path | Change | Current minimum |
|
||||
| Path | Change | Review requirement |
|
||||
|---|---|---|
|
||||
| `SessionHeader.seedLength` | `property-removed` | `version-bump` |
|
||||
| `SessionHeader.isSeeded` | `required-property-added` | `version-bump` |
|
||||
| `event:agent/inbox/spliced.data.inserted[].source` | `union-variants-changed` | `version-bump` |
|
||||
| `event:session/title-llm-request.data.messages[].source` | `union-variants-changed` | `version-bump` |
|
||||
| `event:user/message.data.source` | `union-variants-changed` | `version-bump` |
|
||||
| `SessionHeader.seedLength` | `property-removed` | `review-required` |
|
||||
| `SessionHeader.isSeeded` | `required-property-added` | `review-required` |
|
||||
| `event:agent/inbox/spliced.data.inserted[].source` | `union-variants-changed` | `review-required` |
|
||||
| `event:session/title-llm-request.data.messages[].source` | `union-variants-changed` | `review-required` |
|
||||
| `event:user/message.data.source` | `union-variants-changed` | `review-required` |
|
||||
|
||||
<!-- persistence-release-changes:end -->
|
||||
|
||||
|
||||
@@ -68,15 +68,15 @@ changes:
|
||||
|
||||
<!-- persistence-release-changes:start -->
|
||||
|
||||
检测到 4 个根类型变化、5 项结构差异。下表的最低要求按当前规则计算,只用于比较;不表示旧版本曾遵守这些规则,也不证明迁移或运行时兼容性。
|
||||
检测到 4 个根类型变化、5 项结构差异。下表的审查要求按当前规则计算,只用于比较;不表示旧版本曾遵守这些规则,也不证明迁移或运行时兼容性。
|
||||
|
||||
| 路径 | 变化 | 当前最低要求 |
|
||||
| 路径 | 变化 | 审查要求 |
|
||||
|---|---|---|
|
||||
| `SessionHeader.seedLength` | `property-removed` | `version-bump` |
|
||||
| `SessionHeader.isSeeded` | `required-property-added` | `version-bump` |
|
||||
| `event:agent/inbox/spliced.data.inserted[].source` | `union-variants-changed` | `version-bump` |
|
||||
| `event:session/title-llm-request.data.messages[].source` | `union-variants-changed` | `version-bump` |
|
||||
| `event:user/message.data.source` | `union-variants-changed` | `version-bump` |
|
||||
| `SessionHeader.seedLength` | `property-removed` | `review-required` |
|
||||
| `SessionHeader.isSeeded` | `required-property-added` | `review-required` |
|
||||
| `event:agent/inbox/spliced.data.inserted[].source` | `union-variants-changed` | `review-required` |
|
||||
| `event:session/title-llm-request.data.messages[].source` | `union-variants-changed` | `review-required` |
|
||||
| `event:user/message.data.source` | `union-variants-changed` | `review-required` |
|
||||
|
||||
<!-- persistence-release-changes:end -->
|
||||
|
||||
|
||||
@@ -21,8 +21,8 @@
|
||||
en: af9cea43334ee692
|
||||
zh: c6c8bd0d6a17718a
|
||||
/persistence-release-dsh-v0-1-3-alpha-1/structural-changes:
|
||||
en: 492c315af4d250aa
|
||||
zh: 17a45e34aeb94791
|
||||
en: 435e41d730a111fe
|
||||
zh: d8f6c30f1dd53184
|
||||
/persistence-release-dsh-v0-1-3-alpha-1/verification:
|
||||
en: 8ccca317964523b1
|
||||
zh: 2c8b3e70a3320778
|
||||
|
||||
@@ -107,34 +107,34 @@ changes:
|
||||
|
||||
<!-- persistence-release-changes:start -->
|
||||
|
||||
Detected 17 changed roots and 25 structural differences. The minimum below is calculated using current rules for comparison only; it does not assert historical compliance, migration correctness, or runtime compatibility.
|
||||
Detected 17 changed roots and 25 structural differences. The review requirement below is calculated using current rules for comparison only; it does not assert historical compliance, migration correctness, or runtime compatibility.
|
||||
|
||||
| Path | Change | Current minimum |
|
||||
| Path | Change | Review requirement |
|
||||
|---|---|---|
|
||||
| `JsonlHeaderLine.seedLength` | `property-removed` | `version-bump` |
|
||||
| `JsonlHeaderLine.isSeeded` | `required-property-added` | `version-bump` |
|
||||
| `SessionHeader.version` | `type-changed` | `version-bump` |
|
||||
| `event:agent/inbox/spliced.data.inserted[].content[]` | `union-variants-changed` | `version-bump` |
|
||||
| `JsonlHeaderLine.seedLength` | `property-removed` | `review-required` |
|
||||
| `JsonlHeaderLine.isSeeded` | `required-property-added` | `review-required` |
|
||||
| `SessionHeader.version` | `type-changed` | `review-required` |
|
||||
| `event:agent/inbox/spliced.data.inserted[].content[]` | `union-variants-changed` | `review-required` |
|
||||
| `event:agent/inbox/spliced.data.inserted[].source.references[].capturedFormatVersion` | `optional-property-added` | `same-version` |
|
||||
| `event:assistant/attempt` | `root-added` | `same-version` |
|
||||
| `event:assistant/chunk` | `root-removed` | `version-bump` |
|
||||
| `event:assistant/message.data.message.content[]` | `union-variants-changed` | `version-bump` |
|
||||
| `event:assistant/message.data.stream` | `required-property-added` | `version-bump` |
|
||||
| `event:compaction/summary.data` | `union-variants-changed` | `version-bump` |
|
||||
| `event:assistant/chunk` | `root-removed` | `review-required` |
|
||||
| `event:assistant/message.data.message.content[]` | `union-variants-changed` | `review-required` |
|
||||
| `event:assistant/message.data.stream` | `required-property-added` | `review-required` |
|
||||
| `event:compaction/summary.data` | `union-variants-changed` | `review-required` |
|
||||
| `event:session-log-deepseek/delivery-accepted.data.sessionFormatVersion` | `optional-property-added` | `same-version` |
|
||||
| `event:session/end-seed.data.inherited` | `optional-property-added` | `same-version` |
|
||||
| `event:session/end-seed.data` | `index-signature-changed` | `version-bump` |
|
||||
| `event:session/title-llm-request.data.messages[].content[]` | `union-variants-changed` | `version-bump` |
|
||||
| `event:session/end-seed.data` | `index-signature-changed` | `review-required` |
|
||||
| `event:session/title-llm-request.data.messages[].content[]` | `union-variants-changed` | `review-required` |
|
||||
| `event:session/title-llm-request.data.messages[].source.references[].capturedFormatVersion` | `optional-property-added` | `same-version` |
|
||||
| `event:team/member.data.version` | `type-changed` | `version-bump` |
|
||||
| `event:team/message/delivered.data.version` | `type-changed` | `version-bump` |
|
||||
| `event:team/message/queued.data.message.content[]` | `union-variants-changed` | `version-bump` |
|
||||
| `event:team/message/queued.data.message.delivery` | `property-removed` | `version-bump` |
|
||||
| `event:team/message/queued.data.version` | `type-changed` | `version-bump` |
|
||||
| `event:team/task.data.version` | `type-changed` | `version-bump` |
|
||||
| `event:tool/code-dispatch.data.content[]` | `union-variants-changed` | `version-bump` |
|
||||
| `event:tool/result.data.message.content[0].content[]` | `union-variants-changed` | `version-bump` |
|
||||
| `event:user/message.data.content[]` | `union-variants-changed` | `version-bump` |
|
||||
| `event:team/member.data.version` | `type-changed` | `review-required` |
|
||||
| `event:team/message/delivered.data.version` | `type-changed` | `review-required` |
|
||||
| `event:team/message/queued.data.message.content[]` | `union-variants-changed` | `review-required` |
|
||||
| `event:team/message/queued.data.message.delivery` | `property-removed` | `review-required` |
|
||||
| `event:team/message/queued.data.version` | `type-changed` | `review-required` |
|
||||
| `event:team/task.data.version` | `type-changed` | `review-required` |
|
||||
| `event:tool/code-dispatch.data.content[]` | `union-variants-changed` | `review-required` |
|
||||
| `event:tool/result.data.message.content[0].content[]` | `union-variants-changed` | `review-required` |
|
||||
| `event:user/message.data.content[]` | `union-variants-changed` | `review-required` |
|
||||
| `event:user/message.data.source.references[].capturedFormatVersion` | `optional-property-added` | `same-version` |
|
||||
|
||||
<!-- persistence-release-changes:end -->
|
||||
|
||||
@@ -107,34 +107,34 @@ changes:
|
||||
|
||||
<!-- persistence-release-changes:start -->
|
||||
|
||||
检测到 17 个根类型变化、25 项结构差异。下表的最低要求按当前规则计算,只用于比较;不表示旧版本曾遵守这些规则,也不证明迁移或运行时兼容性。
|
||||
检测到 17 个根类型变化、25 项结构差异。下表的审查要求按当前规则计算,只用于比较;不表示旧版本曾遵守这些规则,也不证明迁移或运行时兼容性。
|
||||
|
||||
| 路径 | 变化 | 当前最低要求 |
|
||||
| 路径 | 变化 | 审查要求 |
|
||||
|---|---|---|
|
||||
| `JsonlHeaderLine.seedLength` | `property-removed` | `version-bump` |
|
||||
| `JsonlHeaderLine.isSeeded` | `required-property-added` | `version-bump` |
|
||||
| `SessionHeader.version` | `type-changed` | `version-bump` |
|
||||
| `event:agent/inbox/spliced.data.inserted[].content[]` | `union-variants-changed` | `version-bump` |
|
||||
| `JsonlHeaderLine.seedLength` | `property-removed` | `review-required` |
|
||||
| `JsonlHeaderLine.isSeeded` | `required-property-added` | `review-required` |
|
||||
| `SessionHeader.version` | `type-changed` | `review-required` |
|
||||
| `event:agent/inbox/spliced.data.inserted[].content[]` | `union-variants-changed` | `review-required` |
|
||||
| `event:agent/inbox/spliced.data.inserted[].source.references[].capturedFormatVersion` | `optional-property-added` | `same-version` |
|
||||
| `event:assistant/attempt` | `root-added` | `same-version` |
|
||||
| `event:assistant/chunk` | `root-removed` | `version-bump` |
|
||||
| `event:assistant/message.data.message.content[]` | `union-variants-changed` | `version-bump` |
|
||||
| `event:assistant/message.data.stream` | `required-property-added` | `version-bump` |
|
||||
| `event:compaction/summary.data` | `union-variants-changed` | `version-bump` |
|
||||
| `event:assistant/chunk` | `root-removed` | `review-required` |
|
||||
| `event:assistant/message.data.message.content[]` | `union-variants-changed` | `review-required` |
|
||||
| `event:assistant/message.data.stream` | `required-property-added` | `review-required` |
|
||||
| `event:compaction/summary.data` | `union-variants-changed` | `review-required` |
|
||||
| `event:session-log-deepseek/delivery-accepted.data.sessionFormatVersion` | `optional-property-added` | `same-version` |
|
||||
| `event:session/end-seed.data.inherited` | `optional-property-added` | `same-version` |
|
||||
| `event:session/end-seed.data` | `index-signature-changed` | `version-bump` |
|
||||
| `event:session/title-llm-request.data.messages[].content[]` | `union-variants-changed` | `version-bump` |
|
||||
| `event:session/end-seed.data` | `index-signature-changed` | `review-required` |
|
||||
| `event:session/title-llm-request.data.messages[].content[]` | `union-variants-changed` | `review-required` |
|
||||
| `event:session/title-llm-request.data.messages[].source.references[].capturedFormatVersion` | `optional-property-added` | `same-version` |
|
||||
| `event:team/member.data.version` | `type-changed` | `version-bump` |
|
||||
| `event:team/message/delivered.data.version` | `type-changed` | `version-bump` |
|
||||
| `event:team/message/queued.data.message.content[]` | `union-variants-changed` | `version-bump` |
|
||||
| `event:team/message/queued.data.message.delivery` | `property-removed` | `version-bump` |
|
||||
| `event:team/message/queued.data.version` | `type-changed` | `version-bump` |
|
||||
| `event:team/task.data.version` | `type-changed` | `version-bump` |
|
||||
| `event:tool/code-dispatch.data.content[]` | `union-variants-changed` | `version-bump` |
|
||||
| `event:tool/result.data.message.content[0].content[]` | `union-variants-changed` | `version-bump` |
|
||||
| `event:user/message.data.content[]` | `union-variants-changed` | `version-bump` |
|
||||
| `event:team/member.data.version` | `type-changed` | `review-required` |
|
||||
| `event:team/message/delivered.data.version` | `type-changed` | `review-required` |
|
||||
| `event:team/message/queued.data.message.content[]` | `union-variants-changed` | `review-required` |
|
||||
| `event:team/message/queued.data.message.delivery` | `property-removed` | `review-required` |
|
||||
| `event:team/message/queued.data.version` | `type-changed` | `review-required` |
|
||||
| `event:team/task.data.version` | `type-changed` | `review-required` |
|
||||
| `event:tool/code-dispatch.data.content[]` | `union-variants-changed` | `review-required` |
|
||||
| `event:tool/result.data.message.content[0].content[]` | `union-variants-changed` | `review-required` |
|
||||
| `event:user/message.data.content[]` | `union-variants-changed` | `review-required` |
|
||||
| `event:user/message.data.source.references[].capturedFormatVersion` | `optional-property-added` | `same-version` |
|
||||
|
||||
<!-- persistence-release-changes:end -->
|
||||
|
||||
@@ -21,8 +21,8 @@
|
||||
en: af9cea43334ee692
|
||||
zh: c6c8bd0d6a17718a
|
||||
/persistence-release-dsh-v0-1-3-alpha-2/structural-changes:
|
||||
en: be5d82540eb344db
|
||||
zh: 8edd0ca270019992
|
||||
en: fa9bf8d8a44b8b6a
|
||||
zh: 40f45050c132f76e
|
||||
/persistence-release-dsh-v0-1-3-alpha-2/verification:
|
||||
en: 8ccca317964523b1
|
||||
zh: 2c8b3e70a3320778
|
||||
|
||||
@@ -62,9 +62,9 @@ changes:
|
||||
|
||||
<!-- persistence-release-changes:start -->
|
||||
|
||||
Detected 2 changed roots and 2 structural differences. The minimum below is calculated using current rules for comparison only; it does not assert historical compliance, migration correctness, or runtime compatibility.
|
||||
Detected 2 changed roots and 2 structural differences. The review requirement below is calculated using current rules for comparison only; it does not assert historical compliance, migration correctness, or runtime compatibility.
|
||||
|
||||
| Path | Change | Current minimum |
|
||||
| Path | Change | Review requirement |
|
||||
|---|---|---|
|
||||
| `event:feedback/message-delete` | `root-added` | `same-version` |
|
||||
| `event:feedback/message-put` | `root-added` | `same-version` |
|
||||
|
||||
@@ -62,9 +62,9 @@ changes:
|
||||
|
||||
<!-- persistence-release-changes:start -->
|
||||
|
||||
检测到 2 个根类型变化、2 项结构差异。下表的最低要求按当前规则计算,只用于比较;不表示旧版本曾遵守这些规则,也不证明迁移或运行时兼容性。
|
||||
检测到 2 个根类型变化、2 项结构差异。下表的审查要求按当前规则计算,只用于比较;不表示旧版本曾遵守这些规则,也不证明迁移或运行时兼容性。
|
||||
|
||||
| 路径 | 变化 | 当前最低要求 |
|
||||
| 路径 | 变化 | 审查要求 |
|
||||
|---|---|---|
|
||||
| `event:feedback/message-delete` | `root-added` | `same-version` |
|
||||
| `event:feedback/message-put` | `root-added` | `same-version` |
|
||||
|
||||
@@ -21,8 +21,8 @@
|
||||
en: af9cea43334ee692
|
||||
zh: c6c8bd0d6a17718a
|
||||
/persistence-release-dsh-v0-1-5-alpha-1/structural-changes:
|
||||
en: f65ce6c033426a9d
|
||||
zh: 278e167343e54fef
|
||||
en: 978d9c63d58817be
|
||||
zh: b69f074aa306410b
|
||||
/persistence-release-dsh-v0-1-5-alpha-1/verification:
|
||||
en: 8ccca317964523b1
|
||||
zh: 2c8b3e70a3320778
|
||||
|
||||
@@ -92,35 +92,35 @@ changes:
|
||||
|
||||
<!-- persistence-release-changes:start -->
|
||||
|
||||
Detected 12 changed roots and 25 structural differences. The minimum below is calculated using current rules for comparison only; it does not assert historical compliance, migration correctness, or runtime compatibility.
|
||||
Detected 12 changed roots and 25 structural differences. The review requirement below is calculated using current rules for comparison only; it does not assert historical compliance, migration correctness, or runtime compatibility.
|
||||
|
||||
| Path | Change | Current minimum |
|
||||
| Path | Change | Review requirement |
|
||||
|---|---|---|
|
||||
| `SessionEventEnvelope` | `union-variants-changed` | `version-bump` |
|
||||
| `SessionHeader.version` | `type-changed` | `version-bump` |
|
||||
| `event:assistant/message.sourceEventSeqs` | `property-removed` | `version-bump` |
|
||||
| `event:assistant/message.surfaceOp` | `property-made-required` | `version-bump` |
|
||||
| `event:assistant/message.surfaceOp.end` | `property-removed` | `version-bump` |
|
||||
| `event:assistant/message.surfaceOp.start` | `property-removed` | `version-bump` |
|
||||
| `event:assistant/message.surfaceOp.endSeq` | `required-property-added` | `version-bump` |
|
||||
| `event:assistant/message.surfaceOp.startSeq` | `required-property-added` | `version-bump` |
|
||||
| `SessionEventEnvelope` | `union-variants-changed` | `review-required` |
|
||||
| `SessionHeader.version` | `type-changed` | `review-required` |
|
||||
| `event:assistant/message.sourceEventSeqs` | `property-removed` | `review-required` |
|
||||
| `event:assistant/message.surfaceOp` | `property-made-required` | `review-required` |
|
||||
| `event:assistant/message.surfaceOp.end` | `property-removed` | `review-required` |
|
||||
| `event:assistant/message.surfaceOp.start` | `property-removed` | `review-required` |
|
||||
| `event:assistant/message.surfaceOp.endSeq` | `required-property-added` | `review-required` |
|
||||
| `event:assistant/message.surfaceOp.startSeq` | `required-property-added` | `review-required` |
|
||||
| `event:request/context.data.systemPromptUpdate` | `optional-property-added` | `same-version` |
|
||||
| `event:request/header.data.header.system` | `property-removed` | `version-bump` |
|
||||
| `event:system/message` | `root-added` | `version-bump` |
|
||||
| `event:tool/code-dispatch` | `root-removed` | `version-bump` |
|
||||
| `event:tool/code-dispatch-start` | `root-removed` | `version-bump` |
|
||||
| `event:request/header.data.header.system` | `property-removed` | `review-required` |
|
||||
| `event:system/message` | `root-added` | `review-required` |
|
||||
| `event:tool/code-dispatch` | `root-removed` | `review-required` |
|
||||
| `event:tool/code-dispatch-start` | `root-removed` | `review-required` |
|
||||
| `event:tool/ptc-dispatch` | `root-added` | `same-version` |
|
||||
| `event:tool/ptc-dispatch-start` | `root-added` | `same-version` |
|
||||
| `event:tool/result.surfaceOp` | `property-made-required` | `version-bump` |
|
||||
| `event:tool/result.surfaceOp.end` | `property-removed` | `version-bump` |
|
||||
| `event:tool/result.surfaceOp.start` | `property-removed` | `version-bump` |
|
||||
| `event:tool/result.surfaceOp.endSeq` | `required-property-added` | `version-bump` |
|
||||
| `event:tool/result.surfaceOp.startSeq` | `required-property-added` | `version-bump` |
|
||||
| `event:user/message.surfaceOp` | `property-made-required` | `version-bump` |
|
||||
| `event:user/message.surfaceOp.end` | `property-removed` | `version-bump` |
|
||||
| `event:user/message.surfaceOp.start` | `property-removed` | `version-bump` |
|
||||
| `event:user/message.surfaceOp.endSeq` | `required-property-added` | `version-bump` |
|
||||
| `event:user/message.surfaceOp.startSeq` | `required-property-added` | `version-bump` |
|
||||
| `event:tool/result.surfaceOp` | `property-made-required` | `review-required` |
|
||||
| `event:tool/result.surfaceOp.end` | `property-removed` | `review-required` |
|
||||
| `event:tool/result.surfaceOp.start` | `property-removed` | `review-required` |
|
||||
| `event:tool/result.surfaceOp.endSeq` | `required-property-added` | `review-required` |
|
||||
| `event:tool/result.surfaceOp.startSeq` | `required-property-added` | `review-required` |
|
||||
| `event:user/message.surfaceOp` | `property-made-required` | `review-required` |
|
||||
| `event:user/message.surfaceOp.end` | `property-removed` | `review-required` |
|
||||
| `event:user/message.surfaceOp.start` | `property-removed` | `review-required` |
|
||||
| `event:user/message.surfaceOp.endSeq` | `required-property-added` | `review-required` |
|
||||
| `event:user/message.surfaceOp.startSeq` | `required-property-added` | `review-required` |
|
||||
|
||||
<!-- persistence-release-changes:end -->
|
||||
|
||||
|
||||
@@ -92,35 +92,35 @@ changes:
|
||||
|
||||
<!-- persistence-release-changes:start -->
|
||||
|
||||
检测到 12 个根类型变化、25 项结构差异。下表的最低要求按当前规则计算,只用于比较;不表示旧版本曾遵守这些规则,也不证明迁移或运行时兼容性。
|
||||
检测到 12 个根类型变化、25 项结构差异。下表的审查要求按当前规则计算,只用于比较;不表示旧版本曾遵守这些规则,也不证明迁移或运行时兼容性。
|
||||
|
||||
| 路径 | 变化 | 当前最低要求 |
|
||||
| 路径 | 变化 | 审查要求 |
|
||||
|---|---|---|
|
||||
| `SessionEventEnvelope` | `union-variants-changed` | `version-bump` |
|
||||
| `SessionHeader.version` | `type-changed` | `version-bump` |
|
||||
| `event:assistant/message.sourceEventSeqs` | `property-removed` | `version-bump` |
|
||||
| `event:assistant/message.surfaceOp` | `property-made-required` | `version-bump` |
|
||||
| `event:assistant/message.surfaceOp.end` | `property-removed` | `version-bump` |
|
||||
| `event:assistant/message.surfaceOp.start` | `property-removed` | `version-bump` |
|
||||
| `event:assistant/message.surfaceOp.endSeq` | `required-property-added` | `version-bump` |
|
||||
| `event:assistant/message.surfaceOp.startSeq` | `required-property-added` | `version-bump` |
|
||||
| `SessionEventEnvelope` | `union-variants-changed` | `review-required` |
|
||||
| `SessionHeader.version` | `type-changed` | `review-required` |
|
||||
| `event:assistant/message.sourceEventSeqs` | `property-removed` | `review-required` |
|
||||
| `event:assistant/message.surfaceOp` | `property-made-required` | `review-required` |
|
||||
| `event:assistant/message.surfaceOp.end` | `property-removed` | `review-required` |
|
||||
| `event:assistant/message.surfaceOp.start` | `property-removed` | `review-required` |
|
||||
| `event:assistant/message.surfaceOp.endSeq` | `required-property-added` | `review-required` |
|
||||
| `event:assistant/message.surfaceOp.startSeq` | `required-property-added` | `review-required` |
|
||||
| `event:request/context.data.systemPromptUpdate` | `optional-property-added` | `same-version` |
|
||||
| `event:request/header.data.header.system` | `property-removed` | `version-bump` |
|
||||
| `event:system/message` | `root-added` | `version-bump` |
|
||||
| `event:tool/code-dispatch` | `root-removed` | `version-bump` |
|
||||
| `event:tool/code-dispatch-start` | `root-removed` | `version-bump` |
|
||||
| `event:request/header.data.header.system` | `property-removed` | `review-required` |
|
||||
| `event:system/message` | `root-added` | `review-required` |
|
||||
| `event:tool/code-dispatch` | `root-removed` | `review-required` |
|
||||
| `event:tool/code-dispatch-start` | `root-removed` | `review-required` |
|
||||
| `event:tool/ptc-dispatch` | `root-added` | `same-version` |
|
||||
| `event:tool/ptc-dispatch-start` | `root-added` | `same-version` |
|
||||
| `event:tool/result.surfaceOp` | `property-made-required` | `version-bump` |
|
||||
| `event:tool/result.surfaceOp.end` | `property-removed` | `version-bump` |
|
||||
| `event:tool/result.surfaceOp.start` | `property-removed` | `version-bump` |
|
||||
| `event:tool/result.surfaceOp.endSeq` | `required-property-added` | `version-bump` |
|
||||
| `event:tool/result.surfaceOp.startSeq` | `required-property-added` | `version-bump` |
|
||||
| `event:user/message.surfaceOp` | `property-made-required` | `version-bump` |
|
||||
| `event:user/message.surfaceOp.end` | `property-removed` | `version-bump` |
|
||||
| `event:user/message.surfaceOp.start` | `property-removed` | `version-bump` |
|
||||
| `event:user/message.surfaceOp.endSeq` | `required-property-added` | `version-bump` |
|
||||
| `event:user/message.surfaceOp.startSeq` | `required-property-added` | `version-bump` |
|
||||
| `event:tool/result.surfaceOp` | `property-made-required` | `review-required` |
|
||||
| `event:tool/result.surfaceOp.end` | `property-removed` | `review-required` |
|
||||
| `event:tool/result.surfaceOp.start` | `property-removed` | `review-required` |
|
||||
| `event:tool/result.surfaceOp.endSeq` | `required-property-added` | `review-required` |
|
||||
| `event:tool/result.surfaceOp.startSeq` | `required-property-added` | `review-required` |
|
||||
| `event:user/message.surfaceOp` | `property-made-required` | `review-required` |
|
||||
| `event:user/message.surfaceOp.end` | `property-removed` | `review-required` |
|
||||
| `event:user/message.surfaceOp.start` | `property-removed` | `review-required` |
|
||||
| `event:user/message.surfaceOp.endSeq` | `required-property-added` | `review-required` |
|
||||
| `event:user/message.surfaceOp.startSeq` | `required-property-added` | `review-required` |
|
||||
|
||||
<!-- persistence-release-changes:end -->
|
||||
|
||||
|
||||
@@ -21,8 +21,8 @@
|
||||
en: af9cea43334ee692
|
||||
zh: c6c8bd0d6a17718a
|
||||
/persistence-release-dsh-v0-1-5-alpha-2/structural-changes:
|
||||
en: a142ebb2293bce41
|
||||
zh: 488e9e2321625d51
|
||||
en: 9e59c254ff49909e
|
||||
zh: 59957fcede302986
|
||||
/persistence-release-dsh-v0-1-5-alpha-2/verification:
|
||||
en: 8ccca317964523b1
|
||||
zh: 2c8b3e70a3320778
|
||||
|
||||
@@ -68,9 +68,9 @@ changes:
|
||||
|
||||
<!-- persistence-release-changes:start -->
|
||||
|
||||
Detected 4 changed roots and 5 structural differences. The minimum below is calculated using current rules for comparison only; it does not assert historical compliance, migration correctness, or runtime compatibility.
|
||||
Detected 4 changed roots and 5 structural differences. The review requirement below is calculated using current rules for comparison only; it does not assert historical compliance, migration correctness, or runtime compatibility.
|
||||
|
||||
| Path | Change | Current minimum |
|
||||
| Path | Change | Review requirement |
|
||||
|---|---|---|
|
||||
| `event:deliverables/presented` | `root-added` | `same-version` |
|
||||
| `event:feedback/message-put.data.item.category` | `optional-property-added` | `same-version` |
|
||||
|
||||
@@ -68,9 +68,9 @@ changes:
|
||||
|
||||
<!-- persistence-release-changes:start -->
|
||||
|
||||
检测到 4 个根类型变化、5 项结构差异。下表的最低要求按当前规则计算,只用于比较;不表示旧版本曾遵守这些规则,也不证明迁移或运行时兼容性。
|
||||
检测到 4 个根类型变化、5 项结构差异。下表的审查要求按当前规则计算,只用于比较;不表示旧版本曾遵守这些规则,也不证明迁移或运行时兼容性。
|
||||
|
||||
| 路径 | 变化 | 当前最低要求 |
|
||||
| 路径 | 变化 | 审查要求 |
|
||||
|---|---|---|
|
||||
| `event:deliverables/presented` | `root-added` | `same-version` |
|
||||
| `event:feedback/message-put.data.item.category` | `optional-property-added` | `same-version` |
|
||||
|
||||
@@ -15,14 +15,14 @@
|
||||
en: 02d18ba8a66cfb10
|
||||
zh: 54b753bbcb8f8a9e
|
||||
/session-format-version-and-release-status/finalization-record:
|
||||
en: 15b6134dcb3a09d5
|
||||
zh: 4260c82e79baa021
|
||||
en: c84a45a0489804c9
|
||||
zh: 4f4474a678c90ffe
|
||||
/session-format-version-and-release-status/release-record:
|
||||
en: 83ce79a241b630d7
|
||||
zh: 53f3c461ef731f67
|
||||
/session-format-version-and-release-status/updating-the-record:
|
||||
en: 7eaa730a2d163bd6
|
||||
zh: 53ed7618537db2f5
|
||||
en: 0d6e8f65604b1394
|
||||
zh: 0c8dd27ad4d37649
|
||||
/session-format-version-and-release-status/dev-note:
|
||||
en: b75c8458928b72b9
|
||||
zh: 003ba4e17b4b5269
|
||||
@@ -32,7 +32,7 @@ The [format references](persistence-changes/historical-formats/README.md) docume
|
||||
latestFinalizedVersion: 4
|
||||
```
|
||||
|
||||
V4 has an accepted compatibility baseline in the [checkpoint](persistence-changes/finalized/v4.json). Backward-compatible schema changes may remain V4 through new acknowledgement records. Breaking changes require a higher writer version and their own header transition; they cannot reuse the accepted 3→4 transition. Accepted machine records and after schemas remain immutable. [Checkpoint rules](persistence-changes/README.md#compatibility-rules) define the comparison.
|
||||
V4 has an accepted compatibility baseline in the [checkpoint](persistence-changes/finalized/v4.json). Schema changes may remain V4 when the [reader-compatibility rules](persistence-changes/README.md#compatibility-rules) are satisfied and the required review is recorded. A version-bump decision requires a higher writer version and its own header transition; it cannot reuse the accepted 3→4 transition. Accepted machine records and after schemas remain immutable.
|
||||
|
||||
Finalization does not freeze every future V4 addition and does not assert publication. The release record below retains the independently verified published version. Ordinary comments, aliases, source locations, and implementation fixes preserving the accepted meaning do not change this baseline.
|
||||
|
||||
@@ -51,7 +51,7 @@ Evidence: published product tag `dsh-v0.2.0-rc.2`; tagged writer: `packages/core
|
||||
<a id="updating-the-record"></a>
|
||||
## Updating the record
|
||||
|
||||
When a structural writer change is implemented, update the code constant and adjacent catalog together; do not advance this release record before publication. When a product release first publishes a higher Session format, confirm publication and its tagged writer, then advance this record and the evidence tag and tagged writer path in the same bilingual update. Later product releases carrying the same format do not require changing the record. Never lower it on the development trunk.
|
||||
When a Session format bump is implemented, update the code constant and adjacent catalog together; do not advance this release record before publication. When a product release first publishes a higher Session format, confirm publication and its tagged writer, then advance this record and the evidence tag and tagged writer path in the same bilingual update. Later product releases carrying the same format do not require changing the record. Never lower it on the development trunk.
|
||||
|
||||
The [documentation-standard test](../scripts/doc-standard.spec.ts) checks record structure, bilingual equality, evidence-tag and writer-path consistency, and that the documented release does not exceed the checkout writer. This keyless check does not query GitHub or prove that the record is up to date; publication verification remains part of the release update.
|
||||
|
||||
|
||||
@@ -32,7 +32,7 @@
|
||||
latestFinalizedVersion: 4
|
||||
```
|
||||
|
||||
V4 的已接受兼容性基线保存在[检查点](persistence-changes/finalized/v4.json)中。向后兼容的 schema 变更可以通过新的确认记录保留 V4。破坏性变更要求更高的写入器版本及自身的头版本转换,不能复用已接受的 3→4 转换。已接受的机器记录与变更后 schema 保持不可变。[检查点规则](persistence-changes/README.zh.md#compatibility-rules)规定比较方法。
|
||||
V4 的已接受兼容性基线保存在[检查点](persistence-changes/finalized/v4.json)中。满足[读取器兼容性规则](persistence-changes/README.zh.md#compatibility-rules)并记录所需评审时,schema 变更可以保留 V4。升版本决策要求更高的写入器版本及自身的头版本转换,不能复用已接受的 3→4 转换。已接受的机器记录与变更后 schema 保持不可变。
|
||||
|
||||
定稿不冻结 V4 之后的每项新增,也不表示已发布。下方发布记录保留独立验证的已发布版本。普通注释、别名、源码位置,以及保留已接受含义的实现修复,不改变该基线。
|
||||
|
||||
@@ -51,7 +51,7 @@ evidenceTag: dsh-v0.2.0-rc.2
|
||||
<a id="updating-the-record"></a>
|
||||
## 更新记录
|
||||
|
||||
实现结构性写入器变更时,一起更新代码常量与相邻迁移目录;不要在产品发布前推进此发布记录。当产品首次发布更高的 Session 格式时,确认发布事实及对应标签的写入器,然后在同一次双语更新中推进本记录与证据标签及该标签的写入器路径。后续携带相同格式的产品发布无需改变此记录。开发主干上的记录绝不降低。
|
||||
实现 Session 格式升版本时,一起更新代码常量与相邻迁移目录;不要在产品发布前推进此发布记录。当产品首次发布更高的 Session 格式时,确认发布事实及对应标签的写入器,然后在同一次双语更新中推进本记录与证据标签及该标签的写入器路径。后续携带相同格式的产品发布无需改变此记录。开发主干上的记录绝不降低。
|
||||
|
||||
[文档标准测试](../scripts/doc-standard.spec.ts)检查记录结构、双语一致性、证据标签及写入器路径一致性,以及文档中的已发布版本不高于工作区写入器。这个无密钥检查不会查询 GitHub,也不能证明记录是最新的;核实发布事实仍属于发布更新的一部分。
|
||||
|
||||
|
||||
@@ -102,8 +102,8 @@
|
||||
en: 5eb30cfb410d52c0
|
||||
zh: da56365b8ab896da
|
||||
/deepseek-ai-dsh-session/known-limitations-and-deferred-work:
|
||||
en: daaf56dda2bcaff1
|
||||
zh: 7d24de2deb1c5dc5
|
||||
en: 45afe96a4e252db9
|
||||
zh: 920bcc011823817b
|
||||
/deepseek-ai-dsh-session/known-limitations-and-deferred-work/dev-note:
|
||||
en: e67f633dc6fe7773
|
||||
zh: 3bb0c20c93f16f98
|
||||
@@ -200,7 +200,7 @@ Logging causes no invalidation, and exact reconstruction preserves request-prefi
|
||||
These limits define when the session store needs special care. They are current package constraints, not a task backlog.
|
||||
|
||||
- **Live-source Store API** — persisted-but-unloaded sources use the Host observation path. Fork closes only the open tail and does not repair missing results in previously closed steps.
|
||||
- **`SESSION_FORMAT_VERSION` names the [current logical representation](../../../docs/session-format-status.md)** — the current reader rejects retired `header.system` and validates `system/message` payloads and protected-head rewrites. Historical headers and events belong to adjacent format packages; the [adjacent migration chain](../../session/session-format-catalog/README.md) converts supported history before constructing `Session`, and the write-open path publishes only the current-format successor. Equal-version unknown events require the envelope's explicit `ignorable` marker, which does not promise safe structural migration ([mechanism](../../../.agents/notes/implemented/architecture/2026-08-31-released-session-format-migrations.md)).
|
||||
- **`SESSION_FORMAT_VERSION` names the [current logical representation](../../../docs/session-format-status.md)** — the current reader rejects retired `header.system` and validates `system/message` payloads and protected-head rewrites. Historical headers and events belong to adjacent format packages; the [adjacent migration chain](../../session/session-format-catalog/README.md) converts supported history before constructing `Session`, and the write-open path publishes only the current-format successor. Equal-version unknown events require the envelope's explicit `ignorable` marker, which does not promise safe structural migration ([mechanism](../../../.agents/notes/implemented/architecture/2026-08-31-released-session-format-migrations.md)). [Reader compatibility](../../../.agents/notes/implemented/process/2026-10-08-session-reader-compatibility-review.md) determines whether a change needs a version increase.
|
||||
- **`TurnEndReasonMap` omits the ACP-named `refusal` / `max_turn_requests` variants** — producer-gated: they land when an adapter or the loop first emits them.
|
||||
- **No session tree beyond fork** — a pi-style entry tree over branched sessions is deferred unless a consumer needs more than boundary-based forking.
|
||||
|
||||
|
||||
@@ -200,7 +200,7 @@ session.deriveMessages() // the derived model history
|
||||
这些限制说明会话存储何时需要特别留意。它们是当前包约束,不是任务积压。
|
||||
|
||||
- **Store API 仅接受活跃源会话** — 已持久化但未加载的源会话通过 Host 观察路径处理。Fork 仅闭合开放尾部,不修复先前已闭合步骤中缺失的结果。
|
||||
- **`SESSION_FORMAT_VERSION` 命名[当前逻辑表示](../../../docs/session-format-status.zh.md)**——当前读取器拒绝已退役的 `header.system`,并校验 `system/message` 载荷与受保护头节点的重写。历史 header 与事件归相邻格式包所有;[相邻迁移链](../../session/session-format-catalog/README.zh.md)在构造 `Session` 前转换受支持的历史,写打开只发布当前格式的后继代际。同版本未知事件要求信封显式带有 `ignorable` 标记,但这不保证结构迁移的安全性([机制](../../../.agents/notes/implemented/architecture/2026-08-31-released-session-format-migrations.zh.md))。
|
||||
- **`SESSION_FORMAT_VERSION` 命名[当前逻辑表示](../../../docs/session-format-status.zh.md)**——当前读取器拒绝已退役的 `header.system`,并校验 `system/message` 载荷与受保护头节点的重写。历史 header 与事件归相邻格式包所有;[相邻迁移链](../../session/session-format-catalog/README.zh.md)在构造 `Session` 前转换受支持的历史,写打开只发布当前格式的后继代际。同版本未知事件要求信封显式带有 `ignorable` 标记,但这不保证结构迁移的安全性([机制](../../../.agents/notes/implemented/architecture/2026-08-31-released-session-format-migrations.zh.md))。[读取器兼容性](../../../.agents/notes/implemented/process/2026-10-08-session-reader-compatibility-review.zh.md)决定变更是否需要升版本。
|
||||
- **`TurnEndReasonMap` 不含 ACP(Agent Client Protocol)命名的 `refusal`/`max_turn_requests` 变体**:受生产方约束;只有当适配器或循环首次产生这些变体时才加入。
|
||||
- **fork 之外没有会话树**:基于分支会话的 pi 风格条目树被推迟,除非消费方需要超越基于边界的 forking 的能力。
|
||||
|
||||
|
||||
@@ -71,20 +71,20 @@ export type OptionalSessionSeq = SessionSeq | null
|
||||
* event-body read composes the build-static adjacent chain and publishes only
|
||||
* this final generation before constructing a Session.
|
||||
*
|
||||
* The version is a single monotonic integer with no major/minor split. Whether
|
||||
* a bump is needed is decided by what the WRITER emits, never by what a newer
|
||||
* reader can accept: bump exactly when an older runtime could no longer handle
|
||||
* a new log with full semantic correctness ("parses without error" is not
|
||||
* correctness — silently skipping content that shapes reconstruction is a
|
||||
* wrong read). Only structural changes reach that bar: the header shape, the
|
||||
* {@link SessionEvent} envelope, core event semantics, or the surface
|
||||
* mechanism (the {@link SurfaceEventType} set and {@link SurfaceOp} variants).
|
||||
* Adding an ordinary event type does not bump — the per-event
|
||||
* {@link SessionEvent.ignorable} guard covers vocabulary growth instead. When
|
||||
* in doubt, bump: a near-identity upgrade step is almost free, a missed bump
|
||||
* makes older runtimes read new logs wrong silently. The released migration,
|
||||
* immutable prior-generation, and current fast-path rules are recorded in
|
||||
* `.agents/notes/implemented/architecture/2026-08-31-released-session-format-migrations.md`.
|
||||
* The version is a single monotonic integer with no major/minor split. Retain
|
||||
* it when older readers resume correctly, preserve safely ignorable records,
|
||||
* or refuse required features before execution, mutation, or tail repair.
|
||||
* New readers must preserve historical meanings, including logs mixing old
|
||||
* and new record representations. Bump when an existing discriminator cannot
|
||||
* prevent unsafe interpretation; a structural difference alone is insufficient.
|
||||
* Unknown fields and values require evidence of the released reader's actual
|
||||
* admission behavior. The per-event {@link SessionEvent.ignorable} marker
|
||||
* governs unknown event types, not arbitrary nested payload changes.
|
||||
* Same-version readers retain earlier representations: the adjacent migration
|
||||
* chain does not run when stored and current versions match. A subsequent
|
||||
* migration must account for all supported representations of its source.
|
||||
* Compatibility review and the retained migration obligations are recorded in
|
||||
* `.agents/notes/implemented/process/2026-10-08-session-reader-compatibility-review.md`.
|
||||
*/
|
||||
export const SESSION_FORMAT_VERSION = 4
|
||||
|
||||
|
||||
@@ -21,8 +21,8 @@
|
||||
en: 41ecd1db865a9966
|
||||
zh: 54cdfb32cde4df8a
|
||||
/deepseek-ai-dsh-session-format/use-this-package/entry-point:
|
||||
en: 8d67ddec8cc4dea3
|
||||
zh: 0dede278e7c90f37
|
||||
en: 661d5b9e83199487
|
||||
zh: b017f6728047bcad
|
||||
/deepseek-ai-dsh-session-format/understand-the-implementation:
|
||||
en: b864adbc60539188
|
||||
zh: 7589781800eb7937
|
||||
|
||||
@@ -41,7 +41,7 @@ const headerRecord = catalog.encodeCurrentHeader(current.header, current.inherit
|
||||
const eventRecords = current.events.map(catalog.encodeCurrentEvent)
|
||||
```
|
||||
|
||||
`createSessionFormatCatalog()` accepts one frozen codec per supported version, the current record encoder, one migration per adjacent version pair, and current artifact and header restorers. `readHeader()` returns a `current`, `migration-required`, `unsupported`, or `malformed` descriptor without reading events. Body readers create one restore, push each parsed physical row through `decodeRow()`, and call `finish()` once for a current artifact. Writers encode its header and events record by record.
|
||||
`createSessionFormatCatalog()` accepts one codec for each version's supported representations, the current record encoder, one migration per adjacent version pair, and current artifact and header restorers. `readHeader()` returns a `current`, `migration-required`, `unsupported`, or `malformed` descriptor without reading events. Body readers create one restore, push each parsed physical row through `decodeRow()`, and call `finish()` once for a current artifact. Writers encode its header and events record by record. Established record meanings remain stable; [compatibility review](../../../.agents/notes/implemented/process/2026-10-08-session-reader-compatibility-review.md) governs same-version additions.
|
||||
|
||||
The `recovery` option selects strict row failure or recoverable suffix handling. `validation: 'current'` applies all installed current-format validation. `validation: 'transformed'` applies released current-format validation after historical migration, while already-current input receives only its codec's physical validation.
|
||||
|
||||
|
||||
@@ -41,7 +41,7 @@ const headerRecord = catalog.encodeCurrentHeader(current.header, current.inherit
|
||||
const eventRecords = current.events.map(catalog.encodeCurrentEvent)
|
||||
```
|
||||
|
||||
`createSessionFormatCatalog()` 接收每个受支持版本的一个冻结 codec、当前格式的逐记录 encoder、每组相邻版本的一个迁移,以及当前产物与 header 还原器。`readHeader()` 在不读取事件的情况下返回 `current`、`migration-required`、`unsupported` 或 `malformed` 描述符。正文读取方创建一次 restore,把每个已解析物理行传给 `decodeRow()`,再调用一次 `finish()` 获得当前产物。写入方逐条编码其 header 与事件。
|
||||
`createSessionFormatCatalog()` 为每个版本所支持的表示接收一个 codec、当前格式的逐记录 encoder、每组相邻版本的一个迁移,以及当前产物与 header 还原器。`readHeader()` 在不读取事件的情况下返回 `current`、`migration-required`、`unsupported` 或 `malformed` 描述符。正文读取方创建一次 restore,把每个已解析物理行传给 `decodeRow()`,再调用一次 `finish()` 获得当前产物。写入方逐条编码其 header 与事件。已确立的记录含义保持稳定;[兼容性评审](../../../.agents/notes/implemented/process/2026-10-08-session-reader-compatibility-review.zh.md)约束同版本新增。
|
||||
|
||||
`recovery` 选项决定严格拒绝故障行,还是执行可恢复后缀处理。`validation: 'current'` 会执行所有已安装的 current 格式校验。`validation: 'transformed'` 会在历史迁移后执行已发布的 current 格式校验;已经是 current 的输入则只接受其 codec 的物理校验。
|
||||
|
||||
|
||||
@@ -90,7 +90,7 @@ export interface SessionFormatChain {
|
||||
/** Physical-row failure policy selected once for one restore. */
|
||||
export type SessionFormatRecovery = 'strict' | 'recoverable'
|
||||
|
||||
/** Pure physical JSON codec frozen with one released Session format. */
|
||||
/** Pure physical JSON codec for one Session version's supported representations; established meanings remain stable. */
|
||||
export interface SessionFormatCodec {
|
||||
readonly version: number
|
||||
/** Decode one physical header into body-independent logical metadata. */
|
||||
|
||||
@@ -14,6 +14,7 @@ import {
|
||||
classifyPersistenceChange,
|
||||
loadPersistenceHistory,
|
||||
parseHistoricalPersistenceSnapshot,
|
||||
parsePersistenceReview,
|
||||
parsePersistenceSnapshot,
|
||||
runPersistenceChanges as executePersistenceChanges,
|
||||
validatePersistenceHistory,
|
||||
@@ -224,7 +225,7 @@ describe('accepted persistence baseline', () => {
|
||||
}
|
||||
})
|
||||
|
||||
it.each(['required field', 'changed type'] as const)('refuses a breaking V4 %s before rendering or writing', (kind) => {
|
||||
it.each(['required field', 'changed type'] as const)('requires a review for V4 %s before rendering or writing', (kind) => {
|
||||
const root = fixture()
|
||||
finalize(root)
|
||||
const after = inventory(kind === 'required field' ? { value: 'number', label: 'string' } : { value: 'boolean' }, 4)
|
||||
@@ -234,13 +235,13 @@ describe('accepted persistence baseline', () => {
|
||||
rendered = true
|
||||
return []
|
||||
})) as { ok: boolean; code: string; changes: PersistenceTypeChange[] }
|
||||
expect(result).toMatchObject({ ok: false, code: 'finalized-format-changed' })
|
||||
expect(result).toMatchObject({ ok: false, code: 'compatibility-review-required' })
|
||||
expect(result.changes.length).toBeGreaterThan(0)
|
||||
expect(result.changes.some(change => change.requiresVersionBump)).toBe(true)
|
||||
expect(result.changes.some(change => change.requiresCompatibilityReview)).toBe(true)
|
||||
expect(rendered).toBe(false)
|
||||
expect(contents(root)).toEqual(before)
|
||||
commitCurrent(root, after)
|
||||
expect(() => verifyPersistenceChanges(root, after)).toThrow('Breaking changes relative to the accepted Session format 4 baseline')
|
||||
expect(() => verifyPersistenceChanges(root, after)).toThrow('unacknowledged persistence type changes')
|
||||
})
|
||||
|
||||
it('allows qualified V4 attribution additions through a new same-version acknowledgement', () => {
|
||||
@@ -251,7 +252,7 @@ describe('accepted persistence baseline', () => {
|
||||
finalize(root, current)
|
||||
const added = attributedRoot([EXISTING_SOURCE, { kind: 'new-attribution' }], { policy: attributionPolicy(['new-attribution']) })
|
||||
const after = { ...current, roots: current.roots.map(root => root.key === source.key ? added : root) }
|
||||
expect(classifyPersistenceChange(source, added)).toEqual([expect.objectContaining({ kind: 'attribution-kind-added', requiresVersionBump: false })])
|
||||
expect(classifyPersistenceChange(source, added)).toEqual([expect.objectContaining({ kind: 'attribution-kind-added', requiresCompatibilityReview: false })])
|
||||
const before = contents(root)
|
||||
runPersistenceChanges(['--record', COMPATIBLE_ID, '--prose', proseFile(root)], root, () => after)
|
||||
expect(runPersistenceChanges(['--check'], root, () => after)).toContain('roots match')
|
||||
@@ -262,7 +263,7 @@ describe('accepted persistence baseline', () => {
|
||||
}
|
||||
})
|
||||
|
||||
it('keeps later unaccepted compatible records editable and rejects a breaking successor without its own version transition', () => {
|
||||
it('keeps later unaccepted records editable and requires review for a changed optional field', () => {
|
||||
const root = fixture()
|
||||
finalize(root)
|
||||
const first = inventory({ value: 'number', 'label?': 'string' }, 4)
|
||||
@@ -273,10 +274,10 @@ describe('accepted persistence baseline', () => {
|
||||
expect(loadPersistenceFinalization(root, loadPersistenceHistory(root))?.acceptedRecords.has(COMPATIBLE_ID)).toBe(false)
|
||||
const breaking = inventory({ value: 'number', 'label?': 'number', 'extra?': 'boolean' }, 4)
|
||||
const accepted = inventory({ value: 'number' }, 4).roots[2]!
|
||||
expect(classifyPersistenceChange(accepted, breaking.roots[2]!).every(change => !change.requiresVersionBump)).toBe(true)
|
||||
expect(classifyPersistenceChange(accepted, breaking.roots[2]!).every(change => !change.requiresCompatibilityReview)).toBe(true)
|
||||
const before = contents(root)
|
||||
expect(() => runPersistenceChanges(['--record', '2026-09-20-breaking-v4', '--prose', proseFile(root)], root, () => breaking))
|
||||
.toThrow("this record's own increasing SessionHeader.version")
|
||||
.toThrow('requires an authored compatibility review')
|
||||
expect(contents(root)).toEqual(before)
|
||||
})
|
||||
|
||||
@@ -303,7 +304,7 @@ describe('accepted persistence baseline', () => {
|
||||
expect(() => runPersistenceChanges(['--update', FINALIZED_ID], root, () => after)).toThrow('cannot update finalized acknowledgement')
|
||||
const later = inventory({ value: 'string' }, 5)
|
||||
expect(() => runPersistenceChanges(['--record', '2026-09-20-without-version', '--prose', proseFile(root)], root, () => later))
|
||||
.toThrow("this record's own increasing SessionHeader.version")
|
||||
.toThrow('requires an authored compatibility review')
|
||||
})
|
||||
|
||||
it.each([4, 5])('detects a consistently rewritten accepted record while the writer is V%s', (version) => {
|
||||
@@ -429,7 +430,7 @@ describe('persistence change classification', () => {
|
||||
finalize(root, before)
|
||||
const after = { ...before, roots: before.roots.map(root => root.kind === 'event' ? nextEvent : root) }
|
||||
expect(classifyPersistenceChange(oldEvent, nextEvent))
|
||||
.toEqual([expect.objectContaining({ kind: 'payload-version-added', requiresVersionBump: false })])
|
||||
.toEqual([expect.objectContaining({ kind: 'payload-version-added', requiresCompatibilityReview: false })])
|
||||
runPersistenceChanges(['--record', COMPATIBLE_ID, '--prose', proseFile(root)], root, () => after)
|
||||
verifyPersistenceChanges(root, after)
|
||||
})
|
||||
@@ -443,29 +444,29 @@ describe('persistence change classification', () => {
|
||||
versionedEvent([...variants, { version: 0.5, mode: 'unknown' }]),
|
||||
versionedEvent([{ version: 0, mode: 'one-shot', extra: true }, variants[1]!, { version: 1, mode: 'unknown' }]),
|
||||
versionedEvent([{ version: 1, mode: 'unknown' }]),
|
||||
]) expect(classifyPersistenceChange(before, after).some(change => change.requiresVersionBump)).toBe(true)
|
||||
]) expect(classifyPersistenceChange(before, after).some(change => change.requiresCompatibilityReview)).toBe(true)
|
||||
const expanded = versionedEvent([...variants, { version: 1, mode: 'unknown' }])
|
||||
expect(classifyPersistenceChange(expanded, before).some(change => change.requiresVersionBump)).toBe(true)
|
||||
expect(classifyPersistenceChange(expanded, before).some(change => change.requiresCompatibilityReview)).toBe(true)
|
||||
expect(classifyPersistenceChange({ ...before, surface: true }, { ...expanded, surface: true })
|
||||
.some(change => change.requiresVersionBump)).toBe(true)
|
||||
.some(change => change.requiresCompatibilityReview)).toBe(true)
|
||||
expect(classifyPersistenceChange({ ...before, kind: 'header' }, { ...expanded, kind: 'header' })
|
||||
.some(change => change.requiresVersionBump)).toBe(true)
|
||||
.some(change => change.requiresCompatibilityReview)).toBe(true)
|
||||
const unversioned = unionBody([[{ name: 'mode', type: 2, optional: false }]])
|
||||
expect(classifyPersistenceChange(unversioned, expanded).some(change => change.requiresVersionBump)).toBe(true)
|
||||
expect(classifyPersistenceChange(unversioned, expanded).some(change => change.requiresCompatibilityReview)).toBe(true)
|
||||
})
|
||||
|
||||
it('treats a new optional payload subtree as one additive change even with required descendants', () => {
|
||||
const before = typeRoot('event:example/value', { value: 'string' })
|
||||
const after = typeRoot('event:example/value', { value: 'string', 'details?': { name: 'string', count: 'number' } })
|
||||
expect(classifyPersistenceChange(before, after)).toEqual([{ path: 'event:example/value.data.details', kind: 'optional-property-added', description: 'optional property added', requiresVersionBump: false }])
|
||||
expect(classifyPersistenceChange(before, after)).toEqual([{ path: 'event:example/value.data.details', kind: 'optional-property-added', description: 'optional property added', requiresCompatibilityReview: false }])
|
||||
})
|
||||
|
||||
it('permits required-to-optional payload properties while rejecting opposite changes, type changes, and removals', () => {
|
||||
const required = typeRoot('event:example/value', { value: 'string' })
|
||||
const optional = typeRoot('event:example/value', { 'value?': 'string' })
|
||||
expect(classifyPersistenceChange(required, optional).every(change => !change.requiresVersionBump)).toBe(true)
|
||||
expect(classifyPersistenceChange(required, optional).every(change => !change.requiresCompatibilityReview)).toBe(true)
|
||||
for (const after of [required, typeRoot('event:example/value', { 'value?': 'number' }), typeRoot('event:example/value', {})]) {
|
||||
expect(classifyPersistenceChange(optional, after).some(change => change.requiresVersionBump)).toBe(true)
|
||||
expect(classifyPersistenceChange(optional, after).some(change => change.requiresCompatibilityReview)).toBe(true)
|
||||
}
|
||||
})
|
||||
|
||||
@@ -485,7 +486,7 @@ describe('persistence change classification', () => {
|
||||
}
|
||||
const differences = classifyPersistenceChange(unionRoot(false), unionRoot(true))
|
||||
expect(differences.length).toBeGreaterThan(0)
|
||||
expect(differences.every(change => !change.requiresVersionBump)).toBe(true)
|
||||
expect(differences.every(change => !change.requiresCompatibilityReview)).toBe(true)
|
||||
})
|
||||
|
||||
it('allows optional additions across undiscriminated union arms', () => {
|
||||
@@ -493,7 +494,7 @@ describe('persistence change classification', () => {
|
||||
const second = [{ name: 'b', type: 3, optional: false }]
|
||||
const extra = { name: 'x', type: 4, optional: true }
|
||||
const differences = classifyPersistenceChange(unionBody([first, second]), unionBody([[...first, extra], [...second, extra]]))
|
||||
expect(differences).toEqual([expect.objectContaining({ path: 'event:example/value.data.x', requiresVersionBump: false })])
|
||||
expect(differences).toEqual([expect.objectContaining({ path: 'event:example/value.data.x', requiresCompatibilityReview: false })])
|
||||
})
|
||||
|
||||
it('allows required-to-optional fields across undiscriminated union arms', () => {
|
||||
@@ -501,7 +502,7 @@ describe('persistence change classification', () => {
|
||||
const after = unionBody([[{ name: 'a', type: 2, optional: true }], [{ name: 'b', type: 3, optional: true }]])
|
||||
const differences = classifyPersistenceChange(before, after)
|
||||
expect(differences.map(change => change.path).sort()).toEqual(['event:example/value.data.a', 'event:example/value.data.b'])
|
||||
expect(differences.every(change => change.kind === 'property-made-optional' && !change.requiresVersionBump)).toBe(true)
|
||||
expect(differences.every(change => change.kind === 'property-made-optional' && !change.requiresCompatibilityReview)).toBe(true)
|
||||
})
|
||||
|
||||
it('finds a complete matching when one union arm has multiple additive successors', () => {
|
||||
@@ -513,10 +514,10 @@ describe('persistence change classification', () => {
|
||||
for (const after of [unionBody([flexible, constrained]), unionBody([constrained, flexible])]) {
|
||||
const differences = classifyPersistenceChange(before, after)
|
||||
expect(differences.length).toBeGreaterThan(0)
|
||||
expect(differences.every(change => !change.requiresVersionBump)).toBe(true)
|
||||
expect(differences.every(change => !change.requiresCompatibilityReview)).toBe(true)
|
||||
}
|
||||
const noCompleteMatching = unionBody([flexible, [{ name: 'different', type: 4, optional: true }]])
|
||||
expect(classifyPersistenceChange(before, noCompleteMatching).some(change => change.requiresVersionBump)).toBe(true)
|
||||
expect(classifyPersistenceChange(before, noCompleteMatching).some(change => change.requiresCompatibilityReview)).toBe(true)
|
||||
})
|
||||
|
||||
it('keeps union cardinality, property removal, new required fields, and value types strict', () => {
|
||||
@@ -531,7 +532,9 @@ describe('persistence change classification', () => {
|
||||
unionBody([[...first, { name: 'x', type: 4, optional: false }], [...second, { name: 'x', type: 4, optional: true }]]),
|
||||
unionBody([[{ name: 'a', type: 3, optional: true }], [{ name: 'b', type: 3, optional: true }]]),
|
||||
]
|
||||
for (const after of variants) expect(classifyPersistenceChange(before, after).some(change => change.requiresVersionBump)).toBe(true)
|
||||
for (const after of variants) {
|
||||
expect(classifyPersistenceChange(before, after).some(change => change.requiresCompatibilityReview)).toBe(true)
|
||||
}
|
||||
})
|
||||
|
||||
it('checks all recursive union arms without retaining provisional successes between candidates', () => {
|
||||
@@ -541,10 +544,10 @@ describe('persistence change classification', () => {
|
||||
const extra = { name: 'x', type: 4, optional: true }
|
||||
const before = unionBody([first, second])
|
||||
const additive = unionBody([[...first, extra], [...second, extra]])
|
||||
expect(classifyPersistenceChange(before, additive).every(change => !change.requiresVersionBump)).toBe(true)
|
||||
expect(classifyPersistenceChange(before, additive).every(change => !change.requiresCompatibilityReview)).toBe(true)
|
||||
const invalid = unionBody([[...first, extra], [{ name: 'b', type: 2, optional: false }, next, extra]])
|
||||
expect(classifyPersistenceChange(before, invalid).some(change => change.requiresVersionBump)).toBe(true)
|
||||
expect(classifyPersistenceChange(invalid, before).some(change => change.requiresVersionBump)).toBe(true)
|
||||
expect(classifyPersistenceChange(before, invalid).some(change => change.requiresCompatibilityReview)).toBe(true)
|
||||
expect(classifyPersistenceChange(invalid, before).some(change => change.requiresCompatibilityReview)).toBe(true)
|
||||
})
|
||||
|
||||
it('preserves body scope through arrays and tuples and rejects a simultaneous value-type change', () => {
|
||||
@@ -559,18 +562,18 @@ describe('persistence change classification', () => {
|
||||
const schema = canonicalizeSchema(nodes, 0)
|
||||
return { ...typeRoot('event:example/value', {}), schema, digest: schemaDigest(schema) }
|
||||
}
|
||||
expect(classifyPersistenceChange(wrapped(false), wrapped(true)).every(change => !change.requiresVersionBump)).toBe(true)
|
||||
expect(classifyPersistenceChange(wrapped(false), wrapped(true, 'number')).some(change => change.requiresVersionBump)).toBe(true)
|
||||
expect(classifyPersistenceChange(wrapped(false), wrapped(true)).every(change => !change.requiresCompatibilityReview)).toBe(true)
|
||||
expect(classifyPersistenceChange(wrapped(false), wrapped(true, 'number')).some(change => change.requiresCompatibilityReview)).toBe(true)
|
||||
})
|
||||
|
||||
it('keeps optional header/envelope properties and surface event additions strict', () => {
|
||||
for (const key of ['SessionHeader', 'JsonlHeaderLine', 'SessionEventEnvelope']) {
|
||||
expect(classifyPersistenceChange(typeRoot(key, {}), typeRoot(key, { 'metadata?': 'string' }))[0]?.requiresVersionBump).toBe(true)
|
||||
expect(classifyPersistenceChange(typeRoot(key, {}), typeRoot(key, { 'metadata?': 'string' }))[0]?.requiresCompatibilityReview).toBe(true)
|
||||
}
|
||||
const added = typeRoot('event:example/added', { value: 'string' })
|
||||
expect(classifyPersistenceChange(null, added)[0]?.requiresVersionBump).toBe(false)
|
||||
expect(classifyPersistenceChange(null, { ...added, surface: true })[0]?.requiresVersionBump).toBe(true)
|
||||
expect(classifyPersistenceChange(added, null)[0]?.requiresVersionBump).toBe(true)
|
||||
expect(classifyPersistenceChange(null, added)[0]?.requiresCompatibilityReview).toBe(false)
|
||||
expect(classifyPersistenceChange(null, { ...added, surface: true })[0]?.requiresCompatibilityReview).toBe(true)
|
||||
expect(classifyPersistenceChange(added, null)[0]?.requiresCompatibilityReview).toBe(true)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -594,10 +597,10 @@ describe('persistence history verification', () => {
|
||||
expect(() => validatePersistenceHistory([base, cycle])).toThrow('cycle')
|
||||
})
|
||||
|
||||
it('binds breaking decisions to the same record header version transition', () => {
|
||||
it('binds version-bump decisions to the same record header version transition', () => {
|
||||
const base = entry(BASE_ID, inventory(), null, true)
|
||||
const breaking = inventory({ value: 'number' }, 4)
|
||||
expect(() => validatePersistenceHistory([base, entry(NEXT_ID, onlyEvent(breaking), BASE_ID)])).toThrow('requires a format version bump')
|
||||
expect(() => validatePersistenceHistory([base, entry(NEXT_ID, onlyEvent(breaking), BASE_ID)])).toThrow('requires an authored compatibility review')
|
||||
expect(() => validatePersistenceHistory([base, entry(NEXT_ID, onlyEvent(breaking), BASE_ID, false, 'version-bump')])).toThrow("this record's own")
|
||||
const touched = { ...breaking, roots: breaking.roots.filter(root => root.key === 'SessionHeader' || root.kind === 'event') }
|
||||
expect(validatePersistenceHistory([base, entry(NEXT_ID, touched, BASE_ID, false, 'version-bump')]).tips.get('SessionHeader')?.id).toBe(NEXT_ID)
|
||||
@@ -704,7 +707,7 @@ describe('persistence changes current-tree commands', () => {
|
||||
verifyPersistenceChanges(root, after)
|
||||
})
|
||||
|
||||
it('rejects inferred bumps without a header transition and explicit incorrect assertions before writing', () => {
|
||||
it('requires evidence for structural changes before writing', () => {
|
||||
const root = fixture()
|
||||
baseline(root)
|
||||
const prose = proseFile(root)
|
||||
@@ -713,12 +716,13 @@ describe('persistence changes current-tree commands', () => {
|
||||
const inventoryPath = join(root, 'docs/persistence-schema.json')
|
||||
const before = readFileSync(inventoryPath, 'utf8')
|
||||
for (const [args, code] of [
|
||||
[[], 'version-transition-required'],
|
||||
[['--decision', 'same-version'], 'version-bump-required'],
|
||||
[[], 'compatibility-review-required'],
|
||||
[['--decision', 'same-version'], 'compatibility-review-required'],
|
||||
] as const) {
|
||||
const result = jsonResult(runPersistenceChanges(['--record', NEXT_ID, '--prose', prose, '--json', ...args], root,
|
||||
() => inventory({ value: 'number' })))
|
||||
expect(result).toMatchObject({ ok: false, code, files: [], changes: [expect.objectContaining({ requiresVersionBump: true })] })
|
||||
expect(result).toMatchObject({ ok: false, code, files: [],
|
||||
changes: [expect.objectContaining({ requiresCompatibilityReview: true })] })
|
||||
expect(readFileSync(inventoryPath, 'utf8')).toBe(before)
|
||||
expect(readdirSync(directory).sort()).toEqual(files)
|
||||
}
|
||||
@@ -731,7 +735,7 @@ describe('persistence changes current-tree commands', () => {
|
||||
const pending = jsonResult(runPersistenceChanges(['--check', '--json'], root, () => after))
|
||||
expect(pending).toMatchObject({ ok: false, operation: 'check', code: 'stale-artifacts',
|
||||
roots: [{ root: 'event:example/value', kind: 'event', before: inventory().roots[2]?.digest, after: after.roots[2]?.digest }], changes: [
|
||||
{ root: 'event:example/value', path: 'event:example/value.data.label', kind: 'optional-property-added', requiresVersionBump: false },
|
||||
{ root: 'event:example/value', path: 'event:example/value.data.label', kind: 'optional-property-added', requiresCompatibilityReview: false },
|
||||
] })
|
||||
const written = jsonResult(runPersistenceChanges(['--record', NEXT_ID, '--decision', 'same-version', '--prose', proseFile(root), '--json'], root, () => after))
|
||||
expect(written).toMatchObject({ ok: true, operation: 'record', recordId: NEXT_ID })
|
||||
@@ -783,7 +787,7 @@ describe('persistence changes current-tree commands', () => {
|
||||
const before = readFileSync(path, 'utf8')
|
||||
const invalid = inventory({ value: 'number' })
|
||||
const result = jsonResult(runPersistenceChanges(['--update', NEXT_ID, '--decision', 'same-version', '--json'], root, () => invalid))
|
||||
expect(result).toMatchObject({ ok: false, code: 'version-bump-required', changes: [expect.objectContaining({ kind: 'type-changed', requiresVersionBump: true })] })
|
||||
expect(result).toMatchObject({ ok: false, code: 'compatibility-review-required', changes: [expect.objectContaining({ kind: 'type-changed', requiresCompatibilityReview: true })] })
|
||||
expect(readFileSync(path, 'utf8')).toBe(before)
|
||||
for (const value of [
|
||||
{ ...AUTHORED_PROSE, extra: 'unsupported' },
|
||||
@@ -861,14 +865,14 @@ describe('persistence changes current-tree commands', () => {
|
||||
expect(rejected.error).toBeUndefined()
|
||||
expect(rejected.signal).toBeNull()
|
||||
expect(rejected.status).toBe(1)
|
||||
expect(String(rejected.stderr)).toContain('version-bump required')
|
||||
expect(String(rejected.stderr)).toContain('compatibility review required')
|
||||
const structured = cli('--check', '--json')
|
||||
expect(structured.error).toBeUndefined()
|
||||
expect(structured.signal).toBeNull()
|
||||
expect(structured.status).toBe(1)
|
||||
const structuredResult: unknown = JSON.parse(String(structured.stdout))
|
||||
expect(structuredResult).toMatchObject({
|
||||
ok: false, code: 'unacknowledged-changes', changes: [expect.objectContaining({ kind: 'type-changed', requiresVersionBump: true })],
|
||||
ok: false, code: 'unacknowledged-changes', changes: [expect.objectContaining({ kind: 'type-changed', requiresCompatibilityReview: true })],
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -927,7 +931,7 @@ describe('recorded source compatibility policy', () => {
|
||||
const before = attributedRoot([EXISTING_SOURCE], { role })
|
||||
const after = attributedRoot([EXISTING_SOURCE, NEW_SOURCE], { role, policy: attributionPolicy([NEW_SOURCE.kind], role) })
|
||||
expect(after.digest).not.toBe(before.digest)
|
||||
expect(classifyPersistenceChange(before, after)).toEqual([expect.objectContaining({ kind: 'attribution-kind-added', requiresVersionBump: false })])
|
||||
expect(classifyPersistenceChange(before, after)).toEqual([expect.objectContaining({ kind: 'attribution-kind-added', requiresCompatibilityReview: false })])
|
||||
const snapshot: PersistenceSchemaInventory = { formatVersion: 2, roots: [after], types: [] }
|
||||
expect(parsePersistenceSnapshot(JSON.parse(JSON.stringify(snapshot)))).toEqual(snapshot)
|
||||
})
|
||||
@@ -937,7 +941,7 @@ describe('recorded source compatibility policy', () => {
|
||||
const after = attributedRoot([{ ...EXISTING_SOURCE, extra: 'optional' }, NEW_SOURCE], { policy: attributionPolicy([NEW_SOURCE.kind]) })
|
||||
const changes = classifyPersistenceChange(before, after)
|
||||
expect(changes.map(change => change.kind).sort()).toEqual(['attribution-kind-added', 'optional-property-added'])
|
||||
expect(changes.every(change => !change.requiresVersionBump)).toBe(true)
|
||||
expect(changes.every(change => !change.requiresCompatibilityReview)).toBe(true)
|
||||
})
|
||||
|
||||
it.each([
|
||||
@@ -948,35 +952,35 @@ describe('recorded source compatibility policy', () => {
|
||||
['renamed existing kind', [{ ...EXISTING_SOURCE, kind: 'renamed' }, NEW_SOURCE], { policy: attributionPolicy([NEW_SOURCE.kind]) }],
|
||||
['unrelated required payload', [EXISTING_SOURCE, NEW_SOURCE], { policy: attributionPolicy([NEW_SOURCE.kind]), extra: true }],
|
||||
['removed promise', [EXISTING_SOURCE, NEW_SOURCE], { policy: null }],
|
||||
] as const)('keeps %s breaking despite qualified additions', (_name, sources, options) => {
|
||||
] as const)('requires review for %s despite qualified additions', (_name, sources, options) => {
|
||||
const changes = classifyPersistenceChange(attributedRoot([EXISTING_SOURCE]), attributedRoot(sources, options))
|
||||
expect(changes.some(change => change.requiresVersionBump)).toBe(true)
|
||||
expect(changes.some(change => change.requiresCompatibilityReview)).toBe(true)
|
||||
})
|
||||
|
||||
it('keeps variants within an existing wire-kind group strict', () => {
|
||||
const before = attributedRoot([EXISTING_SOURCE])
|
||||
const after = attributedRoot([EXISTING_SOURCE, { ...EXISTING_SOURCE, value: 'number' }, NEW_SOURCE], { policy: attributionPolicy([NEW_SOURCE.kind]) })
|
||||
expect(classifyPersistenceChange(before, after)).toEqual(expect.arrayContaining([
|
||||
expect.objectContaining({ kind: 'union-variants-changed', requiresVersionBump: true }),
|
||||
expect.objectContaining({ kind: 'attribution-kind-added', requiresVersionBump: false }),
|
||||
expect.objectContaining({ kind: 'union-variants-changed', requiresCompatibilityReview: true }),
|
||||
expect.objectContaining({ kind: 'attribution-kind-added', requiresCompatibilityReview: false }),
|
||||
]))
|
||||
})
|
||||
|
||||
it('treats qualification changes for an existing kind as policy changes', () => {
|
||||
const before = attributedRoot([EXISTING_SOURCE])
|
||||
const after = attributedRoot([EXISTING_SOURCE], { policy: attributionPolicy([EXISTING_SOURCE.kind]) })
|
||||
expect(classifyPersistenceChange(before, after)).toEqual([expect.objectContaining({ kind: 'source-policy-changed', requiresVersionBump: true })])
|
||||
expect(classifyPersistenceChange(before, after)).toEqual([expect.objectContaining({ kind: 'source-policy-changed', requiresCompatibilityReview: true })])
|
||||
})
|
||||
|
||||
it('keeps legacy source transitions strict and never infers promises from the successor', () => {
|
||||
const before = attributedRoot([EXISTING_SOURCE], { policy: null })
|
||||
const after = attributedRoot([EXISTING_SOURCE, NEW_SOURCE], { policy: attributionPolicy([NEW_SOURCE.kind]) })
|
||||
expect(classifyPersistenceChange(before, after).some(change => change.requiresVersionBump)).toBe(true)
|
||||
expect(classifyPersistenceChange(before, after).some(change => change.requiresCompatibilityReview)).toBe(true)
|
||||
const legacyAfter = attributedRoot([EXISTING_SOURCE, NEW_SOURCE], { policy: null })
|
||||
const snapshots = [before, legacyAfter].map(root => ({ formatVersion: 1 as const, roots: [root], types: [] }))
|
||||
expect(() => validatePersistenceHistory([
|
||||
entry(BASE_ID, snapshots[0]!, null, true), entry(NEXT_ID, snapshots[1]!, BASE_ID),
|
||||
])).toThrow('requires a format version bump')
|
||||
])).toThrow('requires an authored compatibility review')
|
||||
})
|
||||
|
||||
it('validates policy-bearing history without source names or type records', () => {
|
||||
@@ -1013,3 +1017,129 @@ describe('recorded source compatibility policy', () => {
|
||||
expect(() => parsePersistenceSnapshot({ ...snapshot, roots: [unknownKind] })).toThrow('invalid source compatibility')
|
||||
})
|
||||
})
|
||||
|
||||
const COMPATIBILITY_REVIEW = {
|
||||
oldReaders: 'Readers resume when env is absent and reject an unknown env before execution or repair.',
|
||||
newReaders: 'The reader retains absent-env behavior and understands the explicit env field.',
|
||||
verification: 'Compatibility fixture covers absence, required-feature refusal, and retained historical decoding.',
|
||||
}
|
||||
|
||||
function reviewFile(root: string, review: unknown = COMPATIBILITY_REVIEW): string {
|
||||
const path = join(root, 'review.json')
|
||||
writeFileSync(path, JSON.stringify(review))
|
||||
return path
|
||||
}
|
||||
|
||||
describe('authored same-version compatibility review', () => {
|
||||
it.each(['optional header', 'removed field', 'changed field'] as const)('admits a reviewed %s after finalization', (kind) => {
|
||||
const root = fixture()
|
||||
finalize(root)
|
||||
const previous = contents(root)
|
||||
let after: PersistenceSchemaInventory
|
||||
if (kind === 'optional header') {
|
||||
const current = inventory({ value: 'number' }, 4)
|
||||
after = { ...current, roots: current.roots.map((root) => {
|
||||
if (root.key === 'SessionHeader') return typeRoot('SessionHeader', { id: 'string', 'env?': 'string' }, 4)
|
||||
return root
|
||||
}) }
|
||||
} else if (kind === 'removed field') {
|
||||
after = inventory({}, 4)
|
||||
} else {
|
||||
after = inventory({ value: 'boolean' }, 4)
|
||||
}
|
||||
const review = reviewFile(root)
|
||||
const prose = proseFile(root)
|
||||
expect(jsonResult(runPersistenceChanges(['--record', NEXT_ID, '--review', review, '--prose', prose, '--json'], root, () => after)).ok).toBe(true)
|
||||
const saved = loadPersistenceHistory(root).entries.find(entry => entry.record.id === NEXT_ID)!
|
||||
expect(saved.record.review).toEqual(COMPATIBILITY_REVIEW)
|
||||
expect(saved.record.changes.every(change => change.decision === 'same-version')).toBe(true)
|
||||
expect(runPersistenceChanges(['--check'], root, () => after)).toContain('roots match')
|
||||
for (const [path, content] of Object.entries(previous).filter(([path]) => path.startsWith('docs/persistence-changes/'))) {
|
||||
expect(readFileSync(join(root, path), 'utf8')).toBe(content)
|
||||
}
|
||||
})
|
||||
|
||||
it.each([null, {}, { ...COMPATIBILITY_REVIEW, extra: true }, { ...COMPATIBILITY_REVIEW, oldReaders: ' ' },
|
||||
{ ...COMPATIBILITY_REVIEW, newReaders: 3 }, { ...COMPATIBILITY_REVIEW, verification: 'TODO: test this' }])('rejects invalid review evidence before writing', (review) => {
|
||||
const root = fixture()
|
||||
baseline(root)
|
||||
const path = reviewFile(root, review)
|
||||
const before = contents(root)
|
||||
expect(jsonResult(runPersistenceChanges(['--record', NEXT_ID, '--review', path, '--json'], root, () => inventory({ value: 'number' }))).ok).toBe(false)
|
||||
expect(contents(root)).toEqual(before)
|
||||
expect(() => parsePersistenceReview(review)).toThrow()
|
||||
})
|
||||
|
||||
it('limits --review to acknowledgements and cannot waive a version transition', () => {
|
||||
const root = fixture()
|
||||
baseline(root)
|
||||
const review = reviewFile(root)
|
||||
const before = contents(root)
|
||||
for (const args of [['--check'], ['--baseline', NEXT_ID]]) {
|
||||
expect(() => runPersistenceChanges([...args, '--review', review], root, () => inventory())).toThrow('--review requires --record or --update')
|
||||
}
|
||||
for (const version of [2, 4]) {
|
||||
expect(jsonResult(runPersistenceChanges(['--record', NEXT_ID, '--review', review, '--decision', 'same-version', '--json'], root, () => inventory({ value: 'string' }, version))))
|
||||
.toMatchObject({ ok: false, code: 'version-transition-required' })
|
||||
}
|
||||
expect(jsonResult(runPersistenceChanges(['--record', NEXT_ID, '--review', review, '--decision', 'version-bump', '--json'], root, () => inventory({ value: 'string' }, 2))))
|
||||
.toMatchObject({ ok: false, code: 'version-transition-required' })
|
||||
expect(contents(root)).toEqual(before)
|
||||
})
|
||||
|
||||
it('requires renewed evidence when an update changes reviewed schemas', () => {
|
||||
const root = fixture()
|
||||
baseline(root)
|
||||
const review = reviewFile(root)
|
||||
const prose = proseFile(root)
|
||||
const after = inventory({ value: 'number' })
|
||||
runPersistenceChanges(['--record', NEXT_ID, '--review', review, '--prose', prose], root, () => after)
|
||||
runPersistenceChanges(['--update', NEXT_ID, '--prose', prose], root, () => after)
|
||||
expect(loadPersistenceHistory(root).entries.find(entry => entry.record.id === NEXT_ID)?.record.review).toEqual(COMPATIBILITY_REVIEW)
|
||||
const before = contents(root)
|
||||
const changed = inventory({ value: 'boolean' })
|
||||
expect(() => runPersistenceChanges(['--update', NEXT_ID], root, () => changed)).toThrow('renewed --review')
|
||||
expect(contents(root)).toEqual(before)
|
||||
runPersistenceChanges(['--update', NEXT_ID, '--review', review], root, () => changed)
|
||||
expect(runPersistenceChanges(['--check'], root, () => changed)).toContain('roots match')
|
||||
})
|
||||
|
||||
it('binds review evidence to exact history snapshots and leaves uncovered roots rejected', () => {
|
||||
const root = fixture()
|
||||
baseline(root)
|
||||
const after = inventory({ value: 'number' })
|
||||
runPersistenceChanges(['--record', NEXT_ID, '--review', reviewFile(root), '--prose', proseFile(root)], root, () => after)
|
||||
const uncovered = { ...after, roots: after.roots.map(root => root.key === 'SessionHeader'
|
||||
? typeRoot('SessionHeader', { id: 'string', 'env?': 'string' }) : root) }
|
||||
commitCurrent(root, uncovered)
|
||||
expect(() => verifyPersistenceChanges(root, uncovered)).toThrow('unacknowledged persistence type changes')
|
||||
const history = loadPersistenceHistory(root)
|
||||
const reviewed = history.entries.find(entry => entry.record.id === NEXT_ID)!
|
||||
const invalid = { ...reviewed, snapshot: onlyEvent(inventory({ value: 'boolean' })) }
|
||||
expect(() => validatePersistenceHistory([...history.entries.filter(entry => entry !== reviewed), invalid])).toThrow('after digest mismatch')
|
||||
})
|
||||
|
||||
it('locks finalized review semantics while ignoring review key ordering', () => {
|
||||
const root = fixture()
|
||||
baseline(root)
|
||||
const after = inventory({ value: 'number' })
|
||||
runPersistenceChanges(['--record', NEXT_ID, '--review', reviewFile(root), '--prose', proseFile(root)], root, () => after)
|
||||
const history = loadPersistenceHistory(root)
|
||||
const checkpoint = createPersistenceFinalizationCheckpoint(history, after)
|
||||
const reordered = { ...history, entries: history.entries.map(entry => entry.record.review === undefined ? entry : {
|
||||
...entry, record: { ...entry.record, review: {
|
||||
verification: COMPATIBILITY_REVIEW.verification,
|
||||
newReaders: COMPATIBILITY_REVIEW.newReaders,
|
||||
oldReaders: COMPATIBILITY_REVIEW.oldReaders,
|
||||
} },
|
||||
}) }
|
||||
expect(createPersistenceFinalizationCheckpoint(reordered, after)).toEqual(checkpoint)
|
||||
mkdirSync(join(root, 'docs/persistence-changes/finalized'))
|
||||
writeFileSync(join(root, 'docs/persistence-changes/finalized/v3.json'), JSON.stringify(checkpoint))
|
||||
for (const suffix of ['.md', '.zh.md']) writeFileSync(join(root, `docs/session-format-status${suffix}`), '```yaml session-format-finalization\nlatestFinalizedVersion: 3\n```\n')
|
||||
const changed = { ...history, entries: history.entries.map(entry => entry.record.review === undefined ? entry : {
|
||||
...entry, record: { ...entry.record, review: { ...entry.record.review, oldReaders: 'A different reader claim.' } },
|
||||
}) }
|
||||
expect(() => loadPersistenceFinalization(root, changed)).toThrow('was removed or changed')
|
||||
})
|
||||
})
|
||||
@@ -31,12 +31,20 @@ export interface PersistenceChange {
|
||||
readonly decision: PersistenceDecision
|
||||
}
|
||||
|
||||
/** Authored reader behavior and evidence covering every transition in one acknowledgement. */
|
||||
export interface PersistenceCompatibilityReview {
|
||||
readonly oldReaders: string
|
||||
readonly newReaders: string
|
||||
readonly verification: string
|
||||
}
|
||||
|
||||
/** A document's machine record, independent of its translated prose. */
|
||||
export interface PersistenceChangeRecord {
|
||||
readonly schemaVersion: 1
|
||||
readonly id: string
|
||||
readonly baseline: boolean
|
||||
readonly changes: readonly PersistenceChange[]
|
||||
readonly review?: PersistenceCompatibilityReview
|
||||
}
|
||||
|
||||
/** A parsed acknowledgement and its self-contained after schemas. */
|
||||
@@ -50,7 +58,7 @@ export interface PersistenceTypeChange {
|
||||
readonly kind: PersistenceTypeChangeKind
|
||||
readonly path: string
|
||||
readonly description: string
|
||||
readonly requiresVersionBump: boolean
|
||||
readonly requiresCompatibilityReview: boolean
|
||||
}
|
||||
|
||||
const CHANGE_DESCRIPTIONS = {
|
||||
@@ -380,19 +388,19 @@ function matchUnionVariants(candidates: readonly (readonly number[])[]): number[
|
||||
/** Classify structural differences using the reader promises saved with each schema.
|
||||
* @param before - predecessor root, or absence for an addition.
|
||||
* @param after - successor root, or absence for deletion.
|
||||
* @returns concrete changes and their format-bump requirement.
|
||||
* @returns concrete changes and their compatibility-review requirement.
|
||||
*/
|
||||
export function classifyPersistenceChange(before: PersistenceRoot | null, after: PersistenceRoot | null): PersistenceTypeChange[] {
|
||||
if (before === null) {
|
||||
return after === null ? [] : [{ path: after.key, kind: 'root-added', description: 'root added',
|
||||
requiresVersionBump: after.kind !== 'event' || after.surface !== false }]
|
||||
requiresCompatibilityReview: after.kind !== 'event' || after.surface !== false }]
|
||||
}
|
||||
if (after === null) return [{ path: before.key, kind: 'root-removed', description: 'root removed', requiresVersionBump: true }]
|
||||
if (after === null) return [{ path: before.key, kind: 'root-removed', description: 'root removed', requiresCompatibilityReview: true }]
|
||||
const key = after.key
|
||||
const oldRoot = before
|
||||
const newRoot = after
|
||||
const describe = (path: string, kind: PersistenceTypeChangeKind, requiresVersionBump = true): PersistenceTypeChange =>
|
||||
({ path, kind, description: CHANGE_DESCRIPTIONS[kind], requiresVersionBump })
|
||||
const describe = (path: string, kind: PersistenceTypeChangeKind, requiresCompatibilityReview = true): PersistenceTypeChange =>
|
||||
({ path, kind, description: CHANGE_DESCRIPTIONS[kind], requiresCompatibilityReview })
|
||||
const changes: PersistenceTypeChange[] = []
|
||||
if (before.kind !== after.kind || before.surface !== after.surface) changes.push(describe(key, 'root-classification-changed'))
|
||||
if (before.digest === after.digest) return changes
|
||||
@@ -413,8 +421,8 @@ export function classifyPersistenceChange(before: PersistenceRoot | null, after:
|
||||
// or unmatched union arm cannot leave a cached success for another candidate.
|
||||
const active = new Set(ancestors).add(pair)
|
||||
const differences: PersistenceTypeChange[] = []
|
||||
const add = (path: string, kind: PersistenceTypeChangeKind, requiresVersionBump = true): void => {
|
||||
differences.push(describe(path, kind, requiresVersionBump))
|
||||
const add = (path: string, kind: PersistenceTypeChangeKind, requiresCompatibilityReview = true): void => {
|
||||
differences.push(describe(path, kind, requiresCompatibilityReview))
|
||||
}
|
||||
const descend = (oldType: number, newType: number, child: string, childScope: Scope): void => {
|
||||
differences.push(...compare(oldType, newType, child, childScope, active))
|
||||
@@ -511,7 +519,7 @@ export function classifyPersistenceChange(before: PersistenceRoot | null, after:
|
||||
if (oldTypes.length !== newTypes.length) return [describe(path, 'union-variants-changed')]
|
||||
const candidates = oldTypes.map(oldType => newTypes.map(newType => compare(oldType, newType, path, scope, active)))
|
||||
const matching = matchUnionVariants(candidates.map(row => row.flatMap((candidate, index) =>
|
||||
candidate.every(change => !change.requiresVersionBump) ? [index] : [])))
|
||||
candidate.every(change => !change.requiresCompatibilityReview) ? [index] : [])))
|
||||
if (matching !== undefined) return matching.flatMap((next, previous) => candidates[previous]?.[next] ?? [])
|
||||
const oldByHash = new Map(oldTypes.map(index => [fingerprint(oldRoot.schema, index, 0), index]))
|
||||
const newByHash = new Map(newTypes.map(index => [fingerprint(newRoot.schema, index, 1), index]))
|
||||
@@ -522,7 +530,8 @@ export function classifyPersistenceChange(before: PersistenceRoot | null, after:
|
||||
}
|
||||
changes.push(...compare(0, 0, key, before.kind === 'event' ? 'event' : 'strict', new Set()))
|
||||
if (changes.length === 0) changes.push(describe(key, 'type-changed'))
|
||||
return [...new Map(changes.map(change => [JSON.stringify([change.path, change.kind, change.requiresVersionBump]), change])).values()]
|
||||
return [...new Map(changes.map(change =>
|
||||
[JSON.stringify([change.path, change.kind, change.requiresCompatibilityReview]), change])).values()]
|
||||
}
|
||||
|
||||
function parseDocument(source: string, filename: string, allowIncomplete = false): PersistenceChangeRecord {
|
||||
@@ -532,7 +541,9 @@ function parseDocument(source: string, filename: string, allowIncomplete = false
|
||||
const block = /^```yaml persistence-change[^\S\n]*\n([\s\S]*?)^```[^\S\n]*$/mu.exec(source)
|
||||
if (openings.length !== 1 || block === null) throw new Error(`${filename}: expected exactly one persistence-change block`)
|
||||
const input = record(load(block[1] as string, { schema: JSON_SCHEMA }), filename)
|
||||
keys(input, ['schemaVersion', 'id', 'baseline', 'changes'], filename)
|
||||
keys(input, ['schemaVersion', 'id', 'baseline', 'changes'], filename, ['review'])
|
||||
if (input.review !== undefined) parsePersistenceReview(input.review)
|
||||
if (input.baseline === true && input.review !== undefined) throw new Error(`${filename}: baseline cannot contain a compatibility review`)
|
||||
if (input.schemaVersion !== 1) throw new Error(`${filename}: unsupported acknowledgement schema version`)
|
||||
const id = identifier(input.id, filename)
|
||||
if (basename(filename) !== `${id}.md`) throw new Error(`${filename}: record id does not match filename`)
|
||||
@@ -571,6 +582,10 @@ export function validatePersistenceHistory(entries: readonly PersistenceHistoryE
|
||||
if (entries.filter(entry => entry.record.baseline).length !== 1) throw new Error('persistence history requires exactly one baseline')
|
||||
const records = new Map<string, PersistenceHistoryEntry>()
|
||||
for (const entry of entries) {
|
||||
if (entry.record.review !== undefined) {
|
||||
parsePersistenceReview(entry.record.review)
|
||||
if (entry.record.baseline) throw new Error('baseline cannot contain a compatibility review')
|
||||
}
|
||||
if (records.has(entry.record.id)) throw new Error(`duplicate persistence record ${entry.record.id}`)
|
||||
records.set(entry.record.id, entry)
|
||||
const expected = entry.record.changes.filter(change => change.after !== null).map(change => change.root).sort()
|
||||
@@ -604,10 +619,14 @@ export function validatePersistenceHistory(entries: readonly PersistenceHistoryE
|
||||
if (!found.entry.record.baseline) {
|
||||
const differences = classifyPersistenceChange(before, after)
|
||||
if (differences.length === 0) throw new Error(`${id}: unchanged acknowledgement for ${root}`)
|
||||
if (differences.some(change => change.requiresVersionBump) && found.change.decision !== 'version-bump') {
|
||||
if (root === 'SessionHeader' && headerVersion(before) !== headerVersion(after) && found.change.decision !== 'version-bump') {
|
||||
throw new PersistenceChangeFailure(`${id}: SessionHeader.version changes require an increasing version-bump transition`,
|
||||
'version-transition-required', differences.map(change => ({ root, ...change })), [rootTransition(before, after)])
|
||||
}
|
||||
if (differences.some(change => change.requiresCompatibilityReview) && found.change.decision !== 'version-bump' && found.entry.record.review === undefined) {
|
||||
throw new PersistenceChangeFailure(
|
||||
`${id}: ${root} requires a format version bump (${differences.filter(change => change.requiresVersionBump).map(change => change.path + ': ' + change.description).join('; ')})`,
|
||||
'version-bump-required', differences.map(change => ({ root, ...change })), [rootTransition(before, after)],
|
||||
`${id}: ${root} requires an authored compatibility review or an increasing version bump (${differences.filter(change => change.requiresCompatibilityReview).map(change => change.path + ': ' + change.description).join('; ')})`,
|
||||
'compatibility-review-required', differences.map(change => ({ root, ...change })), [rootTransition(before, after)],
|
||||
)
|
||||
}
|
||||
if (found.change.decision === 'version-bump') {
|
||||
@@ -690,24 +709,6 @@ function verifyFinalization(
|
||||
if (writer === undefined || writer < finalized.version) {
|
||||
throw new PersistenceChangeFailure(`Session writer must not precede finalized format ${finalized.version}`, 'finalized-version-order')
|
||||
}
|
||||
if (writer > finalized.version) return
|
||||
const afterRoots = new Map(current.roots.map(root => [root.key, root]))
|
||||
const changes: ReportedChange[] = []
|
||||
const transitions: RootTransition[] = []
|
||||
for (const key of [...new Set([...finalized.roots.keys(), ...afterRoots.keys()])].sort()) {
|
||||
const before = finalized.roots.get(key) ?? null
|
||||
const after = afterRoots.get(key) ?? null
|
||||
const differences = classifyPersistenceChange(before, after)
|
||||
if (differences.length === 0) continue
|
||||
changes.push(...differences.map(change => ({ root: key, ...change })))
|
||||
transitions.push(rootTransition(before, after))
|
||||
}
|
||||
if (changes.some(change => change.requiresVersionBump)) {
|
||||
throw new PersistenceChangeFailure(
|
||||
`Breaking changes relative to the accepted Session format ${writer} baseline require format ${writer + 1} or later`,
|
||||
'finalized-format-changed', changes, transitions,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/** Verify current generated output and acknowledgement tips together.
|
||||
@@ -729,7 +730,7 @@ export function verifyPersistenceChanges(root: string, current: PersistenceSchem
|
||||
throw new PersistenceChangeFailure(`${CURRENT_SCHEMA} is stale; regenerate the persistence catalog`, 'stale-artifacts', differences, transitions)
|
||||
}
|
||||
if (differences.length !== 0) {
|
||||
const details = differences.map(change => ` ${change.path}: ${change.description} (${change.requiresVersionBump ? 'version-bump required' : 'same-version allowed'})`)
|
||||
const details = differences.map(change => ` ${change.path}: ${change.description} (${change.requiresCompatibilityReview ? 'compatibility review required' : 'same-version allowed'})`)
|
||||
throw new PersistenceChangeFailure(`unacknowledged persistence type changes:\n${details.join('\n')}`, 'unacknowledged-changes', differences, transitions)
|
||||
}
|
||||
return history
|
||||
@@ -752,7 +753,11 @@ function reportedDifferences(history: PersistenceHistory, current: PersistenceSc
|
||||
}
|
||||
|
||||
function machineBlock(change: PersistenceChangeRecord): string {
|
||||
return ['```yaml persistence-change', 'schemaVersion: 1', `id: ${change.id}`, `baseline: ${String(change.baseline)}`, 'changes:',
|
||||
return ['```yaml persistence-change', 'schemaVersion: 1', `id: ${change.id}`, `baseline: ${String(change.baseline)}`,
|
||||
...(change.review === undefined ? [] : ['review:',
|
||||
` oldReaders: ${JSON.stringify(change.review.oldReaders)}`,
|
||||
` newReaders: ${JSON.stringify(change.review.newReaders)}`,
|
||||
` verification: ${JSON.stringify(change.review.verification)}`]), 'changes:',
|
||||
...change.changes.flatMap(item => [` - root: ${JSON.stringify(item.root)}`, ` previous: ${item.previous === null ? 'null' : JSON.stringify(item.previous)}`, ` after: ${item.after === null ? 'null' : JSON.stringify(item.after)}`, ` decision: ${item.decision}`]), '```'].join('\n')
|
||||
}
|
||||
|
||||
@@ -769,6 +774,23 @@ function scaffold(change: PersistenceChangeRecord, chinese: boolean, prose?: Per
|
||||
'<a id="verification"></a>', `## ${verification}`, '', prose?.verification ?? EVIDENCE_PLACEHOLDER, '', '<a id="dev-note"></a>', `## ${chinese ? '开发备注' : 'Dev Note'}`, '', chinese ? '无。' : 'None.', ''].join('\n')
|
||||
}
|
||||
|
||||
/** Parse authored compatibility evidence for the exact schemas in an acknowledgement.
|
||||
* @param value - decoded JSON supplied through --review or a saved machine record.
|
||||
* @returns validated evidence with canonical field ordering.
|
||||
*/
|
||||
export function parsePersistenceReview(value: unknown): PersistenceCompatibilityReview {
|
||||
const input = record(value, 'compatibility review')
|
||||
keys(input, ['oldReaders', 'newReaders', 'verification'], 'compatibility review')
|
||||
const authored = (name: string): string => {
|
||||
const text = textValue(input[name], `compatibility review ${name}`)
|
||||
if (text.trim().length === 0 || /\b(?:TODO|TBD|FIXME)\b/iu.test(text)) {
|
||||
throw new Error(`compatibility review ${name} requires authored text without placeholders`)
|
||||
}
|
||||
return text
|
||||
}
|
||||
return { oldReaders: authored('oldReaders'), newReaders: authored('newReaders'), verification: authored('verification') }
|
||||
}
|
||||
|
||||
/** Parse explicit authored prose without supplying compatibility or validation claims.
|
||||
* @param value - decoded JSON supplied through --prose.
|
||||
* @returns complete English and Chinese section text.
|
||||
@@ -822,12 +844,14 @@ function executeCommand(
|
||||
): CommandResult {
|
||||
const { values } = parseArgs({ args: [...args], strict: true, allowPositionals: false, options: {
|
||||
check: { type: 'boolean' }, baseline: { type: 'string' }, record: { type: 'string' }, update: { type: 'string' },
|
||||
decision: { type: 'string' }, root: { type: 'string' }, prose: { type: 'string' }, json: { type: 'boolean' },
|
||||
decision: { type: 'string' }, root: { type: 'string' }, prose: { type: 'string' }, review: { type: 'string' }, json: { type: 'boolean' },
|
||||
} })
|
||||
if (values.root !== undefined) root = resolve(values.root)
|
||||
const selected = [values.check === true, values.baseline !== undefined, values.record !== undefined, values.update !== undefined]
|
||||
if (selected.filter(Boolean).length > 1) throw new Error('choose exactly one of --check, --baseline ID, --record ID, or --update ID')
|
||||
if (values.record === undefined && values.update === undefined && values.decision !== undefined) throw new Error('--decision requires --record or --update')
|
||||
if (values.review !== undefined && values.record === undefined && values.update === undefined) throw new Error('--review requires --record or --update')
|
||||
let review = values.review === undefined ? undefined : parsePersistenceReview(JSON.parse(readFileSync(resolve(root, values.review), 'utf8')))
|
||||
const operation = commandOperation(args)
|
||||
if (operation === 'check' && values.prose !== undefined) throw new Error('--prose requires --baseline, --record, or --update')
|
||||
const prose = values.prose === undefined ? undefined : parsePersistenceProse(JSON.parse(readFileSync(resolve(root, values.prose), 'utf8')))
|
||||
@@ -858,18 +882,28 @@ function executeCommand(
|
||||
const changed = baseline ? current.roots.map(root => root.key) : currentDifferences(history as PersistenceHistory, current)
|
||||
if (changed.length === 0) throw new Error('no persistence type changes to acknowledge')
|
||||
const differences = history === undefined ? [] : reportedDifferences(history, current)
|
||||
const decision = values.decision ?? (differences.some(change => change.requiresVersionBump) ? 'version-bump' : 'same-version')
|
||||
const previousHeader = history?.tips.get('SessionHeader')?.root ?? null
|
||||
const currentHeader = current.roots.find(root => root.key === 'SessionHeader') ?? null
|
||||
const versionChanged = !baseline && headerVersion(previousHeader) !== headerVersion(currentHeader)
|
||||
const decision = values.decision ?? (versionChanged ? 'version-bump' : 'same-version')
|
||||
const roots = current.roots.filter(root => changed.includes(root.key))
|
||||
const change: PersistenceChangeRecord = { schemaVersion: 1, id, baseline, changes: changed.sort().map(key => ({
|
||||
root: key, previous: history?.tips.get(key)?.id ?? null,
|
||||
after: roots.find(root => root.key === key)?.digest ?? null, decision,
|
||||
})) }
|
||||
if (review === undefined && existing?.record.review !== undefined) {
|
||||
if (JSON.stringify(existing.record.changes) !== JSON.stringify(change.changes)) {
|
||||
throw new PersistenceChangeFailure(`${id}: changed schemas require renewed --review evidence`, 'compatibility-review-required', differences, rootTransitions(history, current))
|
||||
}
|
||||
review = existing.record.review
|
||||
}
|
||||
const reviewedChange = review === undefined ? change : { ...change, review }
|
||||
const snapshot: PersistenceSchemaInventory = { formatVersion: current.formatVersion, roots, types: [] }
|
||||
validatePersistenceHistory([...prior, { record: change, snapshot }])
|
||||
validatePersistenceHistory([...prior, { record: reviewedChange, snapshot }])
|
||||
const document = (chinese: boolean): string => {
|
||||
const supplied = chinese ? prose?.zh : prose?.en
|
||||
return existing === undefined ? scaffold(change, chinese, supplied)
|
||||
: updateDocument(readFileSync(join(directory, `${id}${chinese ? '.zh' : ''}.md`), 'utf8'), change, chinese, supplied)
|
||||
return existing === undefined ? scaffold(reviewedChange, chinese, supplied)
|
||||
: updateDocument(readFileSync(join(directory, `${id}${chinese ? '.zh' : ''}.md`), 'utf8'), reviewedChange, chinese, supplied)
|
||||
}
|
||||
const english = document(false)
|
||||
const chinese = document(true)
|
||||
|
||||
@@ -23,7 +23,7 @@ function toolAdditionSchema() {
|
||||
}
|
||||
|
||||
describe('developer field compatibility', () => {
|
||||
it('requires a version bump before a future declaration can use the rejected inline tool field', () => {
|
||||
it('requires compatibility review before a future declaration can use the rejected inline tool field', () => {
|
||||
const { before, nodes, blockIndex, block, stringIndex } = toolAdditionSchema()
|
||||
nodes[blockIndex] = { ...block, properties: [
|
||||
...block.properties.filter(property => property.name !== 'tool'),
|
||||
@@ -31,7 +31,7 @@ describe('developer field compatibility', () => {
|
||||
] }
|
||||
const schema = canonicalizeSchema(nodes, 0)
|
||||
expect(classifyPersistenceChange(before, { ...before, schema, digest: schemaDigest(schema) }))
|
||||
.toContainEqual(expect.objectContaining({ requiresVersionBump: true }))
|
||||
.toContainEqual(expect.objectContaining({ requiresCompatibilityReview: true }))
|
||||
const reserved = block.properties.find(property => property.name === 'tool')
|
||||
expect(reserved).toMatchObject({ optional: true })
|
||||
expect(before.schema.nodes[reserved!.type]).toEqual({ kind: 'primitive', type: 'never' })
|
||||
@@ -47,7 +47,7 @@ describe('developer field compatibility', () => {
|
||||
const { before, nodes, blockIndex, block, stringIndex } = toolAdditionSchema()
|
||||
nodes[blockIndex] = { ...block, properties: [...block.properties, { name: 'traceId', type: stringIndex, optional: true }] }
|
||||
const schema = canonicalizeSchema(nodes, 0)
|
||||
expect(classifyPersistenceChange(before, { ...before, schema, digest: schemaDigest(schema) })).toMatchObject([{ kind: 'optional-property-added', requiresVersionBump: false }])
|
||||
expect(classifyPersistenceChange(before, { ...before, schema, digest: schemaDigest(schema) })).toMatchObject([{ kind: 'optional-property-added', requiresCompatibilityReview: false }])
|
||||
const header = { version: 4, id: 'optional-developer-field', createdAt: 1, isSeeded: false, delegationDepth: 0 }
|
||||
const message = { id: 'developer', role: 'developer', source: { kind: 'tool-registry', extra: true },
|
||||
content: [{ type: 'tool-addition', toolName: 'search', traceId: 'retained' }], extra: true }
|
||||
|
||||
@@ -15,7 +15,7 @@ function property(nodes: readonly SchemaNode[], index: number, name: string): nu
|
||||
return field.type
|
||||
}
|
||||
|
||||
it('requires a version bump before request headers can carry retired system text', { timeout: 60_000 }, () => {
|
||||
it('requires compatibility review before request headers can carry retired system text', { timeout: 60_000 }, () => {
|
||||
const inventory = extractPersistenceSchema(resolve(import.meta.dirname, '..'))
|
||||
const before = inventory.roots.find(root => root.key === 'event:request/header')
|
||||
if (before === undefined) throw new Error('generated schema omits request/header')
|
||||
@@ -41,7 +41,7 @@ it('requires a version bump before request headers can carry retired system text
|
||||
path: 'event:request/header.data.header.system',
|
||||
kind: 'type-changed',
|
||||
description: 'type changed',
|
||||
requiresVersionBump: true,
|
||||
requiresCompatibilityReview: true,
|
||||
}])
|
||||
expect(() => { assertV4RowAdmission({ type: 'request/header', data: { header: { system: 'retired text' } } }) })
|
||||
.toThrow('format v4 request/header rejects retired header.system')
|
||||
|
||||
@@ -47,7 +47,9 @@ function recordDigest(entry: PersistenceHistoryEntry): string {
|
||||
const semantic = { record: { schemaVersion: record.schemaVersion, id: record.id, baseline: record.baseline,
|
||||
changes: [...record.changes].sort((a, b) => compare(a.root, b.root)).map(change => ({
|
||||
root: change.root, previous: change.previous, after: change.after, decision: change.decision,
|
||||
})) }, snapshot: { formatVersion: entry.snapshot.formatVersion,
|
||||
})), ...(record.review === undefined ? {} : { review: {
|
||||
oldReaders: record.review.oldReaders, newReaders: record.review.newReaders, verification: record.review.verification,
|
||||
} }) }, snapshot: { formatVersion: entry.snapshot.formatVersion,
|
||||
roots: [...entry.snapshot.roots].sort((a, b) => compare(a.key, b.key)).map(root => ({
|
||||
key: root.key, ...identity(root), schema: root.schema,
|
||||
})) } }
|
||||
|
||||
@@ -35,12 +35,12 @@ function structuralChanges(entry: PersistenceReleaseEntry, language: Language):
|
||||
: '规范化后的根类型及其传递引用摘要与前一 tag 相同。'
|
||||
}
|
||||
const summary = language === 'en'
|
||||
? `Detected ${count(entry.record.changes.length, 'changed root')} and ${count(entry.differences.length, 'structural difference')}. The minimum below is calculated using current rules for comparison only; it does not assert historical compliance, migration correctness, or runtime compatibility.`
|
||||
: `检测到 ${entry.record.changes.length} 个根类型变化、${entry.differences.length} 项结构差异。下表的最低要求按当前规则计算,只用于比较;不表示旧版本曾遵守这些规则,也不证明迁移或运行时兼容性。`
|
||||
const heading = language === 'en' ? '| Path | Change | Current minimum |' : '| 路径 | 变化 | 当前最低要求 |'
|
||||
? `Detected ${count(entry.record.changes.length, 'changed root')} and ${count(entry.differences.length, 'structural difference')}. The review requirement below is calculated using current rules for comparison only; it does not assert historical compliance, migration correctness, or runtime compatibility.`
|
||||
: `检测到 ${entry.record.changes.length} 个根类型变化、${entry.differences.length} 项结构差异。下表的审查要求按当前规则计算,只用于比较;不表示旧版本曾遵守这些规则,也不证明迁移或运行时兼容性。`
|
||||
const heading = language === 'en' ? '| Path | Change | Review requirement |' : '| 路径 | 变化 | 审查要求 |'
|
||||
const rows = entry.differences.map((change) => {
|
||||
const path = change.path.replaceAll('`', '\\`').replaceAll('|', '\\|')
|
||||
return `| \`${path}\` | \`${change.kind}\` | \`${change.requiresVersionBump ? 'version-bump' : 'same-version'}\` |`
|
||||
return `| \`${path}\` | \`${change.kind}\` | \`${change.requiresCompatibilityReview ? 'review-required' : 'same-version'}\` |`
|
||||
})
|
||||
return [summary, '', heading, '|---|---|---|', ...rows].join('\n')
|
||||
}
|
||||
|
||||
@@ -145,7 +145,7 @@ describe('pinned persistence releases', () => {
|
||||
expect(archive.entries).toHaveLength(3)
|
||||
expect(archive.entries[0]!.roots.get('event:example/value')!.digest).toBe(event('string').digest)
|
||||
expect(archive.entries[1]!.roots.get('event:example/value')!.digest).toBe(event('number').digest)
|
||||
expect(archive.entries[1]!.differences).toEqual([expect.objectContaining({ root: 'event:example/value', kind: 'type-changed', requiresVersionBump: true })])
|
||||
expect(archive.entries[1]!.differences).toEqual([expect.objectContaining({ root: 'event:example/value', kind: 'type-changed', requiresCompatibilityReview: true })])
|
||||
expect(archive.entries[2]!.roots).toEqual(archive.entries[1]!.roots)
|
||||
expect(archive.entries[2]!.differences).toEqual([])
|
||||
expect(() => parsePersistenceSnapshot(data.snapshots[1])).toThrow('surface metadata')
|
||||
@@ -249,7 +249,7 @@ describe('pinned persistence releases', () => {
|
||||
saveRecord(data.directory, { ...data.records[2]!, changes: [{ root: 'event:example/value', before: event('number').digest, after: null }] })
|
||||
const result = loadPersistenceReleases(data.root)
|
||||
expect(result.entries[2]!.roots.has('event:example/value')).toBe(false)
|
||||
expect(result.entries[2]!.differences).toEqual([expect.objectContaining({ kind: 'root-removed', requiresVersionBump: true })])
|
||||
expect(result.entries[2]!.differences).toEqual([expect.objectContaining({ kind: 'root-removed', requiresCompatibilityReview: true })])
|
||||
})
|
||||
|
||||
it('rejects missing, duplicate, and unreferenced reachable types', () => {
|
||||
@@ -305,7 +305,9 @@ describe('release facts', () => {
|
||||
expect(after['README.md']).toContain(`| [${TAGS[1]}](${TAGS[1]}.md) | 2026-09-01 | 0 | 4 / 7 | 1 |`)
|
||||
expect(after[TAGS[1] + '.md']).toContain('4 roots / 7 types')
|
||||
expect(after[TAGS[1] + '.md']).toContain('Detected 1 changed root and 1 structural difference.')
|
||||
expect(after[TAGS[1] + '.md']).toContain('| `event:example/value.data` | `type-changed` | `version-bump` |')
|
||||
expect(after[TAGS[1] + '.md']).toContain('| `event:example/value.data` | `type-changed` | `review-required` |')
|
||||
expect(after[TAGS[1] + '.md']).toContain('| Path | Change | Review requirement |')
|
||||
expect(after[TAGS[1] + '.zh.md']).toContain('| 路径 | 变化 | 审查要求 |')
|
||||
for (const [name, source] of Object.entries(before)) {
|
||||
if (name.endsWith('.json')) expect(after[name]).toBe(source)
|
||||
if (name.endsWith('.md')) {
|
||||
@@ -342,7 +344,7 @@ describe('release facts', () => {
|
||||
['difference count', '1 structural difference', '9 structural differences'],
|
||||
['path', '`event:example/value.data`', '`event:example/value.wrong`'],
|
||||
['kind', '`type-changed`', '`optional-property-added`'],
|
||||
['minimum', '`version-bump`', '`same-version`'],
|
||||
['review requirement', '`review-required`', '`same-version`'],
|
||||
])('rejects stale record %s', (_label, original, replacement) => {
|
||||
const data = fixture()
|
||||
runPersistenceReleases(['--write'], data.root)
|
||||
|
||||
@@ -81,7 +81,7 @@ describe('persistence review', () => {
|
||||
const report = reviewPersistenceSchemas(fixture(), fixture({ extraKind: true, changed: true }))
|
||||
expect(report.evidence.map(item => item.kind).sort()).toEqual(['kind-added', 'type-changed'])
|
||||
expect(report.evidence.find(item => item.kind === 'type-changed')?.locations[0]?.path).toBe('event:agent/inbox/spliced.data.inserted[].source[kind="changing"].value')
|
||||
expect(report.roots.every(root => root.changes.some(change => change.requiresVersionBump))).toBe(true)
|
||||
expect(report.roots.every(root => root.changes.some(change => change.requiresCompatibilityReview))).toBe(true)
|
||||
})
|
||||
|
||||
it('keeps ambiguous kind matches as additions and removals even when another arm is unchanged', () => {
|
||||
|
||||
@@ -201,8 +201,8 @@ export function renderPersistenceReview(report: PersistenceReview): string {
|
||||
}
|
||||
lines.push('', '## Authoritative compatibility results', '')
|
||||
for (const root of report.roots) {
|
||||
lines.push(`### ${code(root.root)}`, '', `Decision: **${root.changes.some(change => change.requiresVersionBump) ? 'version-bump' : 'same-version'}**.`, '',
|
||||
...root.changes.map(change => `- ${code(change.path)}: ${change.description} (${code(change.kind)}; requiresVersionBump: ${String(change.requiresVersionBump)}).`), '')
|
||||
lines.push(`### ${code(root.root)}`, '', `Assessment: **${root.changes.some(change => change.requiresCompatibilityReview) ? 'compatibility review required' : 'same-version allowed'}**.`, '',
|
||||
...root.changes.map(change => `- ${code(change.path)}: ${change.description} (${code(change.kind)}; requiresCompatibilityReview: ${String(change.requiresCompatibilityReview)}).`), '')
|
||||
}
|
||||
return lines.join('\n').trimEnd() + '\n'
|
||||
}
|
||||
|
||||
@@ -346,7 +346,7 @@ describe('explicitly reserved JSON properties', () => {
|
||||
const before = extractedEvent('export interface Payload { id: string;\n/** @persistenceReserved */\ntool?: never }')
|
||||
const after = extractedEvent('export interface Payload { id: string; tool?: string }')
|
||||
expect(classifyPersistenceChange(before, after)).toEqual([expect.objectContaining({
|
||||
path: 'event:test/record.data.tool', kind: 'type-changed', requiresVersionBump: true,
|
||||
path: 'event:test/record.data.tool', kind: 'type-changed', requiresCompatibilityReview: true,
|
||||
})])
|
||||
})
|
||||
|
||||
@@ -420,7 +420,7 @@ describe('source compatibility authoring', () => {
|
||||
expect(property.compatibility).toEqual({ version: 1, policy: 'session-source-attribution', binding: `session.${role}-message.source`,
|
||||
discriminator: 'kind', unknownKinds: 'preserve', attributionKinds: ['attribution'] })
|
||||
expect(classifyPersistenceChange(before.roots.find(root => root.kind === 'event')!, root))
|
||||
.toEqual([expect.objectContaining({ kind: 'attribution-kind-added', requiresVersionBump: false })])
|
||||
.toEqual([expect.objectContaining({ kind: 'attribution-kind-added', requiresCompatibilityReview: false })])
|
||||
expect(parsePersistenceSnapshot({ ...after, types: [] })).toEqual({ ...after, types: [] })
|
||||
})
|
||||
|
||||
@@ -502,18 +502,18 @@ declare module './message.js' {
|
||||
}`)
|
||||
const after = extractPersistenceSchema(root)
|
||||
expect(classifyPersistenceChange(before.roots.find(root => root.kind === 'event')!, after.roots.find(root => root.kind === 'event')!))
|
||||
.toEqual([expect.objectContaining({ kind: 'attribution-kind-added', requiresVersionBump: false })])
|
||||
.toEqual([expect.objectContaining({ kind: 'attribution-kind-added', requiresCompatibilityReview: false })])
|
||||
})
|
||||
|
||||
it('does not qualify unmarked additions or structurally equal unbound fields', () => {
|
||||
const before = sourceFixture('')
|
||||
const after = sourceFixture("unmarked: {kind: 'new'}")
|
||||
expect(classifyPersistenceChange(extractPersistenceSchema(before).roots.find(root => root.kind === 'event')!,
|
||||
extractPersistenceSchema(after).roots.find(root => root.kind === 'event')!).some(change => change.requiresVersionBump)).toBe(true)
|
||||
extractPersistenceSchema(after).roots.find(root => root.kind === 'event')!).some(change => change.requiresCompatibilityReview)).toBe(true)
|
||||
const left = fixture("export interface Payload { source: {kind: 'old'} }")
|
||||
const right = fixture("export interface Payload { source: {kind: 'old'} | {kind: 'new'} }")
|
||||
expect(classifyPersistenceChange(extractPersistenceSchema(left).roots.find(root => root.kind === 'event')!,
|
||||
extractPersistenceSchema(right).roots.find(root => root.kind === 'event')!).some(change => change.requiresVersionBump)).toBe(true)
|
||||
extractPersistenceSchema(right).roots.find(root => root.kind === 'event')!).some(change => change.requiresCompatibilityReview)).toBe(true)
|
||||
})
|
||||
|
||||
it.each([
|
||||
|
||||
Reference in new issue
Block a user