dsh-log-contract 0.3.5 → 0.3.6
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 +192 -99
- package/README.zh.md +253 -0
- package/lib/checks.js +5 -1
- package/lib/repair.js +2 -1
- package/package.json +3 -2
package/README.md
CHANGED
|
@@ -1,64 +1,143 @@
|
|
|
1
|
-
|
|
1
|
+
<div align="center">
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
> 原名 `log-contract-validator`(候选二号),按 Offer快 三件套规划定名 **`dsh-log-contract`**。
|
|
3
|
+
# 🔒 dsh-log-contract
|
|
5
4
|
|
|
6
|
-
|
|
5
|
+
**Log Contract Guard** — the structural fuse for DeepSeek Harness session logs:
|
|
6
|
+
offline health check + pre-write validation. The business layer's **doctor**.
|
|
7
7
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
-
|
|
8
|
+
[](https://www.npmjs.com/package/dsh-log-contract)
|
|
9
|
+
[](https://www.npmjs.com/package/dsh-log-contract)
|
|
10
|
+
[](https://github.com/yamingmou/dsh-log-contract/blob/main/LICENSE)
|
|
11
|
+
[](https://github.com/topics/dsh-plugin)
|
|
12
|
+
[](https://github.com/yamingmou/dsh-log-contract/pulls)
|
|
13
|
+
|
|
14
|
+
**English** · [简体中文](./README.zh.md)
|
|
15
|
+
|
|
16
|
+
</div>
|
|
17
|
+
|
|
18
|
+
Formerly `log-contract-validator` (candidate #2); named **`dsh-log-contract`**
|
|
19
|
+
per the OfferKuai three-piece plan.
|
|
20
|
+
|
|
21
|
+
A fuse for DSH session logs (`*.jsonl` / `*.jsonl.zstd`): format drift that humans
|
|
22
|
+
cannot see but parsers crash on is caught and reported here. It does **not** judge
|
|
23
|
+
whether log *content* is right — only whether log *structure* breaks the
|
|
24
|
+
expectations of downstream consumers (the Harness read path, the client engine,
|
|
25
|
+
plugin marker semantics).
|
|
26
|
+
|
|
27
|
+
- **`check <session-log>`** — offline health check: official decoder full decode +
|
|
28
|
+
per-rule contract validation + foldSurface final verification, with a violation
|
|
29
|
+
report.
|
|
30
|
+
- **`prewrite <edit-file> --log <session-log>`** — ★ pre-write validation: any
|
|
31
|
+
write (append / frame-level surgery) passes the three-layer contract before it
|
|
32
|
+
lands; violations are blocked.
|
|
33
|
+
- **`contracts`** — list the built-in contract rule catalog (each with its
|
|
34
|
+
official source reference).
|
|
35
|
+
|
|
36
|
+
---
|
|
37
|
+
|
|
38
|
+
## Where it sits in the business layer
|
|
39
|
+
|
|
40
|
+
> **dsh-log-contract is the core capability component of
|
|
41
|
+
> [dsh-retrace](https://github.com/yamingmou/dsh-retrace)** — the "doctor" module
|
|
42
|
+
> of the business layer: session-log **check & repair**, so every recall/edit/rewind
|
|
43
|
+
> lands on a legal log and `/compact` never breaks.
|
|
44
|
+
|
|
45
|
+
| Layer | What it is | Components |
|
|
46
|
+
|---|---|---|
|
|
47
|
+
| **Agent business layer (production-grade)** | Framework-agnostic core: session hygiene / retraceability / auditability / recoverability | Four modules: governance / retrospect / archaeology / **doctor** |
|
|
48
|
+
| **dsh-retrace** | The business layer's DeepSeek Harness implementation | recall/edit/version/rollback/watchdog |
|
|
49
|
+
| **dsh-log-contract** | dsh-retrace's core component = the business layer's **doctor** (check & repair) | check / prewrite / fix / extract / audit |
|
|
50
|
+
|
|
51
|
+
**Meaning**: dsh-log-contract is published standalone (for direct use or
|
|
52
|
+
re-implementation), but it is first the "check & repair" capability of dsh-retrace —
|
|
53
|
+
together they form the **Agent business layer (production-grade guarantees)** on
|
|
54
|
+
DSH (see the [dsh-retrace roadmap](https://github.com/yamingmou/dsh-retrace/blob/main/docs/ROADMAP.md)).
|
|
11
55
|
|
|
12
56
|
---
|
|
13
57
|
|
|
14
|
-
##
|
|
58
|
+
## Why it exists
|
|
15
59
|
|
|
16
|
-
**#3632
|
|
60
|
+
**#3632 "one log, two consumers, two verdicts"**: one log is consumed by both
|
|
61
|
+
humans and automated programs. Humans tolerate format drift; programs depend on
|
|
62
|
+
strict contracts. Once the format drifts, humans see nothing wrong while programs
|
|
63
|
+
crash or misreport.
|
|
17
64
|
|
|
18
|
-
**
|
|
65
|
+
**Real incidents shaped every rule here** — see the [Incident log](#-incident-log)
|
|
66
|
+
below. Each incident is a regression fixture: a corrupted session that this tool
|
|
67
|
+
must flag, and a fixed session that it must pass.
|
|
19
68
|
|
|
20
69
|
---
|
|
21
70
|
|
|
22
|
-
##
|
|
71
|
+
## Three-layer contract (the model)
|
|
23
72
|
|
|
24
|
-
|
|
73
|
+
> 30+ rules across the layers below (`contracts` lists them all, each with its
|
|
74
|
+
> official source reference).
|
|
75
|
+
|
|
76
|
+
| Layer | Rules | What it guards |
|
|
25
77
|
|---|---|---|
|
|
26
|
-
|
|
|
27
|
-
|
|
|
28
|
-
|
|
|
78
|
+
| **Persistence** | H/R/E/S (incl. **S5**) + **S9** | seq contiguous, known types, legal `surfaceOp`, `sourceEventSeqs` fully covers shadowed nodes, file-physical seq monotonic, `foldSurface` not throwing |
|
|
79
|
+
| **Client engine** | **M1** + **T1 / T2 / I1** | turn-null markers only as replace; token-meter pairing; cross-step source refs; inbox seed-relative replay |
|
|
80
|
+
| **Wire message flow** | **W1 / W2** | tool messages follow an assistant with tool_calls; no user text between tool_calls and results |
|
|
81
|
+
| **Plugin semantics** | P1/P2 | marker id prefixes recognizable; a marker's seq not in its own shadowed set |
|
|
29
82
|
|
|
30
|
-
>
|
|
83
|
+
> Philosophy: first an incremental replay with official-equivalent semantics for
|
|
84
|
+
> **per-event attribution** (pinpoint seq/line), then the official `foldSurface` as
|
|
85
|
+
> the **final verdict** (not throwing = pass) — both green to pass.
|
|
31
86
|
|
|
32
87
|
---
|
|
33
88
|
|
|
34
|
-
##
|
|
89
|
+
## ⚡ Incident log — why "production-grade" is not a slogan
|
|
90
|
+
|
|
91
|
+
Every rule below was born from a **real incident** in our workspace. These are the
|
|
92
|
+
sessions that made us build this tool. Dates and shapes are real; session ids are
|
|
93
|
+
omitted for privacy.
|
|
94
|
+
|
|
95
|
+
| # | Date | What happened | The rule / fix it produced |
|
|
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 |
|
|
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` |
|
|
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` |
|
|
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 |
|
|
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) |
|
|
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` |
|
|
103
|
+
|
|
104
|
+
> **Takeaway**: every rule in this tool is a scar from a real session — validated
|
|
105
|
+
> against the actual corrupted-session fixtures, not synthetic theory. That is what
|
|
106
|
+
> "production-grade" means here.
|
|
107
|
+
|
|
108
|
+
---
|
|
109
|
+
|
|
110
|
+
## Installation
|
|
35
111
|
|
|
36
112
|
```bash
|
|
37
|
-
pnpm add -D dsh-log-contract #
|
|
113
|
+
pnpm add -D dsh-log-contract # or npm install
|
|
38
114
|
pnpm dlx dsh-log-contract --help
|
|
39
115
|
```
|
|
40
116
|
|
|
41
|
-
>
|
|
42
|
-
>
|
|
43
|
-
>
|
|
117
|
+
> **Using dsh-retrace?** No separate install needed — `dsh-retrace` declares
|
|
118
|
+
> `dsh-log-contract` as a dependency, so the contract guard (check / pre-write /
|
|
119
|
+
> repair primitives) comes with the plugin automatically. This package is published
|
|
120
|
+
> standalone for direct use or re-implementation.
|
|
44
121
|
>
|
|
45
|
-
>
|
|
46
|
-
>
|
|
122
|
+
> **Downloaded the repo as a ZIP?** `cd dsh-log-contract && npm install && npm run build`,
|
|
123
|
+
> then `node bin/dsh-log-contract.mjs check <session-log>` — no global install needed.
|
|
47
124
|
|
|
48
|
-
|
|
125
|
+
Dependencies: Node ≥ 22 (`node:zlib` has built-in zstd), `@deepseek-ai/dsh-session`
|
|
126
|
+
(peer; validation/decode reuse the official implementation, so it stays in sync
|
|
127
|
+
with the Harness read path).
|
|
49
128
|
|
|
50
129
|
---
|
|
51
130
|
|
|
52
|
-
## CLI
|
|
131
|
+
## CLI
|
|
53
132
|
|
|
54
|
-
### 1.
|
|
133
|
+
### 1. Offline health check
|
|
55
134
|
|
|
56
135
|
```bash
|
|
57
136
|
dsh-log-contract check ~/.dsh/sessions/<id>.jsonl.zstd
|
|
58
|
-
dsh-log-contract check ~/.dsh/sessions/<id>.jsonl.zstd --json #
|
|
137
|
+
dsh-log-contract check ~/.dsh/sessions/<id>.jsonl.zstd --json # machine-readable
|
|
59
138
|
```
|
|
60
139
|
|
|
61
|
-
|
|
140
|
+
Sample output (the CLI reports in Chinese — it is the tool's UI language):
|
|
62
141
|
|
|
63
142
|
```
|
|
64
143
|
📋 dsh-log-contract check —— backup-session-xxxx.jsonl.zstd
|
|
@@ -71,139 +150,153 @@ dsh-log-contract check ~/.dsh/sessions/<id>.jsonl.zstd --json # 机器可读
|
|
|
71
150
|
❌ 未通过:见上方违规明细(error 级 = 会话不可读/不可写)
|
|
72
151
|
```
|
|
73
152
|
|
|
74
|
-
|
|
153
|
+
Exit code: 0 = pass (no error-level violations); 1 = error-level violations exist.
|
|
75
154
|
|
|
76
|
-
`check`
|
|
77
|
-
|
|
78
|
-
tool_calls
|
|
79
|
-
`INVALID_REQUEST
|
|
155
|
+
`check` adds **W1/W2 wire-level checks** since 0.2.0: expand the model request
|
|
156
|
+
stream in surface order and catch "dangling tool messages" (a tool result with no
|
|
157
|
+
preceding assistant tool_calls) and "user text between tool_calls and their
|
|
158
|
+
results" — tolerated by some endpoints, `INVALID_REQUEST` on strict ones
|
|
159
|
+
(MiMo, verified 2026-08-27).
|
|
80
160
|
|
|
81
|
-
###
|
|
161
|
+
### 2. Repair (`fix`)
|
|
82
162
|
|
|
83
163
|
```bash
|
|
84
|
-
#
|
|
164
|
+
# Dry run (report only): strict seq scan + full contract check + removable-marker count
|
|
85
165
|
dsh-log-contract fix ~/.dsh/sessions/<id>.jsonl.zstd --remove-markers
|
|
86
166
|
|
|
87
|
-
#
|
|
167
|
+
# Apply: backup first, then write (.zstd rebuilt in official frame format: frame1=header,
|
|
168
|
+
# frame2=rest, checksum, single trailing newline)
|
|
88
169
|
dsh-log-contract fix ~/.dsh/sessions/<id>.jsonl.zstd --remove-markers --apply
|
|
89
170
|
```
|
|
90
171
|
|
|
91
|
-
- `--remove-markers
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
-
|
|
95
|
-
|
|
96
|
-
|
|
172
|
+
- `--remove-markers`: remove retrace/message-editor markers and renumber everything
|
|
173
|
+
(seq/seq0/sourceEventSeqs/surfaceOp in sync) — for large marker-shadowed history
|
|
174
|
+
or markers that left dangling tools.
|
|
175
|
+
- `--neutralize`: in-place neutralization of turn-null markers (incident #6) —
|
|
176
|
+
type → `retrace/marker` + `ignorable:true`, drops surfaceOp/sourceEventSeqs,
|
|
177
|
+
seq/line count unchanged (safe while the session is resident).
|
|
178
|
+
- `--clip-crossstep`: trim cross-step sourceEventSeqs (incident #6) — keep only
|
|
179
|
+
same-turn/step chunk references.
|
|
180
|
+
- Surgery safety protocol: back up first, re-verify after (strictScan + check +
|
|
181
|
+
foldSurface); markers may only shadow earlier nodes; a marker must never become
|
|
182
|
+
append (M1 crashes the client engine).
|
|
183
|
+
- ⚠️ If the session is resident in a running app, **restart the app** after fixing
|
|
184
|
+
the file (hard-kill to avoid dirty state flushing back).
|
|
97
185
|
|
|
98
|
-
###
|
|
186
|
+
### 3. Pre-write validation (`prewrite`)
|
|
99
187
|
|
|
100
|
-
`edit-file`
|
|
188
|
+
`edit-file` is JSON with two shapes:
|
|
101
189
|
|
|
102
190
|
```jsonc
|
|
103
|
-
//
|
|
191
|
+
// Append one event to the log tail (seq omitted = auto-assigned as nextSeq)
|
|
104
192
|
{ "append": { "type": "assistant/message", "surfaceOp": { "op": "replace", "start": 121774, "end": 156421 }, "sourceEventSeqs": [121774, 121779, "…"], "data": { "turn": null, "step": null, "message": { "…": "…" }, "editor": { "targetSeq": 156430, "text": "…" } } } }
|
|
105
193
|
|
|
106
|
-
//
|
|
107
|
-
|
|
194
|
+
// Frame-level surgery: the complete event list after the edit (both baseline and
|
|
195
|
+
// result must be green before it may land)
|
|
196
|
+
{ "edit": [ "…full event list…" ] }
|
|
108
197
|
```
|
|
109
198
|
|
|
110
199
|
```bash
|
|
111
200
|
dsh-log-contract prewrite marker-write.json --log ~/.dsh/sessions/<id>.jsonl.zstd
|
|
112
201
|
```
|
|
113
202
|
|
|
114
|
-
-
|
|
115
|
-
-
|
|
203
|
+
- A baseline with error-level violations is rejected outright (safety protocol
|
|
204
|
+
step 2: **the pre-surgery baseline must be green**).
|
|
205
|
+
- Only a pass may land — **validate first, commit later** (same idea as the
|
|
206
|
+
official `SurfaceManager.validateNext`).
|
|
116
207
|
|
|
117
|
-
###
|
|
208
|
+
### 4. Contract catalog
|
|
118
209
|
|
|
119
210
|
```bash
|
|
120
211
|
dsh-log-contract contracts
|
|
121
212
|
```
|
|
122
213
|
|
|
123
|
-
|
|
214
|
+
Full catalog in [docs/CONTRACTS.md](docs/CONTRACTS.md).
|
|
215
|
+
|
|
216
|
+
### 5. Session archaeology (`extract` / `audit-report`)
|
|
217
|
+
|
|
218
|
+
Every tool call's full input/output is persisted in the session log — a data and
|
|
219
|
+
audit asset. Read-only archaeology:
|
|
220
|
+
|
|
221
|
+
```sh
|
|
222
|
+
# Export tool outputs matching a command regex (original text preserved)
|
|
223
|
+
dsh-log-contract extract <session-log> --pattern "seed-scale" --min-size 50 --out ./found
|
|
224
|
+
|
|
225
|
+
# Archaeology audit report: call count / pairing rate / orphans / command distribution
|
|
226
|
+
dsh-log-contract audit-report <session-log>
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
Contract rules P3 (tool/call↔tool/result pairing integrity) and P4 (output
|
|
230
|
+
structure parseable) keep the dig working: orphan calls and abnormal `text` fields
|
|
231
|
+
are flagged in `check`.
|
|
124
232
|
|
|
125
233
|
---
|
|
126
234
|
|
|
127
|
-
## Node API
|
|
235
|
+
## Node API (embed pre-write validation in your script)
|
|
128
236
|
|
|
129
237
|
```js
|
|
130
238
|
import { loadSessionLog, validateSessionLog, createPreWriter } from 'dsh-log-contract';
|
|
131
239
|
|
|
132
|
-
// ①
|
|
240
|
+
// ① Baseline check (the pre-surgery baseline must be green)
|
|
133
241
|
const log = loadSessionLog('session.jsonl.zstd');
|
|
134
242
|
const baseline = validateSessionLog(log);
|
|
135
|
-
if (!baseline.ok) throw new Error('
|
|
243
|
+
if (!baseline.ok) throw new Error('baseline is broken; repair it first');
|
|
136
244
|
|
|
137
|
-
// ②
|
|
245
|
+
// ② Pre-write validation: about to write a marker replace
|
|
138
246
|
const prewriter = createPreWriter({ events: log.events.map((e) => e.event) });
|
|
139
247
|
const verdict = prewriter.validateAppend({
|
|
140
248
|
type: 'assistant/message',
|
|
141
249
|
surfaceOp: { op: 'replace', start: 121774, end: 156421 },
|
|
142
|
-
sourceEventSeqs: [121774, 121779 /*
|
|
250
|
+
sourceEventSeqs: [121774, 121779 /* …must fully cover shadowed nodes… */],
|
|
143
251
|
data: { turn: null, step: null, message: { /* … */ } },
|
|
144
252
|
});
|
|
145
253
|
if (!verdict.ok) {
|
|
146
254
|
for (const v of verdict.violations) console.error(v.id, v.message);
|
|
147
|
-
process.exit(1); //
|
|
255
|
+
process.exit(1); // do not land
|
|
148
256
|
}
|
|
149
|
-
// ③
|
|
257
|
+
// ③ Only a pass writes
|
|
150
258
|
```
|
|
151
259
|
|
|
152
260
|
---
|
|
153
261
|
|
|
154
|
-
##
|
|
262
|
+
## Tests
|
|
155
263
|
|
|
156
264
|
```bash
|
|
157
|
-
pnpm check && pnpm test #
|
|
265
|
+
pnpm check && pnpm test # syntax check + 79 unit tests (incl. incident regressions)
|
|
158
266
|
```
|
|
159
267
|
|
|
160
|
-
-
|
|
161
|
-
-
|
|
268
|
+
- **Synthetic fixtures** (in-repo): legal session / seq gap / empty sourceEventSeqs
|
|
269
|
+
/ turn-null append / unknown type / bad chunk row / torn tail frame / unknown
|
|
270
|
+
marker prefix / self-shadowing etc.
|
|
271
|
+
- **Real fossils** (not in-repo, contain user data): run locally
|
|
162
272
|
|
|
163
273
|
```bash
|
|
164
|
-
node scripts/check-local-fossils.mjs #
|
|
274
|
+
node scripts/check-local-fossils.mjs # scans ../ for backup-session-*.jsonl.zstd
|
|
165
275
|
```
|
|
166
276
|
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
| 工具 | 象限 | 状态 |
|
|
174
|
-
|---|---|---|
|
|
175
|
-
| [workbuddy-session-fork](https://github.com/yamingmou/workbuddy-session-fork) | 会话分叉 · 状态管理 | ✅ 已发布 v1.2.0 |
|
|
176
|
-
| **dsh-log-contract**(本仓库) | 日志契约 · 接口稳定性 | ✅ Phase 1(check/prewrite)+ Phase 1.5(fix)0.2.0 |
|
|
177
|
-
| dsh-turn-guard(规划中) | 中断回合 · 异常韧性 | 待立项 |
|
|
178
|
-
|
|
179
|
-
三者共享同一份 DSH 日志事件契约认知(59 条审计发现 = spec,aborted/corrupt/seqgap 化石 = 测试集)。dsh-retrace(回溯时间线)可把本工具的违规标记渲染到时间线上;本工具是 retrace 投影源健康度的**前置保险**。
|
|
277
|
+
Known truth table: incident-repaired sessions PASS; `seqgap`/`corrupt`/
|
|
278
|
+
`rewritten-230542` FAIL; `spliced-orphan` PASS (legal for the persistence layer —
|
|
279
|
+
#3632's "consumer path deems it unreadable" is a different contract; this tool only
|
|
280
|
+
guards the persistence contract layer, see the boundary note in
|
|
281
|
+
[docs/CONTRACTS.md](docs/CONTRACTS.md)).
|
|
180
282
|
|
|
181
283
|
---
|
|
182
284
|
|
|
183
285
|
## Roadmap
|
|
184
286
|
|
|
185
|
-
- [x] **Phase 1
|
|
186
|
-
- [x] **Phase 1.5
|
|
187
|
-
|
|
188
|
-
-
|
|
189
|
-
|
|
190
|
-
|
|
287
|
+
- [x] **Phase 1 (0.1.0)**: CLI offline check + pre-write validation + contract catalog
|
|
288
|
+
- [x] **Phase 1.5 (0.2.0)**: `fix` subcommand (strict seq scan + W1/W2 wire checks +
|
|
289
|
+
marker removal with renumbering + official frame rebuild); CI integration
|
|
290
|
+
(`dsh-log-contract check` as a scheduled guard over the Harness session dir)
|
|
291
|
+
- [x] **0.3.x (2026-08-30 incident hardening)**: T1 token-meter pairing → 0.3.1 W1/W2
|
|
292
|
+
fold-position fix → 0.3.2 `tailSeq` → 0.3.3 `fix --neutralize` (in-place
|
|
293
|
+
turn-null neutralization) → 0.3.4 `fix --clip-crossstep` (cross-step clipping) →
|
|
294
|
+
0.3.5 **T2/S9/I1 rules** (cross-step source refs / physical order / inbox replay)
|
|
295
|
+
- [ ] Phase 2: runtime guard (subscribe to the session append stream, validate live,
|
|
296
|
+
mark violations as `dsh/contract-violation`, policy configurable alert/block) —
|
|
297
|
+
DSH plugin form
|
|
298
|
+
- [ ] Phase 3: link with dsh-turn-guard / dsh-retrace timeline
|
|
299
|
+
|
|
300
|
+
## License
|
|
191
301
|
|
|
192
302
|
MIT © OfferKuai Team
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
## 🧭 会话考古(extract / audit-report)
|
|
196
|
-
|
|
197
|
-
DSH 会话日志持久化了每次工具调用的完整输入输出——数据资产与审计资产。
|
|
198
|
-
本工具提供只读考古能力:
|
|
199
|
-
|
|
200
|
-
```sh
|
|
201
|
-
# 按命令正则导出工具输出(保留原始文本)
|
|
202
|
-
dsh-log-contract extract <session-log> --pattern "seed-scale" --min-size 50 --out ./found
|
|
203
|
-
|
|
204
|
-
# 考古审计报告:调用数 / 配对率 / 孤儿数 / 命令分布
|
|
205
|
-
dsh-log-contract audit-report <session-log>
|
|
206
|
-
```
|
|
207
|
-
|
|
208
|
-
契约规则 P3(tool/call↔tool/result 配对完整性)与 P4(输出结构可解析)
|
|
209
|
-
守护"挖得动":孤儿调用、text 字段异常在 check 中告警。
|
package/README.zh.md
ADDED
|
@@ -0,0 +1,253 @@
|
|
|
1
|
+
<div align="center">
|
|
2
|
+
|
|
3
|
+
# 🔒 dsh-log-contract
|
|
4
|
+
|
|
5
|
+
**日志契约守护** —— DSH 会话日志的结构契约保险丝:离线体检 + 写前校验。业务层的**医生**。
|
|
6
|
+
|
|
7
|
+
[](https://www.npmjs.com/package/dsh-log-contract)
|
|
8
|
+
[](https://www.npmjs.com/package/dsh-log-contract)
|
|
9
|
+
[](https://github.com/yamingmou/dsh-log-contract/blob/main/LICENSE)
|
|
10
|
+
[](https://github.com/topics/dsh-plugin)
|
|
11
|
+
[](https://github.com/yamingmou/dsh-log-contract/pulls)
|
|
12
|
+
|
|
13
|
+
[English](./README.md) · **简体中文**
|
|
14
|
+
|
|
15
|
+
</div>
|
|
16
|
+
|
|
17
|
+
# dsh-log-contract · 日志契约守护
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
> DSH(DeepSeek Harness)会话日志的**结构契约保险丝**:离线体检 + 写前校验。
|
|
21
|
+
> 原名 `log-contract-validator`(候选二号),按 Offer快 三件套规划定名 **`dsh-log-contract`**。
|
|
22
|
+
|
|
23
|
+
给 DSH 会话日志(`*.jsonl` / `*.jsonl.zstd`)装一条保险丝:人眼看不出、程序解析会崩的日志格式漂移,在它这里被拦下并告警。它不判断日志**内容**对不对,只守护日志**结构**是否破坏了下游消费者(Harness 读路径、客户端引擎、插件 marker 语义)的预期。
|
|
24
|
+
|
|
25
|
+
- **`check <session-log>`** —— 离线体检:官方解码器全量解码 + 契约逐条校验 + foldSurface 终验,产出违规报告。
|
|
26
|
+
- **`prewrite <edit-file> --log <session-log>`** —— ★ 写前校验:任何写入(追加 / 帧级手术)在落盘之前先过三层契约,违约即拦。
|
|
27
|
+
- **`contracts`** —— 列出内置契约规则目录(每条附官方源码出处)。
|
|
28
|
+
|
|
29
|
+
---
|
|
30
|
+
|
|
31
|
+
## 它在业务层里的位置
|
|
32
|
+
|
|
33
|
+
> **dsh-log-contract 是 [dsh-retrace](https://github.com/yamingmou/dsh-retrace) 的核心能力组件**(业务层的「医生」模块):负责会话日志的**体检与修复**——让每一次撤回/编辑/回退都落在合法日志上,让 /compact 永不失效。
|
|
34
|
+
|
|
35
|
+
| 层 | 是什么 | 组件 |
|
|
36
|
+
|---|---|---|
|
|
37
|
+
| **Agent 业务层(生产级保证)** | 抽象核心能力:会话卫生 / 可回溯 / 可审计 / 可恢复,与平台无关 | 四模块:治理 / 看 / 考古 / **医生** |
|
|
38
|
+
| **dsh-retrace** | 业务层在 DeepSeek Harness 上的实现(生产级业务插件) | 撤回/编辑/版本/回退/看门狗 |
|
|
39
|
+
| **dsh-log-contract** | dsh-retrace 的核心能力组件 = 业务层的**医生**(体检/修复) | check / prewrite / fix / extract / audit |
|
|
40
|
+
|
|
41
|
+
**含义**:dsh-log-contract 独立发布(供单独使用或二次开发),但它首先是
|
|
42
|
+
dsh-retrace 的「日志体检与修复」能力——与 dsh-retrace 一起构成
|
|
43
|
+
**Agent 业务层(生产级保证)** 在 DSH 上的落地(详见 [dsh-retrace 路线图](https://github.com/yamingmou/dsh-retrace/blob/main/docs/ROADMAP.md))。
|
|
44
|
+
|
|
45
|
+
---
|
|
46
|
+
|
|
47
|
+
## 为什么需要它
|
|
48
|
+
|
|
49
|
+
**#3632「one log, two consumers, two verdicts」**:一条日志同时被人类与自动化程序消费,人眼容忍格式微调,程序解析依赖严格契约;格式一旦漂移,人看不出问题,程序直接崩溃或误报。
|
|
50
|
+
|
|
51
|
+
**这里每一条规则都来自真实事故**——见下方 [⚡ 事故记录](#-事故记录)。每起事故都是一个回归夹具:损坏的会话本工具必须报出,修复后的会话必须通过。
|
|
52
|
+
|
|
53
|
+
---
|
|
54
|
+
|
|
55
|
+
## 三层契约(判定模型)
|
|
56
|
+
|
|
57
|
+
> 30+ 条规则,覆盖以下三层(`contracts` 列出全部,每条附官方源码出处)。
|
|
58
|
+
|
|
59
|
+
| 层 | 规则 | 守护什么 |
|
|
60
|
+
|---|---|---|
|
|
61
|
+
| **持久化层** | H/R/E/S(含 **S5**)+ **S9** | seq 连续、type 已知、surfaceOp 合法、`sourceEventSeqs` 完整覆盖被替换节点、文件物理序单调、`foldSurface` 不抛 |
|
|
62
|
+
| **客户端引擎层** | **M1** + **T1 / T2 / I1** | turn-null marker 只能 replace;token-meter 配对;跨 step 源引用;inbox seed 相对重放 |
|
|
63
|
+
| **wire 消息流** | **W1 / W2** | tool 消息跟在带 tool_calls 的 assistant 之后;user 文本不插在 tool_calls 与结果之间 |
|
|
64
|
+
| **插件语义层** | P1/P2 | marker 前缀可识别;marker 自身 seq 不进自身 shadowed 集 |
|
|
65
|
+
|
|
66
|
+
> 校验哲学:先用与官方同语义的增量重放做**逐事件归因**(定位到 seq/行号),再跑官方 `foldSurface` 做**终验**(不抛才算过)——两套都绿才过。
|
|
67
|
+
|
|
68
|
+
---
|
|
69
|
+
|
|
70
|
+
## ⚡ 事故记录 ——「生产级保证」不是口号
|
|
71
|
+
|
|
72
|
+
下面的每一条规则都来自我们工作区的一起**真实事故**。日期与形态真实,会话 id 为隐私省略。
|
|
73
|
+
|
|
74
|
+
| # | 日期 | 发生了什么 | 产出的规则/修复 |
|
|
75
|
+
|---|---|---|---|
|
|
76
|
+
| 1 | 2026-08-25 | 一次「恢复被隐藏内容」的修复写了**清空 `sourceEventSeqs`** 的 replace marker → 会话加载被拒(`SessionPersistenceCorruptionError`);第二次尝试把 marker 改成 **append** → 客户端引擎崩溃。两次都是**违约写入没被拦**。 | **S5**(sourceEventSeqs 必须覆盖被替换节点)、**M1**(turn-null 的 assistant/message 只能 replace)、写前校验 |
|
|
77
|
+
| 2 | 2026-08-27~28 | 中断/暂停的轮次恢复时按**过期内存光标**重放,把旧 seq 追加到文件尾(尾部回归、重复批次);两个写入者交织 → **文件物理序非单调**(`734056 → 733539 → 735470`)。会话 `seq gap` 加载失败。 | **S9**(物理序单调)、fix `--tail-renumber` |
|
|
78
|
+
| 3 | 2026-08-27~28 | **fork 边界孤儿 spliced**:fork 的「移除父待处理提示词」splice 假设父会话 inbox;子会话 seed 相对重放里 inbox 为空 → `resume failed: invalid persisted inbox splice`。 | **I1**(inbox seed 相对重放)、fix `--neutralize-orphan` |
|
|
79
|
+
| 4 | 2026-08-28 | 超限会话(**1,052,557 tokens** vs 1M 窗口)既无法继续也无法 `/compact`;裁剪预算估算对中文低估 ~3.7×。 | T1(token-meter 配对,保障可压缩)、`fix --trim` 预算指引 |
|
|
80
|
+
| 5 | 2026-08-29 | **W1/W2 wire 违规**:marker 遮蔽了带 tool_calls 的 assistant 但漏盖 tool 结果 → 悬空 tool,严格端点 `INVALID_REQUEST` 拒绝请求流。 | **W1 / W2**(wire 消息流) |
|
|
81
|
+
| 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` |
|
|
82
|
+
|
|
83
|
+
> **结论**:这个工具的每条规则都是一次真实会话留下的疤——用真实的损坏会话夹具验证过,不是合成理论。这就是这里「生产级」的含义。
|
|
84
|
+
|
|
85
|
+
---
|
|
86
|
+
|
|
87
|
+
## 安装
|
|
88
|
+
|
|
89
|
+
```bash
|
|
90
|
+
pnpm add -D dsh-log-contract # 或 npm install
|
|
91
|
+
pnpm dlx dsh-log-contract --help
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
> **你是 dsh-retrace 用户?** 无需单独安装——`dsh-retrace` 已把 `dsh-log-contract`
|
|
95
|
+
> 声明为依赖,装 retrace 时自动带好契约守护(体检/写前校验/修复原语全部随插件生效)。
|
|
96
|
+
> 本包独立发布,供愿意单独使用或二次开发的用户直接引入。
|
|
97
|
+
>
|
|
98
|
+
> **从 GitHub 下载了 ZIP?** 解压后 `cd dsh-log-contract && npm install && npm run build`,
|
|
99
|
+
> 然后 `node bin/dsh-log-contract.mjs check <session-log>` 即可使用(无需全局安装)。
|
|
100
|
+
|
|
101
|
+
依赖:Node ≥ 22(`node:zlib` 内置 zstd)、`@deepseek-ai/dsh-session`(peer,校验/解码复用官方实现,保证与 Harness 读路径同源)。
|
|
102
|
+
|
|
103
|
+
---
|
|
104
|
+
|
|
105
|
+
## CLI 用法
|
|
106
|
+
|
|
107
|
+
### 1. 离线体检
|
|
108
|
+
|
|
109
|
+
```bash
|
|
110
|
+
dsh-log-contract check ~/.dsh/sessions/<id>.jsonl.zstd
|
|
111
|
+
dsh-log-contract check ~/.dsh/sessions/<id>.jsonl.zstd --json # 机器可读
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
输出示例:
|
|
115
|
+
|
|
116
|
+
```
|
|
117
|
+
📋 dsh-log-contract check —— backup-session-xxxx.jsonl.zstd
|
|
118
|
+
事件 204754 | surface 节点 16 | replace 代数 5 | 帧 8620(3439.5KiB → 8191.3KiB)
|
|
119
|
+
违规 1(error 1 / warning 0)
|
|
120
|
+
|
|
121
|
+
[error] S5 @ seq 156425 / line 778 (assistant/message)
|
|
122
|
+
surface replace: sourceEventSeqs 必须覆盖每个被替换节点;缺失 121774, 121779(共 2 个)
|
|
123
|
+
|
|
124
|
+
❌ 未通过:见上方违规明细(error 级 = 会话不可读/不可写)
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
退出码:0 = 通过(无 error 级违规);1 = 存在 error 级违规。
|
|
128
|
+
|
|
129
|
+
`check` 自 0.2.0 起新增 **W1/W2 wire 级检查**:按 surface 顺序展开模型请求消息流,
|
|
130
|
+
捕获"悬空 tool 消息"(tool 结果没有前置 assistant tool_calls)与"user 文本插在
|
|
131
|
+
tool_calls 与其结果之间"——这类问题 DeepSeek 曾容忍,但 MiMo 等严格端点会直接
|
|
132
|
+
`INVALID_REQUEST`(2026-08-27 实锤)。
|
|
133
|
+
|
|
134
|
+
### 2. 修复(`fix`)
|
|
135
|
+
|
|
136
|
+
```bash
|
|
137
|
+
# 干跑(只报告):严格 seq 连续扫描 + 全契约体检(含 W1/W2)+ 可移除 marker 数
|
|
138
|
+
dsh-log-contract fix ~/.dsh/sessions/<id>.jsonl.zstd --remove-markers
|
|
139
|
+
|
|
140
|
+
# 应用:备份后落盘(.zstd 走官方帧格式重建:帧1=header、帧2=其余、checksum、单个结尾换行)
|
|
141
|
+
dsh-log-contract fix ~/.dsh/sessions/<id>.jsonl.zstd --remove-markers --apply
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
- `--remove-markers`:移除 retrace/message-editor marker 并全量重编号
|
|
145
|
+
(seq/seq0/sourceEventSeqs/surfaceOp 同步)——用于大范围 marker 遮蔽历史、
|
|
146
|
+
marker 漏盖 tool/result 导致的悬空 tool。
|
|
147
|
+
- `--neutralize`:原地中和 turn-null marker(事故 #6)——type → `retrace/marker` +
|
|
148
|
+
`ignorable:true`,删 surfaceOp/sourceEventSeqs,seq/行数不变(会话驻留也安全)。
|
|
149
|
+
- `--clip-crossstep`:裁剪跨 step sourceEventSeqs(事故 #6)——只保留同 turn/step 的 chunk 引用。
|
|
150
|
+
- 手术安全协议:改前备份、改后全量复检(strictScan + check + foldSurface)、
|
|
151
|
+
marker 只能遮蔽其之前的节点、marker 绝不能改成 append(M1 客户端崩溃)。
|
|
152
|
+
- ⚠️ 若会话已被运行中的应用驻留内存,修复文件后需**重启应用**(强杀避免脏状态刷回)。
|
|
153
|
+
|
|
154
|
+
### 3. 写前校验(`prewrite`)
|
|
155
|
+
|
|
156
|
+
`edit-file` 为 JSON,两种形状:
|
|
157
|
+
|
|
158
|
+
```jsonc
|
|
159
|
+
// 拟追加一个事件到日志尾部(seq 缺省 = 自动按 nextSeq 赋值)
|
|
160
|
+
{ "append": { "type": "assistant/message", "surfaceOp": { "op": "replace", "start": 121774, "end": 156421 }, "sourceEventSeqs": [121774, 121779, "…"], "data": { "turn": null, "step": null, "message": { "…": "…" }, "editor": { "targetSeq": 156430, "text": "…" } } } }
|
|
161
|
+
|
|
162
|
+
// 帧级手术后的完整事件列表(改后确认,与改前基线双绿才允许落盘)
|
|
163
|
+
{ "edit": [ "…完整事件列表…" ] }
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
```bash
|
|
167
|
+
dsh-log-contract prewrite marker-write.json --log ~/.dsh/sessions/<id>.jsonl.zstd
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
- 基线本身有 error 级违规时直接拒绝校验(安全修复协议第 2 步:**改前基线必须绿**)。
|
|
171
|
+
- 判定通过才允许落盘——**validate first, commit later**(与官方 `SurfaceManager.validateNext` 同思路)。
|
|
172
|
+
|
|
173
|
+
### 4. 契约目录
|
|
174
|
+
|
|
175
|
+
```bash
|
|
176
|
+
dsh-log-contract contracts
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
完整契约清单见 [docs/CONTRACTS.md](docs/CONTRACTS.md)。
|
|
180
|
+
|
|
181
|
+
### 5. 会话考古(`extract` / `audit-report`)
|
|
182
|
+
|
|
183
|
+
DSH 会话日志持久化了每次工具调用的完整输入输出——数据资产与审计资产。
|
|
184
|
+
只读考古能力:
|
|
185
|
+
|
|
186
|
+
```sh
|
|
187
|
+
# 按命令正则导出工具输出(保留原始文本)
|
|
188
|
+
dsh-log-contract extract <session-log> --pattern "seed-scale" --min-size 50 --out ./found
|
|
189
|
+
|
|
190
|
+
# 考古审计报告:调用数 / 配对率 / 孤儿数 / 命令分布
|
|
191
|
+
dsh-log-contract audit-report <session-log>
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
契约规则 P3(tool/call↔tool/result 配对完整性)与 P4(输出结构可解析)
|
|
195
|
+
守护"挖得动":孤儿调用、text 字段异常在 check 中告警。
|
|
196
|
+
|
|
197
|
+
---
|
|
198
|
+
|
|
199
|
+
## Node API(写前校验嵌入你的脚本)
|
|
200
|
+
|
|
201
|
+
```js
|
|
202
|
+
import { loadSessionLog, validateSessionLog, createPreWriter } from 'dsh-log-contract';
|
|
203
|
+
|
|
204
|
+
// ① 基线体检(改前基线必须绿)
|
|
205
|
+
const log = loadSessionLog('session.jsonl.zstd');
|
|
206
|
+
const baseline = validateSessionLog(log);
|
|
207
|
+
if (!baseline.ok) throw new Error('基线已坏,先修基线');
|
|
208
|
+
|
|
209
|
+
// ② 写前校验:拟写入一个 marker replace
|
|
210
|
+
const prewriter = createPreWriter({ events: log.events.map((e) => e.event) });
|
|
211
|
+
const verdict = prewriter.validateAppend({
|
|
212
|
+
type: 'assistant/message',
|
|
213
|
+
surfaceOp: { op: 'replace', start: 121774, end: 156421 },
|
|
214
|
+
sourceEventSeqs: [121774, 121779 /* …必须完整覆盖被替换节点… */],
|
|
215
|
+
data: { turn: null, step: null, message: { /* … */ } },
|
|
216
|
+
});
|
|
217
|
+
if (!verdict.ok) {
|
|
218
|
+
for (const v of verdict.violations) console.error(v.id, v.message);
|
|
219
|
+
process.exit(1); // 不落盘
|
|
220
|
+
}
|
|
221
|
+
// ③ 通过后才写
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
---
|
|
225
|
+
|
|
226
|
+
## 测试
|
|
227
|
+
|
|
228
|
+
```bash
|
|
229
|
+
pnpm check && pnpm test # 语法检查 + 79 个单测(含事故回归用例)
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
- **合成夹具**(入库):合法会话 / seq 缺口 / 空 sourceEventSeqs / turn=null append / 未知 type / 坏 chunk 行 / 撕裂尾帧 / 未知 marker 前缀 / 自指 shadowed 等。
|
|
233
|
+
- **真实化石**(不入库,含用户隐私):本地跑
|
|
234
|
+
|
|
235
|
+
```bash
|
|
236
|
+
node scripts/check-local-fossils.mjs # 扫描 ../ 下 backup-session-*.jsonl.zstd
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
已知真值表:事故修复后会话 PASS;`seqgap`/`corrupt`/`rewritten-230542` FAIL;`spliced-orphan` PASS(持久化层合法——#3632 的"消费路径判不可读"属于另一类契约,本工具只守护持久化契约层,见 [docs/CONTRACTS.md](docs/CONTRACTS.md) 边界说明)。
|
|
240
|
+
|
|
241
|
+
---
|
|
242
|
+
|
|
243
|
+
## Roadmap
|
|
244
|
+
|
|
245
|
+
- [x] **Phase 1(0.1.0)**:CLI 离线体检 + 写前校验 + 契约目录
|
|
246
|
+
- [x] **Phase 1.5(0.2.0)**:`fix` 子命令(严格 seq 扫描 + W1/W2 wire 检查 + 移除 marker 重编号 + 官方帧格式重建);CI 集成(`dsh-log-contract check` 作为 Harness 会话目录的定时守护)
|
|
247
|
+
- [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 重放)
|
|
248
|
+
- [ ] Phase 2:运行时守护(订阅 session append 事件流实时校验,断裂即标记 `dsh/contract-violation` 事件,策略可配 告警/拦截)——DSH 插件形态
|
|
249
|
+
- [ ] Phase 3:与 dsh-turn-guard / dsh-retrace 时间线联动
|
|
250
|
+
|
|
251
|
+
## 许可
|
|
252
|
+
|
|
253
|
+
MIT © OfferKuai Team
|
package/lib/checks.js
CHANGED
|
@@ -452,6 +452,7 @@ export function tokenMeterSourceViolations(events) {
|
|
|
452
452
|
const turn = event.data?.turn;
|
|
453
453
|
const step = event.data?.step;
|
|
454
454
|
if (turn == null || step == null) continue; // turn-null marker 由 T1 覆盖
|
|
455
|
+
if (event.data?.usage === void 0) continue; // 无 usage 不触发 _estimateProviderAssistant(官方 :592 前提)——replace marker(S5 遮蔽语义,sourceEventSeqs 含非 chunk 节点)合法
|
|
455
456
|
const seen = new Set();
|
|
456
457
|
for (const s of event.sourceEventSeqs) {
|
|
457
458
|
if (s >= event.seq) {
|
|
@@ -464,7 +465,10 @@ export function tokenMeterSourceViolations(events) {
|
|
|
464
465
|
}
|
|
465
466
|
seen.add(s);
|
|
466
467
|
const src = bySeq.get(s);
|
|
467
|
-
if (!src || src.event.type !== 'assistant/chunk')
|
|
468
|
+
if (!src || src.event.type !== 'assistant/chunk') {
|
|
469
|
+
out.push(violation('T2', { seq: event.seq, lineNo, eventType: event.type }, `assistant/message at seq ${event.seq} source seq ${s} is not assistant/chunk(实际 ${src ? src.event.type : 'MISSING'})——token meter 折叠会抛错(_estimateProviderAssistant :644 对非 chunk 引用直接 throw),/compact 与压力测量永久失败`));
|
|
470
|
+
break; // 官方抛一次即停(consumedEvents 不前进),只报首条
|
|
471
|
+
}
|
|
468
472
|
if (src.event.data?.turn !== turn || src.event.data?.step !== step) {
|
|
469
473
|
out.push(violation('T2', { seq: event.seq, lineNo, eventType: event.type }, `assistant/message at seq ${event.seq} source seq ${s} belongs to another step(消息 turn ${turn}/step ${step},源 turn ${String(src.event.data?.turn)}/step ${String(src.event.data?.step)})——token meter 折叠会抛错,/compact 与压力测量永久失败(DSH resend 在 step 未关时跨 step 引用)`));
|
|
470
474
|
break; // 官方抛一次即停(consumedEvents 不前进),只报首条
|
package/lib/repair.js
CHANGED
|
@@ -156,10 +156,11 @@ export function clipCrossStepSourcesText(text) {
|
|
|
156
156
|
if (event.type !== 'assistant/message' || !Array.isArray(event.sourceEventSeqs) || event.sourceEventSeqs.length === 0) continue;
|
|
157
157
|
const { turn, step } = event.data ?? {};
|
|
158
158
|
if (turn == null || step == null) continue; // turn-null marker 由 neutralize 处理
|
|
159
|
+
if (event.data?.usage === void 0) continue; // 无 usage 不触发 _estimateProviderAssistant(官方 :592 前提)——replace marker 的 sourceEventSeqs 是 S5 遮蔽语义,非 chunk 引用合法,不可裁
|
|
159
160
|
let dirty = false;
|
|
160
161
|
const kept = event.sourceEventSeqs.filter((s) => {
|
|
161
162
|
const src = bySeq.get(s)?.event;
|
|
162
|
-
if (!src || src.type !== 'assistant/chunk')
|
|
163
|
+
if (!src || src.type !== 'assistant/chunk') { dirty = true; return false; } // 非 chunk/缺失引用官方 :644 直接 throw → 裁剪
|
|
163
164
|
if (src.data?.turn === turn && src.data?.step === step) return true;
|
|
164
165
|
dirty = true;
|
|
165
166
|
return false;
|
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.6",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "lib/index.js",
|
|
7
7
|
"bin": {
|
|
@@ -24,7 +24,8 @@
|
|
|
24
24
|
"bin/**/*.mjs",
|
|
25
25
|
"docs/**/*.md",
|
|
26
26
|
"README.md",
|
|
27
|
-
"LICENSE"
|
|
27
|
+
"LICENSE",
|
|
28
|
+
"README.zh.md"
|
|
28
29
|
],
|
|
29
30
|
"keywords": [
|
|
30
31
|
"dsh",
|