dsh-context-compression-improved 0.5.0 → 0.5.2
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/CHANGELOG.ja.md +51 -0
- package/CHANGELOG.ko.md +51 -0
- package/CHANGELOG.md +55 -0
- package/CHANGELOG.zh.md +45 -0
- package/package.json +1 -1
- package/packages/selector/lib/advisor-state.js +4 -231
- package/packages/selector/lib/client.d.ts +0 -24
- package/packages/selector/lib/client.js +6 -501
- package/packages/selector/lib/index.d.ts +4 -10
- package/packages/selector/lib/index.js +65 -235
- package/packages/selector/lib/pruner.d.ts +13 -248
- package/packages/selector/lib/pruner.js +148 -552
- package/packages/selector/src/client/EstimatorControls.tsx +277 -378
- package/packages/selector/src/client/index.ts +0 -17
- package/packages/selector/src/client/locales.ts +196 -234
- package/packages/selector/src/client/preset-options.ts +3 -2
- package/packages/selector/src/client/settings-section.tsx +8 -17
- package/packages/selector/src/index.ts +463 -710
- package/packages/selector/src/preset-overlay.ts +60 -1
- package/packages/selector/src/profiles.ts +4 -27
- package/packages/selector/src/pruner/state.ts +50 -73
- package/packages/selector/src/pruner.ts +2402 -2730
- package/packages/selector/src/runtime/audit.ts +27 -21
- package/packages/selector/src/runtime/config.ts +6 -32
- package/packages/selector/src/runtime/tokenpilot/advisor-state.ts +16 -0
- package/packages/selector/src/runtime/tokenpilot/benefit.ts +200 -0
- package/packages/selector/src/runtime/types.ts +0 -17
- package/packages/selector/tests/built/client-artifact.spec.ts +9 -5
- package/packages/selector/tests/preset-options-write.client.spec.ts +7 -23
- package/packages/selector/tests/runtime/advice-never-withholds.host.spec.ts +232 -0
- package/packages/selector/tests/runtime/audit.spec.ts +35 -21
- package/packages/selector/tests/runtime/deprecated-preset-options.spec.ts +96 -0
- package/packages/selector/tests/runtime/tokenpilot/benefit.spec.ts +217 -0
- package/packages/selector/tests/runtime/tokenpilot/profile-baseline.spec.ts +4 -5
- package/packages/selector/tests/settings-seat.client.spec.ts +16 -10
- package/packages/selector/tests/standing-generation.host.spec.ts +54 -5
- package/scripts/packed-components-smoke.mjs +30 -8
- package/scripts/packed-install-e2e.mjs +69 -15
- package/packages/selector/src/client/ReviewOverlay.tsx +0 -320
- package/packages/selector/src/client/review-scope.ts +0 -16
- package/packages/selector/src/runtime/tokenpilot/proposal.ts +0 -267
- package/packages/selector/src/runtime/tokenpilot/review-queue.ts +0 -231
- package/packages/selector/src/runtime/tokenpilot/review-registry.ts +0 -117
- package/packages/selector/src/runtime/tokenpilot/review-storage.ts +0 -122
- package/packages/selector/tests/review-overlay.client.spec.tsx +0 -118
- package/packages/selector/tests/review-routes-registry.host.spec.ts +0 -142
- package/packages/selector/tests/review-routes.host.spec.ts +0 -290
- package/packages/selector/tests/runtime/tokenpilot/proposal.spec.ts +0 -393
- package/packages/selector/tests/runtime/tokenpilot/pruner-review.spec.ts +0 -382
- package/packages/selector/tests/runtime/tokenpilot/review-queue.spec.ts +0 -168
|
@@ -1,393 +0,0 @@
|
|
|
1
|
-
import { describe, expect, it } from 'vitest'
|
|
2
|
-
import {
|
|
3
|
-
classifyCandidates,
|
|
4
|
-
computeBenefit,
|
|
5
|
-
proposalId,
|
|
6
|
-
} from '../../../src/runtime/tokenpilot/proposal.ts'
|
|
7
|
-
|
|
8
|
-
/** Reference values follow the spec formulas exactly:
|
|
9
|
-
* R = Σ(before − after); penalty = (1−α)·tail; payback = penalty / (α·R);
|
|
10
|
-
* expectedSaving = α·R·max(0, Ŝ − payback). */
|
|
11
|
-
|
|
12
|
-
describe('computeBenefit', () => {
|
|
13
|
-
it('returns zero recovery and no payback for an empty batch', () => {
|
|
14
|
-
const result = computeBenefit([], { alpha: 0.1, tailTokens: 4000 })
|
|
15
|
-
expect(result.recoveredTokens).toBe(0)
|
|
16
|
-
expect(result.penaltyTokens).toBe(3600)
|
|
17
|
-
expect(result.paybackTurns).toBeUndefined()
|
|
18
|
-
expect(result.expectedSaving).toBeUndefined()
|
|
19
|
-
})
|
|
20
|
-
|
|
21
|
-
it('computes the single-candidate benefit with a known remaining-turn estimate', () => {
|
|
22
|
-
// R = 4000 − 400 = 3600; penalty = 0.9·4000 = 3600; per-turn = 0.1·3600 = 360
|
|
23
|
-
const result = computeBenefit(
|
|
24
|
-
[{ sourceSeq: 7, tokensBefore: 4000, tokensAfter: 400 }],
|
|
25
|
-
{ alpha: 0.1, tailTokens: 4000, remainingTurns: 12 },
|
|
26
|
-
)
|
|
27
|
-
expect(result.recoveredTokens).toBe(3600)
|
|
28
|
-
expect(result.penaltyTokens).toBeCloseTo(3600)
|
|
29
|
-
expect(result.paybackTurns).toBeCloseTo(10)
|
|
30
|
-
// α·R·max(0, 12 − 10) = 360·2
|
|
31
|
-
expect(result.expectedSaving).toBeCloseTo(720)
|
|
32
|
-
})
|
|
33
|
-
|
|
34
|
-
it('omits expectedSaving while Ŝ is unknown', () => {
|
|
35
|
-
const result = computeBenefit(
|
|
36
|
-
[{ sourceSeq: 7, tokensBefore: 4000, tokensAfter: 400 }],
|
|
37
|
-
{ alpha: 0.1, tailTokens: 4000 },
|
|
38
|
-
)
|
|
39
|
-
expect(result.paybackTurns).toBeCloseTo(10)
|
|
40
|
-
expect(result.expectedSaving).toBeUndefined()
|
|
41
|
-
})
|
|
42
|
-
|
|
43
|
-
it('merges candidates into one batch so the penalty is paid once', () => {
|
|
44
|
-
const result = computeBenefit(
|
|
45
|
-
[
|
|
46
|
-
{ sourceSeq: 1, tokensBefore: 2000, tokensAfter: 500 },
|
|
47
|
-
{ sourceSeq: 2, tokensBefore: 3000, tokensAfter: 1000 },
|
|
48
|
-
],
|
|
49
|
-
{ alpha: 0.1, tailTokens: 4000, remainingTurns: 20 },
|
|
50
|
-
)
|
|
51
|
-
expect(result.recoveredTokens).toBe(3500)
|
|
52
|
-
expect(result.penaltyTokens).toBeCloseTo(3600)
|
|
53
|
-
expect(result.paybackTurns).toBeCloseTo(3600 / 350)
|
|
54
|
-
expect(result.expectedSaving).toBeCloseTo(350 * (20 - 3600 / 350))
|
|
55
|
-
})
|
|
56
|
-
|
|
57
|
-
it('clamps a growing candidate to zero recovery instead of negative batch credit', () => {
|
|
58
|
-
const result = computeBenefit(
|
|
59
|
-
[{ sourceSeq: 3, tokensBefore: 100, tokensAfter: 400 }],
|
|
60
|
-
{ alpha: 0.1, tailTokens: 4000 },
|
|
61
|
-
)
|
|
62
|
-
expect(result.recoveredTokens).toBe(0)
|
|
63
|
-
expect(result.paybackTurns).toBeUndefined()
|
|
64
|
-
})
|
|
65
|
-
|
|
66
|
-
it('stays finite as α → 0: no division, payback undefined, expectedSaving negative', () => {
|
|
67
|
-
const result = computeBenefit(
|
|
68
|
-
[{ sourceSeq: 4, tokensBefore: 4000, tokensAfter: 400 }],
|
|
69
|
-
{ alpha: 0, tailTokens: 4000, remainingTurns: 12 },
|
|
70
|
-
)
|
|
71
|
-
expect(result.recoveredTokens).toBe(3600)
|
|
72
|
-
expect(result.paybackTurns).toBeUndefined()
|
|
73
|
-
// With zero per-turn saving the batch can only lose the refill penalty.
|
|
74
|
-
expect(result.expectedSaving).toBeCloseTo(-4000)
|
|
75
|
-
})
|
|
76
|
-
|
|
77
|
-
it('never reports a positive expectedSaving once Ŝ is inside the payback window', () => {
|
|
78
|
-
// payback = 10 turns; Ŝ = 5 → max(0, Ŝ − payback) = 0
|
|
79
|
-
const result = computeBenefit(
|
|
80
|
-
[{ sourceSeq: 7, tokensBefore: 4000, tokensAfter: 400 }],
|
|
81
|
-
{ alpha: 0.1, tailTokens: 4000, remainingTurns: 5 },
|
|
82
|
-
)
|
|
83
|
-
expect(result.expectedSaving).toBeCloseTo(0)
|
|
84
|
-
})
|
|
85
|
-
|
|
86
|
-
it('waives the refill penalty for a refill-exempt batch: payback 0, saving α·R·Ŝ', () => {
|
|
87
|
-
// Fresh-stage shaping: the content never entered the KV cache, so no
|
|
88
|
-
// refill penalty applies — the whole discounted recovery is pure gain.
|
|
89
|
-
const result = computeBenefit(
|
|
90
|
-
[{ sourceSeq: 7, tokensBefore: 4000, tokensAfter: 400 }],
|
|
91
|
-
{ alpha: 0.1, tailTokens: 4000, remainingTurns: 12, refillPenaltyExempt: true },
|
|
92
|
-
)
|
|
93
|
-
expect(result.recoveredTokens).toBe(3600)
|
|
94
|
-
expect(result.penaltyTokens).toBe(0)
|
|
95
|
-
expect(result.paybackTurns).toBe(0)
|
|
96
|
-
// α·R·max(0, Ŝ − 0) = 360·12
|
|
97
|
-
expect(result.expectedSaving).toBeCloseTo(4320)
|
|
98
|
-
})
|
|
99
|
-
|
|
100
|
-
it('keeps the refill penalty when the exemption flag is absent', () => {
|
|
101
|
-
const result = computeBenefit(
|
|
102
|
-
[{ sourceSeq: 7, tokensBefore: 4000, tokensAfter: 400 }],
|
|
103
|
-
{ alpha: 0.1, tailTokens: 4000 },
|
|
104
|
-
)
|
|
105
|
-
expect(result.penaltyTokens).toBeCloseTo(3600)
|
|
106
|
-
})
|
|
107
|
-
})
|
|
108
|
-
|
|
109
|
-
describe('proposalId', () => {
|
|
110
|
-
it('is stable for the same digest list and length-sensitive', () => {
|
|
111
|
-
const digests = ['aa'.repeat(32), 'bb'.repeat(32)]
|
|
112
|
-
expect(proposalId(digests)).toBe(proposalId([...digests]))
|
|
113
|
-
expect(proposalId(digests)).toHaveLength(12)
|
|
114
|
-
expect(proposalId(digests)).not.toBe(proposalId([digests[0]!, digests[1]!, digests[0]!]))
|
|
115
|
-
})
|
|
116
|
-
|
|
117
|
-
it('distinguishes an empty batch from a single digest', () => {
|
|
118
|
-
expect(proposalId([])).not.toBe(proposalId(['cc'.repeat(32)]))
|
|
119
|
-
})
|
|
120
|
-
})
|
|
121
|
-
|
|
122
|
-
/** Triage fixtures: α=0.5 keeps the arithmetic obvious —
|
|
123
|
-
* payback = 0.5·tail / (0.5·R) = tail / R. */
|
|
124
|
-
const TRIAGE = { alpha: 0.5, tailTokens: 1000, reviewHighImpactTokens: 4000 }
|
|
125
|
-
|
|
126
|
-
function candidate(overrides: {
|
|
127
|
-
sourceSeq?: number
|
|
128
|
-
tokensBefore?: number
|
|
129
|
-
tokensAfter?: number
|
|
130
|
-
reducer?: string
|
|
131
|
-
component?: string
|
|
132
|
-
}): Parameters<typeof classifyCandidates>[0][number] {
|
|
133
|
-
return {
|
|
134
|
-
sourceSeq: overrides.sourceSeq ?? 1,
|
|
135
|
-
tokensBefore: overrides.tokensBefore ?? 1000,
|
|
136
|
-
tokensAfter: overrides.tokensAfter ?? 500,
|
|
137
|
-
reducer: overrides.reducer ?? 'native-whole-result',
|
|
138
|
-
component: overrides.component ?? 'native-tool-result',
|
|
139
|
-
content: [{ type: 'text', text: 'opaque result body' }],
|
|
140
|
-
}
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
describe('classifyCandidates', () => {
|
|
144
|
-
it('auto-applies the clearly profitable band and drops negative recovery', () => {
|
|
145
|
-
// tail/R = 1000/1000 = 1 → payback = 1 → auto; R = 0 → drop.
|
|
146
|
-
const result = classifyCandidates(
|
|
147
|
-
[candidate({ sourceSeq: 1, tokensBefore: 2000, tokensAfter: 1000 }), candidate({ sourceSeq: 2, tokensBefore: 500, tokensAfter: 500 })],
|
|
148
|
-
TRIAGE,
|
|
149
|
-
)
|
|
150
|
-
expect(result.auto.map(entry => entry.sourceSeq)).toEqual([1])
|
|
151
|
-
expect(result.drop.map(entry => entry.sourceSeq)).toEqual([2])
|
|
152
|
-
expect(result.review).toEqual([])
|
|
153
|
-
})
|
|
154
|
-
|
|
155
|
-
it('sends high-impact candidates to review even inside the auto band', () => {
|
|
156
|
-
// payback = 1000/5000 = 0.2 ≤ 1, but tokensBefore ≥ 4000 → review ("直接送审").
|
|
157
|
-
const result = classifyCandidates(
|
|
158
|
-
[candidate({ tokensBefore: 5000, tokensAfter: 4000 })],
|
|
159
|
-
TRIAGE,
|
|
160
|
-
)
|
|
161
|
-
expect(result.auto).toEqual([])
|
|
162
|
-
expect(result.review).toHaveLength(1)
|
|
163
|
-
expect(result.review[0]!.items[0]!.tokensBefore).toBe(5000)
|
|
164
|
-
})
|
|
165
|
-
|
|
166
|
-
it('keeps the Ŝ-gated edge band in review and closes it while Ŝ is unknown', () => {
|
|
167
|
-
// payback = 1000/200 = 5: with Ŝ = 40 → 5 ≤ 0.25·40 = 10 → auto.
|
|
168
|
-
const known = classifyCandidates(
|
|
169
|
-
[candidate({ tokensBefore: 1200, tokensAfter: 1000 })],
|
|
170
|
-
{ ...TRIAGE, remainingTurns: 40 },
|
|
171
|
-
)
|
|
172
|
-
expect(known.auto).toHaveLength(1)
|
|
173
|
-
// payback = 1000/50 = 20: with Ŝ = 40, 20 > 0.25·40 and > 3 → drop.
|
|
174
|
-
const slow = classifyCandidates(
|
|
175
|
-
[candidate({ tokensBefore: 1050, tokensAfter: 1000 })],
|
|
176
|
-
{ ...TRIAGE, remainingTurns: 40 },
|
|
177
|
-
)
|
|
178
|
-
expect(slow.drop).toHaveLength(1)
|
|
179
|
-
// Same slow candidate with Ŝ unknown → no edge band, no auto → drop.
|
|
180
|
-
const unknown = classifyCandidates(
|
|
181
|
-
[candidate({ tokensBefore: 1050, tokensAfter: 1000 })],
|
|
182
|
-
TRIAGE,
|
|
183
|
-
)
|
|
184
|
-
expect(unknown.drop).toHaveLength(1)
|
|
185
|
-
// payback = 1000/400 = 2.5: Ŝ = 8 → 2.5 > 0.25·8 = 2 and ≤ 3 → review.
|
|
186
|
-
const edge = classifyCandidates(
|
|
187
|
-
[candidate({ tokensBefore: 1400, tokensAfter: 1000 })],
|
|
188
|
-
{ ...TRIAGE, remainingTurns: 8 },
|
|
189
|
-
)
|
|
190
|
-
expect(edge.review).toHaveLength(1)
|
|
191
|
-
// Same edge candidate with Ŝ unknown → drop.
|
|
192
|
-
const edgeUnknown = classifyCandidates(
|
|
193
|
-
[candidate({ tokensBefore: 1400, tokensAfter: 1000 })],
|
|
194
|
-
TRIAGE,
|
|
195
|
-
)
|
|
196
|
-
expect(edgeUnknown.drop).toHaveLength(1)
|
|
197
|
-
})
|
|
198
|
-
|
|
199
|
-
it('freezes digest, kind, and benefit into the review skeleton', () => {
|
|
200
|
-
const result = classifyCandidates(
|
|
201
|
-
[candidate({ sourceSeq: 9, reducer: 'dedupe-pointer', component: 'fresh' })],
|
|
202
|
-
// payback = 1000/500 = 2: Ŝ = 6 → 2 > 0.25·6 = 1.5 and ≤ 3 → review.
|
|
203
|
-
{ ...TRIAGE, remainingTurns: 6 },
|
|
204
|
-
)
|
|
205
|
-
const skeleton = result.review[0]!
|
|
206
|
-
expect(skeleton.kind).toBe('dedup')
|
|
207
|
-
expect(skeleton.items[0]!.seq).toBe(9)
|
|
208
|
-
expect(skeleton.items[0]!.digest).toMatch(/^[0-9a-f]{64}$/)
|
|
209
|
-
expect(skeleton.items[0]!.kind).toBe('dedup')
|
|
210
|
-
expect(skeleton.benefit.recoveredTokens).toBe(500)
|
|
211
|
-
expect(skeleton.id).toEqual(proposalId([skeleton.items[0]!.digest]))
|
|
212
|
-
})
|
|
213
|
-
|
|
214
|
-
it('labels estimator-channel seqs without changing the triage math', () => {
|
|
215
|
-
const result = classifyCandidates(
|
|
216
|
-
[candidate({ sourceSeq: 11, reducer: 'superseded-read-whole-result', component: 'history' })],
|
|
217
|
-
{ ...TRIAGE, remainingTurns: 6, estimatorSeqs: new Set([11]) },
|
|
218
|
-
)
|
|
219
|
-
expect(result.review[0]!.kind).toBe('estimator')
|
|
220
|
-
})
|
|
221
|
-
|
|
222
|
-
it('degenerates to the per-candidate behavior for a single-candidate batch', () => {
|
|
223
|
-
// payback = 1000/400 = 2.5: Ŝ = 8 → 2.5 > 2 and ≤ 3 → review, one item.
|
|
224
|
-
const result = classifyCandidates(
|
|
225
|
-
[candidate({ tokensBefore: 1400, tokensAfter: 1000 })],
|
|
226
|
-
{ ...TRIAGE, remainingTurns: 8 },
|
|
227
|
-
)
|
|
228
|
-
expect(result.auto).toEqual([])
|
|
229
|
-
expect(result.review).toHaveLength(1)
|
|
230
|
-
expect(result.review[0]!.items).toHaveLength(1)
|
|
231
|
-
})
|
|
232
|
-
})
|
|
233
|
-
|
|
234
|
-
/** Fresh-stage batches are exempt from the tail-refill penalty: their content
|
|
235
|
-
* never entered the KV cache, so shaping it before the first request causes
|
|
236
|
-
* no cache break. The same batch that history pricing would drop must land
|
|
237
|
-
* auto. Fixture: α=0.1, tail=64,000 → history penalty = 57,600; batch
|
|
238
|
-
* R = 2×450 = 900 → history payback = 57,600/90 = 640 > 3, Ŝ unknown →
|
|
239
|
-
* drop. Fresh pricing: penalty = 0 → payback = 0 → auto. */
|
|
240
|
-
describe('classifyCandidates (fresh-stage refill exemption)', () => {
|
|
241
|
-
const FRESH = {
|
|
242
|
-
alpha: 0.1,
|
|
243
|
-
tailTokens: 64_000,
|
|
244
|
-
reviewHighImpactTokens: 4_000,
|
|
245
|
-
}
|
|
246
|
-
|
|
247
|
-
it('lands a fresh batch auto that history pricing would drop', () => {
|
|
248
|
-
const batch = [
|
|
249
|
-
candidate({ sourceSeq: 1, tokensBefore: 2000, tokensAfter: 1550 }),
|
|
250
|
-
candidate({ sourceSeq: 2, tokensBefore: 2000, tokensAfter: 1550 }),
|
|
251
|
-
]
|
|
252
|
-
const history = classifyCandidates(batch, FRESH)
|
|
253
|
-
expect(history.drop.map(entry => entry.sourceSeq)).toEqual([1, 2])
|
|
254
|
-
const fresh = classifyCandidates(batch, { ...FRESH, stage: 'fresh' })
|
|
255
|
-
expect(fresh.auto.map(entry => entry.sourceSeq)).toEqual([1, 2])
|
|
256
|
-
expect(fresh.review).toEqual([])
|
|
257
|
-
expect(fresh.drop).toEqual([])
|
|
258
|
-
})
|
|
259
|
-
|
|
260
|
-
it('still routes a high-impact fresh candidate to review', () => {
|
|
261
|
-
const result = classifyCandidates(
|
|
262
|
-
[candidate({ tokensBefore: 5000, tokensAfter: 4000 })],
|
|
263
|
-
{ ...FRESH, stage: 'fresh' },
|
|
264
|
-
)
|
|
265
|
-
expect(result.auto).toEqual([])
|
|
266
|
-
expect(result.review).toHaveLength(1)
|
|
267
|
-
})
|
|
268
|
-
|
|
269
|
-
it('defaults to history pricing when stage is omitted', () => {
|
|
270
|
-
const result = classifyCandidates(
|
|
271
|
-
[candidate({ sourceSeq: 1, tokensBefore: 2000, tokensAfter: 1550 })],
|
|
272
|
-
FRESH,
|
|
273
|
-
)
|
|
274
|
-
expect(result.drop.map(entry => entry.sourceSeq)).toEqual([1])
|
|
275
|
-
})
|
|
276
|
-
})
|
|
277
|
-
|
|
278
|
-
/** R1 batch-level benefit: one merged mutation pays the tail refill ONCE.
|
|
279
|
-
* Fixture: α=0.02, tail=64,000, Ŝ=60. Per candidate R = 50,000 − 500 = 49,500.
|
|
280
|
-
* Batch: R = 247,500 → payback = 62,720 / 4,950 ≈ 12.67 ≤ 0.25·60 = 15 → auto.
|
|
281
|
-
* Per candidate: payback = 62,720 / 990 ≈ 63.35 > 3 → every one drops. */
|
|
282
|
-
describe('classifyCandidates (batch-level benefit, R1)', () => {
|
|
283
|
-
const BATCH = {
|
|
284
|
-
alpha: 0.02,
|
|
285
|
-
tailTokens: 64_000,
|
|
286
|
-
reviewHighImpactTokens: 200_000,
|
|
287
|
-
remainingTurns: 60,
|
|
288
|
-
}
|
|
289
|
-
|
|
290
|
-
function batchCandidate(seq: number, overrides: { tokensBefore?: number, tokensAfter?: number, reducer?: string } = {}) {
|
|
291
|
-
return candidate({
|
|
292
|
-
sourceSeq: seq,
|
|
293
|
-
tokensBefore: overrides.tokensBefore ?? 50_000,
|
|
294
|
-
tokensAfter: overrides.tokensAfter ?? 500,
|
|
295
|
-
...overrides.reducer !== undefined ? { reducer: overrides.reducer } : {},
|
|
296
|
-
})
|
|
297
|
-
}
|
|
298
|
-
|
|
299
|
-
it('pays the refill penalty once per batch: 5×50k lands auto', () => {
|
|
300
|
-
const result = classifyCandidates(
|
|
301
|
-
[1, 2, 3, 4, 5].map(seq => batchCandidate(seq)),
|
|
302
|
-
BATCH,
|
|
303
|
-
)
|
|
304
|
-
expect(result.auto.map(entry => entry.sourceSeq)).toEqual([1, 2, 3, 4, 5])
|
|
305
|
-
expect(result.review).toEqual([])
|
|
306
|
-
expect(result.drop).toEqual([])
|
|
307
|
-
})
|
|
308
|
-
|
|
309
|
-
it('counter-proof: priced per candidate the same five would all drop', () => {
|
|
310
|
-
// Under per-candidate pricing each candidate pays the full refill alone:
|
|
311
|
-
// payback = 62,720 / (0.02·49,500) ≈ 63.35 > 3 → drop. The batch-level
|
|
312
|
-
// classifier above lands the identical five in auto.
|
|
313
|
-
const perCandidate = computeBenefit(
|
|
314
|
-
[{ sourceSeq: 1, tokensBefore: 50_000, tokensAfter: 500 }],
|
|
315
|
-
BATCH,
|
|
316
|
-
)
|
|
317
|
-
expect(perCandidate.paybackTurns).toBeGreaterThan(3)
|
|
318
|
-
})
|
|
319
|
-
|
|
320
|
-
it('a zero-recovery candidate is priced out before the batch verdict', () => {
|
|
321
|
-
// Batch of five real candidates + one growing one: the five land auto
|
|
322
|
-
// (payback ≈ 12.67) and the zero-recovery one never joins the pricing.
|
|
323
|
-
const result = classifyCandidates(
|
|
324
|
-
[
|
|
325
|
-
...[1, 2, 3, 4, 5].map(seq => batchCandidate(seq)),
|
|
326
|
-
batchCandidate(6, { tokensBefore: 500, tokensAfter: 500 }),
|
|
327
|
-
],
|
|
328
|
-
BATCH,
|
|
329
|
-
)
|
|
330
|
-
expect(result.auto.map(entry => entry.sourceSeq)).toEqual([1, 2, 3, 4, 5])
|
|
331
|
-
expect(result.drop.map(entry => entry.sourceSeq)).toEqual([6])
|
|
332
|
-
})
|
|
333
|
-
|
|
334
|
-
it('high impact covers the whole batch: one ≥H candidate sends everything to review', () => {
|
|
335
|
-
const result = classifyCandidates(
|
|
336
|
-
[batchCandidate(1), batchCandidate(2, { tokensBefore: 250_000 })],
|
|
337
|
-
BATCH,
|
|
338
|
-
)
|
|
339
|
-
expect(result.auto).toEqual([])
|
|
340
|
-
expect(result.drop).toEqual([])
|
|
341
|
-
expect(result.review).toHaveLength(1)
|
|
342
|
-
expect(result.review[0]!.items.map(item => item.seq)).toEqual([1, 2])
|
|
343
|
-
})
|
|
344
|
-
|
|
345
|
-
/** Edge-band review fixture: batch R = 3×430,000 = 1,290,000 →
|
|
346
|
-
* perTurn = 0.02·R = 25,800 → payback = 62,720/25,800 ≈ 2.43.
|
|
347
|
-
* Ŝ = 8 → 0.25·Ŝ = 2: payback ∈ (2, 3] → review. No candidate is
|
|
348
|
-
* high-impact (600,000 < reviewHighImpactTokens 1,000,000). */
|
|
349
|
-
const EDGE_REVIEW = {
|
|
350
|
-
alpha: 0.02,
|
|
351
|
-
tailTokens: 64_000,
|
|
352
|
-
reviewHighImpactTokens: 1_000_000,
|
|
353
|
-
remainingTurns: 8,
|
|
354
|
-
}
|
|
355
|
-
|
|
356
|
-
function edgeCandidate(seq: number, overrides: { tokensBefore?: number, tokensAfter?: number, reducer?: string } = {}) {
|
|
357
|
-
return candidate({
|
|
358
|
-
sourceSeq: seq,
|
|
359
|
-
tokensBefore: overrides.tokensBefore ?? 600_000,
|
|
360
|
-
tokensAfter: overrides.tokensAfter ?? 170_000,
|
|
361
|
-
...overrides.reducer !== undefined ? { reducer: overrides.reducer } : {},
|
|
362
|
-
})
|
|
363
|
-
}
|
|
364
|
-
|
|
365
|
-
it('groups one proposal per kind and the id covers every item digest', () => {
|
|
366
|
-
const result = classifyCandidates(
|
|
367
|
-
[1, 2, 3].map(seq => edgeCandidate(seq, { reducer: 'dedupe-pointer' })),
|
|
368
|
-
EDGE_REVIEW,
|
|
369
|
-
)
|
|
370
|
-
expect(result.review).toHaveLength(1)
|
|
371
|
-
const skeleton = result.review[0]!
|
|
372
|
-
expect(skeleton.kind).toBe('dedup')
|
|
373
|
-
expect(skeleton.items).toHaveLength(3)
|
|
374
|
-
expect(skeleton.id).toBe(proposalId(skeleton.items.map(item => item.digest)))
|
|
375
|
-
})
|
|
376
|
-
|
|
377
|
-
it('splits review proposals by kind when the batch mixes reducers', () => {
|
|
378
|
-
const result = classifyCandidates(
|
|
379
|
-
[
|
|
380
|
-
edgeCandidate(1, { reducer: 'dedupe-pointer' }),
|
|
381
|
-
edgeCandidate(2, { reducer: 'dedupe-pointer' }),
|
|
382
|
-
edgeCandidate(3, { reducer: 'native-whole-result' }),
|
|
383
|
-
],
|
|
384
|
-
EDGE_REVIEW,
|
|
385
|
-
)
|
|
386
|
-
expect(result.review).toHaveLength(2)
|
|
387
|
-
const digests = result.review.flatMap(skeleton => skeleton.items.map(item => item.digest))
|
|
388
|
-
expect(digests).toHaveLength(3)
|
|
389
|
-
for (const skeleton of result.review) {
|
|
390
|
-
expect(skeleton.id).toBe(proposalId(skeleton.items.map(item => item.digest)))
|
|
391
|
-
}
|
|
392
|
-
})
|
|
393
|
-
})
|