dsh-log-contract 0.3.14 → 0.3.16

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/docs/CONTRACTS.md CHANGED
@@ -2,13 +2,14 @@
2
2
 
3
3
  > **自动生成**(2026-09-06 起):本文件由 `node scripts/gen-contracts-doc.mjs`
4
4
  > 从 `lib/contracts.js` 的 `CONTRACT_RULES` 注册表生成——**勿手改**,规则只增不减,
5
- > 新增规则后跑一次生成即同步(此前手工维护滞后 15+ 条,外部审计指出)。
5
+ > 新增规则后跑一次生成即同步(此前手工维护滞后 15+ 条)。
6
+ > **漂移闸**:`pnpm check` 会跑 `--check` 逐字节比对,改了注册表没重生成 ⇒ 直接红。
6
7
  >
7
8
  > DSH 会话日志契约的**可执行 spec**。每条规则在 `lib/checks.js`(逐事件判定)
8
9
  > 与 `lib/prewrite.js`(写前校验)中有对应实现;离线体检(`lib/validate.js`)
9
10
  > 逐条执行并在最后用官方 `foldSurface` 终验(S8)。
10
11
  >
11
- > 规则来源:早期内部审计发现(59 条)+ 三层契约事故复盘 + 官方源码逐行核对
12
+ > 规则来源:历史会话日志的实测事故与契约缺口(持久化/客户端引擎/插件语义三层)+ 官方源码逐行核对
12
13
  > (`@deepseek-ai/dsh-session`,各条出处见下)。后续规则随官方版本演进追加:
13
14
  > T3/T4 = 渲染层白屏事故复盘,T5 = malformed turn/end 事故复盘。
14
15
  >
@@ -87,7 +88,7 @@
87
88
  ### R1 — 每行必须是合法 JSON
88
89
 
89
90
  - **层级**: persistence | **严重度**: error
90
- - **出处**: 审计方法论(scan-seq-gaps.mjs);dsh-session-persistence-jsonl 读路径
91
+ - **出处**: seq 缺口扫描;dsh-session-persistence-jsonl 读路径
91
92
  - **契约**: 非空行无法 JSON.parse = 损坏行。帧边界产生的空行是合法的(跳过)。
92
93
 
93
94
  ### R2 — chunk 行必须满足精确信封形状
@@ -111,7 +112,7 @@
111
112
  ### E2 — seq 严格连续(单写入者假设)
112
113
 
113
114
  - **层级**: persistence | **严重度**: error
114
- - **出处**: @deepseek-ai/dsh-session lib/index.js:398 (planSurfaceEvent "not contiguous");审计 S2/N6
115
+ - **出处**: @deepseek-ai/dsh-session lib/index.js:398 (planSurfaceEvent "not contiguous")
115
116
  - **契约**: seq 必须从 0(或窗口 baseSeq)严格连续递增。缺口/倒退 = 违反单写入者假设(多实例共享存储并发写的痕迹),加载时直接 throw。
116
117
 
117
118
  ### S9 — 文件物理序 seq 单调(多写入者交织现场特征)
@@ -142,7 +143,7 @@
142
143
 
143
144
  - **层级**: persistence | **严重度**: error
144
145
  - **出处**: @deepseek-ai/dsh-session lib/index.js:1273-1277 (assertSupportedRequestHeader)
145
- - **契约**: request/header-delta 与 reason=fallback 的 request/header 是已删除的遗留格式。注意(R-F 订正复核 §1 E5):宿主 Session.append/appendLines 不看 type ⇒ 写入会成功、下一次读取才炸(依据 dsh-session@0.1.5-rc.1 lib/index.js:1170-1210 / persistence-jsonl:3046-3073),不是写入即被拒。
146
+ - **契约**: request/header-delta 与 reason=fallback 的 request/header 是已删除的遗留格式。注意(订正 E5):宿主 Session.append/appendLines 不看 type ⇒ 写入会成功、下一次读取才炸(依据 dsh-session@0.1.5-rc.1 lib/index.js:1170-1210 / persistence-jsonl:3046-3073),不是写入即被拒。
146
147
 
147
148
  ### E6 — 消息类事件消息形状
148
149
 
@@ -177,8 +178,8 @@
177
178
  ### S5 — replace 的 sourceEventSeqs 必须完整覆盖被替换节点
178
179
 
179
180
  - **层级**: persistence | **严重度**: error
180
- - **出处**: @deepseek-ai/dsh-session lib/index.js:335-336 (assertProvenance);复盘事故第 1
181
- - **契约**: ★ 写前校验核心规则:sourceEventSeqs 必须包含每一个被替换(shadowed)的 surface 节点,缺一个 = 会话加载被拒(SessionPersistenceCorruptionError)。2026-08-25 事故第 1 轮(清空 sourceEventSeqs)正是违反此规则。
181
+ - **出处**: @deepseek-ai/dsh-session lib/index.js:335-336 (assertProvenance);复盘事故第 1 次尝试
182
+ - **契约**: ★ 写前校验核心规则:sourceEventSeqs 必须包含每一个被替换(shadowed)的 surface 节点,缺一个 = 会话加载被拒(SessionPersistenceCorruptionError)。2026-08-25 事故第 1 次尝试(清空 sourceEventSeqs)正是违反此规则。
182
183
 
183
184
  ### S6 — sourceEventSeqs 自身约束
184
185
 
@@ -231,49 +232,49 @@
231
232
  ### E7 — ignorable 未知 type 合法性(带被忽略标记的未知事件须有消费者)
232
233
 
233
234
  - **层级**: persistence | **严重度**: warning
234
- - **出处**: 反向挑刺 2026-09-09 T2(E3 ignorable 无合法性校验 = 后门)
235
+ - **出处**: 实测 2026-09-09 T2(E3 ignorable 无合法性校验 = 后门)
235
236
  - **契约**: 未知 type + ignorable:true 被读路径接纳但无人消费 = 静默垃圾。排除已知消费者白名单(retrace/marker、retrace/goal-marker、message-editor/ 前缀等 retrace 客户端消费的插件 marker)后,其余 ignorable 未知事件报 warning。
236
237
 
237
238
  ### E8 — 事件信封键白名单(多余键:seed/restore 路径会拒)
238
239
 
239
240
  - **层级**: persistence | **严重度**: warning
240
241
  - **出处**: @deepseek-ai/dsh-session@0.1.5-rc.1 lib/index.js:849-861(assertSessionEventEnvelope)+ :1063-1068(唯一调用点=seed 路径);load 路径容忍见行为探针 p5
241
- - **契约**: 事件对象只允许 7 个信封键(type/seq/time/data/surfaceOp/sourceEventSeqs/ignorable)。独立复核变异 05 指出"宿主拒、旧契约 0 违规";R-D 行为探针进一步订正口径:**load 路径容忍、seed/restore 路径拒** ⇒ warning。
242
+ - **契约**: 事件对象只允许 7 个信封键(type/seq/time/data/surfaceOp/sourceEventSeqs/ignorable)。实测反例指出"宿主拒、旧契约 0 违规";行为探针进一步订正口径:**load 路径容忍、seed/restore 路径拒** ⇒ warning。
242
243
 
