File size: 21,642 Bytes
488d96b
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
import { describe, expect, it } from 'vitest'
import { checkDoneValue, encodeJsonPlain, hasNonLosslessNumber, hasUnsafeIntegerToken, hostFrameParseCeiling, logTruncationMarker, validateChildFrame } from '../src/index.ts'

describe('logTruncationMarker', () => {
  it('names the configured byte budget', () => {
    expect(logTruncationMarker(65536)).toBe('[dsh-ptc-runtime-python] log capture truncated at 65536 bytes')
    expect(logTruncationMarker(1)).toBe('[dsh-ptc-runtime-python] log capture truncated at 1 bytes')
  })
})

describe('validateChildFrame', () => {
  it('rebuilds boot-ack frames without extra fields', () => {
    expect(validateChildFrame({ type: 'boot-ack' })).toEqual({ type: 'boot-ack' })
    // Forged extras never ride along.
    expect(validateChildFrame({ type: 'boot-ack', extra: 'x' })).toEqual({ type: 'boot-ack' })
  })

  it('rebuilds log frames when the text field is a string', () => {
    expect(validateChildFrame({ type: 'log', text: 'hi' })).toEqual({ type: 'log', text: 'hi' })
    // Non-string text drops.
    expect(validateChildFrame({ type: 'log', text: 42 })).toBeUndefined()
    expect(validateChildFrame({ type: 'log' })).toBeUndefined()
  })

  it('carries a log frame truncation flag only for the literal true', () => {
    // The child's own ledger marker sets `truncated: true`; the host rebuilds
    // it so it stops capturing at the same point.
    expect(validateChildFrame({ type: 'log', text: 'x', truncated: true }))
      .toEqual({ type: 'log', text: 'x', truncated: true })
    // Any other truthy or non-boolean value is a forgery and is dropped from
    // the rebuild — otherwise it would silence capture for the rest of the run.
    expect(validateChildFrame({ type: 'log', text: 'x', truncated: 1 })).toEqual({ type: 'log', text: 'x' })
    expect(validateChildFrame({ type: 'log', text: 'x', truncated: 'yes' })).toEqual({ type: 'log', text: 'x' })
    expect(validateChildFrame({ type: 'log', text: 'x', truncated: false })).toEqual({ type: 'log', text: 'x' })
  })

  it('rebuilds call frames with a numeric id, string global, and string name', () => {
    expect(validateChildFrame({ type: 'call', id: 1, global: 'tools', name: 'echo', args: { x: 1 } }))
      .toEqual({ type: 'call', id: 1, global: 'tools', name: 'echo', args: { x: 1 } })
    // A frame with NO args key drops whole: rebuilding it as `undefined`
    // would invoke the binding with a non-JSON value, bypassing the
    // lossless-JSON argument boundary. Any present value is JSON-plain by
    // construction (frames arrive via JSON.parse), so null passes.
    expect(validateChildFrame({ type: 'call', id: 2, global: 'tools', name: 'echo' })).toBeUndefined()
    expect(validateChildFrame({ type: 'call', id: 2, global: 'tools', name: 'echo', args: null }))
      .toEqual({ type: 'call', id: 2, global: 'tools', name: 'echo', args: null })
    // A missing/mistyped required field drops.
    expect(validateChildFrame({ type: 'call', id: '1', global: 'tools', name: 'echo' })).toBeUndefined()
    expect(validateChildFrame({ type: 'call', id: 1, global: 7, name: 'echo' })).toBeUndefined()
    expect(validateChildFrame({ type: 'call', id: 1, global: 'tools' })).toBeUndefined()
  })

  it('rebuilds done frames with optional value/error', () => {
    expect(validateChildFrame({ type: 'done' })).toEqual({ type: 'done' })
    expect(validateChildFrame({ type: 'done', value: 42 })).toEqual({ type: 'done', value: 42 })
    expect(validateChildFrame({ type: 'done', error: { kind: 'exception', message: 'boom' } }))
      .toEqual({ type: 'done', error: { kind: 'exception', message: 'boom' } })
    expect(validateChildFrame({ type: 'done', error: { kind: 'invalid-output', message: 'lossy' } }))
      .toEqual({ type: 'done', error: { kind: 'invalid-output', message: 'lossy' } })
    expect(validateChildFrame({ type: 'done', error: { kind: 'output-limit', message: 'big' } }))
      .toEqual({ type: 'done', error: { kind: 'output-limit', message: 'big' } })
    expect(validateChildFrame({ type: 'done', value: 1, error: { kind: 'exception', message: 'boom' } }))
      .toEqual({ type: 'done', value: 1, error: { kind: 'exception', message: 'boom' } })
    // A `value: undefined` field is dropped (JSON never carries it, but a forged
    // shape might; the rebuild coalesces to the absent case).
    expect(validateChildFrame({ type: 'done', value: undefined })).toEqual({ type: 'done' })
    // A missing or unrecognized kind drops the frame: the child always sends
    // one of the three, so anything else is a forgery.
    expect(validateChildFrame({ type: 'done', error: { message: 'boom' } })).toBeUndefined()
    expect(validateChildFrame({ type: 'done', error: { kind: 'timeout', message: 'x' } })).toBeUndefined()
  })

  it('rejects malformed done frames', () => {
    // error must be an object.
    expect(validateChildFrame({ type: 'done', error: 'boom' })).toBeUndefined()
    expect(validateChildFrame({ type: 'done', error: null })).toBeUndefined()
    // error.message must be a string.
    expect(validateChildFrame({ type: 'done', error: {} })).toBeUndefined()
    expect(validateChildFrame({ type: 'done', error: { message: 42 } })).toBeUndefined()
  })

  it('drops non-object inputs and unknown types silently', () => {
    expect(validateChildFrame(null)).toBeUndefined()
    expect(validateChildFrame(undefined)).toBeUndefined()
    expect(validateChildFrame(42)).toBeUndefined()
    expect(validateChildFrame('str')).toBeUndefined()
    expect(validateChildFrame({})).toBeUndefined()
    expect(validateChildFrame({ type: 'unknown' })).toBeUndefined()
  })

  it('drops CALL frames whose args are non-finite or negative zero', () => {
    // JSON.parse turns 1e400 into Infinity and preserves -0; the honest child
    // rejects both before sending, so a call frame carrying one is forged.
    expect(validateChildFrame({ type: 'call', id: 1, global: 'tools', name: 'x', args: { n: Infinity } })).toBeUndefined()
    expect(validateChildFrame({ type: 'call', id: Infinity, global: 'tools', name: 'x', args: null })).toBeUndefined()
    // Plain zero and ordinary floats pass.
    expect(validateChildFrame({ type: 'call', id: 1, global: 'tools', name: 'x', args: [0, 1.5] }))
      .toEqual({ type: 'call', id: 1, global: 'tools', name: 'x', args: [0, 1.5] })
  })

  it('drops a CALL frame whose id is negative zero', () => {
    // `-0` passes Number.isFinite, but the reply re-serializes it as `0`
    // (JSON.stringify({id:-0}) === '{"id":0}'), so a forged `-0` id would
    // collide with a real call whose id is `0`. The honest child never sends it.
    expect(validateChildFrame({ type: 'call', id: -0, global: 'tools', name: 'x', args: null })).toBeUndefined()
    // Plain positive zero is a legitimate id and passes.
    expect(validateChildFrame({ type: 'call', id: 0, global: 'tools', name: 'x', args: null }))
      .toEqual({ type: 'call', id: 0, global: 'tools', name: 'x', args: null })
  })

  it('passes DONE values through untouched — losslessness is metered later', () => {
    // validateChildFrame no longer scans done.value: an unbounded scan would
    // push every member of a wide forged payload before any byte cap ran. The
    // done handler's checkDoneValue folds losslessness into the metered walk.
    expect(validateChildFrame({ type: 'done', value: Infinity })).toEqual({ type: 'done', value: Infinity })
    expect(validateChildFrame({ type: 'done', value: [{ x: -0 }] })).toEqual({ type: 'done', value: [{ x: -0 }] })
    expect(validateChildFrame({ type: 'done', value: [0, 1.5] })).toEqual({ type: 'done', value: [0, 1.5] })
  })
})

