circuitjson-toolkit 1.2.0 → 1.3.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/README.md CHANGED
@@ -55,6 +55,20 @@ metadata ownership traversals now support deeply nested, valid ECAD documents
55
55
  up to 256 levels. See the
56
56
  [1.2.0 release notes](docs/release-notes-v1.2.0.md).
57
57
 
58
+ Version 1.2.1 keeps large canonical documents and multi-document projects
59
+ inside explicit worker budgets. Selected extensions may contain up to four
60
+ million structured items, while exact project envelopes receive independent
61
+ per-document, project-metadata, aggregate-byte, and repeated-work limits. See
62
+ the [1.2.1 release notes](docs/release-notes-v1.2.1.md).
63
+
64
+ Version 1.3.0 adds an explicit fast preparation path for documents received
65
+ through platform structured clone. Large native extension graphs can be owned
66
+ without exception-driven binary probing of every ordinary record, while the
67
+ general preparation path remains prototype-independent for arbitrary caller
68
+ input. Cross-realm and altered-prototype buffers, typed views, byte limits,
69
+ defensive copies, and all existing return shapes remain supported.
70
+ See the [1.3.0 release notes](docs/release-notes-v1.3.0.md).
71
+
58
72
  Before 1.1.0:
59
73
 
60
74
  ```js
@@ -100,7 +114,7 @@ const model = document.model
100
114
  - Strict `ecad-toolkit.worker.v1` parsing/project protocol with progress,
101
115
  cancellation, and controlled buffer transfer
102
116
  - One-pass ownership for selected source extensions, with a separate 128 MiB /
103
- 2,000,000-item bound and exact direct/worker result parity
117
+ 4,000,000-item bound and exact direct/worker result parity
104
118
  - Machine-readable capability inventory and packed downstream conformance
105
119
  harness
106
120
  - Explicit `/extensions` surface retaining every previous specialized API
@@ -201,11 +215,14 @@ falls back to direct execution only when worker construction is unavailable;
201
215
  explicit worker and runtime failures remain visible.
202
216
 
203
217
  Selected source-native extensions are captured once into an immutable owned
204
- snapshot. Their separate 128 MiB payload and 2,000,000-item ceilings permit
205
- realistic renderer/model graphs without weakening the worker protocol's 250 MB
206
- total-result ceiling; an over-limit extension fails visibly instead of being
207
- silently truncated. Binary extension values remain byte-backed and return
208
- defensive copies instead of expanding into JavaScript number arrays.
218
+ snapshot. Their separate 128 MiB payload and 4,000,000-item ceilings permit
219
+ realistic renderer/model graphs. Canonical standalone documents remain within
220
+ a 250 MB result ceiling, while exact multi-document project envelopes use a
221
+ 256 MiB aggregate ceiling plus independent 250 MB per-document and metadata
222
+ ceilings. Item and repeated-alias work budgets remain separately bounded; an
223
+ over-limit extension or result fails visibly instead of being silently
224
+ truncated. Binary extension values remain byte-backed and return defensive
225
+ copies instead of expanding into JavaScript number arrays.
209
226
  When extensions are disabled, native documents and projects return the exact
210
227
  empty map `{}`. Bounded ZIP consumers can preflight local/central filenames and
211
228
  CRC32/size metadata with
@@ -319,6 +336,8 @@ copy while keeping sync, direct async, and worker results mutation-isolated.
319
336
  - [1.1.1 release notes](docs/release-notes-v1.1.1.md)
320
337
  - [1.1.2 release notes](docs/release-notes-v1.1.2.md)
321
338
  - [1.2.0 release notes](docs/release-notes-v1.2.0.md)
339
+ - [1.2.1 release notes](docs/release-notes-v1.2.1.md)
340
+ - [1.3.0 release notes](docs/release-notes-v1.3.0.md)
322
341
  - [Library scope](spec/library-scope.md)
323
342
 
324
343
  ## Package scope
package/docs/api.md CHANGED
@@ -53,12 +53,13 @@ make the common shape smaller.
53
53
 
54
54
  Validated source documents own selected extension values as one deeply
55
55
  immutable, descriptor-safe snapshot. This uses a distinct 128 MiB payload and
56
- 2,000,000-item ceiling so large native renderer graphs do not inherit the
56
+ 4,000,000-item ceiling so large native renderer graphs do not inherit the
57
57
  compact metadata limit. The shared worker protocol applies the same ownership
58
- boundary after structured cloning and retains its 250 MB whole-result ceiling.
59
- Binary values preserve their `ArrayBuffer`/typed-view shape behind defensive
60
- copy access, so payload bytes do not inflate into one JavaScript number per
61
- byte.
58
+ boundary after structured cloning. Canonical standalone documents retain a
59
+ 250 MB byte ceiling; exact multi-document projects use a 256 MiB aggregate
60
+ ceiling with independent 250 MB document and project-metadata limits. Binary
61
+ values preserve their `ArrayBuffer`/typed-view shape behind defensive copy
62
+ access, so payload bytes do not inflate into one JavaScript number per byte.
62
63
 
63
64
  ### Errors
64
65
 
@@ -108,6 +109,28 @@ Validation, indexes, render primitives, and other derived values are cached by
108
109
  the context. Public outputs remain detached or immutable; a caller cannot
109
110
  mutate later results through a returned object.
110
111
 
112
+ Hosts that receive a document from the platform structured-clone algorithm may
113
+ use the explicit provenance-aware entry point:
114
+
115
+ ```js
116
+ const context = CircuitJsonDocumentContext.prepareStructuredClone(
117
+ message.data,
118
+ {
119
+ indexes: ['elements', 'relations']
120
+ }
121
+ )
122
+ ```
123
+
124
+ `prepareStructuredClone()` has the same options, return type, validation,
125
+ ownership, limits, and derived-cache behavior as `prepare()`. It avoids
126
+ exception-driven raw-buffer brand checks for ordinary objects by relying on the
127
+ standard local prototypes guaranteed by a completed platform structured clone.
128
+ Use it only for the exact structured-cloned result (or a graph created entirely
129
+ by the toolkit after that boundary). Use `prepare()` for arbitrary caller-owned,
130
+ cross-realm, proxy-backed, or prototype-modified input. The general path keeps
131
+ intrinsic binary slots authoritative and preserves altered-prototype
132
+ `ArrayBuffer`, `SharedArrayBuffer`, typed-array, and `DataView` values.
133
+
111
134
  ## Root entrypoint
112
135
 
113
136
  `circuitjson-toolkit` has an exact 17-class root. The 14 canonical classes are:
@@ -102,15 +102,19 @@ must not duplicate the CircuitJSON model or keep a renamed renderer model.
102
102
  Default parsing excludes raw/base64/full-payload graphs.
103
103
 
104
104
  When a caller explicitly selects a native extension, the toolkit captures and
105
- freezes that graph once under a distinct ceiling of 2,000,000 structured items
105
+ freezes that graph once under a distinct ceiling of 4,000,000 structured items
106
106
  and 128 MiB of string or binary content. The ceiling is shared across the
107
- selected namespace, applies equally after worker transfer, and remains below
108
- the worker's 250 MB whole-result limit. General document metadata keeps its
109
- smaller budget, so a large native projection cannot consume the canonical
110
- metadata allowance. Oversized graphs are rejected; they are never truncated or
111
- partially retained. Binary extension values remain byte-backed with their
112
- common buffer/view type and are exposed as defensive copies, preserving
113
- mutation isolation without expanding bytes into plain numeric arrays.
107
+ selected namespace and applies equally after worker transfer. Canonical
108
+ standalone documents allow the extension plus envelope overhead within five
109
+ million result values and 250 MB. Exact project envelopes allow eight million
110
+ aggregate values and 256 MiB, while each document stays within five million
111
+ values and 250 MB and non-document project metadata stays within two million
112
+ values and 250 MB. Reused graphs are charged independently to each local scope,
113
+ and repeated accounting work is itself bounded by the aggregate project
114
+ budget. Oversized graphs are rejected; they are never truncated or partially
115
+ retained. Binary extension values remain byte-backed with their common
116
+ buffer/view type and are exposed as defensive copies, preserving mutation
117
+ isolation without expanding bytes into plain numeric arrays.
114
118
 
115
119
  Supported pre-union CircuitJSON aliases may be projected through
116
120
  `CircuitJsonDocument.normalizeModel()`. The copy-on-write form preserves exact
@@ -0,0 +1,26 @@
1
+ # circuitjson-toolkit 1.2.1
2
+
3
+ ## Large canonical worker results
4
+
5
+ This patch release keeps valid high-fidelity documents and multi-document
6
+ projects inside explicit, format-neutral worker limits:
7
+
8
+ - Selected native extensions may contain up to 4,000,000 structured items and
9
+ 128 MiB of string or binary content.
10
+ - A canonical standalone document may contain up to 5,000,000 result values
11
+ within the existing 250 MB byte ceiling.
12
+ - An exact canonical project may contain up to 8,000,000 aggregate values and
13
+ 256 MiB, while every document retains its own 5,000,000-value and 250 MB
14
+ limits.
15
+ - Non-document project metadata retains the generic 2,000,000-value and 250 MB
16
+ limits.
17
+
18
+ Elevated budgets require the complete canonical document or project envelope;
19
+ a schema string alone does not change generic result limits. Reused object
20
+ graphs are charged independently to document and project-metadata scopes in a
21
+ property-order-independent way. Repeated alias-accounting work is also capped
22
+ by the project aggregate budget, keeping transport time bounded.
23
+
24
+ No public class, method, package subpath, parameter, or result field is removed
25
+ or renamed. Oversized results continue to fail visibly rather than being
26
+ truncated.
@@ -0,0 +1,45 @@
1
+ # circuitjson-toolkit 1.3.0
2
+
3
+ ## Faster canonical extension ownership
4
+
5
+ This minor release adds an explicit fast ownership path for large native
6
+ extension graphs received through the platform structured-clone algorithm.
7
+ Ordinary records, arrays, and standard local data containers are classified
8
+ without deliberately invoking incompatible intrinsic getters, while genuine
9
+ buffers and views still use captured platform slots as the final authority.
10
+
11
+ The new `CircuitJsonDocumentContext.prepareStructuredClone(document, options?)`
12
+ method has the same options and return shape as `prepare()`. It is intended for
13
+ the exact result of a completed platform structured clone, or for a graph
14
+ created entirely by the toolkit after that boundary. Hosts must continue to use
15
+ `prepare()` for arbitrary caller-owned, cross-realm, proxy-backed, or
16
+ prototype-modified input. The optimization is source-format-neutral and
17
+ requires no example-specific handling.
18
+
19
+ Measured on the deterministic standard-built-in metadata workload used during
20
+ development, capturing 50,000 populated records fell from about 3.7 seconds to
21
+ about 0.16 seconds. Browser timing for the combined library and ECAD Forge
22
+ release is documented by the application release because network transfer,
23
+ application interaction preparation, and SVG mounting are outside this
24
+ library's ownership boundary.
25
+
26
+ ## Compatibility and API changes
27
+
28
+ - No public class, package subpath, parameter, or return field is removed or
29
+ renamed.
30
+ - `CircuitJsonDocumentContext.prepareStructuredClone(document, options?)` is a
31
+ new opt-in method for the explicit structured-clone provenance contract.
32
+ - Parser, project, renderer, worker, and extension result shapes are unchanged.
33
+ - The existing `prepare()` path remains exact and prototype-independent.
34
+ Cross-realm and altered-prototype `ArrayBuffer`, `SharedArrayBuffer`, typed
35
+ array, and `DataView` values, resizable buffers, byte ceilings, and
36
+ defensive-copy behavior are retained.
37
+ - Proven standard plain-data graphs no longer generate caught exceptions merely
38
+ to prove that each ordinary node is not binary data.
39
+
40
+ The new regression coverage pauses on every thrown exception in an isolated
41
+ runtime, proving that a representative proven-standard metadata graph completes
42
+ with zero binary-probe exceptions. Exact-path regressions cover altered and
43
+ cross-realm binary objects plus proxy prototype traps. Existing adversarial and
44
+ full-suite contracts continue to cover hostile accessors, worker parity,
45
+ mutation isolation, and bounded extension ownership.
package/docs/testing.md CHANGED
@@ -21,6 +21,13 @@ checked-in artifacts. Check mode is read-only. It also preserves upstream
21
21
  transform rejection boundaries, including malformed SI-unit strings that the
22
22
  upstream schema rejects by throwing instead of returning a failed parse.
23
23
 
24
+ `npm test` first runs the benchmark contract in an uncontended process, then
25
+ runs the functional test modules with normal Node test concurrency, and finally
26
+ runs the remaining elapsed-time modules in isolated phases. This preserves
27
+ complete test coverage while preventing unrelated CPU-heavy suites from
28
+ contaminating timing comparisons. `npm test -- <node-test-arguments>` still
29
+ forwards focused arguments directly to the Node test runner.
30
+
24
31
  The schema differential suite samples every upstream union leaf, compares
25
32
  required-field failures, exercises transform-owned resistor, capacitor,
26
33
  inductor, current-source, and crystal fields, and verifies that a fresh compile
@@ -92,6 +99,13 @@ round-trip behavior, and visible rejection beyond the separate 128 MiB
92
99
  extension payload ceiling. A 3 MiB binary regression additionally requires
93
100
  byte-backed `Uint8Array` shape, defensive-copy mutation isolation, direct and
94
101
  worker parity, bounded elapsed time, and bounded JavaScript heap growth.
102
+ An isolated inspector regression pauses on all thrown exceptions and requires
103
+ ordinary populated records and arrays in an explicitly proven standard-built-in
104
+ graph to complete ownership without exception-driven binary probing. Separate
105
+ exact-path regressions require cross-realm buffers, altered-prototype
106
+ `ArrayBuffer` and `SharedArrayBuffer` values, altered view prototypes, and
107
+ proxy-backed non-binary candidates to retain the prior prototype-independent
108
+ behavior.
95
109
 
96
110
  Variant-geometry tests also lock rotation-local polygon-plated pad extents and
97
111
  pill drill dimensions, independent outer/drill rotations, and every legal
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "circuitjson-toolkit",
3
- "version": "1.2.0",
3
+ "version": "1.3.0",
4
4
  "description": "Canonical CircuitJSON parsing, project, rendering, query, manufacturing, simulation, and scene contracts",
5
5
  "keywords": [
6
6
  "circuitjson",
@@ -54,6 +54,8 @@
54
54
  "docs/release-notes-v1.1.1.md",
55
55
  "docs/release-notes-v1.1.2.md",
56
56
  "docs/release-notes-v1.2.0.md",
57
+ "docs/release-notes-v1.2.1.md",
58
+ "docs/release-notes-v1.3.0.md",
57
59
  "docs/testing.md",
58
60
  "spec",
59
61
  "LICENSE",
@@ -66,7 +68,7 @@
66
68
  "REUSE.toml"
67
69
  ],
68
70
  "scripts": {
69
- "test": "node --test",
71
+ "test": "node scripts/run-tests.mjs",
70
72
  "format": "prettier --write .",
71
73
  "check:format": "prettier --check .",
72
74
  "sync:schema": "node scripts/sync-circuit-json-schema.mjs",
@@ -22,6 +22,10 @@ const TYPED_ARRAY_LENGTH = Object.getOwnPropertyDescriptor(
22
22
  TYPED_ARRAY_PROTOTYPE,
23
23
  'byteLength'
24
24
  )?.get
25
+ const TYPED_ARRAY_TAG = Object.getOwnPropertyDescriptor(
26
+ TYPED_ARRAY_PROTOTYPE,
27
+ Symbol.toStringTag
28
+ )?.get
25
29
  const DATA_VIEW_BUFFER = Object.getOwnPropertyDescriptor(
26
30
  DataView.prototype,
27
31
  'buffer'
@@ -34,7 +38,40 @@ const DATA_VIEW_LENGTH = Object.getOwnPropertyDescriptor(
34
38
  DataView.prototype,
35
39
  'byteLength'
36
40
  )?.get
41
+ const ARRAY_BUFFER_IS_VIEW = ArrayBuffer.isView
42
+ const ARRAY_IS_ARRAY = Array.isArray
43
+ const OBJECT_GET_PROTOTYPE_OF = Object.getPrototypeOf
44
+ const REFLECT_APPLY = Reflect.apply
45
+ const ARRAY_BUFFER_PROTOTYPE = ArrayBuffer.prototype
46
+ const SHARED_ARRAY_BUFFER_PROTOTYPE =
47
+ typeof SharedArrayBuffer === 'function' ? SharedArrayBuffer.prototype : null
48
+ const ARRAY_PROTOTYPE = Array.prototype
49
+ const OBJECT_PROTOTYPE = Object.prototype
50
+ const DATE_PROTOTYPE = Date.prototype
51
+ const REGEXP_PROTOTYPE = RegExp.prototype
52
+ const MAP_PROTOTYPE = Map.prototype
53
+ const SET_PROTOTYPE = Set.prototype
37
54
  const UINT8_ARRAY_SET = Uint8Array.prototype.set
55
+ const TYPED_ARRAY_CONSTRUCTORS = new Map([
56
+ ['Int8Array', Int8Array],
57
+ ['Uint8Array', Uint8Array],
58
+ ['Uint8ClampedArray', Uint8ClampedArray],
59
+ ['Int16Array', Int16Array],
60
+ ['Uint16Array', Uint16Array],
61
+ ['Int32Array', Int32Array],
62
+ ['Uint32Array', Uint32Array],
63
+ ...(typeof globalThis.Float16Array === 'function'
64
+ ? [['Float16Array', globalThis.Float16Array]]
65
+ : []),
66
+ ['Float32Array', Float32Array],
67
+ ['Float64Array', Float64Array],
68
+ ...(typeof BigInt64Array === 'function'
69
+ ? [
70
+ ['BigInt64Array', BigInt64Array],
71
+ ['BigUint64Array', BigUint64Array]
72
+ ]
73
+ : [])
74
+ ])
38
75
 
39
76
  /**
40
77
  * Reads and copies binary platform objects through captured intrinsic slots.
@@ -46,6 +83,104 @@ export class BinaryDataSnapshot {
46
83
  * @returns {{ buffer: ArrayBuffer | SharedArrayBuffer, byteOffset: number, byteLength: number, kind: 'buffer' | 'typed-array' | 'data-view' } | null} Intrinsic binary range.
47
84
  */
48
85
  static describe(value) {
86
+ return BinaryDataSnapshot.#describe(value, false)
87
+ }
88
+
89
+ /**
90
+ * Describes binary data in a graph whose platform built-ins are proven to
91
+ * have their standard local prototypes.
92
+ * @param {unknown} value Binary candidate from a normalized graph.
93
+ * @returns {{ buffer: ArrayBuffer | SharedArrayBuffer, byteOffset: number, byteLength: number, kind: 'buffer' | 'typed-array' | 'data-view' } | null} Intrinsic binary range.
94
+ */
95
+ static describeStandard(value) {
96
+ return BinaryDataSnapshot.#describe(value, true)
97
+ }
98
+
99
+ /**
100
+ * Classifies one binary candidate with either exact or proven-standard
101
+ * raw-buffer handling.
102
+ * @param {unknown} value Binary candidate.
103
+ * @param {boolean} standardBuiltins Whether local prototype identity is proven.
104
+ * @returns {{ buffer: ArrayBuffer | SharedArrayBuffer, byteOffset: number, byteLength: number, kind: 'buffer' | 'typed-array' | 'data-view' } | null} Intrinsic binary range.
105
+ */
106
+ static #describe(value, standardBuiltins) {
107
+ if (value === null || typeof value !== 'object') return null
108
+
109
+ if (BinaryDataSnapshot.#isView(value)) {
110
+ const typed = BinaryDataSnapshot.#view(
111
+ value,
112
+ TYPED_ARRAY_BUFFER,
113
+ TYPED_ARRAY_OFFSET,
114
+ TYPED_ARRAY_LENGTH,
115
+ 'typed-array'
116
+ )
117
+ if (typed) return typed
118
+ return BinaryDataSnapshot.#view(
119
+ value,
120
+ DATA_VIEW_BUFFER,
121
+ DATA_VIEW_OFFSET,
122
+ DATA_VIEW_LENGTH,
123
+ 'data-view'
124
+ )
125
+ }
126
+
127
+ if (!standardBuiltins) {
128
+ return BinaryDataSnapshot.#rawBuffer(value)
129
+ }
130
+
131
+ let prototype
132
+ try {
133
+ prototype = OBJECT_GET_PROTOTYPE_OF(value)
134
+ } catch {
135
+ return null
136
+ }
137
+
138
+ if (prototype === ARRAY_BUFFER_PROTOTYPE) {
139
+ const byteLength = BinaryDataSnapshot.#callLength(
140
+ ARRAY_BUFFER_LENGTH,
141
+ value
142
+ )
143
+ return byteLength === null
144
+ ? null
145
+ : {
146
+ buffer: value,
147
+ byteOffset: 0,
148
+ byteLength,
149
+ kind: 'buffer'
150
+ }
151
+ }
152
+ if (
153
+ SHARED_ARRAY_BUFFER_PROTOTYPE &&
154
+ prototype === SHARED_ARRAY_BUFFER_PROTOTYPE
155
+ ) {
156
+ const byteLength = BinaryDataSnapshot.#callLength(
157
+ SHARED_ARRAY_BUFFER_LENGTH,
158
+ value
159
+ )
160
+ return byteLength === null
161
+ ? null
162
+ : {
163
+ buffer: value,
164
+ byteOffset: 0,
165
+ byteLength,
166
+ kind: 'buffer'
167
+ }
168
+ }
169
+ if (BinaryDataSnapshot.#isKnownNonBinary(value, prototype)) {
170
+ return null
171
+ }
172
+
173
+ // Unknown prototypes can represent genuine buffers from another realm.
174
+ // Keep intrinsic brand checks as the authority for that uncommon path.
175
+ return BinaryDataSnapshot.#rawBuffer(value)
176
+ }
177
+
178
+ /**
179
+ * Applies exact raw ArrayBuffer and SharedArrayBuffer brand getters.
180
+ * @param {object} value Raw-buffer candidate.
181
+ * @returns {{ buffer: ArrayBuffer | SharedArrayBuffer, byteOffset: 0, byteLength: number, kind: 'buffer' } | null} Raw buffer range.
182
+ */
183
+ static #rawBuffer(value) {
49
184
  const arrayBufferLength = BinaryDataSnapshot.#callLength(
50
185
  ARRAY_BUFFER_LENGTH,
51
186
  value
@@ -71,21 +206,7 @@ export class BinaryDataSnapshot {
71
206
  }
72
207
  }
73
208
 
74
- const typed = BinaryDataSnapshot.#view(
75
- value,
76
- TYPED_ARRAY_BUFFER,
77
- TYPED_ARRAY_OFFSET,
78
- TYPED_ARRAY_LENGTH,
79
- 'typed-array'
80
- )
81
- if (typed) return typed
82
- return BinaryDataSnapshot.#view(
83
- value,
84
- DATA_VIEW_BUFFER,
85
- DATA_VIEW_OFFSET,
86
- DATA_VIEW_LENGTH,
87
- 'data-view'
88
- )
209
+ return null
89
210
  }
90
211
 
91
212
  /**
@@ -133,8 +254,7 @@ export class BinaryDataSnapshot {
133
254
  if (range.kind === 'buffer') return bytes.buffer
134
255
  if (range.kind === 'data-view') return new DataView(bytes.buffer)
135
256
 
136
- const prototype = Object.getPrototypeOf(value)
137
- const Constructor = BinaryDataSnapshot.#typedArrayConstructor(prototype)
257
+ const Constructor = BinaryDataSnapshot.#typedArrayConstructor(value)
138
258
  if (!Constructor || Constructor === Uint8Array) return bytes
139
259
  const bytesPerElement = Constructor.BYTES_PER_ELEMENT
140
260
  if (bytes.byteLength % bytesPerElement !== 0) return bytes
@@ -150,12 +270,44 @@ export class BinaryDataSnapshot {
150
270
  static #callLength(getter, value) {
151
271
  if (typeof getter !== 'function') return null
152
272
  try {
153
- return getter.call(value)
273
+ return REFLECT_APPLY(getter, value, [])
154
274
  } catch {
155
275
  return null
156
276
  }
157
277
  }
158
278
 
279
+ /**
280
+ * Uses the platform's side-effect-free view brand classifier.
281
+ * @param {unknown} value Candidate value.
282
+ * @returns {boolean} Whether the value has ArrayBuffer view slots.
283
+ */
284
+ static #isView(value) {
285
+ try {
286
+ return REFLECT_APPLY(ARRAY_BUFFER_IS_VIEW, ArrayBuffer, [value])
287
+ } catch {
288
+ return false
289
+ }
290
+ }
291
+
292
+ /**
293
+ * Returns true for common local containers that cannot be binary objects.
294
+ * @param {object} value Candidate object.
295
+ * @param {object | null} prototype Captured prototype.
296
+ * @returns {boolean} Whether intrinsic buffer probing can be skipped.
297
+ */
298
+ static #isKnownNonBinary(value, prototype) {
299
+ return (
300
+ ARRAY_IS_ARRAY(value) ||
301
+ prototype === ARRAY_PROTOTYPE ||
302
+ prototype === OBJECT_PROTOTYPE ||
303
+ prototype === null ||
304
+ prototype === DATE_PROTOTYPE ||
305
+ prototype === REGEXP_PROTOTYPE ||
306
+ prototype === MAP_PROTOTYPE ||
307
+ prototype === SET_PROTOTYPE
308
+ )
309
+ }
310
+
159
311
  /**
160
312
  * Reads captured intrinsic view slots without ordinary property access.
161
313
  * @param {unknown} value View candidate.
@@ -175,9 +327,9 @@ export class BinaryDataSnapshot {
175
327
  }
176
328
  try {
177
329
  return {
178
- buffer: bufferGetter.call(value),
179
- byteOffset: offsetGetter.call(value),
180
- byteLength: lengthGetter.call(value),
330
+ buffer: REFLECT_APPLY(bufferGetter, value, []),
331
+ byteOffset: REFLECT_APPLY(offsetGetter, value, []),
332
+ byteLength: REFLECT_APPLY(lengthGetter, value, []),
181
333
  kind
182
334
  }
183
335
  } catch {
@@ -186,30 +338,18 @@ export class BinaryDataSnapshot {
186
338
  }
187
339
 
188
340
  /**
189
- * Maps a genuine typed-array prototype to its platform constructor.
190
- * @param {object | null} prototype Candidate prototype.
341
+ * Maps a genuine typed array's intrinsic tag to a local constructor.
342
+ * @param {unknown} value Genuine typed-array value.
191
343
  * @returns {Function | null} Matching typed-array constructor.
192
344
  */
193
- static #typedArrayConstructor(prototype) {
194
- const constructors = [
195
- Int8Array,
196
- Uint8Array,
197
- Uint8ClampedArray,
198
- Int16Array,
199
- Uint16Array,
200
- Int32Array,
201
- Uint32Array,
202
- Float32Array,
203
- Float64Array,
204
- ...(typeof BigInt64Array === 'function'
205
- ? [BigInt64Array, BigUint64Array]
206
- : [])
207
- ]
208
- return (
209
- constructors.find(
210
- (Constructor) => Constructor.prototype === prototype
211
- ) || null
212
- )
345
+ static #typedArrayConstructor(value) {
346
+ if (typeof TYPED_ARRAY_TAG !== 'function') return null
347
+ try {
348
+ const tag = REFLECT_APPLY(TYPED_ARRAY_TAG, value, [])
349
+ return TYPED_ARRAY_CONSTRUCTORS.get(tag) || null
350
+ } catch {
351
+ return null
352
+ }
213
353
  }
214
354
  }
215
355
 
@@ -74,9 +74,31 @@ export class CircuitJsonDocumentContext {
74
74
  * @returns {CircuitJsonDocumentContext} Prepared request-scoped context.
75
75
  */
76
76
  static prepare(input, options = {}) {
77
+ return CircuitJsonDocumentContext.#prepare(input, options, false)
78
+ }
79
+
80
+ /**
81
+ * Prepares a document whose platform built-ins were normalized by the
82
+ * structured-clone algorithm.
83
+ * @param {unknown} input Structured-cloned document result or existing context.
84
+ * @param {{ indexes?: unknown }} [options] Requested context options.
85
+ * @returns {CircuitJsonDocumentContext} Prepared request-scoped context.
86
+ */
87
+ static prepareStructuredClone(input, options = {}) {
88
+ return CircuitJsonDocumentContext.#prepare(input, options, true)
89
+ }
90
+
91
+ /**
92
+ * Prepares one context with explicit metadata provenance.
93
+ * @param {unknown} input Document result, CircuitJSON model, or context.
94
+ * @param {{ indexes?: unknown }} options Requested context options.
95
+ * @param {boolean} standardBuiltins Whether metadata built-ins have standard local prototypes.
96
+ * @returns {CircuitJsonDocumentContext} Prepared request-scoped context.
97
+ */
98
+ static #prepare(input, options, standardBuiltins) {
77
99
  const context = CircuitJsonDocumentContext.#isContext(input)
78
100
  ? input
79
- : CircuitJsonDocumentContext.#fromInput(input)
101
+ : CircuitJsonDocumentContext.#fromInput(input, standardBuiltins)
80
102
  context.#indexes.ensure(options?.indexes || [])
81
103
  return context
82
104
  }
@@ -181,15 +203,20 @@ export class CircuitJsonDocumentContext {
181
203
  /**
182
204
  * Creates one context and establishes a matching immutable model proof.
183
205
  * @param {unknown} input Document result or CircuitJSON model.
206
+ * @param {boolean} standardBuiltins Whether metadata built-ins have standard local prototypes.
184
207
  * @returns {CircuitJsonDocumentContext} New context.
185
208
  */
186
- static #fromInput(input) {
209
+ static #fromInput(input, standardBuiltins) {
187
210
  const document = CircuitJsonDocumentContext.#normalizeDocument(input)
188
211
  const validationPasses = CircuitJsonValidationProof.has(document)
189
212
  ? 0
190
213
  : 1
191
- const readonlyDocument =
192
- CircuitJsonValidationProof.validateAndAttach(document)
214
+ const readonlyDocument = CircuitJsonValidationProof.validateAndAttach(
215
+ document,
216
+ {
217
+ standardBuiltins
218
+ }
219
+ )
193
220
  const model = CircuitJsonDocumentContext.#ownData(
194
221
  readonlyDocument,
195
222
  'model'