243
244
  ### E9 — system/message 必须带 plugin source
244
245
 
245
246
  - **层级**: persistence | **严重度**: error
246
247
  - **出处**: @deepseek-ai/dsh-session@0.1.5-rc.1 lib/index.js:942-944("must have plugin source");角色表 :917-926
247
- - **契约**: v3 新增的 system/message:role 必须为 system,source.kind 必须为 plugin 且 plugin 非空。实测(变异 03):source.kind='user' 宿主拒、旧契约 0 违规 ⇒ 漏检。
248
+ - **契约**: v3 新增的 system/message:role 必须为 system,source.kind 必须为 plugin 且 plugin 非空。实测(反例):source.kind='user' 宿主拒、旧契约 0 违规 ⇒ 漏检。
248
249
 
249
250
  ### E10 — request/header 的 data.header 字段约束
250
251
 
251
252
  - **层级**: persistence | **严重度**: error
252
253
  - **出处**: @deepseek-ai/dsh-session@0.1.5-rc.1 lib/index.js:231-248(validateSessionEventData:omit header.system / omit empty tools / omit empty adapterDefaults)
253
- - **契约**: request/header 必须省略 header.system(系统提示改走 system/message)、空 tools、空 adapterDefaults。实测(变异 12):带 header.system 的写入宿主拒、旧契约 0 违规 ⇒ 漏检。
254
+ - **契约**: request/header 必须省略 header.system(系统提示改走 system/message)、空 tools、空 adapterDefaults。实测(反例):带 header.system 的写入宿主拒、旧契约 0 违规 ⇒ 漏检。
254
255
 
255
256
  ### Z3 — 空会话文件(有 header 无事件)显式报出
256
257
 
257
258
  - **层级**: framing | **严重度**: warning
258
- - **出处**: 反向挑刺 2026-09-09 T3(36 条规则全来自有内容事故,空态无覆盖)
259
+ - **出处**: 实测 2026-09-09 T3(36 条规则全来自有内容事故,空态无覆盖)
259
260
  - **契约**: 有 header 但零事件 = 异常空会话(新建即空或写入未落盘)。空态不在任何有内容规则的覆盖下,显式 warning 供人判断。
260
261
 
261
262
  ### P3 — tool/call ↔ tool/result 配对完整性(考古 B1)
262
263
 
263
264
  - **层级**: plugin | **严重度**: warning
264
- - **出处**: dsh-会话日志考古-插件任务与方法.md §2/§4.2(callId 配对,不可用"上一个 call"推断)
265
+ - **出处**: 考古方法 §2/§4.2(callId 配对,不可用"上一个 call"推断)
265
266
  - **契约**: 每个 tool/call 的 data.callId 必须能在 tool/result 的 data.message.source.callId 中找到配对;孤儿 call(无 result)告警——中断/失败轮次可能产生孤儿(合法但要审计),考古提取将缺该输出。
266
267
 
267
268
  ### P4 — tool/result 输出结构可解析(考古 B2)
268
269
 
269
270
  - **层级**: plugin | **严重度**: warning
270
- - **出处**: dsh-会话日志考古-插件任务与方法.md §2/§4.2(content 递归 text 结构)
271
+ - **出处**: 考古方法 §2/§4.2(content 递归 text 结构)
271
272
  - **契约**: tool/result 的 data.message.content 必须可递归解析(list[dict{type:text,text}] 或等价);不可解析片段 = 考古提取将漏数据。空 content(失败/无输出)合法。
272
273
 
273
274
  ### M1 — turn/step 为 null 的 assistant/message 只能 replace,不能 append
274
275
 
275
276
  - **层级**: engine | **严重度**: error
276
- - **出处**: 复盘事故第 2 轮(rt.js:6816 崩溃);实证 data.turn/data.step:正常消息为数字、插件 marker 为 null
277
+ - **出处**: 复盘事故第 2 次尝试(rt.js:6816 崩溃);实证 data.turn/data.step:正常消息为数字、插件 marker 为 null
277
278
  - **契约**: data.turn/data.step 为 null 的 assistant/message(如插件 marker)只能以 replace 承载(走插件 marker 定义);作为 append 会落进核心 assistant-step 定义,因 turn=null 发布 location data 导致客户端引擎崩溃。
278
279
 
279
280
  ### P1 — marker id 前缀必须被识别
@@ -291,19 +292,19 @@
291
292
  ### C1 — seq 缺口/倒退提示多写入者
292
293
 
293
294
  - **层级**: concurrency | **严重度**: warning
294
- - **出处**: 审计 N6:dsh-session-persistence-jsonl appendLines 无锁(:1200-1227),全仓无会话级排他锁
295
+ - **出处**: dsh-session-persistence-jsonl appendLines 无锁(:1200-1227),全仓无会话级排他锁
295
296
  - **契约**: 离线体检无法直接观测跨进程竞态,但 E2 暴露的缺口/倒退即是"≥2 个 Host 进程共享同一 session 目录并发写"的后果。单实例部署不触发。
296
297
 
297
298
  ### Z1 — zstd 尾帧撕裂
298
299
 
299
300
  - **层级**: framing | **严重度**: warning
300
- - **出处**: 审计 N5 相关;帧扫描方法论
301
+ - **出处**: zstd 帧布局(dsh-session-persistence-jsonl 多帧)
301
302
  - **契约**: 尾帧不完整(torn):可能正在写入(in-flight)或文件被截断。若这是唯一异常,通常可等待写入完成;若持续存在则是截断证据。
302
303
 
303
304
  ### Z2 — zstd 帧解码失败 = 单帧全损
304
305
 
305
306
  - **层级**: framing | **严重度**: error
306
- - **出处**: 审计 N5:多帧单帧全损 → 整会话不可读
307
+ - **出处**: 多帧单帧全损 → 整会话不可读
307
308
  - **契约**: 任一帧解码失败(磁盘 bitrot / 传输截断 / 并发写撕裂)即整会话不可读;帧越多,单帧损坏下丢失概率线性上升。
308
309
 
309
310
  ### W1 — wire 流:tool 消息必须跟在带 tool-call 的 assistant 消息之后
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * dsh-log-contract · lib/archaeology.js
3
3
  *
4
- * 会话日志考古(任务书 dsh-会话日志考古-插件任务与方法.md)——两插件共享:
4
+ * 会话日志考古 —— 两插件共享:
5
5
  * retrace 的考古界面/导出(A1-A4)与 log-contract 的 extract/audit-report
6
6
  * (B3/B4)都消费这里的纯函数。**只读不写**(纪律 §8.1)。
7
7
  *
