dsh-log-contract 0.3.16 → 0.3.17
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/README.md +3 -3
- package/README.zh.md +3 -3
- package/docs/CONTRACTS.md +15 -1
- package/lib/contracts.js +16 -0
- package/lib/log-reader.js +13 -1
- package/lib/validate.js +10 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -344,11 +344,11 @@ MIT © OfferKuai Team
|
|
|
344
344
|
|
|
345
345
|
---
|
|
346
346
|
|
|
347
|
-
## ⚠️ Upgrading to 0.3.
|
|
347
|
+
## ⚠️ Upgrading to 0.3.17 — behaviour changes you must know (0.3.12 → 0.3.17)
|
|
348
348
|
|
|
349
|
-
> **0.3.
|
|
349
|
+
> **0.3.17 changes no behaviour.** It rewrites comments, test titles, a few diagnostic strings and the
|
|
350
350
|
> README so that they describe the product and its contract only. Every verdict, exit code and `--json`
|
|
351
|
-
> field is unchanged from 0.3.15, so everything below applies unchanged when going from 0.3.12 to 0.3.
|
|
351
|
+
> field is unchanged from 0.3.15, so everything below applies unchanged when going from 0.3.12 to 0.3.17.
|
|
352
352
|
>
|
|
353
353
|
> 0.3.15 was a documentation-only release as well.
|
|
354
354
|
|
package/README.zh.md
CHANGED
|
@@ -295,11 +295,11 @@ MIT © OfferKuai Team
|
|
|
295
295
|
|
|
296
296
|
---
|
|
297
297
|
|
|
298
|
-
## ⚠️ 升级到 0.3.
|
|
298
|
+
## ⚠️ 升级到 0.3.17 —— 必须知道的行为变更(0.3.12 → 0.3.17)
|
|
299
299
|
|
|
300
|
-
> **0.3.
|
|
300
|
+
> **0.3.17 不含行为变更。** 它只重写了注释、测试标题、少量诊断文案与 README,让它们
|
|
301
301
|
> 只描述产品与契约本身。所有判定结果、退出码与 `--json` 字段与 0.3.15 完全一致,
|
|
302
|
-
> 因此从 0.3.12 升到 0.3.
|
|
302
|
+
> 因此从 0.3.12 升到 0.3.17 时,下面各条与原来完全一致。
|
|
303
303
|
>
|
|
304
304
|
> 0.3.15 同样只是文档层面的发布。
|
|
305
305
|
|
package/docs/CONTRACTS.md
CHANGED
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
>
|
|
23
23
|
> 严重度:**error** = 违反即会话不可加载/写入被拒(fail-loud);**warning** = 合法但可疑。
|
|
24
24
|
|
|
25
|
-
## 规则索引(共
|
|
25
|
+
## 规则索引(共 46 条)
|
|
26
26
|
|
|
27
27
|
| id | 严重度 | 层级 | 规则 |
|
|
28
28
|
|---|---|---|---|
|
|
@@ -65,6 +65,8 @@
|
|
|
65
65
|
| C1 | warning | concurrency | seq 缺口/倒退提示多写入者 |
|
|
66
66
|
| Z1 | warning | framing | zstd 尾帧撕裂 |
|
|
67
67
|
| Z2 | error | framing | zstd 帧解码失败 = 单帧全损 |
|
|
68
|
+
| Z4 | error | framing | 首帧必须恰好一行 header |
|
|
69
|
+
| Z5 | error | framing | 多行日志不得压成单帧 |
|
|
68
70
|
| W1 | error | engine | wire 流:tool 消息必须跟在带 tool-call 的 assistant 消息之后 |
|
|
69
71
|
| W2 | error | engine | wire 流:user 文本不得插在 tool_calls 与其 tool 结果之间 |
|
|
70
72
|
| G1 | warning | migration | 迁移预检:v0 源文件的 subagent/descriptor.data.version 必须为 3 |
|
|
@@ -307,6 +309,18 @@
|
|
|
307
309
|
- **出处**: 多帧单帧全损 → 整会话不可读
|
|
308
310
|
- **契约**: 任一帧解码失败(磁盘 bitrot / 传输截断 / 并发写撕裂)即整会话不可读;帧越多,单帧损坏下丢失概率线性上升。
|
|
309
311
|
|
|
312
|
+
### Z4 — 首帧必须恰好一行 header
|
|
313
|
+
|
|
314
|
+
- **层级**: framing | **严重度**: error
|
|
315
|
+
- **出处**: 宿主 assertZstdHeaderFrame(0.1.5-rc.2 lib/index.js:2184):首帧非"恰好一行"即抛
|
|
316
|
+
- **契约**: 官方写盘形状是 header **独占第一帧**。首帧装多行时宿主抛错,且抛点在 listArtifacts **遍历全库**、无 per-session 容错 ⇒ **整个客户端起不来**(2026-09-16 真机事故:有人把整份日志重压成单帧写回)。
|
|
317
|
+
|
|
318
|
+
### Z5 — 多行日志不得压成单帧
|
|
319
|
+
|
|
320
|
+
- **层级**: framing | **严重度**: error
|
|
321
|
+
- **出处**: 官方形状:header 第一帧 + 事件批次逐帧追加
|
|
322
|
+
- **契约**: 把整份日志重压成 1 帧会同时触发 Z4;任何"重写会话"的路径都必须**保形**(逐帧解、逐帧回写,或走宿主写入器),不得整体重压缩。
|
|
323
|
+
|
|
310
324
|
### W1 — wire 流:tool 消息必须跟在带 tool-call 的 assistant 消息之后
|
|
311
325
|
|
|
312
326
|
- **层级**: engine | **严重度**: error
|
package/lib/contracts.js
CHANGED
|
@@ -408,6 +408,22 @@ export const CONTRACT_RULES = [
|
|
|
408
408
|
source: '多帧单帧全损 → 整会话不可读',
|
|
409
409
|
description: '任一帧解码失败(磁盘 bitrot / 传输截断 / 并发写撕裂)即整会话不可读;帧越多,单帧损坏下丢失概率线性上升。',
|
|
410
410
|
},
|
|
411
|
+
{
|
|
412
|
+
id: 'Z4',
|
|
413
|
+
title: '首帧必须恰好一行 header',
|
|
414
|
+
layer: LAYER.FRAMING,
|
|
415
|
+
severity: SEVERITY.ERROR,
|
|
416
|
+
source: '宿主 assertZstdHeaderFrame(0.1.5-rc.2 lib/index.js:2184):首帧非"恰好一行"即抛',
|
|
417
|
+
description: '官方写盘形状是 header **独占第一帧**。首帧装多行时宿主抛错,且抛点在 listArtifacts **遍历全库**、无 per-session 容错 ⇒ **整个客户端起不来**(2026-09-16 真机事故:有人把整份日志重压成单帧写回)。',
|
|
418
|
+
},
|
|
419
|
+
{
|
|
420
|
+
id: 'Z5',
|
|
421
|
+
title: '多行日志不得压成单帧',
|
|
422
|
+
layer: LAYER.FRAMING,
|
|
423
|
+
severity: SEVERITY.ERROR,
|
|
424
|
+
source: '官方形状:header 第一帧 + 事件批次逐帧追加',
|
|
425
|
+
description: '把整份日志重压成 1 帧会同时触发 Z4;任何"重写会话"的路径都必须**保形**(逐帧解、逐帧回写,或走宿主写入器),不得整体重压缩。',
|
|
426
|
+
},
|
|
411
427
|
|
|
412
428
|
// ── W · wire 消息流(模型请求序列)───────────────────────────────────────
|
|
413
429
|
{
|
package/lib/log-reader.js
CHANGED
|
@@ -173,7 +173,19 @@ export function loadSessionLog(path) {
|
|
|
173
173
|
plain = Buffer.alloc(0);
|
|
174
174
|
}
|
|
175
175
|
const { frames, torn } = scanZstdFrames(buf);
|
|
176
|
-
|
|
176
|
+
// 帧形状事实(Z4/Z5,2026-09-16 真机事故):官方写盘形状 = **header 独占第一帧** +
|
|
177
|
+
// 事件逐帧追加;读取端有硬断言 `assertZstdHeaderFrame`(首帧必须**恰好一行**)。
|
|
178
|
+
// 这里把该事实带出来给 validate 判:整文件被重压成单帧时,首帧装的是全部行 ⇒ 宿主必抛,
|
|
179
|
+
// 而抛点在 `listArtifacts` **遍历全库**、无 per-session 容错 ⇒ 整个客户端起不来。
|
|
180
|
+
let firstFrameOneLine = true, firstFrameLines = 1;
|
|
181
|
+
try {
|
|
182
|
+
const [s0, e0] = frames[0];
|
|
183
|
+
const first = zstdDecompressSync(buf.subarray(s0, e0)).toString('utf8');
|
|
184
|
+
const nl = first.indexOf('\n');
|
|
185
|
+
firstFrameOneLine = nl === first.length - 1 && first.length > 1;
|
|
186
|
+
firstFrameLines = first.split('\n').filter((x) => x !== '').length;
|
|
187
|
+
} catch { /* 首帧解码失败另有 Z2 报 */ }
|
|
188
|
+
frameInfo = { frames: frames.length, torn, compressedBytes: buf.length, plaintextBytes: plain.length, firstFrameOneLine, firstFrameLines };
|
|
177
189
|
} else {
|
|
178
190
|
plain = buf;
|
|
179
191
|
frameInfo = { frames: 0, torn: false, compressedBytes: 0, plaintextBytes: plain.length };
|
package/lib/validate.js
CHANGED
|
@@ -81,6 +81,16 @@ export function validateSessionLog(log, opts = {}) {
|
|
|
81
81
|
if (frameInfo?.error) {
|
|
82
82
|
violations.push(violation('Z2', { lineNo: null }, frameInfo.error));
|
|
83
83
|
}
|
|
84
|
+
// Z4 · 首帧必须恰好一行 header(2026-09-16 真机事故:整份日志被重压成单帧 ⇒ 客户端起不来)
|
|
85
|
+
if (frameInfo?.firstFrameOneLine === false) {
|
|
86
|
+
violations.push(violation('Z4', { lineNo: null },
|
|
87
|
+
`首帧不是"恰好一行 header"(实测 ${frameInfo.firstFrameLines ?? '?'} 行)——宿主 assertZstdHeaderFrame 必抛;抛点在 listArtifacts 遍历全库 ⇒ 整个客户端不可用`));
|
|
88
|
+
}
|
|
89
|
+
// Z5 · 多行日志不得压成单帧
|
|
90
|
+
if (Number.isSafeInteger(frameInfo?.frames) && frameInfo.frames === 1 && Array.isArray(rows) && rows.length > 1) {
|
|
91
|
+
violations.push(violation('Z5', { lineNo: null },
|
|
92
|
+
`整份日志被压成 1 帧(${rows.length} 行)——官方形状是 header 独占第一帧 + 事件逐帧追加;不要这样写回`));
|
|
93
|
+
}
|
|
84
94
|
|
|
85
95
|
// ── H · header ─────────────────────────────────────────────────────────
|
|
86
96
|
if (header === null) {
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-log-contract",
|
|
3
3
|
"description": "日志契约守护 — DSH session log contract guard: offline health check (CLI) + pre-write validation for DeepSeek Harness session logs",
|
|
4
|
-
"version": "0.3.
|
|
4
|
+
"version": "0.3.17",
|
|
5
5
|
"packageManager": "pnpm@11.7.0",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"main": "lib/index.js",
|