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/README.md +53 -39
- package/README.zh.md +35 -31
- package/bin/dsh-log-contract.mjs +17 -17
- package/docs/CONTRACTS.md +19 -18
- package/lib/archaeology.js +2 -2
- package/lib/checks.js +19 -19
- package/lib/contracts.js +24 -25
- package/lib/host-probes.js +3 -3
- package/lib/log-reader.js +5 -6
- package/lib/prewrite.js +8 -8
- package/lib/repair.js +13 -15
- package/lib/validate.js +15 -15
- package/lib/version-support.js +1 -1
- package/lib/vocab.js +4 -4
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -8,15 +8,14 @@ offline health check + pre-write validation. The business layer's **doctor**.
|
|
|
8
8
|
[](https://www.npmjs.com/package/dsh-log-contract)
|
|
9
9
|
[](https://www.npmjs.com/package/dsh-log-contract)
|
|
10
10
|
[](https://github.com/yamingmou/dsh-log-contract/blob/main/LICENSE)
|
|
11
|
-
[](https://github.com/topics/dsh-plugin)
|
|
12
12
|
[](https://github.com/yamingmou/dsh-log-contract/pulls)
|
|
13
13
|
|
|
14
14
|
**English** · [简体中文](./README.zh.md)
|
|
15
15
|
|
|
16
16
|
</div>
|
|
17
17
|
|
|
18
|
-
Formerly `log-contract-validator
|
|
19
|
-
per the OfferKuai three-piece plan.
|
|
18
|
+
Formerly `log-contract-validator`; now **`dsh-log-contract`**.
|
|
20
19
|
|
|
21
20
|
A fuse for DSH session logs (`*.jsonl` / `*.jsonl.zstd`): format drift that humans
|
|
22
21
|
cannot see but parsers crash on is caught and reported here. It does **not** judge
|
|
@@ -39,31 +38,33 @@ plugin marker semantics).
|
|
|
39
38
|
|
|
40
39
|
> **dsh-log-contract is the core capability component of [dsh-retrace](https://github.com/yamingmou/dsh-retrace)** (the business layer's "doctor" module): it **checks and repairs** session logs so every recall/edit/rollback lands on a legal log — pre-write validation (`prewrite`) rejects error-level violations **before** anything is written.
|
|
41
40
|
>
|
|
42
|
-
> **About `/compact` (stated honestly)**: what this tool guarantees is that **new writes** stop creating token-meter pairing debt — since two-segment atomic pairs (marker + paired segment) landed, new markers pass T1 by construction. **Legacy** (single-segment) markers are still **known design debt**: pre-write validation downgrades their T1 violation to a warning (`
|
|
41
|
+
> **About `/compact` (stated honestly)**: what this tool guarantees is that **new writes** stop creating token-meter pairing debt — since two-segment atomic pairs (marker + paired segment) landed, new markers pass T1 by construction. **Legacy** (single-segment) markers are still **known design debt**: pre-write validation downgrades their T1 violation to a warning (`legacyMarkerKindOf` in `lib/prewrite.js`, which applies to **historical markers only**), so such sessions need **one check + cleanup before compaction** (`check` to locate → `fix --remove-markers`; the plugin side calls this "the doctor"). Otherwise the T1 self-check blocks `/compact`.
|
|
43
42
|
|
|
44
43
|
| Layer | What it is | Components |
|
|
45
44
|
|---|---|---|
|
|
46
45
|
| **Agent business layer (production-grade)** | Framework-agnostic core: session hygiene / retraceability / auditability / recoverability | Four modules: governance / retrospect / archaeology / **doctor** |
|
|
47
46
|
| **dsh-retrace** | The business layer's DeepSeek Harness implementation | recall/edit/version/rollback/watchdog |
|
|
48
|
-
| **dsh-log-contract** | dsh-retrace's core component = the business layer's **doctor** (check & repair) | check / prewrite / fix / extract / audit |
|
|
47
|
+
| **dsh-log-contract** | dsh-retrace's core component = the business layer's **doctor** (check & repair) | check / prewrite / fix / extract / audit-report |
|
|
49
48
|
|
|
50
49
|
**Meaning**: dsh-log-contract is published standalone (for direct use or
|
|
51
50
|
re-implementation), but it is first the "check & repair" capability of dsh-retrace —
|
|
52
51
|
together they form the **Agent business layer (production-grade guarantees)** on
|
|
53
|
-
DSH (see the [dsh-retrace
|
|
52
|
+
DSH (see the [dsh-retrace repository](https://github.com/yamingmou/dsh-retrace)).
|
|
54
53
|
|
|
55
54
|
---
|
|
56
55
|
|
|
57
56
|
## Why it exists
|
|
58
57
|
|
|
59
|
-
|
|
58
|
+
**"one log, two consumers, two verdicts"**: one log is consumed by both
|
|
60
59
|
humans and automated programs. Humans tolerate format drift; programs depend on
|
|
61
60
|
strict contracts. Once the format drifts, humans see nothing wrong while programs
|
|
62
61
|
crash or misreport.
|
|
63
62
|
|
|
64
|
-
**Real incidents shaped
|
|
65
|
-
below.
|
|
66
|
-
must flag, and
|
|
63
|
+
**Real incidents shaped most of the rules here** — see the [Incident log](#-incident-log)
|
|
64
|
+
below. The incident-derived cases are pinned by regression tests: a corrupt log shape
|
|
65
|
+
this tool must flag, and the repaired shape it must pass. The remaining rules come from
|
|
66
|
+
line-by-line verification against the official Host source (each rule carries its
|
|
67
|
+
`source`).
|
|
67
68
|
|
|
68
69
|
---
|
|
69
70
|
|
|
@@ -87,21 +88,21 @@ must flag, and a fixed session that it must pass.
|
|
|
87
88
|
|
|
88
89
|
## ⚡ Incident log — why "production-grade" is not a slogan
|
|
89
90
|
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
omitted
|
|
91
|
+
Each entry below is a **real incident** from production sessions — the cases that
|
|
92
|
+
made us build this tool. Dates and shapes are real; session ids and file names are
|
|
93
|
+
omitted.
|
|
93
94
|
|
|
94
95
|
| # | Date | What happened | The rule / fix it produced |
|
|
95
96
|
|---|---|---|---|
|
|
96
97
|
| 1 | 2026-08-25 | A "restore hidden content" repair wrote a replace marker with **emptied `sourceEventSeqs`** → the session refused to load (`SessionPersistenceCorruptionError`); a second attempt changed the marker to **append** → the client engine crashed. Both were **violating writes that nothing caught**. | **S5** (sourceEventSeqs must cover shadowed nodes), **M1** (turn-null assistant/message can only be replace), pre-write validation |
|
|
97
98
|
| 2 | 2026-08-27~28 | Interrupted/restarted turns replayed with a **stale in-memory cursor**, re-appending old seqs to the file tail (tail regression, duplicate batches); two writers interleaved → **file-physical order non-monotonic** (`734056 → 733539 → 735470`). Sessions failed to load with `seq gap`. | **S9** (physical-order monotonic), fix `--tail-renumber` |
|
|
98
99
|
| 3 | 2026-08-27~28 | **Fork-boundary orphan splice**: the fork's "remove parent's pending prompt" splice assumed the parent's inbox; the child's seed-relative replay has an empty inbox → `resume failed: invalid persisted inbox splice`. | **I1** (inbox seed-relative replay), fix `--neutralize-orphan` |
|
|
99
|
-
| 4 | 2026-08-28 | An oversized session (**1,052,557 tokens** vs the 1M window) could neither continue nor `/compact`; the trim budget estimator underpriced CJK by ~3.7×. | T1 (token-meter pairing) for compactability, `fix --trim`
|
|
100
|
+
| 4 | 2026-08-28 | An oversized session (**1,052,557 tokens** vs the 1M window) could neither continue nor `/compact`; the trim budget estimator underpriced CJK by ~3.7×. | T1 (token-meter pairing) for compactability, `fix --trim-budget` guidance |
|
|
100
101
|
| 5 | 2026-08-29 | **W1/W2 wire violations**: markers shadowed an assistant with tool_calls but left the tool results dangling → strict endpoints (`INVALID_REQUEST`) reject the session's request stream. | **W1 / W2** (wire message flow) |
|
|
101
102
|
| 6 | 2026-08-30 | A single **turn-null marker** made the token-meter listener throw on **every** appended event (`consumedEvents` never advanced → full-prefix re-fold per event) → **30s / 10,008 log lines**, host event loop crushed, all sessions locked. Same session also had a **cross-step sourceEventSeqs** (steps 7/8/9 mixed in one assistant message) — offline checks were green, the live meter crashed. | **T1** (turn/step pairing), **T2** (cross-step source refs), `fix --neutralize`, `fix --clip-crossstep` |
|
|
102
103
|
|
|
103
|
-
> **Takeaway**:
|
|
104
|
-
>
|
|
104
|
+
> **Takeaway**: these rules are not synthetic theory — each one is pinned by a
|
|
105
|
+
> regression test that reproduces the corrupt shape it guards against. That is what
|
|
105
106
|
> "production-grade" means here.
|
|
106
107
|
|
|
107
108
|
---
|
|
@@ -112,7 +113,7 @@ omitted for privacy.
|
|
|
112
113
|
|
|
113
114
|
| Item | Baseline | Note |
|
|
114
115
|
|---|---|---|
|
|
115
|
-
| Host package | `@deepseek-ai/dsh-session@0.1.5-rc.1` | version
|
|
116
|
+
| Host package | `@deepseek-ai/dsh-session@0.1.5-rc.1` | the version this rule set is developed and CI-tested against (CI runs `pnpm check` + `pnpm test`; the four `prepublishOnly` steps are green locally on it) |
|
|
116
117
|
| Session format | **v3** (`SESSION_FORMAT_VERSION = 3`) | v3 uses the runtime vocabulary + official `foldSurface` final check |
|
|
117
118
|
| Declared range | `peerDependencies: ^0.1.0-rc.7 || ^0.1.5-rc.1` | installable — a declared range is **not** a per-version verification |
|
|
118
119
|
| Known formats | 0 / 1 / 2 / 3 | 0–2 are supported by this package's **vendored** vocabulary + local equivalent fold (`legacyFoldSurface`), independent of the host |
|
|
@@ -146,8 +147,8 @@ pnpm dlx dsh-log-contract --help
|
|
|
146
147
|
> repair primitives) comes with the plugin automatically. This package is published
|
|
147
148
|
> standalone for direct use or re-implementation.
|
|
148
149
|
>
|
|
149
|
-
> **Downloaded the repo as a ZIP?** `cd dsh-log-contract && npm install
|
|
150
|
-
>
|
|
150
|
+
> **Downloaded the repo as a ZIP?** `cd dsh-log-contract && npm install`, then
|
|
151
|
+
> `node bin/dsh-log-contract.mjs check <session-log>` — no build step and no global install needed.
|
|
151
152
|
|
|
152
153
|
Dependencies: Node ≥ 22 (`node:zlib` has built-in zstd), `@deepseek-ai/dsh-session`
|
|
153
154
|
(peer; validation/decode reuse the official implementation, so it stays in sync
|
|
@@ -177,7 +178,8 @@ Sample output (the CLI reports in Chinese — it is the tool's UI language):
|
|
|
177
178
|
❌ 未通过:见上方违规明细(error 级 = 会话不可读/不可写)
|
|
178
179
|
```
|
|
179
180
|
|
|
180
|
-
Exit code: 0 = pass (no error-level violations); 1 = error-level violations exist
|
|
181
|
+
Exit code: 0 = pass (no error-level violations); 1 = error-level violations exist;
|
|
182
|
+
3 = not assessable on this host; 4 = the migration pre-check is blocked (see the upgrade notes below).
|
|
181
183
|
|
|
182
184
|
`check` adds **W1/W2 wire-level checks** since 0.2.0: expand the model request
|
|
183
185
|
stream in surface order and catch "dangling tool messages" (a tool result with no
|
|
@@ -247,7 +249,7 @@ audit asset. Read-only archaeology:
|
|
|
247
249
|
|
|
248
250
|
```sh
|
|
249
251
|
# Export tool outputs matching a command regex (original text preserved)
|
|
250
|
-
dsh-log-contract extract <session-log> --pattern "
|
|
252
|
+
dsh-log-contract extract <session-log> --pattern "build-report" --min-size 50 --out ./found
|
|
251
253
|
|
|
252
254
|
# Archaeology audit report: call count / pairing rate / orphans / command distribution
|
|
253
255
|
dsh-log-contract audit-report <session-log>
|
|
@@ -289,29 +291,32 @@ if (!verdict.ok) {
|
|
|
289
291
|
## Tests
|
|
290
292
|
|
|
291
293
|
```bash
|
|
292
|
-
pnpm check && pnpm test # syntax check +
|
|
294
|
+
pnpm check && pnpm test # syntax check + contract-doc drift gate + all unit tests
|
|
293
295
|
```
|
|
294
296
|
|
|
295
297
|
- **Synthetic fixtures** (in-repo): legal session / seq gap / empty sourceEventSeqs
|
|
296
298
|
/ turn-null append / unknown type / bad chunk row / torn tail frame / unknown
|
|
297
299
|
marker prefix / self-shadowing etc.
|
|
298
|
-
- **Real fossils** (not in-repo, contain user data): run locally
|
|
299
300
|
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
301
|
+
> **Scope of the published package**: it contains the runtime code and documentation only.
|
|
302
|
+
> Local maintainer tooling is not part of the public repository, so `pnpm check` and `pnpm test`
|
|
303
|
+
> work from a clean clone.
|
|
303
304
|
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
305
|
+
Verdict by **log shape** — what the tool must say about each defect class, not about
|
|
306
|
+
any particular file:
|
|
307
|
+
|
|
308
|
+
| Log shape | Verdict | Rules that fire |
|
|
309
|
+
|---|---|---|
|
|
310
|
+
| seq gap, or non-monotonic file-physical order | FAIL | S8 / C1 / T1 / E2 (plus S9 when seq goes backwards) |
|
|
311
|
+
| a rewrite that introduces a gap | FAIL | S8 / C1 / T1 / E2 / I1 |
|
|
312
|
+
| invalid inbox splice + turn-null marker | FAIL | T1 / I1 |
|
|
313
|
+
| pre-fix shape: legacy turn-null markers still present | FAIL | T1 |
|
|
314
|
+
| a multi-thousand-span replace marker with full coverage | PASS | error 0 — data-legal, official `foldSurface` replays cleanly |
|
|
315
|
+
|
|
316
|
+
Rule evolution moves rows: 0.3.5 added I1, which flipped the "invalid inbox splice"
|
|
317
|
+
shape from PASS to FAIL. And "a repaired session passes" holds only for the repaired
|
|
318
|
+
artifact — the pre-repair form of the same session is normally still a FAIL. See the
|
|
319
|
+
boundary note in [docs/CONTRACTS.md](docs/CONTRACTS.md).
|
|
315
320
|
|
|
316
321
|
---
|
|
317
322
|
|
|
@@ -325,6 +330,9 @@ usually pre-fix bad samples. See the boundary note in
|
|
|
325
330
|
fold-position fix → 0.3.2 `tailSeq` → 0.3.3 `fix --neutralize` (in-place
|
|
326
331
|
turn-null neutralization) → 0.3.4 `fix --clip-crossstep` (cross-step clipping) →
|
|
327
332
|
0.3.5 **T2/S9/I1 rules** (cross-step source refs / physical order / inbox replay)
|
|
333
|
+
- [x] **0.3.6 → 0.3.15**: per-version rule routing for v0–v3 (T3/T4/T5, L3–L5, E8/E9/E10),
|
|
334
|
+
exit codes `3`/`4` for the host-capability and migration gates, and a version-support
|
|
335
|
+
report that says what was *not* verified (see [CHANGELOG.md](CHANGELOG.md))
|
|
328
336
|
- [ ] Phase 2: runtime guard (subscribe to the session append stream, validate live,
|
|
329
337
|
mark violations as `dsh/contract-violation`, policy configurable alert/block) —
|
|
330
338
|
DSH plugin form
|
|
@@ -336,7 +344,13 @@ MIT © OfferKuai Team
|
|
|
336
344
|
|
|
337
345
|
---
|
|
338
346
|
|
|
339
|
-
## ⚠️ Upgrading to 0.3.
|
|
347
|
+
## ⚠️ Upgrading to 0.3.16 — behaviour changes you must know (0.3.12 → 0.3.16)
|
|
348
|
+
|
|
349
|
+
> **0.3.16 changes no behaviour.** It rewrites comments, test titles, a few diagnostic strings and the
|
|
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.16.
|
|
352
|
+
>
|
|
353
|
+
> 0.3.15 was a documentation-only release as well.
|
|
340
354
|
|
|
341
355
|
**1. New exit codes `3` / `4` — a defect fix that is also a breaking change.**
|
|
342
356
|
- `3` = **not assessable on this host**: the file's `header.version` is higher than the host supports
|
|
@@ -354,7 +368,7 @@ MIT © OfferKuai Team
|
|
|
354
368
|
|
|
355
369
|
| value | meaning |
|
|
356
370
|
|---|---|
|
|
357
|
-
| `full` | file is already the host's target format (no migration) — coverage is complete
|
|
371
|
+
| `full` | file is already the host's target format (no migration) — coverage is complete |
|
|
358
372
|
| `partial` | file needs migration ⇒ the migration pre-check covers **2 rules only; 6 classes are uncovered** (≠ pass; see `coverage.uncovered`) |
|
|
359
373
|
| `none` | **not assessable on this host** — no "usable / upgradable" claim is certified |
|
|
360
374
|
|
package/README.zh.md
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
[](https://www.npmjs.com/package/dsh-log-contract)
|
|
8
8
|
[](https://www.npmjs.com/package/dsh-log-contract)
|
|
9
9
|
[](https://github.com/yamingmou/dsh-log-contract/blob/main/LICENSE)
|
|
10
|
-
[](https://github.com/topics/dsh-plugin)
|
|
11
11
|
[](https://github.com/yamingmou/dsh-log-contract/pulls)
|
|
12
12
|
|
|
13
13
|
[English](./README.md) · **简体中文**
|
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
|
|
19
19
|
|
|
20
20
|
> DSH(DeepSeek Harness)会话日志的**结构契约保险丝**:离线体检 + 写前校验。
|
|
21
|
-
>
|
|
21
|
+
> 本包名 **`dsh-log-contract`**;早期工作名 `log-contract-validator`。
|
|
22
22
|
|
|
23
23
|
给 DSH 会话日志(`*.jsonl` / `*.jsonl.zstd`)装一条保险丝:人眼看不出、程序解析会崩的日志格式漂移,在它这里被拦下并告警。它不判断日志**内容**对不对,只守护日志**结构**是否破坏了下游消费者(Harness 读路径、客户端引擎、插件 marker 语义)的预期。
|
|
24
24
|
|
|
@@ -32,25 +32,25 @@
|
|
|
32
32
|
|
|
33
33
|
> **dsh-log-contract 是 [dsh-retrace](https://github.com/yamingmou/dsh-retrace) 的核心能力组件**(业务层的「医生」模块):负责会话日志的**体检与修复**——让每一次撤回/编辑/回退都落在合法日志上,写前校验(`prewrite`)会把 error 级违约**拦在落盘之前**。
|
|
34
34
|
>
|
|
35
|
-
> **关于 `/compact`(如实表述)**:本工具保证的是**新写入**不再制造 token-meter 配对债——自两段原子成对(marker + 配对段)起,新 marker 天然通过 T1。**历史**(单段)marker 仍属**已知设计债**:写前校验对它的 T1 违规降级为 warning(`lib/prewrite.js` 的 `
|
|
35
|
+
> **关于 `/compact`(如实表述)**:本工具保证的是**新写入**不再制造 token-meter 配对债——自两段原子成对(marker + 配对段)起,新 marker 天然通过 T1。**历史**(单段)marker 仍属**已知设计债**:写前校验对它的 T1 违规降级为 warning(`lib/prewrite.js` 的 `legacyMarkerKindOf` 白名单,**只适用于历史 marker**),因此这类会话**压缩(`/compact`)前需要先跑一次体检 + 清理**(`check` 定位 → `fix --remove-markers`;插件侧即「医生」),否则 T1 自检会拦住压缩。
|
|
36
36
|
|
|
37
37
|
| 层 | 是什么 | 组件 |
|
|
38
38
|
|---|---|---|
|
|
39
|
-
| **Agent 业务层(生产级保证)** | 抽象核心能力:会话卫生 / 可回溯 / 可审计 / 可恢复,与平台无关 |
|
|
39
|
+
| **Agent 业务层(生产级保证)** | 抽象核心能力:会话卫生 / 可回溯 / 可审计 / 可恢复,与平台无关 | 四模块:治理(governance)/ 回溯(retrospect)/ 考古(archaeology)/ **医生(doctor)** |
|
|
40
40
|
| **dsh-retrace** | 业务层在 DeepSeek Harness 上的实现(生产级业务插件) | 撤回/编辑/版本/回退/看门狗 |
|
|
41
|
-
| **dsh-log-contract** | dsh-retrace 的核心能力组件 = 业务层的**医生**(体检/修复) | check / prewrite / fix / extract / audit |
|
|
41
|
+
| **dsh-log-contract** | dsh-retrace 的核心能力组件 = 业务层的**医生**(体检/修复) | check / prewrite / fix / extract / audit-report |
|
|
42
42
|
|
|
43
43
|
**含义**:dsh-log-contract 独立发布(供单独使用或二次开发),但它首先是
|
|
44
44
|
dsh-retrace 的「日志体检与修复」能力——与 dsh-retrace 一起构成
|
|
45
|
-
**Agent 业务层(生产级保证)** 在 DSH 上的落地(详见 [dsh-retrace
|
|
45
|
+
**Agent 业务层(生产级保证)** 在 DSH 上的落地(详见 [dsh-retrace 仓库](https://github.com/yamingmou/dsh-retrace))。
|
|
46
46
|
|
|
47
47
|
---
|
|
48
48
|
|
|
49
49
|
## 为什么需要它
|
|
50
50
|
|
|
51
|
-
|
|
51
|
+
**「one log, two consumers, two verdicts」**:一条日志同时被人类与自动化程序消费,人眼容忍格式微调,程序解析依赖严格契约;格式一旦漂移,人看不出问题,程序直接崩溃或误报。
|
|
52
52
|
|
|
53
|
-
|
|
53
|
+
**这里大多数规则都来自真实事故**——见下方 [⚡ 事故记录](#-事故记录)。事故派生的用例都有回归测试钉住:损坏的日志形态本工具必须报出,修复后的形态必须通过。其余规则来自对官方宿主源码的逐行核对(每条规则都带 `source`)。
|
|
54
54
|
|
|
55
55
|
---
|
|
56
56
|
|
|
@@ -71,18 +71,18 @@ dsh-retrace 的「日志体检与修复」能力——与 dsh-retrace 一起构
|
|
|
71
71
|
|
|
72
72
|
## ⚡ 事故记录 ——「生产级保证」不是口号
|
|
73
73
|
|
|
74
|
-
|
|
74
|
+
下面每一条都是一起来自生产会话的**真实事故**——正是这些案例促成了本工具。日期与形态真实,会话 id 与文件名省略。
|
|
75
75
|
|
|
76
76
|
| # | 日期 | 发生了什么 | 产出的规则/修复 |
|
|
77
77
|
|---|---|---|---|
|
|
78
78
|
| 1 | 2026-08-25 | 一次「恢复被隐藏内容」的修复写了**清空 `sourceEventSeqs`** 的 replace marker → 会话加载被拒(`SessionPersistenceCorruptionError`);第二次尝试把 marker 改成 **append** → 客户端引擎崩溃。两次都是**违约写入没被拦**。 | **S5**(sourceEventSeqs 必须覆盖被替换节点)、**M1**(turn-null 的 assistant/message 只能 replace)、写前校验 |
|
|
79
79
|
| 2 | 2026-08-27~28 | 中断/暂停的轮次恢复时按**过期内存光标**重放,把旧 seq 追加到文件尾(尾部回归、重复批次);两个写入者交织 → **文件物理序非单调**(`734056 → 733539 → 735470`)。会话 `seq gap` 加载失败。 | **S9**(物理序单调)、fix `--tail-renumber` |
|
|
80
80
|
| 3 | 2026-08-27~28 | **fork 边界孤儿 spliced**:fork 的「移除父待处理提示词」splice 假设父会话 inbox;子会话 seed 相对重放里 inbox 为空 → `resume failed: invalid persisted inbox splice`。 | **I1**(inbox seed 相对重放)、fix `--neutralize-orphan` |
|
|
81
|
-
| 4 | 2026-08-28 | 超限会话(**1,052,557 tokens** vs 1M 窗口)既无法继续也无法 `/compact`;裁剪预算估算对中文低估 ~3.7×。 | T1(token-meter 配对,保障可压缩)、`fix --trim` 预算指引 |
|
|
81
|
+
| 4 | 2026-08-28 | 超限会话(**1,052,557 tokens** vs 1M 窗口)既无法继续也无法 `/compact`;裁剪预算估算对中文低估 ~3.7×。 | T1(token-meter 配对,保障可压缩)、`fix --trim-budget` 预算指引 |
|
|
82
82
|
| 5 | 2026-08-29 | **W1/W2 wire 违规**:marker 遮蔽了带 tool_calls 的 assistant 但漏盖 tool 结果 → 悬空 tool,严格端点 `INVALID_REQUEST` 拒绝请求流。 | **W1 / W2**(wire 消息流) |
|
|
83
83
|
| 6 | 2026-08-30 | 单个 **turn-null marker** 让 token-meter 监听器在**每条**追加事件上抛错(`consumedEvents` 不前进 → 每事件全前缀重折)→ **30 秒 / 10,008 行日志**、host 事件循环被压垮、全部会话锁定。同会话还有**跨 step sourceEventSeqs**(step 7/8/9 混进一条 assistant 消息)——离线 check 全绿、实机 meter 崩溃。 | **T1**(turn/step 配对)、**T2**(跨 step 源引用)、`fix --neutralize`、`fix --clip-crossstep` |
|
|
84
84
|
|
|
85
|
-
>
|
|
85
|
+
> **结论**:这些规则不是合成理论——每一条都有回归测试复现它守护的那种损坏形态。这就是这里「生产级」的含义。
|
|
86
86
|
|
|
87
87
|
---
|
|
88
88
|
|
|
@@ -92,7 +92,7 @@ dsh-retrace 的「日志体检与修复」能力——与 dsh-retrace 一起构
|
|
|
92
92
|
|
|
93
93
|
| 项 | 基线 | 说明 |
|
|
94
94
|
|---|---|---|
|
|
95
|
-
| 宿主包 | `@deepseek-ai/dsh-session@0.1.5-rc.1` |
|
|
95
|
+
| 宿主包 | `@deepseek-ai/dsh-session@0.1.5-rc.1` | 本规则集开发与 CI 验证所用的版本(CI 跑 `pnpm check` + `pnpm test`;`prepublishOnly` 四步在本机该版本上全绿) |
|
|
96
96
|
| 会话格式 | **v3**(`SESSION_FORMAT_VERSION = 3`) | v3 走运行时词表 + 官方 `foldSurface` 终验 |
|
|
97
97
|
| 声明范围 | `peerDependencies: ^0.1.0-rc.7 || ^0.1.5-rc.1` | 允许安装;**范围内不等于逐个验证过** |
|
|
98
98
|
| 已知格式 | 0 / 1 / 2 / 3 | 0–2 由本包**自带** vendored 词表 + 本地等价折叠(`legacyFoldSurface`)支持,与宿主版本无关 |
|
|
@@ -126,8 +126,8 @@ pnpm dlx dsh-log-contract --help
|
|
|
126
126
|
> 声明为依赖,装 retrace 时自动带好契约守护(体检/写前校验/修复原语全部随插件生效)。
|
|
127
127
|
> 本包独立发布,供愿意单独使用或二次开发的用户直接引入。
|
|
128
128
|
>
|
|
129
|
-
> **从 GitHub 下载了 ZIP?** 解压后 `cd dsh-log-contract && npm install
|
|
130
|
-
> 然后 `node bin/dsh-log-contract.mjs check <session-log>`
|
|
129
|
+
> **从 GitHub 下载了 ZIP?** 解压后 `cd dsh-log-contract && npm install`,
|
|
130
|
+
> 然后 `node bin/dsh-log-contract.mjs check <session-log>` 即可使用(无需构建、无需全局安装)。
|
|
131
131
|
|
|
132
132
|
依赖:Node ≥ 22(`node:zlib` 内置 zstd)、`@deepseek-ai/dsh-session`(peer,校验/解码复用官方实现,保证与 Harness 读路径同源)。
|
|
133
133
|
|
|
@@ -216,7 +216,7 @@ DSH 会话日志持久化了每次工具调用的完整输入输出——数据
|
|
|
216
216
|
|
|
217
217
|
```sh
|
|
218
218
|
# 按命令正则导出工具输出(保留原始文本)
|
|
219
|
-
dsh-log-contract extract <session-log> --pattern "
|
|
219
|
+
dsh-log-contract extract <session-log> --pattern "build-report" --min-size 50 --out ./found
|
|
220
220
|
|
|
221
221
|
# 考古审计报告:调用数 / 配对率 / 孤儿数 / 命令分布
|
|
222
222
|
dsh-log-contract audit-report <session-log>
|
|
@@ -257,29 +257,26 @@ if (!verdict.ok) {
|
|
|
257
257
|
## 测试
|
|
258
258
|
|
|
259
259
|
```bash
|
|
260
|
-
pnpm check && pnpm test # 语法检查 +
|
|
260
|
+
pnpm check && pnpm test # 语法检查 + 契约文档漂移闸 + 全部单测(含事故回归用例)
|
|
261
261
|
```
|
|
262
262
|
|
|
263
263
|
- **合成夹具**(入库):合法会话 / seq 缺口 / 空 sourceEventSeqs / turn=null append / 未知 type / 坏 chunk 行 / 撕裂尾帧 / 未知 marker 前缀 / 自指 shadowed 等。
|
|
264
|
-
- **真实化石**(不入库,含用户隐私):本地跑
|
|
265
264
|
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
```
|
|
265
|
+
> **发布包范围**:只含运行时代码与文档。维护者本地工具不在公开仓内,
|
|
266
|
+
> 因此 `pnpm check` / `pnpm test` 在干净的克隆里即可通过。
|
|
269
267
|
|
|
270
|
-
|
|
268
|
+
按**日志形态**给出的判定——本工具对每类缺陷该说什么,而不是对某个具体文件:
|
|
271
269
|
|
|
272
|
-
|
|
|
270
|
+
| 日志形态 | 判定 | 触发的规则 |
|
|
273
271
|
|---|---|---|
|
|
274
|
-
|
|
|
275
|
-
|
|
|
276
|
-
|
|
|
277
|
-
|
|
|
278
|
-
|
|
|
279
|
-
| `<session>-pre-cleanstate-20260831` | **PASS** | error 0(数千跨度的 replace marker 数据合规,官方 foldSurface 重放通过) |
|
|
272
|
+
| seq 缺口,或文件物理序非单调 | FAIL | S8 / C1 / T1 / E2(seq 倒退时另加 S9) |
|
|
273
|
+
| 重写引入缺口 | FAIL | S8 / C1 / T1 / E2 / I1 |
|
|
274
|
+
| inbox splice 无效 + turn-null marker | FAIL | T1 / I1 |
|
|
275
|
+
| 修复前形态:遗留 turn-null marker | FAIL | T1 |
|
|
276
|
+
| 完整覆盖的数千跨度 replace marker | PASS | error 0——数据合规,官方 `foldSurface` 重放通过 |
|
|
280
277
|
|
|
281
|
-
>
|
|
282
|
-
>
|
|
278
|
+
> 规则演进会移动表中行:0.3.5 新增 I1 后,「inbox splice 无效」这一形态从 PASS 变 FAIL。
|
|
279
|
+
> 而「修复后会话通过」只对被修复后的产物成立——同一会话修复前的形态通常仍是 FAIL。
|
|
283
280
|
|
|
284
281
|
---
|
|
285
282
|
|
|
@@ -288,6 +285,7 @@ node scripts/check-local-fossils.mjs # 扫描 ../ 下 backup-session-*.jsonl.z
|
|
|
288
285
|
- [x] **Phase 1(0.1.0)**:CLI 离线体检 + 写前校验 + 契约目录
|
|
289
286
|
- [x] **Phase 1.5(0.2.0)**:`fix` 子命令(严格 seq 扫描 + W1/W2 wire 检查 + 移除 marker 重编号 + 官方帧格式重建);CI 集成(`dsh-log-contract check` 作为 Harness 会话目录的定时守护)
|
|
290
287
|
- [x] **0.3.x(2026-08-30 事故固化)**:T1 token-meter 配对 → 0.3.1 W1/W2 折叠位置修复 → 0.3.2 `tailSeq` → 0.3.3 `fix --neutralize`(turn-null marker 原地中和)→ 0.3.4 `fix --clip-crossstep`(跨 step 引用裁剪)→ 0.3.5 **T2/S9/I1 规则**(跨 step 源引用 / 物理序单调 / inbox 重放)
|
|
288
|
+
- [x] **0.3.6 → 0.3.15**:按被检文件版本择路的 v0–v3 规则(T3/T4/T5、L3–L5、E8/E9/E10)、宿主能力与迁移预检的退出码 `3`/`4`,以及如实报告"哪些版本**未**验证"的版本支持说明(详见 [CHANGELOG.md](CHANGELOG.md))
|
|
291
289
|
- [ ] Phase 2:运行时守护(订阅 session append 事件流实时校验,断裂即标记 `dsh/contract-violation` 事件,策略可配 告警/拦截)——DSH 插件形态
|
|
292
290
|
- [ ] Phase 3:与 dsh-turn-guard / dsh-retrace 时间线联动
|
|
293
291
|
|
|
@@ -297,7 +295,13 @@ MIT © OfferKuai Team
|
|
|
297
295
|
|
|
298
296
|
---
|
|
299
297
|
|
|
300
|
-
## ⚠️ 升级到 0.3.
|
|
298
|
+
## ⚠️ 升级到 0.3.16 —— 必须知道的行为变更(0.3.12 → 0.3.16)
|
|
299
|
+
|
|
300
|
+
> **0.3.16 不含行为变更。** 它只重写了注释、测试标题、少量诊断文案与 README,让它们
|
|
301
|
+
> 只描述产品与契约本身。所有判定结果、退出码与 `--json` 字段与 0.3.15 完全一致,
|
|
302
|
+
> 因此从 0.3.12 升到 0.3.16 时,下面各条与原来完全一致。
|
|
303
|
+
>
|
|
304
|
+
> 0.3.15 同样只是文档层面的发布。
|
|
301
305
|
|
|
302
306
|
**1. 新增退出码 `3` / `4`:本质是缺陷修复,形式上也是破坏性变更。**
|
|
303
307
|
- `3` = **不可在本宿主评估**:被检文件的 `header.version` 高于当前宿主支持的上限
|
package/bin/dsh-log-contract.mjs
CHANGED
|
@@ -16,9 +16,9 @@ import fs from 'node:fs';
|
|
|
16
16
|
import { loadSessionLog, validateSessionLog, resumeVerdict, migrationVerdict, assessmentScope, createPreWriter, repairSession, readSessionHeader, CONTRACT_RULES, ruleById, extractToolOutputs, auditToolCalls, hostCapability, HOST_MAX_FILE_VERSION, detectSupport } from '../lib/index.js';
|
|
17
17
|
|
|
18
18
|
/**
|
|
19
|
-
* 同步写 fd
|
|
19
|
+
* 同步写 fd——`process.stdout.write` 在**管道**下是异步的,紧跟着的
|
|
20
20
|
* `process.exit()` 不会等待 flush:>~64KiB 的输出会丢尾,`--json` 因此变成非法 JSON
|
|
21
|
-
*
|
|
21
|
+
* (实测在 3000 事件夹具与真实 14.5MB v3 会话上复现,stdout 56648B 处 "Unterminated string")。
|
|
22
22
|
* 这里直接同步写 fd 1/2(非阻塞管道 EAGAIN 时自旋重试),做到"先写完再 exit"。
|
|
23
23
|
*/
|
|
24
24
|
function writeAllSync(fd, text) {
|
|
@@ -49,7 +49,7 @@ const USAGE = `dsh-log-contract —— 日志契约守护(DSH session log cont
|
|
|
49
49
|
--resume 输出分层结论:可加载 / 可继续 / 可压缩(本工具规则集内)
|
|
50
50
|
+ 迁移预检(官方 v0/v1/v2→当前格式会不会拒);--json 时附带。
|
|
51
51
|
--fail-on-migration migration.ready===false(工具判"官方升级会拒")时退出码 4。
|
|
52
|
-
|
|
52
|
+
**注意:默认已经是这个行为**(已把"报告与退出码不一致"的假阴性修掉);
|
|
53
53
|
本开关是**显式别名**,便于 CI 把意图写进命令行。
|
|
54
54
|
--no-fail-on-migration **真 opt-out**:恢复 0.3.11 的行为——迁移 blocked 不再改退出码
|
|
55
55
|
(结构绿即 exit 0)。两者同时给时 **opt-out 优先**。若你的流水线依赖旧退出码,
|
|
@@ -74,7 +74,7 @@ const USAGE = `dsh-log-contract —— 日志契约守护(DSH session log cont
|
|
|
74
74
|
ignorable:true,删 surfaceOp/sourceEventSeqs,seq/行数不变)
|
|
75
75
|
—— token-meter 不再刷屏,会话驻留也安全(2026-08-30 事故)
|
|
76
76
|
--neutralize-legacy-markers
|
|
77
|
-
|
|
77
|
+
**一次性根治历史载体债**:只中和
|
|
78
78
|
assistant/message + data.editor 形态的历史 retrace marker
|
|
79
79
|
(新载体是 user/message + data.id,不在此列)。清理后,
|
|
80
80
|
写前校验不再需要"≤v2 历史 marker T1 降级"那条豁免。
|
|
@@ -106,7 +106,7 @@ const USAGE = `dsh-log-contract —— 日志契约守护(DSH session log cont
|
|
|
106
106
|
|
|
107
107
|
dsh-log-contract extract <session-log> --pattern <regex> [--out DIR] [--min-size N] [--json]
|
|
108
108
|
考古提取:按命令正则导出工具输出(只读)。--out 写到目录(保留原始文本),
|
|
109
|
-
否则打印前 3 条摘要。--min-size 过滤小输出(默认 50
|
|
109
|
+
否则打印前 3 条摘要。--min-size 过滤小输出(默认 50)。
|
|
110
110
|
|
|
111
111
|
dsh-log-contract audit-report <session-log> [--json]
|
|
112
112
|
考古审计报告:调用数 / 配对率 / 孤儿数 / 命令分布。
|
|
@@ -138,7 +138,7 @@ function printViolations(violations, maxDetails = 8) {
|
|
|
138
138
|
}
|
|
139
139
|
}
|
|
140
140
|
|
|
141
|
-
/**
|
|
141
|
+
/** 漂移抬头(2026-09-14):漂移清单 + 未复核清单 + "勿据此跑 fix --apply"。 */
|
|
142
142
|
function driftLines(drift, probes) {
|
|
143
143
|
if (!drift) return '';
|
|
144
144
|
const host = probes ? `${probes.hostPackage}(SESSION_FORMAT_VERSION=${probes.sessionFormatVersion},词表 ${probes.knownTypes} 类)` : '未知';
|
|
@@ -150,8 +150,8 @@ function driftLines(drift, probes) {
|
|
|
150
150
|
if (drift.unverifiedRules.length > 0) {
|
|
151
151
|
out += ` ⛔ **UNVERIFIED**(探针与规则假设不一致,结论不可采信):${drift.unverifiedRules.join('、')}\n`;
|
|
152
152
|
}
|
|
153
|
-
out += `
|
|
154
|
-
out += `
|
|
153
|
+
out += ` 出处未复核(已知债,仅告警):${drift.driftedSources.join('、')}\n`;
|
|
154
|
+
out += ` 判定前提存疑(仅告警):${drift.premiseStale.join('、')}${drift.undecidable.length ? `;无法判定:${drift.undecidable.join('、')}` : ''}\n`;
|
|
155
155
|
if (drift.fixApplyBlocked) {
|
|
156
156
|
out += ' ⛔ **禁止据此跑 `fix --apply`**(存在未验证规则:写入类动作不可逆)——先按 report 复核这些规则或显式确认。\n';
|
|
157
157
|
}
|
|
@@ -186,7 +186,7 @@ function notAssessableBanner(result) {
|
|
|
186
186
|
/** 迁移预检(独立维度)文本。
|
|
187
187
|
* @param {object} mig `migrationVerdict()` 结果
|
|
188
188
|
* @param {boolean} partial 待迁移文件(`migration.applies===true`)⇒ 迁移维度只部分覆盖,
|
|
189
|
-
* 不得给无条件绿(2026-09-14
|
|
189
|
+
* 不得给无条件绿(2026-09-14 规格 (i)+(iii))。
|
|
190
190
|
*/
|
|
191
191
|
function migrationLine(mig, partial = false) {
|
|
192
192
|
if (!mig) return '';
|
|
@@ -213,7 +213,7 @@ function migrationLine(mig, partial = false) {
|
|
|
213
213
|
function cmdCheck(args) {
|
|
214
214
|
const json = args.includes('--json');
|
|
215
215
|
const resume = args.includes('--resume');
|
|
216
|
-
//
|
|
216
|
+
// `--fail-on-migration` 是**默认已生效行为**的显式别名;
|
|
217
217
|
// `--no-fail-on-migration` 是**真 opt-out**(恢复旧行为:迁移 blocked 不再改退出码)。
|
|
218
218
|
// 两者同时给 ⇒ opt-out 优先(help/README 写明)。
|
|
219
219
|
const failOnMigration = args.includes('--fail-on-migration');
|
|
@@ -233,11 +233,11 @@ function cmdCheck(args) {
|
|
|
233
233
|
const { summary, violations, ok } = result;
|
|
234
234
|
const notAssessable = result.assessable === false;
|
|
235
235
|
const migration = result.migration ?? migrationVerdict(result);
|
|
236
|
-
//
|
|
236
|
+
// 机器可判的评估范围(partial = 待迁移文件,迁移维度只部分覆盖)。
|
|
237
237
|
const scope = result.assessmentScope ?? assessmentScope(result);
|
|
238
238
|
const partial = scope === 'partial';
|
|
239
239
|
const migBlocked = migration?.applies === true && migration.ready === false;
|
|
240
|
-
// 默认:`ready===false` ⇒ 非 0
|
|
240
|
+
// 默认:`ready===false` ⇒ 非 0(修掉的假阴性)。
|
|
241
241
|
// `--fail-on-migration` = 显式别名(同默认);`--no-fail-on-migration` = 真 opt-out(恢复旧行为)。
|
|
242
242
|
const migGate = !noFailOnMigration && (migBlocked || (failOnMigration && migration?.ready === false));
|
|
243
243
|
// 退出码:3 不可评估 > 1 结构 error > 4 迁移 blocked > 0。
|
|
@@ -363,7 +363,7 @@ function cmdPrewrite(args) {
|
|
|
363
363
|
|
|
364
364
|
out(`\n✍️ dsh-log-contract prewrite —— ${file}(op: ${result.op},nextSeq: ${prewriter.nextSeq})\n\n`);
|
|
365
365
|
if (result.legacyMarkerDebt) {
|
|
366
|
-
//
|
|
366
|
+
// 「降级可见」:历史 marker 债不只是 warning 里的一句话,这里显式打印并给出根治命令。
|
|
367
367
|
const d = result.legacyMarkerDebt;
|
|
368
368
|
out(` ⚠️ 检测到**历史 marker 载体**(${d.kind} 形态,id=${d.id},targetSeq=${d.targetSeq},seq=${d.seq}):\n`);
|
|
369
369
|
out(` T1 已按 ≤v${d.formatVersion <= 2 ? 2 : d.formatVersion} 白名单降级为 warning(仅历史载体、仅旧格式文件)——\n`);
|
|
@@ -382,7 +382,7 @@ function cmdPrewrite(args) {
|
|
|
382
382
|
}
|
|
383
383
|
|
|
384
384
|
function cmdContracts() {
|
|
385
|
-
out('dsh-log-contract 契约规则目录(spec
|
|
385
|
+
out('dsh-log-contract 契约规则目录(spec:官方源码逐行核对 + 实测事故固化)\n\n');
|
|
386
386
|
for (const r of CONTRACT_RULES) {
|
|
387
387
|
const flags = [
|
|
388
388
|
r.candidate ? '候选规则' : null,
|
|
@@ -397,7 +397,7 @@ function cmdFix(args) {
|
|
|
397
397
|
const json = args.includes('--json');
|
|
398
398
|
const removeMarkers = args.includes('--remove-markers');
|
|
399
399
|
const neutralize = args.includes('--neutralize');
|
|
400
|
-
//
|
|
400
|
+
// 一次性根治路径:只中和**历史载体**(assistant/message + data.editor)的 retrace marker
|
|
401
401
|
const neutralizeLegacyMarkers = args.includes('--neutralize-legacy-markers');
|
|
402
402
|
const clipCrossStep = args.includes('--clip-crossstep');
|
|
403
403
|
const dropFailedTurns = args.includes('--drop-failed-turns');
|
|
@@ -407,7 +407,7 @@ function cmdFix(args) {
|
|
|
407
407
|
const trimBudget = budgetIdx >= 0 && args[budgetIdx + 1] ? Number(args[budgetIdx + 1]) : undefined;
|
|
408
408
|
const compactIdx = args.indexOf('--compact-last');
|
|
409
409
|
const compactLast = compactIdx >= 0 && args[compactIdx + 1] ? Number(args[compactIdx + 1]) : undefined;
|
|
410
|
-
// L4 新原语(2026-08-30
|
|
410
|
+
// L4 新原语(2026-08-30 收编外部验证工具)
|
|
411
411
|
const tailIdx = args.indexOf('--tail-renumber');
|
|
412
412
|
const tailRenumberDelta = tailIdx >= 0 && args[tailIdx + 1] ? Number(args[tailIdx + 1]) : undefined;
|
|
413
413
|
const neutralizeOrphan = args.includes('--neutralize-orphan');
|
|
@@ -423,7 +423,7 @@ function cmdFix(args) {
|
|
|
423
423
|
const file = args.find((a) => !a.startsWith('-'));
|
|
424
424
|
if (!file) fail(USAGE);
|
|
425
425
|
|
|
426
|
-
// S2
|
|
426
|
+
// S2 安全闸:本宿主评估不了的文件**不许**修。危害路径(实测指出):rc.7 上把健康 v3
|
|
427
427
|
// 日志报成 broken → 用户以为日志坏了去跑 `fix --apply` ⇒ 在健康日志上动手。这里直接拒绝。
|
|
428
428
|
const head = readSessionHeader(file);
|
|
429
429
|
const fileVersion = Number.isSafeInteger(head?.version) ? head.version : 0;
|