@@ -100,7 +100,7 @@ export function indexToolCalls(events) {
100
100
  *
101
101
  * @param events - 会话事件数组。
102
102
  * @param pattern - 命令正则(字符串或 RegExp;字符串按子串匹配,空则全量)。
103
- * @param opts.minSize - 输出最小字节数过滤(默认 0;任务书用 50 过滤噪声)。
103
+ * @param opts.minSize - 输出最小字节数过滤(默认 0;CLI 默认用 50 过滤噪声)。
104
104
  * @returns {{ pairs: Array<{ callId, command, text, size }>, matched: number, total: number }}
105
105
  */
106
106
  export function extractToolOutputs(events, pattern = '', { minSize = 0 } = {}) {
package/lib/checks.js CHANGED
@@ -70,10 +70,10 @@ export function envelopeViolations(event, loc, version) {
70
70
  if (event.type === 'request/header' && event.data?.reason === 'fallback') {
71
71
  out.push(violation('E5', loc, 'request/header 使用已删除的遗留 reason "fallback"'));
72
72
  }
73
- // ── R-G(2026-09-14 独立复核:3 例确证漏检补成候选规则)────────────────────
73
+ // ── 2026-09-14 实测:3 例确证漏检补成候选规则 ───────────────────────
74
74
  // E8 信封键白名单:宿主 `assertSessionEventEnvelope`(@deepseek-ai/dsh-session@0.1.5-rc.1
75
75
  // lib/index.js:852-861)逐键 switch,只认 7 个键,其余一律 `invalid event envelope`。
76
- // 实测变异 05:宿主拒、旧契约 0 违规。
76
+ // 实测反例:宿主拒、旧契约 0 违规。
77
77
  if (typeof event === 'object' && event !== null) {
78
78
  const extra = Object.keys(event).filter((k) => !ENVELOPE_ALLOWED_KEYS.has(k));
79
79
  if (extra.length > 0) {
@@ -82,7 +82,7 @@ export function envelopeViolations(event, loc, version) {
82
82
  }
83
83
  // E10 request/header 的 data 字段约束:宿主 `validateSessionEventData`
84
84
  // (dsh-session@0.1.5-rc.1 lib/index.js:231-248)——`header.system` 必须省略、
85
- // 空 tools / 空 adapterDefaults 必须省略。实测变异 12:宿主拒、旧契约 0 违规。
85
+ // 空 tools / 空 adapterDefaults 必须省略。实测反例:宿主拒、旧契约 0 违规。
86
86
  if (event.type === 'request/header') {
87
87
  const header = event.data?.header;
88
88
  if (typeof header !== 'object' || header === null || Array.isArray(header)) {
@@ -106,9 +106,9 @@ export const ENVELOPE_ALLOWED_KEYS = new Set(['type', 'seq', 'time', 'data', 'su
106
106
  /** 镜像官方 assertMessageEventShape(lib/index.js:1242-1266)。 */
107
107
  export function messageShapeViolations(event, loc) {
108
108
  const type = event.type;
109
- // R-G:把 v3 新增的 `system/message` 纳入形状检查(MESSAGE_ROLE_BY_TYPE:system/message → system,
109
+ // v3 新增的 `system/message` 纳入形状检查(MESSAGE_ROLE_BY_TYPE:system/message → system,
110
110
  // dsh-session@0.1.5-rc.1 lib/index.js:917-926;source 要求 :942-944)。旧实现只查 user/assistant/tool
111
- // ⇒ 实测变异 03(system/message 的 source.kind='user')宿主拒、旧契约 0 违规。
111
+ // ⇒ 实测反例(system/message 的 source.kind='user')宿主拒、旧契约 0 违规。
112
112
  if (type !== 'user/message' && type !== 'assistant/message' && type !== 'tool/result' && type !== 'system/message') return [];
113
113
  const out = [];
114
114
  const data = event.data;
@@ -139,7 +139,7 @@ export function messageShapeViolations(event, loc) {
139
139
  }
140
140
  }
141
141
  if (type === 'system/message') {
142
- // E9(R-G 候选规则):system/message 必须 plugin source(kind==='plugin' + plugin 非空)。
142
+ // E9(候选规则):system/message 必须 plugin source(kind==='plugin' + plugin 非空)。
143
143
  // 宿主依据:dsh-session@0.1.5-rc.1 lib/index.js:942-944("must have plugin source")。
144
144
  if (source?.kind !== 'plugin' || typeof source.plugin !== 'string' || source.plugin === '') {
145
145
  out.push(violation('E9', loc, `${shape()}:system/message 必须带 plugin source(kind==='plugin' 且 plugin 非空)——宿主 lib/index.js:942-944`));
@@ -241,13 +241,13 @@ export function replaySurface(events, version) {
241
241
 
242
242
  if (!eligible) {
243
243
  if (op !== undefined || src !== undefined) {
244
- // ── R-B(2026-09-14 独立复核,误报 S2)────────────────────────────────
244
+ // ── 2026-09-14 实测:这里的 S2 是误报 ───────────────────────────────
245
245
  // 宿主 `@deepseek-ai/dsh-session@0.1.5-rc.1` 是**刻意容忍**的:
246
246
  // lib/index.js:270 `if (!KNOWN_SESSION_EVENT_TYPES.has(event.type) && event.ignorable === true) return;`
247
247
  // 同一函数的契约注释 lib/index.js:305 —— "Unknown ignorable records retain opaque
248
248
  // metadata and never change the surface."
249
249
  // ⇒ **未知**类型且 `ignorable===true` 的事件带 surfaceOp/sourceEventSeqs 是合法的不透明
250
- // 元数据(宿主收;实测变异 06:宿主收、旧契约 S2/error)。这里不再报 S2。
250
+ // 元数据(宿主收;实测反例:宿主收、旧契约 S2/error)。这里不再报 S2。
251
251
  // "该未知类型有没有消费者"由 E7 以 **warning** 表达(策略层,非宿主契约)。
252
252
  const unknownIgnorable = !currentVocabulary(version).has(event.type) && event.ignorable === true;
253
253
  if (!unknownIgnorable) {
@@ -343,7 +343,7 @@ export function engineViolations(event, loc) {
343
343
  const out = [];
344
344
  if (event.type === 'assistant/message' && event.surfaceOp === 'append') {
345
345
  // turn/step 位于 event.data 层(实证:正常消息 data.turn/data.step 为数字,
346
- // 插件 marker data.turn/data.step 为 null —— 复盘事故第 2 轮)
346
+ // 插件 marker data.turn/data.step 为 null —— 复盘事故第 2 次尝试)
347
347
  const turn = event.data?.turn;
348
348
  const step = event.data?.step;
349
349
  if (turn == null || step == null) {
@@ -370,7 +370,7 @@ export function pluginViolations(event, loc) {
370
370
  return out;
371
371
  }
372
372
 
373
- /** 折叠终验:**按文件版本**择路(1.3 第二半 + 2026-09-14 复核补强)。
373
+ /** 折叠终验:**按文件版本**择路(2026-09-14 补强)。
374
374
  *
375
375
  * - v3 文件 → 运行时导出的官方 `foldSurface`(与运行时同语义);
376
376
  * - v0/v1/v2 文件 → 本地 `legacyFoldSurface`(rc.7 `foldSurface` 逐条移植)。
@@ -480,7 +480,7 @@ export function turnEndReasonViolations(events) {
480
480
  }
481
481
 
482
482
  /**
483
- * E7 —— ignorable 未知 type 合法性(2026-09-09 反向挑刺 T2 增量)。
483
+ * E7 —— ignorable 未知 type 合法性(2026-09-09 T2 增量)。
484
484
  *
485
485
  * 盲区:E3 对"未知 type + ignorable:true"直接放行(容忍更新版本 harness 写入),
486
486
  * 但 ignorable 标记无合法性校验 = 后门——被读路径接纳却无人消费的未知事件 =
@@ -509,7 +509,7 @@ export function ignorableTypeViolations(events, version) {
509
509
  }
510
510
 
511
511
  /**
512
- * P3 —— tool/call ↔ tool/result 配对完整性(考古任务书 B1)。
512
+ * P3 —— tool/call ↔ tool/result 配对完整性(考古 B1)。
513
513
  * 每个 tool/call 的 `data.callId` 必须能在 tool/result 的
514
514
  * `data.message.source.callId` 中找到配对;孤儿 call(无 result)告警——
515
515
  * 中断/失败轮次可能产生孤儿(合法但要审计)。warning 级:不破坏日志。
@@ -517,7 +517,7 @@ export function ignorableTypeViolations(events, version) {
517
517
  export function toolPairingViolations(events) {
518
518
  const out = [];
519
519
  const calls = new Map(); // callId → { command, loc }
520
- const results = new Map(); // callId → loc(双向:孤儿 result 也要指认,2026-09-09 反向挑刺 T1)
520
+ const results = new Map(); // callId → loc(双向:孤儿 result 也要指认,2026-09-09 T1)
521
521
  for (const { event, lineNo } of events) {
522
522
  if (event.type === 'tool/call') {
523
523
  const callId = event.data?.callId;
@@ -539,7 +539,7 @@ export function toolPairingViolations(events) {
539
539
  out.push(violation('P3', loc, `tool/call ${callId}(命令 ${command || '(未知)'})没有配对的 tool/result——孤儿调用(中断/失败未落结果),考古提取将缺该输出`));
540
540
  }
541
541
  }
542
- // 双向(反向挑刺 T1 增量):孤儿 result = result 无对应 tool/call。
542
+ // 双向(T1 增量):孤儿 result = result 无对应 tool/call。
543
543
  // 折叠后 wire 流中无主 tool 消息 = provider 拒绝风险(W1/W2 同族、不同层);
544
544
  // 与孤儿 call 同为 warning 级——合法场景(中断/修复产物)不破坏日志。
545
545
  for (const [callId, loc] of results) {
@@ -579,7 +579,7 @@ function findUnparsableContent(node, path) {
579
579
  }
580
580
 
581
581
  /**
582
- * P4 —— tool/result 输出结构契约(考古任务书 B2)。
582
+ * P4 —— tool/result 输出结构契约(考古 B2)。
583
583
  * `data.message.content` 必须可递归解析(list[dict{type:text,text}] 或等价);
584
584
  * 不可解析片段 = 考古提取将漏数据。空 content(失败/无输出)合法。warning 级。
585
585
  */
@@ -847,10 +847,10 @@ export function wireViolations(events, version) {
847
847
  * `turn/start`(闭合/预期轮)、继承切点、`assistant/attempt` 配对、`stored log corrupt`、
848
848
  * v0→v1 对其余事件的形状拒绝。
849
849
  *
850
- * **为什么 `turn/start` 不做(第五轮实测结论)**:官方那个状态机(`v0-to-v1:2270` 的
850
+ * **为什么 `turn/start` 不做(实测结论)**:官方那个状态机(`v0-to-v1:2270` 的
851
851
  * `assertReleasedArtifactRelationships`)**不是**在原始 v0 事件上跑的——它由 **v1→v2**
852
852
  * 以 `RELEASED_V2_RELATIONSHIP_EXTENSIONS` 调用在**变换后的 v1/v2 artifact** 上
853
- * (`v1-to-v2/lib/index.js:104`),并带 `cut`(继承切点)处理。第五轮实测:在原始 v0 上照抄该
853
+ * (`v1-to-v2/lib/index.js:104`),并带 `cut`(继承切点)处理。实测:在原始 v0 上照抄该
854
854
  * 状态机会在**已 seed 的会话**上狂报(样本(某真实会话):v0→v1 官方并不以该规则拒绝,
855
855
  * 而原始 v0 上会报 19 条),属"规则文本对、应用对象错"。要忠实复现必须先把 v0→v1→v2 的
856
856
  * 变换做出来 ⇒ 记未覆盖。
@@ -884,12 +884,12 @@ export function migrationPrecheckViolations(events, version) {
884
884
  // 判据:每个被引 seq 必须解析到更早的 `user/message`,且 `data.source.kind === 'user'`;
885
885
  // 另 `session/title` 的 `source.kind === 'user'` ⟺ `messageSeqs` 空。
886
886
  //
887
- // ⚠️ 应用面说明(第五轮实测,2026-09-15):官方 `assertTitleSources` 与 `turn/start` 状态机
887
+ // ⚠️ 应用面说明(实测 2026-09-15):官方 `assertTitleSources` 与 `turn/start` 状态机
888
888
  // 同在 `assertReleasedArtifactRelationships` 里,而该函数被 **v1→v2** 以
889
889
  // `RELEASED_V2_RELATIONSHIP_EXTENSIONS` 调用在**变换后的 v1/v2 artifact** 上
890
890
  // (`dsh-session-format-v1-to-v2/lib/index.js:104`),不是原始 v0 事件。
891
891
  // 对 `messageSeqs` 这类"按 seq 索引 + 事件类型/source 判定"的引用,变换保序保类型
892
- // ⇒ 在原始 v0 上判是**必要条件的近似**;实测 281 个真实 v0 上 **0 误报**(round5/rule-probe)。
892
+ // ⇒ 在原始 v0 上判是**必要条件的近似**;实测 281 个真实 v0 上 **0 误报**。
893
893
  const bySeq = new Map();
894
894
  for (const { event } of events) if (event && typeof event === 'object') bySeq.set(event.seq, event);
895
895
  for (const { event, lineNo } of events) {
package/lib/contracts.js CHANGED
@@ -4,8 +4,7 @@
4
4
  * DSH 会话日志契约规则目录(spec)。
5
5
  *
6
6
  * 规则集来源:
7
- * - `dsh-scale-audit-疑点记录.md`(59 条契约发现 / F1–F7 / N1–N6 / R1–R3)
8
- * - `复盘-会话修复事故-20260825.md`(三层契约:持久化 / 客户端引擎 / 插件语义)
7
+ * - 历史会话日志的实测事故与契约缺口(持久化 / 客户端引擎 / 插件语义三层)
9
8
  * - `@deepseek-ai/dsh-session@0.1.0-rc.7` 官方源码逐行核对(见每条 `source`)
10
9
  *
11
10
  * 每条规则只描述"契约是什么";具体判定逻辑在 `lib/validate.js`(离线体检)
@@ -39,25 +38,25 @@ export const SEVERITY = { ERROR: 'error', WARNING: 'warning', INFO: 'info' };
39
38
  * - G 迁移预检(migration gate,**独立维度**:官方迁移会不会拒;不进 ok/verdict)
40
39
  */
41
40
  /**
42
- * R-F/R-D(2026-09-14 独立复核)——**机读的漂移/未复核标注**。
41
+ * (2026-09-14 实测)——**机读的漂移/未复核标注**。
43
42
  *
44
- * 复核结论:规则的 `source` 大量停在 `@deepseek-ai/dsh-session@0.1.0-rc.7` 的行号上,
43
+ * 核对结论:规则的 `source` 大量停在 `@deepseek-ai/dsh-session@0.1.0-rc.7` 的行号上,
45
44
  * 且其中 4 条(T2/P1/P2/C1)的**判定前提**在真宿主 0.1.5 上已不成立——此前**没有任何机制
46
45
  * 能发现**(`source` 只是人读字符串)。这里把它们变成机读字段,并由 `check` 抬头点名。
47
46
  */
48
47
  export const SOURCE_DRIFT = Object.freeze({
49
- /** 出处停在 rc.7 行号、未在 0.1.5 上复核(复核报告 §1 已给清单)。 */
48
+ /** 出处停在 rc.7 行号、未在 0.1.5 上复核(清单见下)。 */
50
49
  drifted: Object.freeze(['T2', 'P1', 'P2', 'C1', 'I1', 'G3', 'R2', 'R3', 'E5', 'E6']),
51
50
  /**
52
- * **判定前提在 0.1.5 上不成立/无法判定**(复核 §1):
51
+ * **判定前提在 0.1.5 上不成立/无法判定**:
53
52
  * T2 token-meter 已改从 `event.data.stream` 重建(无 sourceEventSeqs/无 belongs to another step);
54
53
  * P1/P2 载体已换成 `user/message + data.id`(无 data.editor);C1 前提"无会话级排他锁"被
55
54
  * `session.lock` flock 租约证伪;T3/T4 半步需真机实验。⇒ 这些规则的结论**不可单独采信**。
56
55
  */
57
56
  premiseStale: Object.freeze(['T2', 'P1', 'P2', 'C1']),
58
- /** 复核明确"无法判定"、需要真机实验的条目(列出来是为了不假装覆盖)。 */
57
+ /** 明确"无法判定"、需要真机实验的条目(列出来是为了不假装覆盖)。 */
59
58
  undecidable: Object.freeze(['T3', 'T4']),
60
- note: '出处/前提漂移是**静默失真**:R-D 行为探针(lib/host-probes.js)负责机器可判的那部分,'
59
+ note: '出处/前提漂移是**静默失真**:行为探针(lib/host-probes.js)负责机器可判的那部分,'
61
60
  + '其余在此显式列出,结论抬头必须带漂移清单与未复核清单。',
62
61
  });
63
62
 
@@ -86,7 +85,7 @@ export const CONTRACT_RULES = [
86
85
  title: '每行必须是合法 JSON',
87
86
  layer: LAYER.PERSISTENCE,
88
87
  severity: SEVERITY.ERROR,
89
- source: '审计方法论(scan-seq-gaps.mjs);dsh-session-persistence-jsonl 读路径',
88
+ source: 'seq 缺口扫描;dsh-session-persistence-jsonl 读路径',
90
89
  description: '非空行无法 JSON.parse = 损坏行。帧边界产生的空行是合法的(跳过)。',
91
90
  },
92
91
  {
@@ -122,7 +121,7 @@ export const CONTRACT_RULES = [
122
121
  title: 'seq 严格连续(单写入者假设)',
123
122
  layer: LAYER.PERSISTENCE,
124
123
  severity: SEVERITY.ERROR,
125
- source: '@deepseek-ai/dsh-session lib/index.js:398 (planSurfaceEvent "not contiguous");审计 S2/N6',
124
+ source: '@deepseek-ai/dsh-session lib/index.js:398 (planSurfaceEvent "not contiguous")',
126
125
  description: 'seq 必须从 0(或窗口 baseSeq)严格连续递增。缺口/倒退 = 违反单写入者假设(多实例共享存储并发写的痕迹),加载时直接 throw。',
127
126
  },
128
127
  {
@@ -165,7 +164,7 @@ export const CONTRACT_RULES = [
165
164
  layer: LAYER.PERSISTENCE,
166
165
  severity: SEVERITY.ERROR,
167
166
  source: '@deepseek-ai/dsh-session lib/index.js:1273-1277 (assertSupportedRequestHeader)',
168
- description: 'request/header-delta 与 reason=fallback 的 request/header 是已删除的遗留格式。注意(R-F 订正复核 §1 E5):宿主 Session.append/appendLines 不看 type ⇒ 写入会成功、下一次读取才炸(依据 dsh-session@0.1.5-rc.1 lib/index.js:1170-1210 / persistence-jsonl:3046-3073),不是写入即被拒。',
167
+ description: 'request/header-delta 与 reason=fallback 的 request/header 是已删除的遗留格式。注意(订正 E5):宿主 Session.append/appendLines 不看 type ⇒ 写入会成功、下一次读取才炸(依据 dsh-session@0.1.5-rc.1 lib/index.js:1170-1210 / persistence-jsonl:3046-3073),不是写入即被拒。',
169
168
  },
170
169
  {
171
170
  id: 'E6',
@@ -215,8 +214,8 @@ export const CONTRACT_RULES = [
215
214
  title: 'replace 的 sourceEventSeqs 必须完整覆盖被替换节点',
216
215
  layer: LAYER.PERSISTENCE,
217
216
  severity: SEVERITY.ERROR,
218
- source: '@deepseek-ai/dsh-session lib/index.js:335-336 (assertProvenance);复盘事故第 1 ',
219
- description: '★ 写前校验核心规则:sourceEventSeqs 必须包含每一个被替换(shadowed)的 surface 节点,缺一个 = 会话加载被拒(SessionPersistenceCorruptionError)。2026-08-25 事故第 1 轮(清空 sourceEventSeqs)正是违反此规则。',
217
+ source: '@deepseek-ai/dsh-session lib/index.js:335-336 (assertProvenance);复盘事故第 1 次尝试',
218
+ description: '★ 写前校验核心规则:sourceEventSeqs 必须包含每一个被替换(shadowed)的 surface 节点,缺一个 = 会话加载被拒(SessionPersistenceCorruptionError)。2026-08-25 事故第 1 次尝试(清空 sourceEventSeqs)正是违反此规则。',
220
219
  },
221
220
  {
222
221
  id: 'S6',
@@ -290,20 +289,20 @@ export const CONTRACT_RULES = [
290
289
  title: 'ignorable 未知 type 合法性(带被忽略标记的未知事件须有消费者)',
291
290
  layer: LAYER.PERSISTENCE,
292
291
  severity: SEVERITY.WARNING,
293
- source: '反向挑刺 2026-09-09 T2(E3 ignorable 无合法性校验 = 后门)',
292
+ source: '实测 2026-09-09 T2(E3 ignorable 无合法性校验 = 后门)',
294
293
  description: '未知 type + ignorable:true 被读路径接纳但无人消费 = 静默垃圾。排除已知消费者白名单(retrace/marker、retrace/goal-marker、message-editor/ 前缀等 retrace 客户端消费的插件 marker)后,其余 ignorable 未知事件报 warning。',
295
294
  },
296
295
  {
297
296
  id: 'E8',
298
297
  title: '事件信封键白名单(多余键:seed/restore 路径会拒)',
299
298
  layer: LAYER.PERSISTENCE,
300
- // R-G 候选规则 + R-D 探针订正:宿主 `assertSessionEventEnvelope`(dsh-session@0.1.5-rc.1
299
+ // 候选规则 + 探针订正:宿主 `assertSessionEventEnvelope`(dsh-session@0.1.5-rc.1
301
300
  // lib/index.js:849-861)确实拒多余键,但其**唯一调用点是 Session 构造器的 seed 路径**
302
301
  // (:1063-1068);JSONL **load 路径**(`adoptSessionEvent`)实测**容忍**(探针 p5 钉住)。
303
302
  // 故本规则按 **warning** 报(不影响"可加载",但该日志作为 seed/restore 输入会被拒)。
304
303
  severity: SEVERITY.WARNING,
305
304
  source: '@deepseek-ai/dsh-session@0.1.5-rc.1 lib/index.js:849-861(assertSessionEventEnvelope)+ :1063-1068(唯一调用点=seed 路径);load 路径容忍见行为探针 p5',
306
- description: '事件对象只允许 7 个信封键(type/seq/time/data/surfaceOp/sourceEventSeqs/ignorable)。独立复核变异 05 指出"宿主拒、旧契约 0 违规";R-D 行为探针进一步订正口径:**load 路径容忍、seed/restore 路径拒** ⇒ warning。',
305
+ description: '事件对象只允许 7 个信封键(type/seq/time/data/surfaceOp/sourceEventSeqs/ignorable)。实测反例指出"宿主拒、旧契约 0 违规";行为探针进一步订正口径:**load 路径容忍、seed/restore 路径拒** ⇒ warning。',
307
306
  candidate: true,
308
307
  },
309
308
  {
@@ -312,7 +311,7 @@ export const CONTRACT_RULES = [
312
311
  layer: LAYER.PERSISTENCE,
313
312
  severity: SEVERITY.ERROR,
314
313
  source: '@deepseek-ai/dsh-session@0.1.5-rc.1 lib/index.js:942-944("must have plugin source");角色表 :917-926',
315
- description: 'v3 新增的 system/message:role 必须为 system,source.kind 必须为 plugin 且 plugin 非空。实测(变异 03):source.kind=\'user\' 宿主拒、旧契约 0 违规 ⇒ 漏检。',
314
+ description: 'v3 新增的 system/message:role 必须为 system,source.kind 必须为 plugin 且 plugin 非空。实测(反例):source.kind=\'user\' 宿主拒、旧契约 0 违规 ⇒ 漏检。',
316
315
  candidate: true,
317
316
  },
318
317
  {
@@ -321,7 +320,7 @@ export const CONTRACT_RULES = [
321
320
  layer: LAYER.PERSISTENCE,
322
321
  severity: SEVERITY.ERROR,
323
322
  source: '@deepseek-ai/dsh-session@0.1.5-rc.1 lib/index.js:231-248(validateSessionEventData:omit header.system / omit empty tools / omit empty adapterDefaults)',
324
- description: 'request/header 必须省略 header.system(系统提示改走 system/message)、空 tools、空 adapterDefaults。实测(变异 12):带 header.system 的写入宿主拒、旧契约 0 违规 ⇒ 漏检。',
323
+ description: 'request/header 必须省略 header.system(系统提示改走 system/message)、空 tools、空 adapterDefaults。实测(反例):带 header.system 的写入宿主拒、旧契约 0 违规 ⇒ 漏检。',
325
324
  candidate: true,
326
325
  },
327
326
  {
@@ -329,7 +328,7 @@ export const CONTRACT_RULES = [
329
328
  title: '空会话文件(有 header 无事件)显式报出',
330
329
  layer: LAYER.FRAMING,
331
330
  severity: SEVERITY.WARNING,
332
- source: '反向挑刺 2026-09-09 T3(36 条规则全来自有内容事故,空态无覆盖)',
331
+ source: '实测 2026-09-09 T3(36 条规则全来自有内容事故,空态无覆盖)',
333
332
  description: '有 header 但零事件 = 异常空会话(新建即空或写入未落盘)。空态不在任何有内容规则的覆盖下,显式 warning 供人判断。',
334
333
  },
335
334
  {
@@ -337,7 +336,7 @@ export const CONTRACT_RULES = [
337
336
  title: 'tool/call ↔ tool/result 配对完整性(考古 B1)',
338
337
  layer: LAYER.PLUGIN,
339
338
  severity: SEVERITY.WARNING,
340
- source: 'dsh-会话日志考古-插件任务与方法.md §2/§4.2(callId 配对,不可用"上一个 call"推断)',
339
+ source: '考古方法 §2/§4.2(callId 配对,不可用"上一个 call"推断)',
341
340
  description: '每个 tool/call 的 data.callId 必须能在 tool/result 的 data.message.source.callId 中找到配对;孤儿 call(无 result)告警——中断/失败轮次可能产生孤儿(合法但要审计),考古提取将缺该输出。',
342
341
  },
343
342
  {
@@ -345,7 +344,7 @@ export const CONTRACT_RULES = [
345
344
  title: 'tool/result 输出结构可解析(考古 B2)',
346
345
  layer: LAYER.PLUGIN,
347
346
  severity: SEVERITY.WARNING,
348
- source: 'dsh-会话日志考古-插件任务与方法.md §2/§4.2(content 递归 text 结构)',
347
+ source: '考古方法 §2/§4.2(content 递归 text 结构)',
349
348
  description: 'tool/result 的 data.message.content 必须可递归解析(list[dict{type:text,text}] 或等价);不可解析片段 = 考古提取将漏数据。空 content(失败/无输出)合法。',
350
349
  },
351
350
  // ── M · 客户端引擎层 ────────────────────────────────────────────────────
@@ -354,7 +353,7 @@ export const CONTRACT_RULES = [
354
353
  title: 'turn/step 为 null 的 assistant/message 只能 replace,不能 append',
355
354
  layer: LAYER.ENGINE,
356
355
  severity: SEVERITY.ERROR,
357
- source: '复盘事故第 2 轮(rt.js:6816 崩溃);实证 data.turn/data.step:正常消息为数字、插件 marker 为 null',
356
+ source: '复盘事故第 2 次尝试(rt.js:6816 崩溃);实证 data.turn/data.step:正常消息为数字、插件 marker 为 null',
358
357
  description: 'data.turn/data.step 为 null 的 assistant/message(如插件 marker)只能以 replace 承载(走插件 marker 定义);作为 append 会落进核心 assistant-step 定义,因 turn=null 发布 location data 导致客户端引擎崩溃。',
359
358
  },
360
359
 
@@ -388,7 +387,7 @@ export const CONTRACT_RULES = [
388
387
  title: 'seq 缺口/倒退提示多写入者',
389
388
  layer: LAYER.CONCURRENCY,
390
389
  severity: SEVERITY.WARNING,
391
- source: '审计 N6:dsh-session-persistence-jsonl appendLines 无锁(:1200-1227),全仓无会话级排他锁',
390
+ source: 'dsh-session-persistence-jsonl appendLines 无锁(:1200-1227),全仓无会话级排他锁',
392
391
  description: '离线体检无法直接观测跨进程竞态,但 E2 暴露的缺口/倒退即是"≥2 个 Host 进程共享同一 session 目录并发写"的后果。单实例部署不触发。',
393
392
  },
394
393
 
@@ -398,7 +397,7 @@ export const CONTRACT_RULES = [
398
397
  title: 'zstd 尾帧撕裂',
399
398
  layer: LAYER.FRAMING,
400
399
  severity: SEVERITY.WARNING,
401
- source: '审计 N5 相关;帧扫描方法论',
400
+ source: 'zstd 帧布局(dsh-session-persistence-jsonl 多帧)',
402
401
  description: '尾帧不完整(torn):可能正在写入(in-flight)或文件被截断。若这是唯一异常,通常可等待写入完成;若持续存在则是截断证据。',
403
402
  },
404
403
  {
@@ -406,7 +405,7 @@ export const CONTRACT_RULES = [
406
405
  title: 'zstd 帧解码失败 = 单帧全损',
407
406
  layer: LAYER.FRAMING,
408
407
  severity: SEVERITY.ERROR,
409
- source: '审计 N5:多帧单帧全损 → 整会话不可读',
408
+ source: '多帧单帧全损 → 整会话不可读',
410
409
  description: '任一帧解码失败(磁盘 bitrot / 传输截断 / 并发写撕裂)即整会话不可读;帧越多,单帧损坏下丢失概率线性上升。',
411
410
  },
412
411
 
@@ -1,8 +1,8 @@
1
1
  /**
2
2
  * dsh-log-contract · lib/host-probes.js —— **行为探针**(漂移检测的正解)
3
- * (2026-09-14 独立复核 §4 + 裁定 R-D
3
+ * (2026-09-14)
4
4
  *
5
- * 为什么需要它(复核原话):本包"承重墙建错了地方"——**委托宿主运行时函数**的判据基本正确,
5
+ * 为什么需要它:本包"承重墙建错了地方"——**委托宿主运行时函数**的判据基本正确,
6
6
  * **作者手写镜像/自建模型**的判据成片出错,而 `hostPackageVersion()`/`hostCapability()`
7
7
  * **只打印不门禁**、规则的 `source` 是**不可机读的字符串** ⇒ 本次 5 类失准**没有任何机制能发现**。
8
8
  *
@@ -84,7 +84,7 @@ export const PROBES = [
84
84
  title: '信封多余键:load 路径(adoptSessionEvent)**容忍**,只在 seed 路径拒(E8 的口径)',
85
85
  rules: ['E8'],
86
86
  expect: 'adoptSessionEvent 不拒(宿主 load 路径容忍)',
87
- // 这条探针**修正了一条规则的过度声称**(R-D 的价值实证):
87
+ // 这条探针**修正了一条规则的过度声称**(探针的价值实证):
88
88
  // · 宿主 `assertSessionEventEnvelope`(dsh-session@0.1.5-rc.1 lib/index.js:849-861)确实拒多余键,
89
89
  // 但它的**唯一调用点**是 Session 构造器的 **seed 路径**(:1063-1068)——不是 JSONL load 路径;
90
90
  // · load 路径(`adoptSessionEvent`)实测**容忍**多余键。
package/lib/log-reader.js CHANGED
@@ -5,11 +5,10 @@
5
5
  * `decodeStorageRecord` 展开(chunk 行展开 / 损坏行报错)。
6
6
  *
7
7
  * 契约来源:
8
- * - 帧扫描/撕裂尾帧判定:复用本项目审计方法论(scan-seq-gaps.mjs),
9
- * 帧头布局对齐 zstd 规范(magic 0xFD2FB528、descriptor、block 头)。
8
+ * - 帧扫描/撕裂尾帧判定:按 zstd 规范逐帧扫(magic 0xFD2FB528、descriptor、block 头)。
10
9
  * - 行解码:本地兼容层 `./compat.js` 的 `decodeStorageRecord`(原官方导出于 0.1.5 移除)
11
10
  * (lib/index.js:1029,validateRow :922 / expandRow :973)。
12
- * - 损坏语义:R2 —— chunk 行损坏 = 整段 run 丢失且加载失败(dsh-session
11
+ * - 损坏语义:chunk 行损坏 = 整段 run 丢失且加载失败(dsh-session
13
12
  * lib/index.js:1022-1024 注释明示 fail-loud,无跳过逃生舱)。
14
13
  */
15
14
  import fs from 'node:fs';
@@ -78,7 +77,7 @@ export function scanZstdFrames(buf) {
78
77
  /** 把 zstd 多帧拼成完整明文;任一帧解码失败即抛错(N5 单帧全损语义)。 */
79
78
  export function decompressZstd(buf, { allowTorn = true } = {}) {
80
79
  const { frames, torn } = scanZstdFrames(buf);
81
- // ── R-B(2026-09-14 独立复核,误报 Z2)────────────────────────────────────
80
+ // ── 2026-09-14 实测:这里的 Z2 是误报 ───────────────────────────────────
82
81
  // 撕裂尾帧**不是**损坏:宿主 `dsh-session-persistence-jsonl@0.1.5-rc.1`
83
82
  // · `readZstdPrefix`(lib/index.js:2791-2849)——"Decode complete frames and retain
84
83
  // complete JSONL records from a torn final frame",恢复并返回 `tornTruncateTo`(:2847);
@@ -243,8 +242,8 @@ export function loadSessionLog(path) {
243
242
  *
244
243
  * 帧布局:帧 1 = header 行(单行 JSON,~几百字节),帧 2+ = 事件流。
245
244
  * 短码推导(工作区 createdAt 序号)需要扫全部会话但只需要 header——
246
- * 读文件前缀 64KiB 足够覆盖完整帧 1,避免全量读大文件(opena 工作区
247
- * ~108MiB 压缩)。失败返回 null(调用方降级)。
245
+ * 读文件前缀 64KiB 足够覆盖完整帧 1,避免全量读大文件(大会话压缩后可达
246
+ * 数百 MiB)。失败返回 null(调用方降级)。
248
247
  *
249
248
  * @param {string} path 会话日志文件路径(.jsonl.zstd 或明文 .jsonl)。
250
249
  * @returns {object|null} header 对象;无法解析返回 null。
package/lib/prewrite.js CHANGED
@@ -3,7 +3,7 @@
3
3
  *
4
4
  * ★ 写前校验(pre-write validation)——本工具的第一公民。
5
5
  *
6
- * 复盘事故(2026-08-25)第 1 轮失败就是"违约写入没被拦":surface-replace
6
+ * 复盘事故(2026-08-25)第 1 次尝试失败就是"违约写入没被拦":surface-replace
7
7
  * 的 `sourceEventSeqs` 被清空后写入,会话加载直接抛
8
8
  * `SessionPersistenceCorruptionError`。如果写入前先校验,会话根本不会被改坏。
9
9
  *
@@ -28,10 +28,10 @@ import { normalizeEventSeqRanges } from './compat.js';
28
28
  const LEGACY_MARKER_ID_PREFIXES = ['retrace', 'message-editor'];
29
29
 
30
30
  /**
31
- * **可识别的历史 retrace 载体**(R-C,2026-09-14 独立复核:白名单收窄)。
31
+ * **可识别的历史 retrace 载体**(2026-09-14 实测:白名单收窄)。
32
32
  *
33
33
  * 旧实现只判"任意 `data.editor` 存在" ⇒ **任何**写 `assistant/message` replace + `data.editor`
34
- * 的第三方插件都能领 T1 豁免(复核判定:"设计粗糙的豁口")。收窄为四条同时成立:
34
+ * 的第三方插件都能领 T1 豁免(实测判定:"设计粗糙的豁口")。收窄为四条同时成立:
35
35
  * ① 类型/操作:`assistant/message` + replace;
36
36
  * ② 载体标记:`data.editor !== undefined`;
37
37
  * ③ **身份**:`data.message.id` 带 retrace 历史 marker 前缀(`retrace-*` / `message-editor-*`);
@@ -66,9 +66,9 @@ function normalizeCandidate(candidate, nextSeq) {
66
66
  * **格式版本(C2)**:`formatVersion` > `header.version` > 事件形状推断 > 0,在**本次
67
67
  * `createPreWriter` 调用内固定**并显式传给每条按版本择路的判定。**不再读写模块级全局**
68
68
  * `fileVersion`——旧实现下同一进程"先 validate(v3) 再 prewrite(v0)"会把同一份合法 v0 输入
69
- * 的结论翻成 S4+S8(独立审核复现 B)。下游 `dsh-retrace` 正是只传 `events` 直接调用
69
+ * 的结论翻成 S4+S8(实测复现)。下游 `dsh-retrace` 正是只传 `events` 直接调用
70
70
  * (`lib/prewrite-guard.js:166`),所以缺省时必须能自行推断,不能把 v3 输入按 0 处理
71
- * (否则首次调用即 10 条 E3/S2/S8 误报,复现 C)。
71
+ * (否则首次调用即 10 条 E3/S2/S8 误报)。
72
72
  *
73
73
  * @param {{ events: Array<object>, baseSeq?: number, formatVersion?: number, header?: object|null }} input
74
74
  * 当前日志的已解码事件(按日志顺序;无 seq 字段的事件按位置补 seq,用于窗口校验)。
@@ -136,7 +136,7 @@ export function createPreWriter(input = {}) {
136
136
  // (check),不在这里重复拦截(否则历史 marker 会让后续编辑全部被拒)。
137
137
  const lastCandidate = candidateEvents[candidateEvents.length - 1];
138
138
  const legacyKind = legacyMarkerKindOf(lastCandidate);
139
- // R-C 版本门(2026-09-14 独立复核):降级**只对 ≤v2 文件**。
139
+ // 版本门(2026-09-14):降级**只对 ≤v2 文件**。
140
140
  // 依据:新载体(`user/message` + `data.id`,retrace 0.4.26)根本不带 `data.editor`,在 v3 上
141
141
  // 降级**救不回任何写入**(v3 禁 assistant/message 带 provenance ⇒ S8 兜住) —— 留在 v3 上
142
142
  // 只会掩盖 T1 的真实原因。v0/v1/v2 才是有价值的作用域(回放/重写历史形态 marker)。
@@ -144,7 +144,7 @@ export function createPreWriter(input = {}) {
144
144
  for (const t1 of tokenMeterViolations(candidateEvents.map((event) => ({ event })))) {
145
145
  if (t1.id !== 'T1' || t1.seq !== lastCandidate?.seq) continue;
146
146
  if (legacyKind && legacyDowngradeAllowed) {
147
- // 降级**可见**(R-C):违规里带 markerKind/id/targetSeq,并由结果字段 `legacyMarkerDebt`
147
+ // 降级**可见**:违规里带 markerKind/id/targetSeq,并由结果字段 `legacyMarkerDebt`
148
148
  // 显式带出;入口(CLI)打印"压缩前需一次性清理",不再"记了没人看"。
149
149
  violations.push({
150
150
  ...t1,
@@ -179,7 +179,7 @@ export function createPreWriter(input = {}) {
179
179
  }
180
180
  const bySeverity = { error: 0, warning: 0, info: 0 };
181
181
  for (const v of violations) bySeverity[v.severity] = (bySeverity[v.severity] ?? 0) + 1;
182
- // R-C「让降级可见」:把"本次写入沿用了历史 marker 形态(债)"作为**结构化字段**带出,
182
+ // 「让降级可见」:把"本次写入沿用了历史 marker 形态(债)"作为**结构化字段**带出,
183
183
  // 入口据此打印"压缩前需一次性清理"。降级不再只是 violations 里的一句 warning。
184
184
  const legacyDebt = legacyKind && legacyDowngradeAllowed && bySeverity.error === 0
185
185
  ? { kind: legacyKind.prefix, id: legacyKind.id, targetSeq: legacyKind.targetSeq, seq: lastCandidate?.seq ?? null, formatVersion }