opencode-cache-engine 0.1.0
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/LICENSE +21 -0
- package/README.md +1032 -0
- package/examples/cache-engine.json +25 -0
- package/opencode-cache-engine-0.1.0.tgz +0 -0
- package/package.json +33 -0
- package/src/cache-engine-core.mjs +728 -0
- package/src/cache-engine.ts +753 -0
- package/test/cache-engine.test.mjs +765 -0
|
@@ -0,0 +1,765 @@
|
|
|
1
|
+
// cache-engine.test.mjs
|
|
2
|
+
//
|
|
3
|
+
// Self-contained validation for cache-engine-core.js using Node's built-in
|
|
4
|
+
// test runner (node:test, available in Node 20). Run with:
|
|
5
|
+
// node --test ~/.config/opencode/plugins/cache-engine.test.mjs
|
|
6
|
+
//
|
|
7
|
+
// These tests exercise the pure logic that the plugin relies on. Integration
|
|
8
|
+
// (plugin loading / hooks) is validated separately via `opencode run`.
|
|
9
|
+
|
|
10
|
+
import { test } from "node:test"
|
|
11
|
+
import assert from "node:assert/strict"
|
|
12
|
+
import { mkdtempSync, readFileSync, writeFileSync } from "node:fs"
|
|
13
|
+
import { homedir, tmpdir } from "node:os"
|
|
14
|
+
import { join } from "node:path"
|
|
15
|
+
import {
|
|
16
|
+
POLICY_DEEPSEEK,
|
|
17
|
+
POLICY_GLM53,
|
|
18
|
+
POLICY_GPT56,
|
|
19
|
+
POLICY_NEUTRAL,
|
|
20
|
+
canonicalStringify,
|
|
21
|
+
commonPrefixLength,
|
|
22
|
+
createRecorder,
|
|
23
|
+
detectPolicy,
|
|
24
|
+
detectReasoningIssues,
|
|
25
|
+
digestDecision,
|
|
26
|
+
expandHome,
|
|
27
|
+
glmHitRatio,
|
|
28
|
+
gptCacheOptionsDelta,
|
|
29
|
+
hitRatePct,
|
|
30
|
+
loadConfig,
|
|
31
|
+
nextProcessedCursor,
|
|
32
|
+
parseConfig,
|
|
33
|
+
relocateVolatileEnvBlock,
|
|
34
|
+
scanPage,
|
|
35
|
+
shapeDiff,
|
|
36
|
+
shapeFieldDiffs,
|
|
37
|
+
shorthash,
|
|
38
|
+
shouldAggregate,
|
|
39
|
+
systemShapeHashes,
|
|
40
|
+
toolFingerprint,
|
|
41
|
+
toolWireFingerprint,
|
|
42
|
+
} from "../src/cache-engine-core.mjs"
|
|
43
|
+
|
|
44
|
+
const asst = (id, read, write) => ({
|
|
45
|
+
info: { id, role: "assistant", tokens: { cache: { read, write } } },
|
|
46
|
+
})
|
|
47
|
+
|
|
48
|
+
// --- 1/2. system prefix: identical -> no change; changed -> detected ---------
|
|
49
|
+
test("identical system hash -> no change reported", () => {
|
|
50
|
+
const prev = { systemHash: "aaa", toolsHash: "bbb" }
|
|
51
|
+
const cur = { systemHash: "aaa", toolsHash: "bbb" }
|
|
52
|
+
assert.deepEqual(shapeDiff(prev, cur), [])
|
|
53
|
+
})
|
|
54
|
+
|
|
55
|
+
test("changed system hash -> exactly ['system']", () => {
|
|
56
|
+
const prev = { systemHash: "aaa", toolsHash: "bbb" }
|
|
57
|
+
const cur = { systemHash: "ccc", toolsHash: "bbb" }
|
|
58
|
+
assert.deepEqual(shapeDiff(prev, cur), ["system"])
|
|
59
|
+
})
|
|
60
|
+
|
|
61
|
+
// --- 3. tool canonicalization is order-independent --------------------------
|
|
62
|
+
test("identical tools with different ordering/schema key order -> same hash", () => {
|
|
63
|
+
const a = [
|
|
64
|
+
{ id: "read", description: "read a file", parameters: { type: "object", properties: { path: { type: "string" } } } },
|
|
65
|
+
{ id: "write", description: "write a file", parameters: { properties: { content: { type: "string" } }, type: "object" } },
|
|
66
|
+
]
|
|
67
|
+
const b = [
|
|
68
|
+
// reversed tool order, reversed object-key insertion order
|
|
69
|
+
{ parameters: { type: "object", properties: { content: { type: "string" } } }, id: "write", description: "write a file" },
|
|
70
|
+
{ description: "read a file", parameters: { properties: { path: { type: "string" } }, type: "object" }, id: "read" },
|
|
71
|
+
]
|
|
72
|
+
assert.equal(toolFingerprint(a), toolFingerprint(b))
|
|
73
|
+
assert.equal(toolFingerprint(a), toolFingerprint(b.slice().reverse()))
|
|
74
|
+
})
|
|
75
|
+
|
|
76
|
+
test("canonicalStringify ignores object key insertion order", () => {
|
|
77
|
+
assert.equal(canonicalStringify({ b: 1, a: 2 }), canonicalStringify({ a: 2, b: 1 }))
|
|
78
|
+
assert.equal(canonicalStringify({ x: { z: 1, y: [3, 2, 1] } }), canonicalStringify({ x: { y: [3, 2, 1], z: 1 } }))
|
|
79
|
+
})
|
|
80
|
+
|
|
81
|
+
// --- 4/5. tool schema change and combined changes ----------------------------
|
|
82
|
+
test("changed tool schema -> exactly ['tools']", () => {
|
|
83
|
+
const t1 = toolFingerprint([{ id: "read", description: "read", parameters: { type: "object", properties: { path: { type: "string" } } } }])
|
|
84
|
+
const t2 = toolFingerprint([{ id: "read", description: "read", parameters: { type: "object", properties: { path: { type: "string" }, mode: { type: "string" } } } }])
|
|
85
|
+
assert.notEqual(t1, t2)
|
|
86
|
+
assert.deepEqual(shapeDiff({ systemHash: "s", toolsHash: t1 }, { systemHash: "s", toolsHash: t2 }), ["tools"])
|
|
87
|
+
})
|
|
88
|
+
|
|
89
|
+
test("system change + tool change -> both dimensions reported", () => {
|
|
90
|
+
const prev = { systemHash: "s1", toolsHash: "t1" }
|
|
91
|
+
const cur = { systemHash: "s2", toolsHash: "t2" }
|
|
92
|
+
assert.deepEqual(shapeDiff(prev, cur).sort(), ["system", "tools"])
|
|
93
|
+
})
|
|
94
|
+
|
|
95
|
+
test("unknown dimension (null) is never reported as a change", () => {
|
|
96
|
+
// system changed (both hashes known) -> reported even when tools unknown
|
|
97
|
+
assert.deepEqual(shapeDiff({ systemHash: "s1", toolsHash: null }, { systemHash: "s2", toolsHash: null }), ["system"])
|
|
98
|
+
// tools going from known -> unknown (e.g. fetch failed) is NOT a change
|
|
99
|
+
assert.deepEqual(shapeDiff({ systemHash: "s1", toolsHash: "t1" }, { systemHash: "s1", toolsHash: null }), [])
|
|
100
|
+
})
|
|
101
|
+
|
|
102
|
+
test("toolFingerprint returns null for unusable input", () => {
|
|
103
|
+
assert.equal(toolFingerprint(undefined), null)
|
|
104
|
+
assert.equal(toolFingerprint("nope"), null)
|
|
105
|
+
})
|
|
106
|
+
|
|
107
|
+
// --- 6. repeated session.idle never double-counts ---------------------------
|
|
108
|
+
test("same assistant message counted once across idle events", () => {
|
|
109
|
+
const page = [asst("m1", 100, 20), asst("m2", 50, 10), asst("m3", 25, 5)]
|
|
110
|
+
const first = scanPage(page, null)
|
|
111
|
+
assert.equal(first.count, 3)
|
|
112
|
+
assert.equal(first.read, 175)
|
|
113
|
+
assert.equal(first.write, 35)
|
|
114
|
+
// Boundary becomes the NEWEST processed message (messages append at the top).
|
|
115
|
+
const cursor = nextProcessedCursor(page, null)
|
|
116
|
+
assert.equal(cursor, "m1")
|
|
117
|
+
|
|
118
|
+
// Second idle: no new messages above the boundary -> nothing counted.
|
|
119
|
+
const second = scanPage(page, cursor)
|
|
120
|
+
assert.equal(second.count, 0)
|
|
121
|
+
assert.equal(second.read, 0)
|
|
122
|
+
assert.equal(second.reachedStart, true)
|
|
123
|
+
})
|
|
124
|
+
|
|
125
|
+
// --- 7. multiple new assistant messages -> each counted once -----------------
|
|
126
|
+
test("only messages newer than the cursor are aggregated", () => {
|
|
127
|
+
const oldPage = [asst("m1", 100, 20), asst("m2", 50, 10)]
|
|
128
|
+
const cursor = nextProcessedCursor(oldPage, null)
|
|
129
|
+
assert.equal(cursor, "m1")
|
|
130
|
+
|
|
131
|
+
const nextPage = [asst("m0", 10, 2), asst("m1", 100, 20), asst("m2", 50, 10)]
|
|
132
|
+
const scan = scanPage(nextPage, cursor)
|
|
133
|
+
assert.equal(scan.count, 1)
|
|
134
|
+
assert.equal(scan.read, 10)
|
|
135
|
+
assert.equal(scan.reachedStart, true)
|
|
136
|
+
})
|
|
137
|
+
|
|
138
|
+
test("paginated tail (boundary not found) advances cursor to newest processed", () => {
|
|
139
|
+
const page = [asst("m1", 10, 2), asst("m2", 20, 4)]
|
|
140
|
+
const scan = scanPage(page, "ghost-cursor")
|
|
141
|
+
assert.equal(scan.reachedStart, false)
|
|
142
|
+
const cursor = nextProcessedCursor(page, "ghost-cursor")
|
|
143
|
+
assert.equal(cursor, "m1")
|
|
144
|
+
})
|
|
145
|
+
|
|
146
|
+
// --- 8. no cache fields -> no fabricated event -------------------------------
|
|
147
|
+
test("no cache fields -> count stays 0 and aggregation is skipped", () => {
|
|
148
|
+
const page = [{ info: { id: "u1", role: "user", tokens: undefined } }]
|
|
149
|
+
const scan = scanPage(page, null)
|
|
150
|
+
assert.equal(scan.count, 0)
|
|
151
|
+
assert.equal(shouldAggregate(scan.count, scan.read, scan.write), false)
|
|
152
|
+
|
|
153
|
+
const withTokensNoCache = [{ info: { id: "a1", role: "assistant", tokens: { input: 100, output: 5 } } }]
|
|
154
|
+
const scan2 = scanPage(withTokensNoCache, null)
|
|
155
|
+
assert.equal(scan2.count, 1)
|
|
156
|
+
assert.equal(scan2.read, 0)
|
|
157
|
+
assert.equal(scan2.write, 0)
|
|
158
|
+
assert.equal(shouldAggregate(scan2.count, scan2.read, scan2.write), false)
|
|
159
|
+
})
|
|
160
|
+
|
|
161
|
+
// --- 9. hit-rate calculation -------------------------------------------------
|
|
162
|
+
test("hit rate = read / (read + write)", () => {
|
|
163
|
+
assert.equal(hitRatePct(80, 20), 80)
|
|
164
|
+
assert.equal(hitRatePct(0, 0), null)
|
|
165
|
+
assert.equal(hitRatePct(100, 0), 100)
|
|
166
|
+
assert.equal(shouldAggregate(2, 180, 40), true)
|
|
167
|
+
})
|
|
168
|
+
|
|
169
|
+
// --- 10. compaction digest is not duplicated for one invocation --------------
|
|
170
|
+
test("digestDecision prevents duplicate insertion per compaction", () => {
|
|
171
|
+
assert.equal(digestDecision({ compactTemplate: true, pendingInsert: true }), true)
|
|
172
|
+
assert.equal(digestDecision({ compactTemplate: true, pendingInsert: false }), false)
|
|
173
|
+
assert.equal(digestDecision({ compactTemplate: false, pendingInsert: true }), false)
|
|
174
|
+
})
|
|
175
|
+
|
|
176
|
+
// --- 11/12. config behavior ---------------------------------------------------
|
|
177
|
+
test("disabled engine -> enabled=false", () => {
|
|
178
|
+
assert.equal(parseConfig({ enabled: false }, undefined).enabled, false)
|
|
179
|
+
})
|
|
180
|
+
|
|
181
|
+
test("malformed config falls back to defaults", () => {
|
|
182
|
+
const dir = mkdtempSync(join(tmpdir(), "ce-"))
|
|
183
|
+
const bad = join(dir, "../src/cache-engine.json")
|
|
184
|
+
writeFileSync(bad, "{ this is not json")
|
|
185
|
+
const cfg = loadConfig({ configPath: bad, env: {} })
|
|
186
|
+
assert.equal(cfg.enabled, true)
|
|
187
|
+
assert.equal(cfg.compactTemplate, true)
|
|
188
|
+
})
|
|
189
|
+
|
|
190
|
+
test("missing config falls back to defaults + env override", () => {
|
|
191
|
+
const cfg = loadConfig({ configPath: "/nonexistent/cache-engine.json", env: {} })
|
|
192
|
+
assert.equal(cfg.enabled, true)
|
|
193
|
+
assert.equal(cfg.logPrefixChanges, true)
|
|
194
|
+
const env = { CACHE_ENGINE_METRICS_FILE: "~/metrics/x.jsonl" }
|
|
195
|
+
const withEnv = parseConfig(undefined, env)
|
|
196
|
+
assert.ok(withEnv.metricsFile.endsWith("/metrics/x.jsonl"))
|
|
197
|
+
assert.ok(!withEnv.metricsFile.startsWith("~"))
|
|
198
|
+
})
|
|
199
|
+
|
|
200
|
+
test("expandHome handles ~ and ~/ safely", () => {
|
|
201
|
+
assert.equal(expandHome("~/a/b"), join(homedir(), "a/b"))
|
|
202
|
+
assert.equal(expandHome("~"), homedir())
|
|
203
|
+
assert.equal(expandHome("/abs/path"), "/abs/path")
|
|
204
|
+
assert.equal(expandHome("rel/path"), "rel/path")
|
|
205
|
+
})
|
|
206
|
+
|
|
207
|
+
// --- 13. telemetry write failure never throws --------------------------------
|
|
208
|
+
test("recorder swallows write failures", () => {
|
|
209
|
+
const rec = createRecorder("/nonexistent-dir-xyz/out.jsonl")
|
|
210
|
+
assert.doesNotThrow(() => rec.record({ kind: "usage", sid: "s", read: 1, write: 0 }))
|
|
211
|
+
})
|
|
212
|
+
|
|
213
|
+
test("recorder writes valid JSONL to a real file", () => {
|
|
214
|
+
const dir = mkdtempSync(join(tmpdir(), "ce-"))
|
|
215
|
+
const file = join(dir, "metrics.jsonl")
|
|
216
|
+
const rec = createRecorder(file)
|
|
217
|
+
rec.record({ kind: "prefix-change", sid: "s", dimensions: ["system"] })
|
|
218
|
+
rec.record({ kind: "usage", sid: "s", read: 5, write: 1 })
|
|
219
|
+
const lines = readFileSync(file, "utf8").trim().split("\n")
|
|
220
|
+
assert.equal(lines.length, 2)
|
|
221
|
+
assert.deepEqual(JSON.parse(lines[0]), { kind: "prefix-change", sid: "s", dimensions: ["system"] })
|
|
222
|
+
})
|
|
223
|
+
|
|
224
|
+
// ===========================================================================
|
|
225
|
+
// Provider-aware model detection
|
|
226
|
+
// ===========================================================================
|
|
227
|
+
|
|
228
|
+
const M = (providerID, modelID, extra = {}) => ({ providerID, modelID, ...extra })
|
|
229
|
+
|
|
230
|
+
test("DeepSeek V4 Flash matches DeepSeek policy (openrouter + direct)", () => {
|
|
231
|
+
assert.equal(detectPolicy(M("openrouter", "deepseek/deepseek-v4-flash-0731")), POLICY_DEEPSEEK)
|
|
232
|
+
assert.equal(detectPolicy(M("deepseek", "deepseek-chat")), POLICY_DEEPSEEK)
|
|
233
|
+
})
|
|
234
|
+
|
|
235
|
+
test("GPT-5.6 variants match GPT policy", () => {
|
|
236
|
+
assert.equal(detectPolicy(M("openrouter", "openai/gpt-5.6-luna")), POLICY_GPT56)
|
|
237
|
+
assert.equal(detectPolicy(M("openrouter", "openai/gpt-5.6-luna:flex")), POLICY_GPT56)
|
|
238
|
+
assert.equal(detectPolicy(M("openrouter", "openai/gpt-5.6-luna-pro:flex")), POLICY_GPT56)
|
|
239
|
+
assert.equal(detectPolicy(M("openai", "gpt-5.6")), POLICY_GPT56)
|
|
240
|
+
assert.equal(detectPolicy(M("azure", "gpt-5.6")), POLICY_GPT56)
|
|
241
|
+
// full Model shape with api.npm
|
|
242
|
+
assert.equal(
|
|
243
|
+
detectPolicy({ providerID: "openrouter", api: { id: "openai/gpt-5.6-luna", npm: "@openrouter/ai-sdk-provider" } }),
|
|
244
|
+
POLICY_GPT56,
|
|
245
|
+
)
|
|
246
|
+
})
|
|
247
|
+
|
|
248
|
+
test("older/other OpenAI models do NOT match GPT policy", () => {
|
|
249
|
+
assert.equal(detectPolicy(M("openai", "gpt-4o")), POLICY_NEUTRAL)
|
|
250
|
+
assert.equal(detectPolicy(M("openai", "gpt-5.2")), POLICY_NEUTRAL)
|
|
251
|
+
assert.equal(detectPolicy(M("azure", "gpt-4.1")), POLICY_NEUTRAL)
|
|
252
|
+
})
|
|
253
|
+
|
|
254
|
+
test("GPT-5.6 string on a non-OpenAI endpoint does NOT match GPT policy", () => {
|
|
255
|
+
// bare openai-compatible gateway, no openai/ or azure/ slug prefix
|
|
256
|
+
assert.equal(detectPolicy(M("openai-compatible", "gpt-5.6")), POLICY_NEUTRAL)
|
|
257
|
+
assert.equal(detectPolicy(M("llama.cpp", "gpt-5.6")), POLICY_NEUTRAL)
|
|
258
|
+
})
|
|
259
|
+
|
|
260
|
+
test("GLM-5.3 Flash matches GLM policy (openrouter + z-ai variants)", () => {
|
|
261
|
+
assert.equal(detectPolicy(M("openrouter", "z-ai/glm-5.3-flash")), POLICY_GLM53)
|
|
262
|
+
assert.equal(detectPolicy(M("openai-compatible", "z-ai/glm-5.3-flash")), POLICY_GLM53)
|
|
263
|
+
assert.equal(detectPolicy(M("zai", "glm-5.3-flash")), POLICY_GLM53)
|
|
264
|
+
assert.equal(detectPolicy(M("zai", "glm-5.3-flash-250807")), POLICY_GLM53)
|
|
265
|
+
})
|
|
266
|
+
|
|
267
|
+
test("unrelated GLM models do NOT match GLM-5.3 policy", () => {
|
|
268
|
+
assert.equal(detectPolicy(M("openrouter", "z-ai/glm-4.6")), POLICY_NEUTRAL)
|
|
269
|
+
assert.equal(detectPolicy(M("zai", "glm-4.5")), POLICY_NEUTRAL)
|
|
270
|
+
})
|
|
271
|
+
|
|
272
|
+
test("unrelated models match neutral policy", () => {
|
|
273
|
+
assert.equal(detectPolicy(M("anthropic", "claude-sonnet-4-5")), POLICY_NEUTRAL)
|
|
274
|
+
assert.equal(detectPolicy(M("openrouter", "x-ai/grok-4")), POLICY_NEUTRAL)
|
|
275
|
+
assert.equal(detectPolicy(undefined), POLICY_NEUTRAL)
|
|
276
|
+
assert.equal(detectPolicy(null), POLICY_NEUTRAL)
|
|
277
|
+
assert.equal(detectPolicy({}), POLICY_NEUTRAL)
|
|
278
|
+
})
|
|
279
|
+
|
|
280
|
+
// ===========================================================================
|
|
281
|
+
// GPT-5.6 cache key
|
|
282
|
+
// ===========================================================================
|
|
283
|
+
|
|
284
|
+
test("same session -> same prompt cache key", () => {
|
|
285
|
+
const a = gptCacheOptionsDelta({}, { key: "ses_abc123" })
|
|
286
|
+
const b = gptCacheOptionsDelta({}, { key: "ses_abc123" })
|
|
287
|
+
assert.equal(a.promptCacheKey, "ses_abc123")
|
|
288
|
+
assert.equal(b.promptCacheKey, a.promptCacheKey)
|
|
289
|
+
})
|
|
290
|
+
|
|
291
|
+
test("different session -> different prompt cache key", () => {
|
|
292
|
+
const a = gptCacheOptionsDelta({}, { key: "ses_abc" })
|
|
293
|
+
const b = gptCacheOptionsDelta({}, { key: "ses_xyz" })
|
|
294
|
+
assert.notEqual(a.promptCacheKey, b.promptCacheKey)
|
|
295
|
+
})
|
|
296
|
+
|
|
297
|
+
test("transient request data does not change the key", () => {
|
|
298
|
+
// the delta depends only on the stable key, never on request-scoped input
|
|
299
|
+
const withReq = gptCacheOptionsDelta({ temperature: 0.7, maxOutputTokens: 4096 }, { key: "ses_stable" })
|
|
300
|
+
const withoutReq = gptCacheOptionsDelta({}, { key: "ses_stable" })
|
|
301
|
+
assert.equal(withReq.promptCacheKey, "ses_stable")
|
|
302
|
+
assert.equal(withReq.promptCacheKey, withoutReq.promptCacheKey)
|
|
303
|
+
})
|
|
304
|
+
|
|
305
|
+
test("key stays within provider constraints (no whitespace, short, printable)", () => {
|
|
306
|
+
const { promptCacheKey } = gptCacheOptionsDelta({}, { key: "ses_" + "a".repeat(200) })
|
|
307
|
+
assert.ok(promptCacheKey.length <= 256)
|
|
308
|
+
assert.ok(!/\s/.test(promptCacheKey))
|
|
309
|
+
assert.ok(/^[\x20-\x7E]+$/.test(promptCacheKey))
|
|
310
|
+
})
|
|
311
|
+
|
|
312
|
+
test("existing key/options are never overwritten", () => {
|
|
313
|
+
const delta = gptCacheOptionsDelta({ promptCacheKey: "existing", promptCacheOptions: { mode: "explicit", ttl: "1h" } }, { key: "ses_new" })
|
|
314
|
+
assert.deepEqual(delta, {})
|
|
315
|
+
})
|
|
316
|
+
|
|
317
|
+
// ===========================================================================
|
|
318
|
+
// GPT-5.6 cache options
|
|
319
|
+
// ===========================================================================
|
|
320
|
+
|
|
321
|
+
test("GPT-5.6 gets implicit + 30m defaults", () => {
|
|
322
|
+
const delta = gptCacheOptionsDelta({}, { key: "ses_abc" })
|
|
323
|
+
assert.deepEqual(delta.promptCacheOptions, { mode: "implicit", ttl: "30m" })
|
|
324
|
+
})
|
|
325
|
+
|
|
326
|
+
test("explicit mode is possible via config but not the default", () => {
|
|
327
|
+
const delta = gptCacheOptionsDelta({}, { key: "ses_abc", mode: "explicit", ttl: "1h" })
|
|
328
|
+
assert.deepEqual(delta.promptCacheOptions, { mode: "explicit", ttl: "1h" })
|
|
329
|
+
const dflt = gptCacheOptionsDelta({}, { key: "ses_abc" })
|
|
330
|
+
assert.equal(dflt.promptCacheOptions.mode, "implicit")
|
|
331
|
+
})
|
|
332
|
+
|
|
333
|
+
test("invalid mode/ttl fall back to safe defaults", () => {
|
|
334
|
+
const d = gptCacheOptionsDelta({}, { key: "k", mode: "banana", ttl: "" })
|
|
335
|
+
assert.deepEqual(d.promptCacheOptions, { mode: "implicit", ttl: "30m" })
|
|
336
|
+
})
|
|
337
|
+
|
|
338
|
+
// ===========================================================================
|
|
339
|
+
// GLM-5.3 system stabilization
|
|
340
|
+
// ===========================================================================
|
|
341
|
+
|
|
342
|
+
const GLM_SYSTEM = [
|
|
343
|
+
"You are a senior software engineer.",
|
|
344
|
+
"You are powered by the model named glm-5.3-flash. The exact model ID is z-ai/glm-5.3-flash",
|
|
345
|
+
"Here is some useful information about the environment you are running in:",
|
|
346
|
+
"<env>",
|
|
347
|
+
"Working directory: /home/dev/project",
|
|
348
|
+
"Is directory a git repo: yes",
|
|
349
|
+
"Today's date: 2026-08-17",
|
|
350
|
+
"</env>",
|
|
351
|
+
"You MUST follow AGENTS.md instructions and keep your responses concise.",
|
|
352
|
+
].join("\n")
|
|
353
|
+
|
|
354
|
+
test("GLM env block is relocated to the end, content preserved", () => {
|
|
355
|
+
const { text, changed } = relocateVolatileEnvBlock(GLM_SYSTEM)
|
|
356
|
+
assert.equal(changed, true)
|
|
357
|
+
assert.ok(text.endsWith("</env>"))
|
|
358
|
+
// same set of lines, different order
|
|
359
|
+
const norm = (t) => t.split("\n").filter((l) => l).sort().join("\n")
|
|
360
|
+
assert.equal(norm(text), norm(GLM_SYSTEM))
|
|
361
|
+
// instructions now precede the env block
|
|
362
|
+
assert.ok(text.indexOf("You MUST follow") < text.indexOf("You are powered"))
|
|
363
|
+
// deterministic
|
|
364
|
+
const again = relocateVolatileEnvBlock(GLM_SYSTEM)
|
|
365
|
+
assert.equal(again.text, text)
|
|
366
|
+
})
|
|
367
|
+
|
|
368
|
+
test("no-op when env markers are absent", () => {
|
|
369
|
+
const plain = "Just a system prompt.\nNo env block here."
|
|
370
|
+
const r = relocateVolatileEnvBlock(plain)
|
|
371
|
+
assert.equal(r.changed, false)
|
|
372
|
+
assert.equal(r.text, plain)
|
|
373
|
+
})
|
|
374
|
+
|
|
375
|
+
test("no-op when block markers are incomplete", () => {
|
|
376
|
+
const broken = "You are powered by the model named glm-5.3-flash\nbut never closed"
|
|
377
|
+
const r = relocateVolatileEnvBlock(broken)
|
|
378
|
+
assert.equal(r.changed, false)
|
|
379
|
+
assert.equal(r.text, broken)
|
|
380
|
+
})
|
|
381
|
+
|
|
382
|
+
test("relocation only moves the volatile block, never reorders instructions", () => {
|
|
383
|
+
const input = ["A: keep1", "B: You are powered by the model named x", "C: <env>", "D: Today's date: y", "E: </env>", "F: keep2"].join("\n")
|
|
384
|
+
const { text, changed } = relocateVolatileEnvBlock(input)
|
|
385
|
+
assert.equal(changed, true)
|
|
386
|
+
// keep1 and keep2 keep relative order and text
|
|
387
|
+
assert.ok(text.indexOf("A: keep1") < text.indexOf("F: keep2"))
|
|
388
|
+
// env block (the marker through </env>) now after F
|
|
389
|
+
assert.ok(text.indexOf("F: keep2") < text.indexOf("You are powered by the model named x"))
|
|
390
|
+
})
|
|
391
|
+
|
|
392
|
+
// ===========================================================================
|
|
393
|
+
// System decomposition (stable prefix vs volatile suffix)
|
|
394
|
+
// ===========================================================================
|
|
395
|
+
|
|
396
|
+
test("system decomposition: identical system -> stable == full, no volatile", () => {
|
|
397
|
+
const h = systemShapeHashes(GLM_SYSTEM, GLM_SYSTEM)
|
|
398
|
+
assert.equal(h.stableSystemPrefixHash, h.fullSystemHash)
|
|
399
|
+
assert.equal(h.volatileSystemSuffixHash, null)
|
|
400
|
+
})
|
|
401
|
+
|
|
402
|
+
test("appending content only changes the volatile suffix, stable prefix unchanged", () => {
|
|
403
|
+
const baseline = "AAAAABBBBB"
|
|
404
|
+
const current = "AAAAABBBBBCCCCC"
|
|
405
|
+
const h = systemShapeHashes(baseline, current)
|
|
406
|
+
assert.notEqual(h.fullSystemHash, shorthash(baseline))
|
|
407
|
+
assert.equal(h.stableSystemPrefixHash, shorthash(baseline))
|
|
408
|
+
assert.ok(h.volatileSystemSuffixHash != null)
|
|
409
|
+
assert.notEqual(h.volatileSystemSuffixHash, shorthash(""))
|
|
410
|
+
})
|
|
411
|
+
|
|
412
|
+
test("head change shrinks the stable prefix", () => {
|
|
413
|
+
const baseline = "AAAAABBBBB"
|
|
414
|
+
const current = "XXXXXBBBBB"
|
|
415
|
+
const h = systemShapeHashes(baseline, current)
|
|
416
|
+
assert.equal(commonPrefixLength(baseline, current), 0)
|
|
417
|
+
assert.notEqual(h.stableSystemPrefixHash, shorthash(baseline))
|
|
418
|
+
})
|
|
419
|
+
|
|
420
|
+
test("date-only change at tail is a volatile-suffix change", () => {
|
|
421
|
+
// env block relocated to the end; only the date line differs
|
|
422
|
+
const withDate = (d) => relocateVolatileEnvBlock(GLM_SYSTEM.replace("2026-08-17", d)).text
|
|
423
|
+
const d1 = withDate("2026-08-17")
|
|
424
|
+
const d2 = withDate("2026-08-18")
|
|
425
|
+
const h = systemShapeHashes(d1, d2)
|
|
426
|
+
// the only difference is inside the date token ("2026-08-1" prefix shared)
|
|
427
|
+
const dateStart = d1.indexOf("2026-08-17")
|
|
428
|
+
const expectedCommon = dateStart + 9 // "2026-08-1"
|
|
429
|
+
assert.equal(commonPrefixLength(d1, d2), expectedCommon)
|
|
430
|
+
// stable prefix (everything up to the differing date digit) is unchanged
|
|
431
|
+
assert.equal(h.stableSystemPrefixHash, shorthash(d1.slice(0, expectedCommon)))
|
|
432
|
+
assert.notEqual(h.fullSystemHash, shorthash(d1))
|
|
433
|
+
assert.ok(h.volatileSystemSuffixHash != null)
|
|
434
|
+
})
|
|
435
|
+
|
|
436
|
+
test("shapeFieldDiffs reports only fields that changed", () => {
|
|
437
|
+
const prev = { fullSystemHash: "a", stableSystemPrefixHash: "s", volatileSystemSuffixHash: "v", semanticToolsHash: "t", wireToolsHash: "w" }
|
|
438
|
+
const cur = { ...prev, volatileSystemSuffixHash: "v2" }
|
|
439
|
+
const diff = shapeFieldDiffs(prev, cur, ["fullSystemHash", "stableSystemPrefixHash", "volatileSystemSuffixHash", "semanticToolsHash", "wireToolsHash"])
|
|
440
|
+
assert.deepEqual(diff, ["volatileSystemSuffixHash"])
|
|
441
|
+
})
|
|
442
|
+
|
|
443
|
+
// ===========================================================================
|
|
444
|
+
// Tool fingerprints: semantic (order-insensitive) vs wire (order-sensitive)
|
|
445
|
+
// ===========================================================================
|
|
446
|
+
|
|
447
|
+
const TOOLS = [
|
|
448
|
+
{ id: "read", description: "read a file", parameters: { type: "object", properties: { path: { type: "string" } } } },
|
|
449
|
+
{ id: "write", description: "write a file", parameters: { type: "object", properties: { content: { type: "string" } } } },
|
|
450
|
+
]
|
|
451
|
+
|
|
452
|
+
test("semantic fingerprint is order-insensitive", () => {
|
|
453
|
+
assert.equal(toolFingerprint(TOOLS), toolFingerprint([...TOOLS].reverse()))
|
|
454
|
+
})
|
|
455
|
+
|
|
456
|
+
test("wire fingerprint is order-sensitive", () => {
|
|
457
|
+
assert.notEqual(toolWireFingerprint(TOOLS), toolWireFingerprint([...TOOLS].reverse()))
|
|
458
|
+
// but equal for identical order
|
|
459
|
+
assert.equal(toolWireFingerprint(TOOLS), toolWireFingerprint([...TOOLS]))
|
|
460
|
+
})
|
|
461
|
+
|
|
462
|
+
test("schema change affects both fingerprints", () => {
|
|
463
|
+
const changed = [{ ...TOOLS[0], parameters: { type: "object", properties: { path: { type: "string" }, mode: { type: "string" } } } }, TOOLS[1]]
|
|
464
|
+
assert.notEqual(toolFingerprint(TOOLS), toolFingerprint(changed))
|
|
465
|
+
assert.notEqual(toolWireFingerprint(TOOLS), toolWireFingerprint(changed))
|
|
466
|
+
})
|
|
467
|
+
|
|
468
|
+
test("wire fingerprint returns null for unusable input", () => {
|
|
469
|
+
assert.equal(toolWireFingerprint(undefined), null)
|
|
470
|
+
assert.equal(toolWireFingerprint("nope"), null)
|
|
471
|
+
})
|
|
472
|
+
|
|
473
|
+
test("shapeDiff reports tools dimension for wire-only reorder", () => {
|
|
474
|
+
const a = { fullSystemHash: "sys", semanticToolsHash: "sem", wireToolsHash: "w1" }
|
|
475
|
+
const b = { fullSystemHash: "sys", semanticToolsHash: "sem", wireToolsHash: "w2" }
|
|
476
|
+
assert.deepEqual(shapeDiff(a, b), ["tools"])
|
|
477
|
+
})
|
|
478
|
+
|
|
479
|
+
test("scanPage captures reasoning-part hashes and input tokens", () => {
|
|
480
|
+
const page = [
|
|
481
|
+
{
|
|
482
|
+
info: { id: "a1", role: "assistant", tokens: { cache: { read: 10, write: 2 }, input: 90 } },
|
|
483
|
+
parts: [{ type: "reasoning", text: "think about step one" }, { type: "reasoning", text: "think about step one" }],
|
|
484
|
+
},
|
|
485
|
+
]
|
|
486
|
+
const scan = scanPage(page, null)
|
|
487
|
+
assert.equal(scan.read, 10)
|
|
488
|
+
assert.equal(scan.input, 90)
|
|
489
|
+
assert.equal(scan.reasoning.length, 1)
|
|
490
|
+
assert.equal(scan.reasoning[0].hashes.length, 2)
|
|
491
|
+
assert.equal(scan.reasoning[0].hashes[0], scan.reasoning[0].hashes[1]) // duplicated reasoning block
|
|
492
|
+
})
|
|
493
|
+
|
|
494
|
+
// ===========================================================================
|
|
495
|
+
// Reasoning integrity (GLM preserved thinking) - pure detection
|
|
496
|
+
// ===========================================================================
|
|
497
|
+
|
|
498
|
+
test("identical reasoning replay passes (no anomaly when seen is empty)", () => {
|
|
499
|
+
const issues = detectReasoningIssues(["h1", "h2"], ["h1", "h2"], new Map())
|
|
500
|
+
assert.equal(issues.withinDuplicates, 0)
|
|
501
|
+
assert.equal(issues.crossDuplicates, 0)
|
|
502
|
+
assert.equal(issues.reordered, false)
|
|
503
|
+
assert.equal(issues.modified, false)
|
|
504
|
+
})
|
|
505
|
+
|
|
506
|
+
test("duplicated reasoning blocks within a message are detected", () => {
|
|
507
|
+
const issues = detectReasoningIssues(["h1", "h1", "h2"], [], new Map())
|
|
508
|
+
assert.equal(issues.withinDuplicates, 1)
|
|
509
|
+
})
|
|
510
|
+
|
|
511
|
+
test("repeated reasoning across messages is detected", () => {
|
|
512
|
+
const seen = new Map([["h1", 1]])
|
|
513
|
+
const issues = detectReasoningIssues(["h1", "h2"], [], seen)
|
|
514
|
+
assert.equal(issues.crossDuplicates, 1)
|
|
515
|
+
})
|
|
516
|
+
|
|
517
|
+
test("reordered reasoning blocks are detected", () => {
|
|
518
|
+
const issues = detectReasoningIssues(["h2", "h1"], ["h1", "h2"], new Map())
|
|
519
|
+
assert.equal(issues.reordered, true)
|
|
520
|
+
assert.equal(issues.modified, false)
|
|
521
|
+
})
|
|
522
|
+
|
|
523
|
+
test("modified historical reasoning is detected (partial content swap)", () => {
|
|
524
|
+
const issues = detectReasoningIssues(["h1", "hX"], ["h1", "h2"], new Map())
|
|
525
|
+
assert.equal(issues.reordered, false)
|
|
526
|
+
assert.equal(issues.modified, true)
|
|
527
|
+
})
|
|
528
|
+
|
|
529
|
+
test("empty current sequence -> no anomalies", () => {
|
|
530
|
+
const issues = detectReasoningIssues([], ["h1"], new Map())
|
|
531
|
+
assert.deepEqual(issues, { withinDuplicates: 0, crossDuplicates: 0, reordered: false, modified: false })
|
|
532
|
+
})
|
|
533
|
+
|
|
534
|
+
// ===========================================================================
|
|
535
|
+
// Config: provider policies
|
|
536
|
+
// ===========================================================================
|
|
537
|
+
|
|
538
|
+
test("config defaults enable all three policies", () => {
|
|
539
|
+
const cfg = parseConfig({}, {})
|
|
540
|
+
assert.deepEqual(cfg.policies.deepseek, { enabled: true })
|
|
541
|
+
assert.deepEqual(cfg.policies.glm53, { enabled: true, stabilizeSystem: true, preserveThinkingIntegrity: true })
|
|
542
|
+
assert.deepEqual(cfg.policies.gpt56, {
|
|
543
|
+
enabled: true,
|
|
544
|
+
promptCacheKey: true,
|
|
545
|
+
cacheRootKey: false,
|
|
546
|
+
compactionCacheIsolation: true,
|
|
547
|
+
reasoningEffortDiagnostics: true,
|
|
548
|
+
mode: "implicit",
|
|
549
|
+
ttl: "30m",
|
|
550
|
+
})
|
|
551
|
+
})
|
|
552
|
+
|
|
553
|
+
test("config policy overrides are honored", () => {
|
|
554
|
+
const cfg = parseConfig(
|
|
555
|
+
{
|
|
556
|
+
policies: {
|
|
557
|
+
gpt56: {
|
|
558
|
+
enabled: false,
|
|
559
|
+
promptCacheKey: false,
|
|
560
|
+
cacheRootKey: false,
|
|
561
|
+
compactionCacheIsolation: false,
|
|
562
|
+
reasoningEffortDiagnostics: false,
|
|
563
|
+
mode: "explicit",
|
|
564
|
+
ttl: "1h",
|
|
565
|
+
},
|
|
566
|
+
glm53: { stabilizeSystem: false },
|
|
567
|
+
deepseek: { enabled: true },
|
|
568
|
+
},
|
|
569
|
+
},
|
|
570
|
+
{},
|
|
571
|
+
)
|
|
572
|
+
assert.equal(cfg.policies.gpt56.enabled, false)
|
|
573
|
+
assert.equal(cfg.policies.gpt56.promptCacheKey, false)
|
|
574
|
+
assert.equal(cfg.policies.gpt56.cacheRootKey, false)
|
|
575
|
+
assert.equal(cfg.policies.gpt56.compactionCacheIsolation, false)
|
|
576
|
+
assert.equal(cfg.policies.gpt56.reasoningEffortDiagnostics, false)
|
|
577
|
+
assert.equal(cfg.policies.gpt56.mode, "explicit")
|
|
578
|
+
assert.equal(cfg.policies.gpt56.ttl, "1h")
|
|
579
|
+
assert.equal(cfg.policies.glm53.stabilizeSystem, false)
|
|
580
|
+
assert.equal(cfg.policies.glm53.preserveThinkingIntegrity, true)
|
|
581
|
+
})
|
|
582
|
+
|
|
583
|
+
test("config invalid policy values fall back to defaults", () => {
|
|
584
|
+
const cfg = parseConfig({ policies: { gpt56: { mode: "banana", ttl: 123 } } }, {})
|
|
585
|
+
assert.equal(cfg.policies.gpt56.mode, "implicit")
|
|
586
|
+
assert.equal(cfg.policies.gpt56.ttl, "30m")
|
|
587
|
+
const cfg2 = parseConfig({ policies: { glm53: "nope" } }, {})
|
|
588
|
+
assert.deepEqual(cfg2.policies.glm53, { enabled: true, stabilizeSystem: true, preserveThinkingIntegrity: true })
|
|
589
|
+
})
|
|
590
|
+
|
|
591
|
+
// ===========================================================================
|
|
592
|
+
// GLM hit ratio (cached / total prompt tokens)
|
|
593
|
+
// ===========================================================================
|
|
594
|
+
|
|
595
|
+
test("glmHitRatio uses cached over total prompt tokens", () => {
|
|
596
|
+
assert.equal(glmHitRatio(80, 10, 10), 80) // 80/(80+10+10)
|
|
597
|
+
assert.equal(glmHitRatio(0, 0, 0), null)
|
|
598
|
+
assert.equal(glmHitRatio(100, 0, 0), 100)
|
|
599
|
+
})
|
|
600
|
+
|
|
601
|
+
// ===========================================================================
|
|
602
|
+
// GPT-5.6 cache-root derivation (pure)
|
|
603
|
+
// ===========================================================================
|
|
604
|
+
|
|
605
|
+
import { gptCacheKeyFor, observeReasoningEffort, prefixChangeReasons, reasoningEffortFromOptions, reasoningIssueReasons, resolveCacheRootSync } from "./cache-engine-core.mjs"
|
|
606
|
+
|
|
607
|
+
const parents = (m) => (id) => m[id] ?? null
|
|
608
|
+
|
|
609
|
+
test("cache root: ordinary session -> self root", () => {
|
|
610
|
+
const r = resolveCacheRootSync("ses_A", parents({}))
|
|
611
|
+
assert.equal(r.root, "ses_A")
|
|
612
|
+
assert.equal(r.source, "self")
|
|
613
|
+
assert.equal(r.hops, 0)
|
|
614
|
+
})
|
|
615
|
+
|
|
616
|
+
test("cache root: one-level fork -> inherited root", () => {
|
|
617
|
+
const r = resolveCacheRootSync("ses_B", parents({ ses_B: "ses_A" }))
|
|
618
|
+
assert.equal(r.root, "ses_A")
|
|
619
|
+
assert.equal(r.source, "parent")
|
|
620
|
+
assert.equal(r.hops, 1)
|
|
621
|
+
})
|
|
622
|
+
|
|
623
|
+
test("cache root: multi-level fork -> original root", () => {
|
|
624
|
+
const r = resolveCacheRootSync("ses_D", parents({ ses_D: "ses_C", ses_C: "ses_B", ses_B: "ses_A" }))
|
|
625
|
+
assert.equal(r.root, "ses_A")
|
|
626
|
+
assert.equal(r.source, "parent")
|
|
627
|
+
assert.equal(r.hops, 3)
|
|
628
|
+
})
|
|
629
|
+
|
|
630
|
+
test("cache root: unrelated sessions -> distinct roots", () => {
|
|
631
|
+
const ra = resolveCacheRootSync("ses_A", parents({}))
|
|
632
|
+
const rd = resolveCacheRootSync("ses_D", parents({}))
|
|
633
|
+
assert.notEqual(ra.root, rd.root)
|
|
634
|
+
})
|
|
635
|
+
|
|
636
|
+
test("cache root: missing/unknown parent metadata -> deterministic fallback", () => {
|
|
637
|
+
// parentOf throws (lookup failure)
|
|
638
|
+
const r = resolveCacheRootSync("ses_X", () => {
|
|
639
|
+
throw new Error("boom")
|
|
640
|
+
})
|
|
641
|
+
assert.equal(r.root, "ses_X")
|
|
642
|
+
assert.equal(r.source, "unknown")
|
|
643
|
+
// cycle guard terminates deterministically
|
|
644
|
+
const cyc = resolveCacheRootSync("ses_A", parents({ ses_A: "ses_B", ses_B: "ses_A" }))
|
|
645
|
+
assert.ok(cyc.root.length > 0)
|
|
646
|
+
assert.equal(cyc.source, "cycle")
|
|
647
|
+
})
|
|
648
|
+
|
|
649
|
+
test("cache root: unknown parent id treated as fallback root", () => {
|
|
650
|
+
// parentOf returns undefined for unknown (chain resolver semantics)
|
|
651
|
+
const r = resolveCacheRootSync("ses_A", (id) => (id === "ses_A" ? undefined : null))
|
|
652
|
+
assert.equal(r.root, "ses_A")
|
|
653
|
+
})
|
|
654
|
+
|
|
655
|
+
// ===========================================================================
|
|
656
|
+
// GPT-5.6 compaction cache-key isolation (pure)
|
|
657
|
+
// ===========================================================================
|
|
658
|
+
|
|
659
|
+
test("compaction: live key remains stable", () => {
|
|
660
|
+
const k1 = gptCacheKeyFor("ses_root")
|
|
661
|
+
const k2 = gptCacheKeyFor("ses_root")
|
|
662
|
+
assert.equal(k1, "ses_root")
|
|
663
|
+
assert.equal(k2, "ses_root")
|
|
664
|
+
})
|
|
665
|
+
|
|
666
|
+
test("compaction: compaction key is distinct and deterministic", () => {
|
|
667
|
+
const live = gptCacheKeyFor("ses_root")
|
|
668
|
+
const compact = gptCacheKeyFor("ses_root", { compaction: true })
|
|
669
|
+
assert.equal(compact, "ses_root:compact")
|
|
670
|
+
assert.notEqual(compact, live)
|
|
671
|
+
})
|
|
672
|
+
|
|
673
|
+
test("compaction: repeated compaction gets the same key", () => {
|
|
674
|
+
const c1 = gptCacheKeyFor("ses_root", { compaction: true })
|
|
675
|
+
const c2 = gptCacheKeyFor("ses_root", { compaction: true })
|
|
676
|
+
assert.equal(c1, c2)
|
|
677
|
+
})
|
|
678
|
+
|
|
679
|
+
test("compaction: distinct roots produce distinct compact namespaces", () => {
|
|
680
|
+
assert.notEqual(gptCacheKeyFor("ses_A", { compaction: true }), gptCacheKeyFor("ses_B", { compaction: true }))
|
|
681
|
+
})
|
|
682
|
+
|
|
683
|
+
test("compaction: key length stays within provider constraints", () => {
|
|
684
|
+
const long = "ses_" + "a".repeat(300)
|
|
685
|
+
assert.equal(gptCacheKeyFor(long), null)
|
|
686
|
+
assert.equal(gptCacheKeyFor(long, { compaction: true }), null)
|
|
687
|
+
const ok = gptCacheKeyFor("ses_" + "a".repeat(200), { compaction: true })
|
|
688
|
+
assert.ok(ok != null && ok.length <= 256)
|
|
689
|
+
})
|
|
690
|
+
|
|
691
|
+
// ===========================================================================
|
|
692
|
+
// GPT-5.6 reasoning-effort diagnostics (pure)
|
|
693
|
+
// ===========================================================================
|
|
694
|
+
|
|
695
|
+
test("reasoning effort: extraction from options record", () => {
|
|
696
|
+
assert.deepEqual(reasoningEffortFromOptions({ reasoningEffort: "high" }), { known: true, value: "high" })
|
|
697
|
+
assert.deepEqual(reasoningEffortFromOptions({ reasoning: { effort: "low" } }), { known: true, value: "low" })
|
|
698
|
+
assert.deepEqual(reasoningEffortFromOptions({}), { known: false, value: null })
|
|
699
|
+
assert.deepEqual(reasoningEffortFromOptions(null), { known: false, value: null })
|
|
700
|
+
})
|
|
701
|
+
|
|
702
|
+
test("reasoning effort: first observation -> baseline, not a change", () => {
|
|
703
|
+
const step = observeReasoningEffort(null, { known: true, value: "medium" })
|
|
704
|
+
assert.equal(step.event, "baseline")
|
|
705
|
+
assert.equal(step.state.value, "medium")
|
|
706
|
+
})
|
|
707
|
+
|
|
708
|
+
test("reasoning effort: unchanged effort -> no event", () => {
|
|
709
|
+
let step = observeReasoningEffort(null, { known: true, value: "medium" })
|
|
710
|
+
assert.equal(step.event, "baseline")
|
|
711
|
+
step = observeReasoningEffort(step.state, { known: true, value: "medium" })
|
|
712
|
+
assert.equal(step.event, "none")
|
|
713
|
+
})
|
|
714
|
+
|
|
715
|
+
test("reasoning effort: changed effort -> change event with before/after", () => {
|
|
716
|
+
let step = observeReasoningEffort(null, { known: true, value: "medium" })
|
|
717
|
+
step = observeReasoningEffort(step.state, { known: true, value: "high" })
|
|
718
|
+
assert.equal(step.event, "change")
|
|
719
|
+
assert.equal(step.previous.value, "medium")
|
|
720
|
+
assert.equal(step.current.value, "high")
|
|
721
|
+
})
|
|
722
|
+
|
|
723
|
+
test("reasoning effort: unknown -> unknown, not a false-positive change", () => {
|
|
724
|
+
let step = observeReasoningEffort(null, { known: true, value: "medium" })
|
|
725
|
+
step = observeReasoningEffort(step.state, { known: false, value: null })
|
|
726
|
+
assert.equal(step.event, "none")
|
|
727
|
+
// and back to known medium again -> no change
|
|
728
|
+
step = observeReasoningEffort(step.state, { known: true, value: "medium" })
|
|
729
|
+
assert.equal(step.event, "none")
|
|
730
|
+
})
|
|
731
|
+
|
|
732
|
+
// ===========================================================================
|
|
733
|
+
// Boundary telemetry reason classification (pure)
|
|
734
|
+
// ===========================================================================
|
|
735
|
+
|
|
736
|
+
test("boundary: reason classification for prefix changes", () => {
|
|
737
|
+
assert.deepEqual(prefixChangeReasons(["stableSystemPrefixHash"]), ["system_stable_prefix_changed"])
|
|
738
|
+
assert.deepEqual(prefixChangeReasons(["volatileSystemSuffixHash"]), ["system_volatile_suffix_changed"])
|
|
739
|
+
assert.deepEqual(prefixChangeReasons(["semanticToolsHash"]), ["tools_semantic_changed"])
|
|
740
|
+
assert.deepEqual(prefixChangeReasons(["wireToolsHash"]), ["tools_wire_changed"])
|
|
741
|
+
assert.deepEqual(prefixChangeReasons(["stableSystemPrefixHash", "wireToolsHash"]).sort(), [
|
|
742
|
+
"system_stable_prefix_changed",
|
|
743
|
+
"tools_wire_changed",
|
|
744
|
+
])
|
|
745
|
+
// full-only falls back to the stable-prefix reason (conservative)
|
|
746
|
+
assert.deepEqual(prefixChangeReasons(["fullSystemHash"]), ["system_stable_prefix_changed"])
|
|
747
|
+
// empty/unknown
|
|
748
|
+
assert.deepEqual(prefixChangeReasons([]), ["unknown"])
|
|
749
|
+
})
|
|
750
|
+
|
|
751
|
+
test("boundary: reasoning-integrity reason tokens", () => {
|
|
752
|
+
assert.deepEqual(reasoningIssueReasons({ withinDuplicates: 1, crossDuplicates: 0, reordered: false, modified: false }), [
|
|
753
|
+
"reasoning_duplicate_detected",
|
|
754
|
+
])
|
|
755
|
+
assert.deepEqual(reasoningIssueReasons({ withinDuplicates: 0, crossDuplicates: 1, reordered: false, modified: false }), [
|
|
756
|
+
"reasoning_duplicate_detected",
|
|
757
|
+
])
|
|
758
|
+
assert.deepEqual(reasoningIssueReasons({ withinDuplicates: 0, crossDuplicates: 0, reordered: true, modified: false }), [
|
|
759
|
+
"reasoning_reordered",
|
|
760
|
+
])
|
|
761
|
+
assert.deepEqual(reasoningIssueReasons({ withinDuplicates: 0, crossDuplicates: 0, reordered: false, modified: true }), [
|
|
762
|
+
"reasoning_modified",
|
|
763
|
+
])
|
|
764
|
+
assert.deepEqual(reasoningIssueReasons(null), [])
|
|
765
|
+
})
|