Replies: 1 comment
|
补一条:本帖对两处根因的判断是准确的。顺带确认—— 有一处可以更精确:descriptor v2 → v3 严格来说不是"字段一致",而是 v3 只多了一个 即 v2 描述符恰好等于省略该字段的 v3 描述符。这一点有实际意义:它证明升版本号 读取侧修复的具体改法、无损性证明,以及一个不依赖任何受影响会话数据的复现脚本 本机 57 个会话日志实测:修前 1/57,修后 57/57; |
0 replies
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Uh oh!
There was an error while loading. Please reload this page.
概要
d1521ea783("feat(session)!: add released format migration",2026-09-01,首发于dsh-v0.1.3-alpha.1)引入的格式迁移,会拒绝两种由更早的官方版本自己写入version: 0会话日志的载荷:subagent/descriptor的version: 2—— 由 0.1.0 和 0.1.1 写入(那两个版本的SUBAGENT_DESCRIPTOR_VERSION = 2),而这些会话头部的SESSION_FORMAT_VERSION已经是0。permission/preset携带origin—— 由dsh-permission-presets从 0.1.1 起写入。两种拒绝都以
SessionFormatUnsupportedMigrationError抛出,导致受影响的会话完全无法打开,且 v0 原件在磁盘上保持不变。任何用过 0.1.0 / 0.1.1、之后升级到 0.1.3-alpha.1 或更新版本的人,都会失去这些历史的读取能力,产品内也没有跳过或隔离问题记录的途径。受影响版本
SESSION_FORMAT_VERSIONSUBAGENT_DESCRIPTOR_VERSIONpermission/preset的origindsh-v0.1.0-rc.8(npm 已发布)dsh-v0.1.1-rc.1(npm 已发布)dsh-v0.1.2-alpha.3dsh-v0.1.3-alpha.1dsh-v0.1.5-rc.2每一行的头部格式号都是
0,所以迁移器里version === 0那个分支正是看到这些归档的地方。也就是说,0.1.0 / 0.1.1 写出的记录,在 0.1.3-alpha.1 发布的那一刻就变得无法迁移了。复现 A ——
subagent/descriptor版本 2任何包含 0.1.0 / 0.1.1 写出的描述符的 v0 会话,都会迁移失败:
实际观察到的记录:
{"type":"subagent/descriptor","seq":142751,"time":...,"data":{"version":2,"mode":"one-shot","provider":"fork","label":"..."}}0.1.0 / 0.1.1 写入了它的证据:
dsh-v0.1.0-rc.8:packages/subagent/subagent/src/descriptor.ts→export const SUBAGENT_DESCRIPTOR_VERSION = 2version: SUBAGENT_DESCRIPTOR_VERSIONdsh-v0.1.1-rc.1:packages/subagent/subagent/src/descriptor.ts→ 仍为2dsh-v0.1.2-alpha.3才改为3HEAD 上执行拒绝的规则:
v2 的载荷本身就落在合法 v3 的形状内
已发布 v0 规范中
one-shot描述符的成员是required: ['mode', 'version', 'provider']加optional: ['label'](packages/session/session-format-v0-to-v1/src/payload-validation.ts的subagentDescriptorValue)。上面那条 v2 记录携带的正是这个成员集合;同一批数据里还存在一条version: 3的记录,成员集合与它完全相同。就载荷而言,此处 v2 与 v3 没有任何区别——两次发布之间改变的只有那个常量。复现 B ——
permission/preset携带origin实际观察到的记录:
{"type":"permission/preset","seq":0,"time":...,"data":{"preset":"workspace-write","origin":"default"}}已发布的 disposition 只承认
preset:但官方写入端在 0.1.1 就加上了
origin——dsh-v0.1.1-rc.1:packages/interaction/permission-presets/src/index.ts:该版本的 README 也记载了这一点:"the preset fact records whether it came from the default, an explicit selection, or legacy-knob inference." 其类型声明的注释还特别说明
origin是可选的,以便 "logs written before origin tracking remain readable" —— 也就是说两种形状本来都预期会出现在历史数据里。迁移过程任何地方都不读取
origin,因此接纳它纯粹是增量性的。为什么这更像迁移器的缺口,而不是脏数据
v0 会话层有意不对插件或产品追加的载荷施加约束;该日志的类型文档明确写着 invariant companion "deliberately constrains nothing here."。而 v0→v1 校验器反过来追溯性地冻结了一份严格的成员清单。于是单个不符合约定的可选成员就会让整个会话拒绝迁移,而不是降级——在本机表现为
failed to observe session "<id>": ... refuses this format v0 Session,原件保持不变。上面两种情形里,出问题的都是迁移过程从不读取的可选元数据。丢弃它(或带着诊断跳过该行)就能让会话打开;拒绝它并没有保护任何对重建有意义的约束。
建议修复
permission/preset—— 把origin加入 disposition 的 optional:(若希望限定取值,可校验它为
'default' | 'selection' | 'inferred'之一。)subagent/descriptor—— 当 v0 时代的version: 2载荷的成员集合本就落在已发布描述符承认的范围内时接纳它,并归一化为 3;如果团队希望保留版本门,则改为带诊断跳过该行,而不是拒绝整个会话。更一般地:对于没有任何迁移阶段读取的、无法表示的可选成员,考虑降级处理,而不是抛出
SessionFormatUnsupportedMigrationError。附注 —— 第三类(仅作背景,不属于本报告)
为完整起见,同一次升级还会拒绝包含
user/message的会话,其source形如{kind:'plugin', form:'instructions', summary:'...'}——summary只在form:'notice'时才被承认。该形状由一个第三方插件写入(dsh-mnemon0.2.16,已在 0.5.7 修复),不属于官方写入端的情形;之所以提及,只是为了避免与上面两类官方问题混淆。如果维护者希望三类统一处理,同样的「降级而非拒绝」论证也适用。环境
@deepseek-ai/dsh0.1.2-alpha.3 升级到 0.1.5-rc.2(npmnext标签)@deepseek-ai/dsh-session-format-v0-to-v1、-v1-to-v2、-v2-to-v3version: 0。其中 33 条subagent/descriptor记录需要归一化(复现 A),5 条permission/preset记录携带origin(复现 B),705 条user/message记录携带第三方的summary(附注)。本地验证方式
每一份日志都通过已安装的
sessionFormatCatalog.createRestore(header, { recovery: 'strict', validation: 'transformed' })跑过——即读取端自己的解码器,也就是完整的 v0→v1→v2→v3 链路。在本地归一化之前,该链路拒绝上面引用的那些记录;之后 178 份日志全部端到端解码成功。两类官方问题正是这样被隔离出来的:它们都从同一条代码路径抛出硬拒绝。All reactions