dsh-log-contract 0.3.7 → 0.3.9
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 +10 -4
- package/README.zh.md +13 -1
- package/lib/checks.js +91 -0
- package/lib/contracts.js +24 -0
- package/lib/index.js +1 -1
- package/lib/prewrite.js +19 -1
- package/lib/validate.js +18 -9
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -274,10 +274,16 @@ pnpm check && pnpm test # syntax check + 79 unit tests (incl. incident regres
|
|
|
274
274
|
node scripts/check-local-fossils.mjs # scans ../ for backup-session-*.jsonl.zstd
|
|
275
275
|
```
|
|
276
276
|
|
|
277
|
-
Known truth table
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
277
|
+
Known truth table (updated 2026-08-31 — after 0.3.5 added I1, `spliced-orphan`
|
|
278
|
+
now FAILs; the old PASS row was stale):
|
|
279
|
+
- `2c3f87d4-corrupt` / `b7713ea1-seqgap` / `rewritten-230542` → FAIL (seq gaps)
|
|
280
|
+
- `2c3f87d4-spliced-orphan` → **FAIL (0.3.5+)** (T1/I1: invalid inbox splice + turn-null)
|
|
281
|
+
- `e61d70da-pre-markerfix-20260825` → FAIL (pre-fix sample: turn-null markers remain)
|
|
282
|
+
- `c2d05ce9-pre-cleansession-20260831` → PASS (3256-span replace marker is data-legal;
|
|
283
|
+
official foldSurface replays cleanly — see the plugin ledger)
|
|
284
|
+
The truth table tracks rule evolution (0.3.5's I1 flipped spliced-orphan PASS→FAIL);
|
|
285
|
+
"repaired sessions PASS" must be verified on rebuilt samples — `pre-` backups are
|
|
286
|
+
usually pre-fix bad samples. See the boundary note in
|
|
281
287
|
[docs/CONTRACTS.md](docs/CONTRACTS.md)).
|
|
282
288
|
|
|
283
289
|
---
|
package/README.zh.md
CHANGED
|
@@ -236,7 +236,19 @@ pnpm check && pnpm test # 语法检查 + 79 个单测(含事故回归用例
|
|
|
236
236
|
node scripts/check-local-fossils.mjs # 扫描 ../ 下 backup-session-*.jsonl.zstd
|
|
237
237
|
```
|
|
238
238
|
|
|
239
|
-
|
|
239
|
+
已知真值表(2026-08-31 实测更新——0.3.5 加 I1 后,`spliced-orphan` 已可报错,旧表 PASS 过时):
|
|
240
|
+
|
|
241
|
+
| 化石 | 判定 | 违规 |
|
|
242
|
+
|---|---|---|
|
|
243
|
+
| `2c3f87d4-corrupt` | FAIL | S8/C1/T1/E2(seq 缺口 → 加载被拒) |
|
|
244
|
+
| `b7713ea1-seqgap` / `recorrupt` | FAIL | S8/C1/T1/E2/S9(seq 缺口/倒退) |
|
|
245
|
+
| `2c3f87d4-rewritten-230542` | FAIL | S8/C1/T1/E2/I1(重写引入缺口) |
|
|
246
|
+
| `2c3f87d4-spliced-orphan` | **FAIL(0.3.5+)** | T1/I1(inbox splice 无效 + turn-null)——旧表 PASS 已过时 |
|
|
247
|
+
| `e61d70da-pre-markerfix-20260825` | FAIL | T1×5(修复前样本:turn-null marker 残留,非「修复后」) |
|
|
248
|
+
| `c2d05ce9-pre-cleansession-20260831` | **PASS** | error 0(3256 跨度 replace marker 数据合规,官方 foldSurface 重放通过——见插件任务台账) |
|
|
249
|
+
|
|
250
|
+
> 说明:真值表随规则演进更新(0.3.5 新增 I1 后 spliced-orphan 从 PASS 变 FAIL);
|
|
251
|
+
> 「修复后会话 PASS」需用重建/修复后的样本验证,pre- 前缀备份多为修复前坏样本。
|
|
240
252
|
|
|
241
253
|
---
|
|
242
254
|
|
package/lib/checks.js
CHANGED
|
@@ -296,6 +296,97 @@ export function finalFold(events) {
|
|
|
296
296
|
}
|
|
297
297
|
}
|
|
298
298
|
|
|
299
|
+
/**
|
|
300
|
+
* T3 —— step 节点 key 冲突(2026-09-02 · 1e99e1ff 白屏真正根因固化)。
|
|
301
|
+
*
|
|
302
|
+
* 客户端渲染消息列表 = 从事件流构建节点,节点 key = `data.turn:data.step`
|
|
303
|
+
* (React 列表 key)。同 turn 内两个 step/start 的 step 号相同 → key 冲突 →
|
|
304
|
+
* React 渲染死循环 → 白屏/不展示(1e99e1ff:seq 580034 step 95/1 vs 580037
|
|
305
|
+
* 编辑块 step 95/1;修复线 2026-09-02 全量扫描另发现 6924781d / 97786207 /
|
|
306
|
+
* 4b149a4a 同型冲突,均整块重编号修复)。
|
|
307
|
+
*
|
|
308
|
+
* 判定:扫 step/start,`data.turn:data.step` 组合重复 = error。
|
|
309
|
+
* - 无任何 step/start 的日志跳过(与 T1 同款宽松);
|
|
310
|
+
* - turn/step 为 null/undefined 的 step/start 不参与 key 判定(那是 T4 的域,
|
|
311
|
+
* 且 null 自身已是致命)。
|
|
312
|
+
*
|
|
313
|
+
* @param events - 行序事件流(`{event, lineNo}`)。
|
|
314
|
+
* @returns T3 违规列表。
|
|
315
|
+
*/
|
|
316
|
+
export function stepKeyViolations(events) {
|
|
317
|
+
const out = []
|
|
318
|
+
if (!events.some(({ event }) => event.type === 'step/start')) return out
|
|
319
|
+
const seen = new Map() // `${turn}:${step}` → { seq, lineNo }
|
|
320
|
+
for (const { event, lineNo } of events) {
|
|
321
|
+
if (event.type !== 'step/start') continue
|
|
322
|
+
const turn = event.data?.turn
|
|
323
|
+
const step = event.data?.step
|
|
324
|
+
if (turn === null || turn === undefined || step === null || step === undefined) continue // T4 域
|
|
325
|
+
const key = `${String(turn)}:${String(step)}`
|
|
326
|
+
const prev = seen.get(key)
|
|
327
|
+
if (prev !== undefined) {
|
|
328
|
+
out.push(violation('T3', { seq: event.seq, lineNo, eventType: event.type }, `step 节点 key ${key} 冲突:seq ${prev.seq} 与 seq ${event.seq} 的 step/start 同 turn 同 step——客户端 React 渲染死循环白屏(1e99e1ff 事故;修复:同 turn 内 step 递增,不得复用;整块重编号)`))
|
|
329
|
+
} else {
|
|
330
|
+
seen.set(key, { seq: event.seq, lineNo })
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
return out
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
/**
|
|
337
|
+
* T4 —— step/消息本体 turn 缺失(null/undefined)(2026-09-01 · D8 1e99e1ff 固化)。
|
|
338
|
+
*
|
|
339
|
+
* 客户端渲染状态机对 turn=null 的 step/消息**无法归属任何 turn** → 渲染死循环 →
|
|
340
|
+
* 白屏「载入历史」(1e99e1ff seq 580037-580039:retrace 0.4.17 写的编辑块
|
|
341
|
+
* step/start+marker+step/end turn 全 null;修复线 check-null-turn.mjs 把 step
|
|
342
|
+
* 包裹/消息本体的 null-turn 判为致命)。
|
|
343
|
+
*
|
|
344
|
+
* 判定:`step/start|step/end|assistant/message` 的 `data.turn === null/undefined` = error。
|
|
345
|
+
* - user/message 天然不带 turn(坐标来自外层 step),不查;
|
|
346
|
+
* - assistant/chunk 坐标可能天然缺失,不查;
|
|
347
|
+
* - retrace/marker(neutralize 产物,ignorable)不查。
|
|
348
|
+
*
|
|
349
|
+
* @param events - 行序事件流(`{event, lineNo}`)。
|
|
350
|
+
* @returns T4 违规列表。
|
|
351
|
+
*/
|
|
352
|
+
export function nullTurnStepViolations(events) {
|
|
353
|
+
const out = []
|
|
354
|
+
for (const { event, lineNo } of events) {
|
|
355
|
+
if (event.type !== 'step/start' && event.type !== 'step/end' && event.type !== 'assistant/message') continue
|
|
356
|
+
const turn = event.data?.turn
|
|
357
|
+
if (turn === null || turn === undefined) {
|
|
358
|
+
out.push(violation('T4', { seq: event.seq, lineNo, eventType: event.type }, `${event.type} 的 data.turn 为 ${String(turn)}(缺失)——客户端渲染状态机无法归属任何 turn → 渲染死循环白屏(D8 1e99e1ff;step/消息本体必须带真实 turn 号)`))
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
return out
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
/**
|
|
365
|
+
* T5 —— turn/end 缺 reason.kind(2026-09-02 · 1f4d986e malformed turn/end 固化)。
|
|
366
|
+
*
|
|
367
|
+
* 官方 agent-loop 写 turn/end 恒带 `reason: { kind }`(dsh-agent-loop/lib/index.js:620;
|
|
368
|
+
* kind ∈ completed|max-tokens|blocked|aborted|error|interrupted,中断恢复补
|
|
369
|
+
* interrupted,见 dsh-session interruptedTurnClosers)。官方 validation 强制
|
|
370
|
+
* reason.kind 存在——缺失 = malformed → 会话加载失败
|
|
371
|
+
* (SessionPersistenceCorruptionError "malformed pre-react-loop turn/end",1f4d986e)。
|
|
372
|
+
*
|
|
373
|
+
* 判定:turn/end 的 `data.reason?.kind` 缺失/非字符串 = error。
|
|
374
|
+
*
|
|
375
|
+
* @param events - 行序事件流(`{event, lineNo}`)。
|
|
376
|
+
* @returns T5 违规列表。
|
|
377
|
+
*/
|
|
378
|
+
export function turnEndReasonViolations(events) {
|
|
379
|
+
const out = []
|
|
380
|
+
for (const { event, lineNo } of events) {
|
|
381
|
+
if (event.type !== 'turn/end') continue
|
|
382
|
+
const reason = event.data?.reason
|
|
383
|
+
if (!reason || typeof reason.kind !== 'string') {
|
|
384
|
+
out.push(violation('T5', { seq: event.seq, lineNo, eventType: event.type }, `turn/end 缺 data.reason.kind(reason=${JSON.stringify(reason)})——官方 validation 拒绝 → 会话加载失败(1f4d986e malformed turn/end;turn/end 必须带 reason.kind,镜像 dsh-agent-loop:620)`))
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
return out
|
|
388
|
+
}
|
|
389
|
+
|
|
299
390
|
/**
|
|
300
391
|
* P3 —— tool/call ↔ tool/result 配对完整性(考古任务书 B1)。
|
|
301
392
|
* 每个 tool/call 的 `data.callId` 必须能在 tool/result 的
|
package/lib/contracts.js
CHANGED
|
@@ -229,6 +229,30 @@ export const CONTRACT_RULES = [
|
|
|
229
229
|
source: '@deepseek-ai/dsh-token-meter lib/index.js:634-650 (_estimateProviderAssistant,:645 belongs to another step)',
|
|
230
230
|
description: 'token meter 重建 provider 输出时,逐条检查 assistant/message 的 sourceEventSeqs:指向 assistant/chunk 的引用必须与消息同 turn/step,且 seq 更早、不重复;跨 step 引用 → 官方抛 belongs to another step → 每次事件追加都重抛(consumedEvents 不前进)→ 刷屏压垮 host(2026-08-30 实测 526f1835 seq 936047 跨 step 7/8/9)。T1 只查 step 配对不查源引用,此条补盲区;修复用 fix --clip-crossstep。',
|
|
231
231
|
},
|
|
232
|
+
{
|
|
233
|
+
id: 'T3',
|
|
234
|
+
title: 'step 节点 key 唯一(同 turn 内 step/start 的 step 号不得复用)',
|
|
235
|
+
layer: LAYER.ENGINE,
|
|
236
|
+
severity: SEVERITY.ERROR,
|
|
237
|
+
source: '复盘 2026-09-02 1e99e1ff 白屏(修复线 session-3f9e4f12):客户端渲染节点 key = turn:step,冲突 → React 渲染死循环;工具 tools/check-step-keys.mjs',
|
|
238
|
+
description: '客户端渲染消息列表从事件流构建节点,节点 key = data.turn:data.step。同 turn 内两个 step/start 的 step 号相同 → key 冲突 → React 渲染死循环 → 白屏/不展示(1e99e1ff:580034 step 95/1 vs 580037 编辑块 step 95/1;6924781d/97786207/4b149a4a 同型)。修复:同 turn 内 step 递增、整块重编号(含块内 chunk/tool/assistant)。',
|
|
239
|
+
},
|
|
240
|
+
{
|
|
241
|
+
id: 'T4',
|
|
242
|
+
title: 'step/消息本体 turn 缺失(null/undefined)→ 渲染死循环',
|
|
243
|
+
layer: LAYER.ENGINE,
|
|
244
|
+
severity: SEVERITY.ERROR,
|
|
245
|
+
source: '复盘 D8 1e99e1ff(2026-09-01):retrace 0.4.17 编辑块 turn:null;修复线 tools/check-null-turn.mjs 判致命',
|
|
246
|
+
description: '客户端渲染状态机对 turn=null 的 step/start|step/end|assistant/message 无法归属任何 turn → 渲染死循环 → 白屏「载入历史」(1e99e1ff seq 580037-580039)。user/message 天然无 turn 不查;chunk 坐标可缺失不查。step/消息本体必须带真实 turn 号。',
|
|
247
|
+
},
|
|
248
|
+
{
|
|
249
|
+
id: 'T5',
|
|
250
|
+
title: 'turn/end 必须带 data.reason.kind',
|
|
251
|
+
layer: LAYER.ENGINE,
|
|
252
|
+
severity: SEVERITY.ERROR,
|
|
253
|
+
source: '官方 dsh-agent-loop lib/index.js:620(turn/end = {turn, reason:{kind}});1f4d986e malformed turn/end 事故(2026-09-02,修复线 check-turn-end-reason.mjs)',
|
|
254
|
+
description: '官方 validation 强制 turn/end 的 data.reason.kind 存在(kind ∈ completed|max-tokens|blocked|aborted|error|interrupted)。缺失 = malformed → 官方 SessionPersistenceCorruptionError → 会话加载失败。1f4d986e:retrace 情形③信封 turn/end 漏 reason → 每次编辑后加载失败(已修 0.4.18)。',
|
|
255
|
+
},
|
|
232
256
|
{
|
|
233
257
|
id: 'P3',
|
|
234
258
|
title: 'tool/call ↔ tool/result 配对完整性(考古 B1)',
|
package/lib/index.js
CHANGED
|
@@ -9,5 +9,5 @@ export { validateSessionLog, resumeVerdict } from './validate.js';
|
|
|
9
9
|
export { createPreWriter, preWriterFromLog } from './prewrite.js';
|
|
10
10
|
export { repairSession, strictScanText, removeMarkersText, neutralizeMarkersText, clipCrossStepSourcesText, dropFailedTurnsText, trimLastMessagesText, trimLastMessagesByBudget, estimateTokensText, compactLastMessagesText, rebuildZstdText, tailRenumberText, neutralizeOrphanText, extractTurnText, keepRangesText } from './repair.js';
|
|
11
11
|
export { CONTRACT_RULES, LAYER, SEVERITY, ruleById } from './contracts.js';
|
|
12
|
-
export { tokenMeterViolations, tokenMeterSourceViolations, physicalOrderViolations, inboxReplayViolations } from './checks.js';
|
|
12
|
+
export { tokenMeterViolations, tokenMeterSourceViolations, stepKeyViolations, nullTurnStepViolations, turnEndReasonViolations, physicalOrderViolations, inboxReplayViolations } from './checks.js';
|
|
13
13
|
export { auditToolCalls, extractText, extractToolOutputs, indexToolCalls, toolCommandOf } from './archaeology.js';
|
package/lib/prewrite.js
CHANGED
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
* 所有判定复用 `lib/checks.js`(与离线体检同一套逻辑),
|
|
18
18
|
* 保证"体检看到的问题 = 写入前拦下的问题"。
|
|
19
19
|
*/
|
|
20
|
-
import { envelopeViolations, engineViolations, finalFold, isSafeInt, pluginViolations, replaySurface, tokenMeterViolations, violation } from './checks.js';
|
|
20
|
+
import { envelopeViolations, engineViolations, finalFold, isSafeInt, nullTurnStepViolations, pluginViolations, replaySurface, stepKeyViolations, tokenMeterViolations, turnEndReasonViolations, violation } from './checks.js';
|
|
21
21
|
|
|
22
22
|
/** retrace 类 marker:data.editor 存在(assistant/message replace,turn/step=null)。 */
|
|
23
23
|
function isKnownMarkerCandidate(event) {
|
|
@@ -98,6 +98,24 @@ export function createPreWriter(input = {}) {
|
|
|
98
98
|
violations.push(t1);
|
|
99
99
|
}
|
|
100
100
|
}
|
|
101
|
+
// T3/T4 —— 渲染层(2026-09-02 1e99e1ff 白屏)。**error 级拒绝**:
|
|
102
|
+
// - T4:拟写事件(step/start|step/end|assistant/message)turn 缺失 → 客户端
|
|
103
|
+
// 渲染死循环白屏(D8),写入前直接拦下(防再犯:任何写 turn:null 的 marker);
|
|
104
|
+
// - T3:拟写事件引入 step 节点 key 冲突(同 turn 同 step 的 step/start 重复)→
|
|
105
|
+
// 同样拒绝。只判拟写事件自身(历史冲突归属离线 check 的 T3/T4 扫描)。
|
|
106
|
+
for (const v of nullTurnStepViolations(candidateEvents.map((event) => ({ event })))) {
|
|
107
|
+
if (v.seq !== lastCandidate?.seq) continue;
|
|
108
|
+
violations.push(v);
|
|
109
|
+
}
|
|
110
|
+
for (const v of stepKeyViolations(candidateEvents.map((event) => ({ event })))) {
|
|
111
|
+
if (v.seq !== lastCandidate?.seq) continue;
|
|
112
|
+
violations.push(v);
|
|
113
|
+
}
|
|
114
|
+
// T5 —— 拟写 turn/end 缺 reason.kind → error 拒绝(1f4d986e 防再犯)
|
|
115
|
+
for (const v of turnEndReasonViolations(candidateEvents.map((event) => ({ event })))) {
|
|
116
|
+
if (v.seq !== lastCandidate?.seq) continue;
|
|
117
|
+
violations.push(v);
|
|
118
|
+
}
|
|
101
119
|
const bySeverity = { error: 0, warning: 0, info: 0 };
|
|
102
120
|
for (const v of violations) bySeverity[v.severity] = (bySeverity[v.severity] ?? 0) + 1;
|
|
103
121
|
return {
|
package/lib/validate.js
CHANGED
|
@@ -15,9 +15,12 @@ import {
|
|
|
15
15
|
finalFold,
|
|
16
16
|
inboxReplayViolations,
|
|
17
17
|
isSafeInt,
|
|
18
|
+
nullTurnStepViolations,
|
|
18
19
|
physicalOrderViolations,
|
|
19
20
|
pluginViolations,
|
|
20
21
|
replaySurface,
|
|
22
|
+
stepKeyViolations,
|
|
23
|
+
turnEndReasonViolations,
|
|
21
24
|
violation,
|
|
22
25
|
tokenMeterViolations,
|
|
23
26
|
tokenMeterSourceViolations,
|
|
@@ -115,6 +118,12 @@ export function validateSessionLog(log, opts = {}) {
|
|
|
115
118
|
violations.push(...tokenMeterViolations(events));
|
|
116
119
|
violations.push(...tokenMeterSourceViolations(events));
|
|
117
120
|
|
|
121
|
+
// ── T3/T4/T5 · 渲染/契约层(复盘 2026-09-02):节点 key 唯一 + turn 缺失 + turn/end reason ──
|
|
122
|
+
// 数据层"健康"≠ 客户端能渲染/能加载:check/官方加载/分页全绿仍可能白屏或加载失败。
|
|
123
|
+
violations.push(...stepKeyViolations(events));
|
|
124
|
+
violations.push(...nullTurnStepViolations(events));
|
|
125
|
+
violations.push(...turnEndReasonViolations(events));
|
|
126
|
+
|
|
118
127
|
// ── I1 · inbox seed 相对重放(fork 边界孤儿;交接书 L1)──────────────
|
|
119
128
|
violations.push(...inboxReplayViolations(events, header));
|
|
120
129
|
|
|
@@ -190,9 +199,9 @@ export function validateSessionLog(log, opts = {}) {
|
|
|
190
199
|
export function resumeVerdict(result) {
|
|
191
200
|
const { ok, violations = [] } = result;
|
|
192
201
|
// 三档各自的「阻断规则集」——按任务书 §L3 档位定义:
|
|
193
|
-
// 可加载:结构层(PERSISTENCE/FRAMING)+ 引擎层非 I1/T1/T2 的 error;
|
|
202
|
+
// 可加载:结构层(PERSISTENCE/FRAMING)+ 引擎层非 I1/T1/T2/T3/T4 的 error;
|
|
194
203
|
// 可继续:+ I1(inbox 重放);
|
|
195
|
-
// 可压缩:+ T1/T2(token-meter
|
|
204
|
+
// 可压缩:+ T1/T2/T3/T4(token-meter 配对 + 渲染层节点 key/turn)。
|
|
196
205
|
const byId = {};
|
|
197
206
|
for (const v of violations) {
|
|
198
207
|
if (v.severity !== 'error') continue;
|
|
@@ -200,11 +209,11 @@ export function resumeVerdict(result) {
|
|
|
200
209
|
}
|
|
201
210
|
const has = (id) => (byId[id]?.length ?? 0) > 0;
|
|
202
211
|
|
|
203
|
-
// 可加载阻断 = 除 I1/T1/T2 外的所有 error 违规(结构/信封/物理序/工具配对等)。
|
|
204
|
-
// 注意:不能用 result.ok(它把 T1/T2/I1 也计为 error)——三档判定按任务书定义,
|
|
205
|
-
// T1/T2 只影响「可压缩」档、I1 只影响「可继续」档。
|
|
212
|
+
// 可加载阻断 = 除 I1/T1/T2/T3/T4 外的所有 error 违规(结构/信封/物理序/工具配对等)。
|
|
213
|
+
// 注意:不能用 result.ok(它把 T1/T2/T3/T4/I1 也计为 error)——三档判定按任务书定义,
|
|
214
|
+
// T1/T2/T3/T4 只影响「可压缩」档、I1 只影响「可继续」档。
|
|
206
215
|
const loadableBlockers = violations.filter(
|
|
207
|
-
(v) => v.severity === 'error' && !['I1', 'T1', 'T2'].includes(v.id),
|
|
216
|
+
(v) => v.severity === 'error' && !['I1', 'T1', 'T2', 'T3', 'T4', 'T5'].includes(v.id),
|
|
208
217
|
).map((v) => v.id);
|
|
209
218
|
const loadable = loadableBlockers.length === 0;
|
|
210
219
|
|
|
@@ -212,9 +221,9 @@ export function resumeVerdict(result) {
|
|
|
212
221
|
const resumable = loadable && !has('I1');
|
|
213
222
|
|
|
214
223
|
const compactableBlockers = resumable
|
|
215
|
-
? (has('T1') || has('T2') ? ['T1', 'T2'].filter((id) => has(id)) : [])
|
|
224
|
+
? (has('T1') || has('T2') || has('T3') || has('T4') || has('T5') ? ['T1', 'T2', 'T3', 'T4', 'T5'].filter((id) => has(id)) : [])
|
|
216
225
|
: [];
|
|
217
|
-
const compactable = resumable && !has('T1') && !has('T2');
|
|
226
|
+
const compactable = resumable && !has('T1') && !has('T2') && !has('T3') && !has('T4') && !has('T5');
|
|
218
227
|
|
|
219
228
|
// 最差档位
|
|
220
229
|
const verdict = !loadable ? 'broken' : !resumable ? 'loadable' : !compactable ? 'resumable' : 'compactable';
|
|
@@ -232,7 +241,7 @@ export function resumeVerdict(result) {
|
|
|
232
241
|
violationsByTier: {
|
|
233
242
|
structural: [...new Set(loadableBlockers)],
|
|
234
243
|
inbox: has('I1') ? ['I1'] : [],
|
|
235
|
-
tokenMeter: has('T1') || has('T2') ? ['T1', 'T2'].filter((id) => has(id)) : [],
|
|
244
|
+
tokenMeter: has('T1') || has('T2') || has('T3') || has('T4') || has('T5') ? ['T1', 'T2', 'T3', 'T4', 'T5'].filter((id) => has(id)) : [],
|
|
236
245
|
},
|
|
237
246
|
};
|
|
238
247
|
}
|
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.9",
|
|
5
5
|
"packageManager": "pnpm@11.7.0",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"main": "lib/index.js",
|