describe('lossless-number scan', () => {
  it('finds non-finite and negative-zero numbers at any depth, iteratively', () => {
    expect(hasNonLosslessNumber(Infinity)).toBe(true)
    expect(hasNonLosslessNumber(-Infinity)).toBe(true)
    expect(hasNonLosslessNumber(NaN)).toBe(true)
    expect(hasNonLosslessNumber(-0)).toBe(true)
    expect(hasNonLosslessNumber({ a: [1, { b: -0 }] })).toBe(true)
    expect(hasNonLosslessNumber({ a: [0, 1.5, 'x', null, true] })).toBe(false)
    // Deep nesting must not overflow the stack.
    let deep: unknown = 0
    for (let i = 0; i < 100000; i++) deep = [deep]
    expect(hasNonLosslessNumber(deep)).toBe(false)
  })

  it('walks wide arrays and objects one member at a time', () => {
    // `call.args` carries no seam byte cap, so a wide forged payload has no
    // budget to be rejected against — the walk must hold one cursor per
    // NESTING LEVEL, not one entry per member, or a flat payload at the top of
    // the host's inbound frame-size cap would allocate tens of millions of stack
    // entries (and `Object.values` a second full-breadth copy). Observable
    // through the boundary: a wide payload whose per-member cost the old shape
    // would have paid still scans, and a violation ANYWHERE in it is found
    // wherever it sits.
    const wideArray = new Array(2_000_000).fill(0) as unknown[]
    expect(hasNonLosslessNumber(wideArray)).toBe(false)
    // Last element, so the cursor must run the whole breadth lazily.
    wideArray[wideArray.length - 1] = -0
    expect(hasNonLosslessNumber(wideArray)).toBe(true)
    const wideObject: Record<string, unknown> = {}
    for (let i = 0; i < 200_000; i++) wideObject[`k${i}`] = i
    expect(hasNonLosslessNumber(wideObject)).toBe(false)
    wideObject.last = Infinity
    expect(hasNonLosslessNumber(wideObject)).toBe(true)
    // Interleaved nesting: a per-level cursor must resume its parent after a
    // child level ends, so a violation after a nested container is still seen.
    expect(hasNonLosslessNumber([[1], { a: 2 }, NaN])).toBe(true)
  })

  it('scans only own enumerable properties', () => {
    // The per-level cursor filters own keys (a prototype-carrying frame is
    // impossible off JSON.parse, but the filter is what keeps the walk equal
    // to what the encoder would serialize).
    const withProto = Object.create({ inherited: -0 }) as Record<string, unknown>
    withProto.own = 1
    expect(hasNonLosslessNumber(withProto)).toBe(false)
  })
})

describe('unsafe-integer token scan', () => {
  it('flags integer tokens outside the safe range, skipping strings and float forms', () => {
    expect(hasUnsafeIntegerToken('{"v":9007199254740993}')).toBe(true)
    // Exact beyond-safe-range tokens are lossless and pass (2**53, 2**64).
    expect(hasUnsafeIntegerToken('{"v":9007199254740992}')).toBe(false)
    expect(hasUnsafeIntegerToken('{"v":18446744073709551616}')).toBe(false)
    // A token that parses to Infinity is trivially lossy.
    expect(hasUnsafeIntegerToken(`{"v":${'9'.repeat(400)}}`)).toBe(true)
    expect(hasUnsafeIntegerToken('{"v":-9007199254740993}')).toBe(true)
    expect(hasUnsafeIntegerToken('{"v":9007199254740991}')).toBe(false)
    expect(hasUnsafeIntegerToken('{"v":"9007199254740993"}')).toBe(false)
    expect(hasUnsafeIntegerToken(String.raw`{"v":"esc\"9007199254740993"}`)).toBe(false)
    expect(hasUnsafeIntegerToken('{"v":9007199254740993.0}')).toBe(false)
    expect(hasUnsafeIntegerToken('{"v":9e99}')).toBe(false)
  })
})

describe('checkDoneValue', () => {
  it('matches the exact encoded size and rejects one byte over', () => {
    const cases: unknown[] = [null, true, false, 0, -1.5, 'a"b\\', [], {}, [1, 'x', null], { a: [1, 2], b: { c: 'd' } }]
    for (const value of cases) {
      const exact = Buffer.byteLength(JSON.stringify(value), 'utf8')
      expect(checkDoneValue(value, exact), JSON.stringify(value)).toEqual({ ok: true, bytes: exact })
      expect(checkDoneValue(value, exact - 1), JSON.stringify(value)).toEqual({ ok: false, reason: 'over-budget' })
      expect(encodeJsonPlain(value)).toBe(JSON.stringify(value))
    }
  })

  it('rejects an over-budget value before its secondary allocations', () => {
    // A huge string is refused on the cheap length lower bound, before its
    // escaped copy is built.
    const huge = { data: 'x'.repeat(1_000_000), tail: 'y' }
    expect(checkDoneValue(huge, 1024)).toEqual({ ok: false, reason: 'over-budget' })
    // A flat array far above the budget fails on the brackets+length bound,
    // before its elements are pushed onto the traversal stack. (The array is
    // already materialized by the upstream parse; this only avoids the extra
    // per-element stack growth.)
    const flat = new Array(10_000_000).fill(0)
    expect(checkDoneValue(flat, 1024)).toEqual({ ok: false, reason: 'over-budget' })
    // A wide object: braces+commas fit the cap, but the per-entry lower bound
    // (quoted key + colon + value = count*4) does not, so it fails before any
    // key is escaped or any value enqueued.
    const wide: Record<string, number> = {}
    for (let i = 0; i < 10; i++) wide[`k${i}`] = i
    expect(checkDoneValue(wide, 12)).toEqual({ ok: false, reason: 'over-budget' })
  })

  it('meters a string\'s exact escaped size without allocating it', () => {
    // A control-heavy string that fits by DECODED length but not once escaped
    // must still reject: 200 NULs are 200 UTF-16 units (would pass a naive
    // length bound against cap 1024) but escape to 200*6 + 2 = 1202 bytes.
    // jsonStringBytesUpTo scans and bails before the escaped copy is built.
    expect(checkDoneValue('\0'.repeat(200), 1024)).toEqual({ ok: false, reason: 'over-budget' })
    // Exact-size acceptance, no false rejection: one NUL serializes to a
    // 6-char \\uXXXX escape, so with the two quotes = 8 bytes.
    expect(checkDoneValue('\0', 8)).toEqual({ ok: true, bytes: 8 })
    expect(checkDoneValue('\0', 7)).toEqual({ ok: false, reason: 'over-budget' })
    // Multi-byte and astral characters meter at their raw UTF-8 width (a valid
    // surrogate pair is 4 bytes, matching JSON.stringify), not a 6-byte escape.
    expect(checkDoneValue('\u00e9', 4)).toEqual({ ok: true, bytes: 4 }) // 2 quotes + 2-byte UTF-8
    expect(checkDoneValue('\u{1f600}', 6)).toEqual({ ok: true, bytes: 6 }) // 2 quotes + 4-byte UTF-8
    expect(checkDoneValue('\u{1f600}', 5)).toEqual({ ok: false, reason: 'over-budget' })
    // A lone surrogate escapes to \\uXXXX = 6, so with quotes = 8.
    expect(checkDoneValue('\ud800', 8)).toEqual({ ok: true, bytes: 8 })
    // A high surrogate followed by a NON-low character is a lone surrogate (6-byte
    // escape) plus that character: `\ud800` + `a` = 2 quotes + 6 + 1 = 9.
    expect(checkDoneValue('\ud800a', 9)).toEqual({ ok: true, bytes: 9 })
    // A BMP 3-byte code point (CJK) meters at its raw UTF-8 width: 2 quotes + 3.
    expect(checkDoneValue('中', 5)).toEqual({ ok: true, bytes: 5 })
    // Same non-allocating meter for object keys, before the value is enqueued.
    expect(checkDoneValue({ ['\0'.repeat(200)]: 1 }, 1024)).toEqual({ ok: false, reason: 'over-budget' })
    // A string reached with less than the two quotes' worth of budget is refused
    // immediately (even the empty escaped form does not fit).
    expect(checkDoneValue('x', 1)).toEqual({ ok: false, reason: 'over-budget' })
  })

  it('meters only own enumerable keys', () => {
    // The walk counts keys with a `for...in` + hasOwn pass rather than
    // Object.keys/entries (which allocate per member before the bound). A
    // prototype-carrying forgery is impossible off JSON.parse, but the own-key
    // filter is what keeps the count equal to the encoder's.
    const withProto = Object.create({ inherited: 'x' }) as Record<string, unknown>
    withProto.own = 1
    expect(checkDoneValue(withProto, 1024)).toEqual({ ok: true, bytes: Buffer.byteLength('{"own":1}', 'utf8') })
  })

  it('rejects non-finite and negative-zero numbers at any depth as non-lossless', () => {
    expect(checkDoneValue(Infinity, 1024)).toEqual({ ok: false, reason: 'non-lossless' })
    expect(checkDoneValue(-Infinity, 1024)).toEqual({ ok: false, reason: 'non-lossless' })
    expect(checkDoneValue(NaN, 1024)).toEqual({ ok: false, reason: 'non-lossless' })
    expect(checkDoneValue(-0, 1024)).toEqual({ ok: false, reason: 'non-lossless' })
    expect(checkDoneValue({ a: [1, { b: -0 }] }, 1024)).toEqual({ ok: false, reason: 'non-lossless' })
    // An ordinary finite value within budget passes with its exact byte count.
    const clean = { a: [0, 1.5, 'x', null, true] }
    expect(checkDoneValue(clean, 1024)).toEqual({ ok: true, bytes: Buffer.byteLength(JSON.stringify(clean), 'utf8') })
  })

  it('classifies an over-budget value as over-budget regardless of member order', () => {
    // A value that is BOTH over-budget and non-lossless must reject as
    // over-budget whichever member the walk reaches first — the non-lossless
    // number is recorded and metering finishes, so the two orders below (the
    // same value) cannot classify differently. Cap 100 with a 1000-char string.
    const big = 'x'.repeat(1000)
    expect(checkDoneValue([big, Infinity], 100)).toEqual({ ok: false, reason: 'over-budget' })
    expect(checkDoneValue([Infinity, big], 100)).toEqual({ ok: false, reason: 'over-budget' })
    // A non-lossless number that DOES fit the budget still rejects as
    // non-lossless (the recorded violation is the verdict once the whole value
    // is confirmed within budget).
    expect(checkDoneValue([Infinity], 100)).toEqual({ ok: false, reason: 'non-lossless' })
    // The non-lossless number's OWN encoded bytes still count toward the budget,
    // so a value whose only over-budget contribution is the non-lossless number
    // itself is classified over-budget, not non-lossless. `[Infinity]` encodes
    // as the 10-byte `[Infinity]`; at cap 3 the byte check wins.
    expect(checkDoneValue([Infinity], 3)).toEqual({ ok: false, reason: 'over-budget' })
    expect(checkDoneValue(Infinity, 3)).toEqual({ ok: false, reason: 'over-budget' })
  })

  it('meters and encodes deep nesting iteratively without overflowing the stack', () => {
    let deep: unknown = 0
    for (let i = 0; i < 100_000; i++) deep = [deep]
    // 100000 '[' + '0' + 100000 ']' = 200001 bytes.
    expect(checkDoneValue(deep, 1_000_000)).toEqual({ ok: true, bytes: 200_001 })
    // encodeJsonPlain's headline contract is the same stack-safety (JSON.stringify
    // recurses per level and throws RangeError a few thousand deep), so exercise
    // it on the same 100k-deep value — JSON.stringify would throw here.
    expect(encodeJsonPlain(deep)).toBe(`${'['.repeat(100_000)}0${']'.repeat(100_000)}`)
  })

  it('emits exact digits for beyond-safe integral doubles', () => {
    // String(2**60) prints the ROUNDED ...847000; echoing that to the child
    // would change the integer. BigInt digits give the exact ...846976.
    const v = JSON.parse('[1152921504606846976]') as unknown
    expect(encodeJsonPlain(v)).toBe('[1152921504606846976]')
    expect(checkDoneValue(v, 100)).toEqual({ ok: true, bytes: Buffer.byteLength('[1152921504606846976]', 'utf8') })
  })

  it('walks wide arrays and objects one member at a time', () => {
    // A completion value has a seam byte budget, but the budget alone does not
    // bound the traversal's AUXILIARY state: a wide value near the frame cap
    // (millions of members) must not have every member's reference copied onto
    // a work stack — that O(width) allocation would OOM the host after the
    // parse already succeeded. The walk holds one cursor per nesting level, so
    // a wide value meters exactly and a violation anywhere in it is found
    // wherever it sits.
    const wideArray = new Array(2_000_000).fill(0) as unknown[]
    const arrayJson = `[${wideArray.join(',')}]`
    const arrayExact = Buffer.byteLength(arrayJson, 'utf8')
    expect(checkDoneValue(wideArray, arrayExact)).toEqual({ ok: true, bytes: arrayExact })
    expect(checkDoneValue(wideArray, arrayExact - 1)).toEqual({ ok: false, reason: 'over-budget' })
    // Last element, so the cursor must run the whole breadth lazily to find it.
    wideArray[wideArray.length - 1] = -0
    expect(checkDoneValue(wideArray, arrayExact)).toEqual({ ok: false, reason: 'non-lossless' })
    wideArray[wideArray.length - 1] = 0
    const wideObject: Record<string, unknown> = {}
    for (let i = 0; i < 100_000; i++) wideObject[`k${i}`] = i
    const objectExact = Buffer.byteLength(JSON.stringify(wideObject), 'utf8')
    expect(checkDoneValue(wideObject, objectExact)).toEqual({ ok: true, bytes: objectExact })
    expect(checkDoneValue(wideObject, objectExact - 1)).toEqual({ ok: false, reason: 'over-budget' })
    wideObject.last = -0
    expect(checkDoneValue(wideObject, Buffer.byteLength(JSON.stringify(wideObject), 'utf8'))).toEqual({ ok: false, reason: 'non-lossless' })
  })
})

describe('hostFrameParseCeiling', () => {
  it('caps the parse at the protocol limit on a default heap and lower on a constrained one', () => {
    // The raw-byte frame cap does not protect the host heap: JSON.parse of a
    // wide-object frame materializes several times the raw bytes in property
    // storage, so the effective cap is min(protocol cap, heap-derived
    // ceiling). A default Node heap (~4 GiB) never binds.
    expect(hostFrameParseCeiling(4 * 1024 * 1024 * 1024)).toBe(64 * 1024 * 1024)
    // A constrained host (--max-old-space-size=256 reports a ~304 MiB limit)
    // derives floor((304 - 64) / 16) = 15 MiB: a 50 MiB budget would be
    // rejected at load, where the address-space gate alone would admit it.
    expect(hostFrameParseCeiling(304 * 1024 * 1024)).toBe(15 * 1024 * 1024)
    // A tiny heap leaves almost no parse room — the load gate fails loud.
    expect(hostFrameParseCeiling(128 * 1024 * 1024)).toBe(4 * 1024 * 1024)
  })
})