@smoothbricks/lmao 0.2.2 → 0.2.3

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.
Files changed (276) hide show
  1. package/dist/allocator.wasm +0 -0
  2. package/dist/index.d.ts +1 -0
  3. package/dist/index.d.ts.map +1 -1
  4. package/dist/index.js +1 -0
  5. package/dist/lib/JsBufferStrategy.d.ts +4 -3
  6. package/dist/lib/JsBufferStrategy.d.ts.map +1 -1
  7. package/dist/lib/JsBufferStrategy.js +18 -11
  8. package/dist/lib/arrow/lease.d.ts +10 -0
  9. package/dist/lib/arrow/lease.d.ts.map +1 -0
  10. package/dist/lib/arrow/lease.js +33 -0
  11. package/dist/lib/arrow/utils.d.ts +3 -4
  12. package/dist/lib/arrow/utils.d.ts.map +1 -1
  13. package/dist/lib/arrow/utils.js +12 -13
  14. package/dist/lib/arrow/vocabularyDictionary.d.ts +21 -0
  15. package/dist/lib/arrow/vocabularyDictionary.d.ts.map +1 -0
  16. package/dist/lib/arrow/vocabularyDictionary.js +95 -0
  17. package/dist/lib/bufferStrategy.d.ts +3 -2
  18. package/dist/lib/bufferStrategy.d.ts.map +1 -1
  19. package/dist/lib/cloudflare/traceRows.d.ts.map +1 -1
  20. package/dist/lib/cloudflare/traceRows.js +3 -2
  21. package/dist/lib/codegen/evaluatorGenerator.d.ts.map +1 -1
  22. package/dist/lib/codegen/evaluatorGenerator.js +12 -11
  23. package/dist/lib/codegen/fixedPositionWriterGenerator.d.ts +44 -82
  24. package/dist/lib/codegen/fixedPositionWriterGenerator.d.ts.map +1 -1
  25. package/dist/lib/codegen/fixedPositionWriterGenerator.js +223 -162
  26. package/dist/lib/codegen/spanLoggerClosureMaterializer.d.ts +63 -0
  27. package/dist/lib/codegen/spanLoggerClosureMaterializer.d.ts.map +1 -0
  28. package/dist/lib/codegen/spanLoggerClosureMaterializer.js +447 -0
  29. package/dist/lib/codegen/spanLoggerGenerator.d.ts +36 -19
  30. package/dist/lib/codegen/spanLoggerGenerator.d.ts.map +1 -1
  31. package/dist/lib/codegen/spanLoggerGenerator.js +164 -158
  32. package/dist/lib/convertToArrow.d.ts +7 -2
  33. package/dist/lib/convertToArrow.d.ts.map +1 -1
  34. package/dist/lib/convertToArrow.js +499 -285
  35. package/dist/lib/defineOpContext.js +1 -1
  36. package/dist/lib/enumMetadata.d.ts +19 -0
  37. package/dist/lib/enumMetadata.d.ts.map +1 -0
  38. package/dist/lib/enumMetadata.js +86 -0
  39. package/dist/lib/flushScheduler.d.ts.map +1 -1
  40. package/dist/lib/flushScheduler.js +13 -4
  41. package/dist/lib/library.d.ts +16 -98
  42. package/dist/lib/library.d.ts.map +1 -1
  43. package/dist/lib/library.js +32 -525
  44. package/dist/lib/logBinding.d.ts +21 -27
  45. package/dist/lib/logBinding.d.ts.map +1 -1
  46. package/dist/lib/op.d.ts +30 -30
  47. package/dist/lib/op.d.ts.map +1 -1
  48. package/dist/lib/op.js +15 -22
  49. package/dist/lib/opContext/createOpGroup.d.ts +1 -1
  50. package/dist/lib/opContext/createOpGroup.d.ts.map +1 -1
  51. package/dist/lib/opContext/createOpGroup.js +22 -29
  52. package/dist/lib/opContext/defineOp.d.ts +4 -4
  53. package/dist/lib/opContext/defineOp.d.ts.map +1 -1
  54. package/dist/lib/opContext/defineOp.js +53 -10
  55. package/dist/lib/opContext/opGroupTypes.d.ts +2 -2
  56. package/dist/lib/opContext/opTypes.d.ts +1 -1
  57. package/dist/lib/opContext/opTypes.d.ts.map +1 -1
  58. package/dist/lib/opContext/spanContextTypes.d.ts +29 -14
  59. package/dist/lib/opContext/spanContextTypes.d.ts.map +1 -1
  60. package/dist/lib/opContext/types.d.ts +4 -4
  61. package/dist/lib/opContext/types.d.ts.map +1 -1
  62. package/dist/lib/physicalLayoutPlan.d.ts +89 -0
  63. package/dist/lib/physicalLayoutPlan.d.ts.map +1 -0
  64. package/dist/lib/physicalLayoutPlan.js +442 -0
  65. package/dist/lib/resolveMessage.d.ts +9 -0
  66. package/dist/lib/resolveMessage.d.ts.map +1 -0
  67. package/dist/lib/resolveMessage.js +63 -0
  68. package/dist/lib/result.d.ts +5 -7
  69. package/dist/lib/result.d.ts.map +1 -1
  70. package/dist/lib/result.js +19 -26
  71. package/dist/lib/runtimeHint.d.ts +35 -0
  72. package/dist/lib/runtimeHint.d.ts.map +1 -0
  73. package/dist/lib/runtimeHint.js +79 -0
  74. package/dist/lib/schema/types.d.ts +1 -1
  75. package/dist/lib/schema/types.d.ts.map +1 -1
  76. package/dist/lib/span-buffer/aot/v1.d.ts +2 -0
  77. package/dist/lib/span-buffer/aot/v1.d.ts.map +1 -0
  78. package/dist/lib/span-buffer/aot/v1.js +24 -0
  79. package/dist/lib/spanBuffer.d.ts +33 -4
  80. package/dist/lib/spanBuffer.d.ts.map +1 -1
  81. package/dist/lib/spanBuffer.js +583 -91
  82. package/dist/lib/spanBufferStats.d.ts +1 -1
  83. package/dist/lib/spanBufferStats.d.ts.map +1 -1
  84. package/dist/lib/spanContext.d.ts +27 -51
  85. package/dist/lib/spanContext.d.ts.map +1 -1
  86. package/dist/lib/spanContext.js +822 -344
  87. package/dist/lib/sqlite/sqlite-common.d.ts.map +1 -1
  88. package/dist/lib/sqlite/sqlite-common.js +20 -21
  89. package/dist/lib/testing/bun-harness.d.ts +8 -3
  90. package/dist/lib/testing/bun-harness.d.ts.map +1 -1
  91. package/dist/lib/testing/bun-harness.js +30 -18
  92. package/dist/lib/testing/extractFacts.d.ts +1 -1
  93. package/dist/lib/testing/extractFacts.d.ts.map +1 -1
  94. package/dist/lib/testing/extractFacts.js +55 -63
  95. package/dist/lib/testing/facts.d.ts +1 -1
  96. package/dist/lib/testing/facts.d.ts.map +1 -1
  97. package/dist/lib/testing/queryable-span.d.ts +1 -1
  98. package/dist/lib/testing/queryable-span.d.ts.map +1 -1
  99. package/dist/lib/testing/queryable-span.js +26 -28
  100. package/dist/lib/testing/span-query.d.ts.map +1 -1
  101. package/dist/lib/testing/span-query.js +19 -22
  102. package/dist/lib/testing/stdio-replay.d.ts.map +1 -1
  103. package/dist/lib/testing/stdio-replay.js +19 -8
  104. package/dist/lib/threadId.d.ts.map +1 -1
  105. package/dist/lib/threadId.js +6 -3
  106. package/dist/lib/traceRoot.d.ts +30 -8
  107. package/dist/lib/traceRoot.d.ts.map +1 -1
  108. package/dist/lib/traceRoot.es.d.ts +17 -15
  109. package/dist/lib/traceRoot.es.d.ts.map +1 -1
  110. package/dist/lib/traceRoot.es.js +68 -34
  111. package/dist/lib/traceRoot.js +11 -0
  112. package/dist/lib/traceRoot.node.d.ts +17 -19
  113. package/dist/lib/traceRoot.node.d.ts.map +1 -1
  114. package/dist/lib/traceRoot.node.js +66 -38
  115. package/dist/lib/traceTopology.d.ts +42 -0
  116. package/dist/lib/traceTopology.d.ts.map +1 -0
  117. package/dist/lib/traceTopology.js +260 -0
  118. package/dist/lib/tracer.d.ts +9 -5
  119. package/dist/lib/tracer.d.ts.map +1 -1
  120. package/dist/lib/tracer.js +65 -71
  121. package/dist/lib/tracers/ArrayQueueTracer.d.ts +1 -1
  122. package/dist/lib/tracers/ArrayQueueTracer.js +1 -1
  123. package/dist/lib/tracers/StdioTracer.d.ts.map +1 -1
  124. package/dist/lib/tracers/StdioTracer.js +15 -13
  125. package/dist/lib/tracers/TestTracer.d.ts +4 -6
  126. package/dist/lib/tracers/TestTracer.d.ts.map +1 -1
  127. package/dist/lib/tracers/TestTracer.js +6 -8
  128. package/dist/lib/types.d.ts +49 -32
  129. package/dist/lib/types.d.ts.map +1 -1
  130. package/dist/lib/vocabularyRegistry.d.ts +25 -0
  131. package/dist/lib/vocabularyRegistry.d.ts.map +1 -0
  132. package/dist/lib/vocabularyRegistry.js +372 -0
  133. package/dist/lib/wasm/WasmBufferStrategy.d.ts +6 -10
  134. package/dist/lib/wasm/WasmBufferStrategy.d.ts.map +1 -1
  135. package/dist/lib/wasm/WasmBufferStrategy.js +44 -37
  136. package/dist/lib/wasm/wasmAllocator.d.ts +27 -22
  137. package/dist/lib/wasm/wasmAllocator.d.ts.map +1 -1
  138. package/dist/lib/wasm/wasmAllocator.js +66 -44
  139. package/dist/lib/wasm/wasmPhysicalLayout.d.ts +67 -0
  140. package/dist/lib/wasm/wasmPhysicalLayout.d.ts.map +1 -0
  141. package/dist/lib/wasm/wasmPhysicalLayout.js +179 -0
  142. package/dist/lib/wasm/wasmSpanBuffer.d.ts +79 -31
  143. package/dist/lib/wasm/wasmSpanBuffer.d.ts.map +1 -1
  144. package/dist/lib/wasm/wasmSpanBuffer.js +442 -272
  145. package/dist/lib/wasm/wasmTraceRoot.d.ts +24 -22
  146. package/dist/lib/wasm/wasmTraceRoot.d.ts.map +1 -1
  147. package/dist/lib/wasm/wasmTraceRoot.js +110 -71
  148. package/dist/vocabulary/register/v1.d.ts +2 -0
  149. package/dist/vocabulary/register/v1.d.ts.map +1 -0
  150. package/dist/vocabulary/register/v1.js +1 -0
  151. package/dist/wasm.d.ts +5 -0
  152. package/dist/wasm.d.ts.map +1 -0
  153. package/dist/wasm.js +4 -0
  154. package/package.json +54 -6
  155. package/src/index.ts +1 -0
  156. package/src/lib/JsBufferStrategy.ts +27 -9
  157. package/src/lib/__tests__/arrow-builder-integration/arrow-conversion.test.ts +335 -37
  158. package/src/lib/__tests__/arrow-builder-integration/binary-arrow-conversion.test.ts +97 -26
  159. package/src/lib/__tests__/arrow-builder-integration/binary-comparison.test.ts +75 -15
  160. package/src/lib/__tests__/arrow-builder-integration/binary-format-compliance.test.ts +30 -6
  161. package/src/lib/__tests__/arrow-builder-integration/buffer-chaining.test.ts +18 -21
  162. package/src/lib/__tests__/arrow-builder-integration/foundation.test.ts +5 -2
  163. package/src/lib/__tests__/arrow-builder-integration/integration.test.ts +25 -7
  164. package/src/lib/__tests__/arrow-builder-integration/lazy-columns.test.ts +3 -2
  165. package/src/lib/__tests__/buffer-overflow.test.ts +24 -18
  166. package/src/lib/__tests__/capability-span-context.test.ts +505 -0
  167. package/src/lib/__tests__/capacity-tuning.test.ts +5 -3
  168. package/src/lib/__tests__/eager-columns.test.ts +345 -0
  169. package/src/lib/__tests__/enum-metadata-aot.test.ts +153 -0
  170. package/src/lib/__tests__/enum-metadata.test.ts +177 -0
  171. package/src/lib/__tests__/fixtures/empty-vocabulary-runtime.fixture.ts +15 -0
  172. package/src/lib/__tests__/flushScheduler.test.ts +5 -1
  173. package/src/lib/__tests__/identity-view.property.test.ts +179 -0
  174. package/src/lib/__tests__/library.test.ts +19 -235
  175. package/src/lib/__tests__/message-buffer-families.test.ts +878 -0
  176. package/src/lib/__tests__/nested-library-tasks.test.ts +14 -15
  177. package/src/lib/__tests__/op-metadata.test.ts +1 -1
  178. package/src/lib/__tests__/op-type-safety.test.ts +5 -16
  179. package/src/lib/__tests__/physical-layout-plan.test.ts +333 -0
  180. package/src/lib/__tests__/remap-descriptor-integration.test.ts +381 -0
  181. package/src/lib/__tests__/root-context-construction.test.ts +414 -0
  182. package/src/lib/__tests__/span-auto-runtime.test.ts +386 -0
  183. package/src/lib/__tests__/span-buffer-segment-stats.test.ts +221 -0
  184. package/src/lib/__tests__/span-lifecycle.test.ts +53 -25
  185. package/src/lib/__tests__/spanbuffer-materializer-parity.test.ts +266 -0
  186. package/src/lib/__tests__/sync-async-trace.test.ts +24 -23
  187. package/src/lib/__tests__/tag-writer-aot.test.ts +214 -0
  188. package/src/lib/__tests__/test-helpers.ts +67 -12
  189. package/src/lib/__tests__/threadId.test.ts +62 -0
  190. package/src/lib/__tests__/timestamp.test.ts +221 -93
  191. package/src/lib/__tests__/traceTopology.property.test.ts +383 -0
  192. package/src/lib/__tests__/type-narrowing.test.ts +63 -0
  193. package/src/lib/__tests__/vocabulary-dense-decoding.test.ts +405 -0
  194. package/src/lib/__tests__/vocabularyRegistry.test.ts +313 -0
  195. package/src/lib/arrow/lease.ts +43 -0
  196. package/src/lib/arrow/utils.ts +10 -14
  197. package/src/lib/arrow/vocabularyDictionary.ts +123 -0
  198. package/src/lib/bufferStrategy.ts +9 -1
  199. package/src/lib/cloudflare/traceRows.ts +3 -2
  200. package/src/lib/codegen/__tests__/evaluatorGenerator.test.ts +21 -141
  201. package/src/lib/codegen/__tests__/fixedPositionWriter-parity.test.ts +273 -0
  202. package/src/lib/codegen/__tests__/fixedPositionWriter-uint64.test.ts +61 -34
  203. package/src/lib/codegen/__tests__/nullBitmap.test.ts +33 -37
  204. package/src/lib/codegen/__tests__/spanLoggerGenerator.test.ts +34 -18
  205. package/src/lib/codegen/__tests__/spanLoggerMaterializerParity.test.ts +503 -0
  206. package/src/lib/codegen/evaluatorGenerator.ts +18 -13
  207. package/src/lib/codegen/fixedPositionWriterGenerator.ts +329 -245
  208. package/src/lib/codegen/spanLoggerClosureMaterializer.ts +598 -0
  209. package/src/lib/codegen/spanLoggerGenerator.ts +241 -177
  210. package/src/lib/convertToArrow.ts +598 -311
  211. package/src/lib/defineOpContext.ts +1 -1
  212. package/src/lib/enumMetadata.ts +121 -0
  213. package/src/lib/flushScheduler.ts +11 -5
  214. package/src/lib/library.ts +37 -595
  215. package/src/lib/logBinding.ts +21 -27
  216. package/src/lib/op.ts +75 -18
  217. package/src/lib/opContext/__tests__/retry.test.ts +27 -21
  218. package/src/lib/opContext/createOpGroup.ts +33 -31
  219. package/src/lib/opContext/defineOp.ts +88 -11
  220. package/src/lib/opContext/opGroupTypes.ts +2 -2
  221. package/src/lib/opContext/opTypes.ts +1 -1
  222. package/src/lib/opContext/spanContextTypes.ts +98 -17
  223. package/src/lib/opContext/types.ts +7 -3
  224. package/src/lib/physicalLayoutPlan.ts +598 -0
  225. package/src/lib/resolveMessage.ts +67 -0
  226. package/src/lib/result.ts +20 -33
  227. package/src/lib/runtimeHint.ts +98 -0
  228. package/src/lib/schema/__tests__/featureFlags.test.ts +2 -1
  229. package/src/lib/schema/builder.ts +1 -1
  230. package/src/lib/schema/types.ts +1 -1
  231. package/src/lib/span-buffer/aot/v1.ts +26 -0
  232. package/src/lib/spanBuffer.ts +658 -92
  233. package/src/lib/spanBufferStats.ts +1 -1
  234. package/src/lib/spanContext.ts +1350 -861
  235. package/src/lib/sqlite/sqlite-common.ts +20 -31
  236. package/src/lib/testing/__tests__/topology-traversal.test.ts +174 -0
  237. package/src/lib/testing/__tests__/vitest-harness.test.ts +2 -1
  238. package/src/lib/testing/bun-harness.ts +94 -43
  239. package/src/lib/testing/extractFacts.ts +64 -78
  240. package/src/lib/testing/facts.ts +1 -1
  241. package/src/lib/testing/queryable-span.ts +23 -30
  242. package/src/lib/testing/span-query.ts +16 -20
  243. package/src/lib/testing/stdio-replay.ts +25 -8
  244. package/src/lib/testing/vitest-harness.ts +1 -1
  245. package/src/lib/threadId.ts +6 -3
  246. package/src/lib/traceRoot.es.ts +77 -42
  247. package/src/lib/traceRoot.node.ts +75 -46
  248. package/src/lib/traceRoot.ts +41 -14
  249. package/src/lib/traceTopology.ts +279 -0
  250. package/src/lib/tracer.ts +109 -98
  251. package/src/lib/tracers/ArrayQueueTracer.ts +1 -1
  252. package/src/lib/tracers/StdioTracer.ts +15 -13
  253. package/src/lib/tracers/TestTracer.ts +6 -8
  254. package/src/lib/tracers/__tests__/ArrayQueueTracer.test.ts +15 -10
  255. package/src/lib/tracers/__tests__/TestTracer.test.ts +23 -13
  256. package/src/lib/types.ts +64 -33
  257. package/src/lib/vocabularyRegistry.ts +439 -0
  258. package/src/lib/wasm/WasmBufferStrategy.ts +56 -48
  259. package/src/lib/wasm/__tests__/WasmBufferStrategy.test.ts +215 -11
  260. package/src/lib/wasm/__tests__/allocatorFreeBlockClamp.test.ts +86 -0
  261. package/src/lib/wasm/__tests__/wasm-identity-view.test.ts +311 -0
  262. package/src/lib/wasm/__tests__/wasm-integration.test.ts +417 -44
  263. package/src/lib/wasm/__tests__/wasm-message-buffer-families.test.ts +588 -0
  264. package/src/lib/wasm/__tests__/wasmAllocator.test.ts +100 -668
  265. package/src/lib/wasm/__tests__/wasmSpanBuffer.test.ts +419 -63
  266. package/src/lib/wasm/__tests__/wasmTraceRoot.test.ts +24 -29
  267. package/src/lib/wasm/__tests__/wasmTracer.test.ts.disabled +2 -2
  268. package/src/lib/wasm/wasmAllocator.ts +131 -96
  269. package/src/lib/wasm/wasmPhysicalLayout.ts +309 -0
  270. package/src/lib/wasm/wasmSpanBuffer.ts +597 -341
  271. package/src/lib/wasm/wasmTraceRoot.ts +140 -78
  272. package/src/vocabulary/register/v1.ts +1 -0
  273. package/src/wasm.ts +19 -0
  274. package/src/lib/__tests__/remapped-buffer-view-integration.test.ts +0 -319
  275. package/src/lib/__tests__/remapped-buffer-view.test.ts +0 -635
  276. package/src/lib/wasm/allocator.zig +0 -948
@@ -1,11 +1,24 @@
1
1
  import { beforeEach, describe, expect, it } from 'bun:test';
2
2
  import { S as ArrowS, Nanoseconds } from '@smoothbricks/arrow-builder';
3
- import { createTestOpMetadata, TEST_TRACER } from '../../__tests__/test-helpers.js';
3
+ import fc from 'fast-check';
4
+ import { createTestOpMetadata, createTestTraceRoot, TEST_TRACER } from '../../__tests__/test-helpers.js';
5
+ import { defineOpContext } from '../../defineOpContext.js';
6
+ import { type EagerColumnDescriptor, resolveEagerColumns } from '../../physicalLayoutPlan.js';
7
+ import { resolveEntryType, resolveMessage } from '../../resolveMessage.js';
8
+ import { RUNTIME_HINT_ANALYZED_VALID, RUNTIME_HINT_RESULT, RUNTIME_HINT_TAG } from '../../runtimeHint.js';
4
9
  import { S } from '../../schema/builder.js';
5
10
  import { defineLogSchema } from '../../schema/defineLogSchema.js';
6
- import { EMPTY_SCOPE } from '../../spanBuffer.js';
11
+ import {
12
+ ENTRY_TYPE_INFO,
13
+ ENTRY_TYPE_SPAN_EXCEPTION,
14
+ ENTRY_TYPE_SPAN_OK,
15
+ ENTRY_TYPE_SPAN_START,
16
+ } from '../../schema/systemSchema.js';
17
+ import { createSpanBuffer, EMPTY_SCOPE } from '../../spanBuffer.js';
7
18
  import { createTraceId } from '../../traceId.js';
19
+ import { iterateSpanChildren, NO_NODE } from '../../traceTopology.js';
8
20
  import { createWasmAllocator, type WasmAllocator } from '../wasmAllocator.js';
21
+ import { getWasmPhysicalLayout } from '../wasmPhysicalLayout.js';
9
22
  import {
10
23
  createWasmChildSpanBuffer,
11
24
  createWasmOverflowBuffer,
@@ -31,6 +44,15 @@ function getColumn<T>(target: object, name: string): T {
31
44
  return Reflect.get(target, name);
32
45
  }
33
46
 
47
+ function eagerDescriptorBytes(descriptor: EagerColumnDescriptor): Uint8Array {
48
+ const bytes = new Uint8Array(descriptor.words.length * Uint32Array.BYTES_PER_ELEMENT);
49
+ const view = new DataView(bytes.buffer);
50
+ for (let index = 0; index < descriptor.words.length; index++) {
51
+ view.setUint32(index * Uint32Array.BYTES_PER_ELEMENT, descriptor.words[index], true);
52
+ }
53
+ return bytes;
54
+ }
55
+
34
56
  describe('WasmSpanBuffer', () => {
35
57
  let allocator: WasmAllocator;
36
58
  const CAPACITY = 64;
@@ -40,18 +62,15 @@ describe('WasmSpanBuffer', () => {
40
62
  allocator = await createWasmAllocator({ capacity: CAPACITY });
41
63
  allocator.reset();
42
64
  allocator.setThreadId(0, 42); // high=0, low=42 -> thread_id = 42n
43
- traceRoot = new WasmTraceRoot(allocator, testTraceId('test-trace'), TEST_TRACER);
65
+ traceRoot = new WasmTraceRoot(allocator, testTraceId('trace-123'), TEST_TRACER);
44
66
  });
45
67
 
46
- function createTestWasmBuffer(overrides: { trace_id?: string; thread_id?: bigint; span_id?: number } = {}) {
68
+ function createTestWasmBuffer() {
47
69
  return createWasmSpanBuffer(
48
70
  testSchema,
49
71
  {
50
72
  allocator,
51
73
  capacity: CAPACITY,
52
- trace_id: overrides.trace_id ? testTraceId(overrides.trace_id) : testTraceId('trace-123'),
53
- thread_id: overrides.thread_id ?? 42n,
54
- span_id: overrides.span_id ?? 1,
55
74
  },
56
75
  traceRoot,
57
76
  EMPTY_SCOPE,
@@ -72,31 +91,150 @@ describe('WasmSpanBuffer', () => {
72
91
  duration: ArrowS.bigUint64(),
73
92
  errorMessage: S.text(),
74
93
  });
94
+ const testEagerColumns = resolveEagerColumns(testSchema);
95
+
96
+ const compilerEagerSchema = defineLogSchema({
97
+ provenNumber: S.number(),
98
+ lazyNumber: S.number(),
99
+ provenString: S.category(),
100
+ lazyString: S.category(),
101
+ });
102
+ const compilerEagerContext = defineOpContext({ logSchema: compilerEagerSchema });
103
+ const compilerEagerOp = compilerEagerContext.defineOp('compiler-eager-wasm', (ctx) => ctx.ok(null), undefined, {
104
+ runtimeHint: RUNTIME_HINT_ANALYZED_VALID | RUNTIME_HINT_TAG | RUNTIME_HINT_RESULT | 8,
105
+ eagerColumns: ['provenString', 'provenNumber', 'provenString'],
106
+ });
107
+
108
+ function createCompilerEagerWasmBuffer() {
109
+ return createWasmSpanBuffer(
110
+ compilerEagerSchema,
111
+ {
112
+ allocator,
113
+ capacity: CAPACITY,
114
+ },
115
+ traceRoot,
116
+ EMPTY_SCOPE,
117
+ compilerEagerOp.metadata,
118
+ compilerEagerOp.metadata,
119
+ );
120
+ }
75
121
 
76
122
  describe('getWasmSpanBufferClass', () => {
77
123
  it('generates a class constructor', () => {
78
- const WasmSpanBufferClass = getWasmSpanBufferClass(testSchema);
124
+ const WasmSpanBufferClass = getWasmSpanBufferClass(testSchema, 'mixed', 'current', testEagerColumns);
79
125
  expect(typeof WasmSpanBufferClass).toBe('function');
80
126
  expect(WasmSpanBufferClass.schema).toBe(testSchema);
81
127
  });
82
128
 
83
129
  it('caches classes per schema', () => {
84
- const class1 = getWasmSpanBufferClass(testSchema);
85
- const class2 = getWasmSpanBufferClass(testSchema);
130
+ const class1 = getWasmSpanBufferClass(testSchema, 'mixed', 'current', testEagerColumns);
131
+ const class2 = getWasmSpanBufferClass(testSchema, 'mixed', 'current', testEagerColumns);
86
132
  expect(class1).toBe(class2);
87
133
  });
88
134
 
89
135
  it('creates different classes for different schemas', () => {
90
136
  const schema2 = defineLogSchema({ otherField: S.number() });
91
- const class1 = getWasmSpanBufferClass(testSchema);
92
- const class2 = getWasmSpanBufferClass(schema2);
137
+ const class1 = getWasmSpanBufferClass(testSchema, 'mixed', 'current', testEagerColumns);
138
+ const class2 = getWasmSpanBufferClass(schema2, 'mixed', 'current', resolveEagerColumns(schema2));
93
139
  expect(class1).not.toBe(class2);
94
140
  });
95
141
  });
96
142
 
143
+ describe('exact physical layout', () => {
144
+ it('produces aligned, exact, non-overlapping family descriptors for arbitrary capacities', () => {
145
+ fc.assert(
146
+ fc.property(fc.integer({ min: 1, max: 512 }), (capacity) => {
147
+ const layout = getWasmPhysicalLayout(testSchema, capacity, 'mixed', 'current', testEagerColumns);
148
+ expect(layout).toBe(getWasmPhysicalLayout(testSchema, capacity, 'mixed', 'current', testEagerColumns));
149
+ expect(Object.isFrozen(layout)).toBe(true);
150
+ expect(layout.system.timestampOffset).toBe(0);
151
+ expect(layout.system.entryTypeOffset).toBe(capacity * 8);
152
+ const messageIdOffset = (capacity * 9 + 1) & ~1;
153
+ const expectedByteLength = (messageIdOffset + capacity * Uint16Array.BYTES_PER_ELEMENT + 7) & ~7;
154
+ expect(layout.messageLayoutFamily).toBe('mixed');
155
+ expect(layout.messagePhysicalLayout).toBe('current');
156
+ expect(layout.system.messageIdOffset).toBe(messageIdOffset);
157
+ expect('messageIdValidityOffset' in layout.system).toBe(false);
158
+ expect(layout.system.messageDenseIndexOffset).toBeNull();
159
+ expect('messageValidityOffset' in layout.system).toBe(false);
160
+ expect(layout.system.rowHeaderOffset).toBeNull();
161
+ expect(layout.system.byteLength).toBe(expectedByteLength);
162
+
163
+ expect(Object.isFrozen(layout.spanSuperblock)).toBe(true);
164
+ expect(Object.isFrozen(layout.overflowSuperblock)).toBe(true);
165
+ expect(layout.spanSuperblock.identityOffset).toBe(0);
166
+ expect(layout.overflowSuperblock.identityOffset).toBeNull();
167
+ expect(layout.spanSuperblock.systemOffset % layout.system.alignment).toBe(0);
168
+ expect(layout.overflowSuperblock.systemOffset % layout.system.alignment).toBe(0);
169
+ expect(layout.spanSuperblock.byteLength).toBeLessThanOrEqual(layout.spanSuperblock.legacyByteLength);
170
+ expect(layout.overflowSuperblock.byteLength).toBeLessThanOrEqual(layout.overflowSuperblock.legacyByteLength);
171
+
172
+ for (const family of ['u8', 'u32', 'f64'] as const) {
173
+ const slab = layout.slabs[family];
174
+ const columns = layout.columns.filter((column) => column.family === family);
175
+ expect(slab === null).toBe(columns.length === 0);
176
+ if (slab === null) continue;
177
+
178
+ const ranges: Array<readonly [number, number]> = [];
179
+ for (const column of columns) {
180
+ expect(column.valueOffset % column.alignment).toBe(0);
181
+ expect(column.nullByteLength).toBe(Math.ceil(capacity / 8));
182
+ expect(column.valueLength).toBe(capacity * column.byteWidth);
183
+ expect(column.nullOffset + column.nullByteLength).toBeLessThanOrEqual(column.valueOffset);
184
+ expect(column.valueOffset + column.valueLength).toBeLessThanOrEqual(slab.byteLength);
185
+
186
+ const columnRanges = [
187
+ [column.nullOffset, column.nullOffset + column.nullByteLength],
188
+ [column.valueOffset, column.valueOffset + column.valueLength],
189
+ ] as const;
190
+ for (const [start, end] of columnRanges) {
191
+ for (const [otherStart, otherEnd] of ranges) {
192
+ expect(end <= otherStart || start >= otherEnd).toBe(true);
193
+ }
194
+ ranges.push([start, end]);
195
+ }
196
+ }
197
+ }
198
+ }),
199
+ );
200
+ });
201
+
202
+ it('allocates one packed superblock and no further blocks on numeric writes', () => {
203
+ const allocsBefore = allocator.getAllocCount();
204
+ const buffer = createTestWasmBuffer();
205
+ expect(allocator.getAllocCount() - allocsBefore).toBe(1);
206
+ expect(buffer._allocationPtr).toBe(buffer._identityPtr);
207
+ expect(buffer._systemPtr).toBe(buffer._allocationPtr + buffer._layout.spanSuperblock.systemOffset);
208
+ expect(buffer._allocationByteLength).toBe(buffer._layout.spanSuperblock.byteLength);
209
+
210
+ const ranges: Array<readonly [number, number]> = [
211
+ [buffer._identityPtr, buffer._identityPtr + 128],
212
+ [buffer._systemPtr, buffer._systemPtr + buffer._layout.system.byteLength],
213
+ ];
214
+ for (const family of ['u8', 'u32', 'f64'] as const) {
215
+ const slab = buffer._layout.slabs[family];
216
+ if (slab === null) continue;
217
+ const start = buffer._familyPtrs[family];
218
+ const end = start + slab.byteLength;
219
+ expect(start % slab.alignment).toBe(0);
220
+ expect(start).toBe(buffer._allocationPtr + buffer._layout.spanSuperblock.familyOffsets[family]);
221
+ for (const [otherStart, otherEnd] of ranges) {
222
+ expect(end <= otherStart || start >= otherEnd).toBe(true);
223
+ }
224
+ ranges.push([start, end]);
225
+ }
226
+
227
+ const count = getMethod<[idx: number, val: number], WasmSpanBufferInstance>(buffer, 'count');
228
+ const isAdmin = getMethod<[idx: number, val: boolean], WasmSpanBufferInstance>(buffer, 'isAdmin');
229
+ count(3, 42);
230
+ isAdmin(4, true);
231
+ expect(allocator.getAllocCount() - allocsBefore).toBe(1);
232
+ });
233
+ });
234
+
97
235
  describe('instance creation', () => {
98
236
  it('creates instance with correct identity', () => {
99
- const buffer = createTestWasmBuffer({ trace_id: 'trace-123', thread_id: 42n, span_id: 1 });
237
+ const buffer = createTestWasmBuffer();
100
238
 
101
239
  expect(buffer.trace_id).toBe(testTraceId('trace-123'));
102
240
  expect(buffer.thread_id).toBe(42n);
@@ -106,14 +244,60 @@ describe('WasmSpanBuffer', () => {
106
244
  expect(buffer._hasParent).toBe(false);
107
245
  });
108
246
 
247
+ it('consumes native lifecycle initialization without restamping row 0', () => {
248
+ const buffer = createTestWasmBuffer();
249
+ const entryTypes = buffer.entry_type;
250
+ if (entryTypes === undefined) throw new Error('missing entry types');
251
+
252
+ expect(buffer._spanStartedAtAllocation).toBe(true);
253
+ expect(buffer._writeIndex).toBe(2);
254
+ expect(entryTypes[0]).toBe(ENTRY_TYPE_SPAN_START);
255
+ expect(entryTypes[1]).toBe(ENTRY_TYPE_SPAN_EXCEPTION);
256
+ buffer.timestamp[0] = 123_456_789n;
257
+
258
+ traceRoot.writeSpanStart(buffer, 'prestarted-span');
259
+
260
+ expect(buffer._spanStartedAtAllocation).toBe(false);
261
+ expect(buffer.timestamp[0]).toBe(123_456_789n);
262
+ const messages = buffer.message_values;
263
+ if (messages === undefined) throw new Error('missing messages');
264
+ expect(messages[0]).toBe('prestarted-span');
265
+ expect(buffer._writeIndex).toBe(2);
266
+ });
267
+
268
+ it('uses each buffer capacity for native lifecycle writes', () => {
269
+ const seededCapacity = 6;
270
+ const metadata = createTestOpMetadata();
271
+ const buffer = createWasmSpanBuffer(
272
+ testSchema,
273
+ { allocator, capacity: seededCapacity },
274
+ traceRoot,
275
+ EMPTY_SCOPE,
276
+ metadata,
277
+ metadata,
278
+ );
279
+
280
+ traceRoot.writeSpanStart(buffer, 'seeded-capacity');
281
+ const row = traceRoot.writeLogEntry(buffer, ENTRY_TYPE_INFO);
282
+ traceRoot.writeSpanEnd(buffer, ENTRY_TYPE_SPAN_OK);
283
+
284
+ expect(buffer._capacity).toBe(seededCapacity);
285
+ expect(row).toBe(2);
286
+ expect(buffer.entry_type?.[0]).toBe(ENTRY_TYPE_SPAN_START);
287
+ expect(buffer.entry_type?.[1]).toBe(ENTRY_TYPE_SPAN_OK);
288
+ expect(buffer.entry_type?.[2]).toBe(ENTRY_TYPE_INFO);
289
+ expect(buffer.timestamp[1]).toBeGreaterThan(0n);
290
+ expect(buffer.timestamp[2]).toBeGreaterThan(0n);
291
+ });
292
+
109
293
  it('creates instance with parent identity via child buffer', () => {
110
294
  // Parent identity is now set by linking buffers, not via opts
111
295
  // Create parent first, then create child that references it
112
- const parent = createTestWasmBuffer({ trace_id: 'trace-456', thread_id: 100n, span_id: 2 });
296
+ const parent = createTestWasmBuffer();
113
297
 
114
298
  const child = createWasmChildSpanBuffer(
115
299
  parent,
116
- { allocator, capacity: CAPACITY, thread_id: 100n, span_id: 5 },
300
+ { allocator, capacity: CAPACITY },
117
301
  traceRoot,
118
302
  EMPTY_SCOPE,
119
303
  createTestOpMetadata(),
@@ -129,17 +313,20 @@ describe('WasmSpanBuffer', () => {
129
313
  it('allocates system block from WASM', () => {
130
314
  const beforeAllocs = allocator.getAllocCount();
131
315
 
132
- createTestWasmBuffer({ trace_id: 'trace-123', thread_id: 1n, span_id: 1 });
316
+ createTestWasmBuffer();
133
317
 
134
318
  // Should have allocated at least the system block
135
319
  expect(allocator.getAllocCount()).toBeGreaterThan(beforeAllocs);
136
320
  });
137
321
 
138
322
  it('initializes tree structure', () => {
139
- const buffer = createTestWasmBuffer({ trace_id: 'trace-123', thread_id: 1n, span_id: 1 });
323
+ const buffer = createTestWasmBuffer();
324
+ traceRoot._topology.registerRoot(buffer);
140
325
 
141
326
  expect(buffer._parent).toBeNull();
142
- expect(buffer._children).toEqual([]);
327
+ expect(buffer._nodeIndex).toBe(traceRoot._topology.root);
328
+ expect(traceRoot._topology.buffers[buffer._nodeIndex]).toBe(buffer);
329
+ expect(traceRoot._topology.firstChild[buffer._nodeIndex]).toBe(NO_NODE);
143
330
  expect(buffer._overflow).toBeNull();
144
331
  });
145
332
  });
@@ -148,7 +335,7 @@ describe('WasmSpanBuffer', () => {
148
335
  let buffer: WasmSpanBufferInstance;
149
336
 
150
337
  beforeEach(() => {
151
- buffer = createTestWasmBuffer({ trace_id: 'trace-123', thread_id: 1n, span_id: 1 });
338
+ buffer = createTestWasmBuffer();
152
339
  });
153
340
 
154
341
  it('provides timestamp view into WASM memory', () => {
@@ -159,6 +346,7 @@ describe('WasmSpanBuffer', () => {
159
346
 
160
347
  it('provides entry_type view into WASM memory', () => {
161
348
  const entryType = buffer.entry_type;
349
+ if (entryType === undefined) throw new Error('Expected split entry-type lane');
162
350
  expect(entryType).toBeInstanceOf(Uint8Array);
163
351
  expect(entryType.length).toBe(CAPACITY);
164
352
  });
@@ -171,17 +359,55 @@ describe('WasmSpanBuffer', () => {
171
359
 
172
360
  it('can write to entry_type array', () => {
173
361
  const entryType = buffer.entry_type;
362
+ if (entryType === undefined) throw new Error('Expected split entry-type lane');
174
363
  entryType[0] = 1; // SPAN_START
175
364
  entryType[1] = 2; // SPAN_OK
176
- expect(buffer.entry_type[0]).toBe(1);
177
- expect(buffer.entry_type[1]).toBe(2);
365
+ expect(resolveEntryType(buffer, 0)).toBe(1);
366
+ expect(resolveEntryType(buffer, 1)).toBe(2);
367
+ });
368
+
369
+ it('reuses canonical typed views while memory is stable', () => {
370
+ const timestamp = buffer.timestamp;
371
+ const entryType = buffer.entry_type;
372
+ const identity = buffer._identity;
373
+ const countValues = getColumn<Float64Array>(buffer, 'count_values');
374
+ const countNulls = getColumn<Uint8Array>(buffer, 'count_nulls');
375
+ const count = getMethod<[idx: number, val: number], WasmSpanBufferInstance>(buffer, 'count');
376
+
377
+ count(5, 999);
378
+
379
+ expect(buffer.timestamp).toBe(timestamp);
380
+ expect(buffer.entry_type).toBe(entryType);
381
+ expect(buffer._identity).toBe(identity);
382
+ expect(getColumn<Float64Array>(buffer, 'count_values')).toBe(countValues);
383
+ expect(getColumn<Uint8Array>(buffer, 'count_nulls')).toBe(countNulls);
384
+ expect(countValues[5]).toBe(999);
178
385
  });
179
386
 
180
- it('views reflect same WASM memory', () => {
181
- buffer.timestamp[5] = 999n;
182
- // Get a new view and verify it sees the same data
183
- const newView = buffer.timestamp;
184
- expect(newView[5]).toBe(999n);
387
+ it('rebuilds every canonical view once after memory growth and preserves data', () => {
388
+ buffer.timestamp[3] = 123n;
389
+ const count = getMethod<[idx: number, val: number], WasmSpanBufferInstance>(buffer, 'count');
390
+ count(4, 456);
391
+ const timestampBefore = buffer.timestamp;
392
+ const identityBefore = buffer._identity;
393
+ const countBefore = getColumn<Float64Array>(buffer, 'count_values');
394
+ const versionBefore = buffer._descriptor.memoryVersion;
395
+
396
+ const growthAllocation = allocator.allocExact(allocator.memory.buffer.byteLength, 8);
397
+ expect(growthAllocation).toBeGreaterThan(0);
398
+ const timestampAfter = buffer.timestamp;
399
+ const identityAfter = buffer._identity;
400
+ const countAfter = getColumn<Float64Array>(buffer, 'count_values');
401
+
402
+ expect(buffer._descriptor.memoryVersion).toBeGreaterThan(versionBefore);
403
+ expect(timestampAfter).not.toBe(timestampBefore);
404
+ expect(identityAfter).not.toBe(identityBefore);
405
+ expect(countAfter).not.toBe(countBefore);
406
+ expect(buffer.timestamp).toBe(timestampAfter);
407
+ expect(buffer._identity).toBe(identityAfter);
408
+ expect(getColumn<Float64Array>(buffer, 'count_values')).toBe(countAfter);
409
+ expect(timestampAfter[3]).toBe(123n);
410
+ expect(countAfter[4]).toBe(456);
185
411
  });
186
412
 
187
413
  it('exposes _spanStartTime from row 0', () => {
@@ -215,17 +441,17 @@ describe('WasmSpanBuffer', () => {
215
441
  let buffer: WasmSpanBufferInstance;
216
442
 
217
443
  beforeEach(() => {
218
- buffer = createTestWasmBuffer({ trace_id: 'trace-123', thread_id: 1n, span_id: 1 });
444
+ buffer = createTestWasmBuffer();
219
445
  });
220
446
 
221
447
  it('can write message values', () => {
222
448
  buffer.message(0, 'span-start message');
223
449
  buffer.message(1, 'span-end message');
224
450
  buffer.message(2, 'log entry');
225
-
226
- expect(buffer._message[0]).toBe('span-start message');
227
- expect(buffer._message[1]).toBe('span-end message');
228
- expect(buffer._message[2]).toBe('log entry');
451
+ expect('message_nulls' in buffer).toBe(false);
452
+ expect(resolveMessage(buffer, 0)).toBe('span-start message');
453
+ expect(resolveMessage(buffer, 1)).toBe('span-end message');
454
+ expect(resolveMessage(buffer, 2)).toBe('log entry');
229
455
  });
230
456
 
231
457
  it('returns this for chaining', () => {
@@ -238,7 +464,7 @@ describe('WasmSpanBuffer', () => {
238
464
  let buffer: WasmSpanBufferInstance;
239
465
 
240
466
  beforeEach(() => {
241
- buffer = createTestWasmBuffer({ trace_id: 'trace-123', thread_id: 1n, span_id: 1 });
467
+ buffer = createTestWasmBuffer();
242
468
  });
243
469
 
244
470
  it('userId column starts undefined', () => {
@@ -254,6 +480,19 @@ describe('WasmSpanBuffer', () => {
254
480
  expect(buffer.getNullsIfAllocated('userId')).toBeDefined();
255
481
  });
256
482
 
483
+ it('allocates lazy storage for transformer-style direct writes', () => {
484
+ const userIdNulls = getColumn<Uint8Array>(buffer, 'userId_nulls');
485
+ const userIdValues = getColumn<string[]>(buffer, 'userId_values');
486
+
487
+ userIdNulls[0] |= 1;
488
+ userIdValues[0] = 'direct-write';
489
+
490
+ expect(buffer.getColumnIfAllocated('userId')).toBe(userIdValues);
491
+ expect(buffer.getNullsIfAllocated('userId')).toBe(userIdNulls);
492
+ expect(userIdValues[0]).toBe('direct-write');
493
+ expect(userIdNulls[0] & 1).toBe(1);
494
+ });
495
+
257
496
  it('writes values to category column', () => {
258
497
  const userId = getMethod<[idx: number, val: string], WasmSpanBufferInstance>(buffer, 'userId');
259
498
 
@@ -304,18 +543,26 @@ describe('WasmSpanBuffer', () => {
304
543
  let buffer: WasmSpanBufferInstance;
305
544
 
306
545
  beforeEach(() => {
307
- buffer = createTestWasmBuffer({ trace_id: 'trace-123', thread_id: 1n, span_id: 1 });
546
+ buffer = createTestWasmBuffer();
308
547
  });
309
548
 
310
- it('count column starts unallocated', () => {
311
- expect(buffer.isColumnAllocated(2)).toBe(false); // count is at index 2
549
+ it('reserves numeric bytes in the family slab without materializing the lazy lane', () => {
550
+ const countIndex = testSchema._columnNames.indexOf('count');
551
+ if (countIndex < 0) throw new Error('missing count schema field');
552
+ expect(buffer._layout.columns.some((column) => column.name === 'count')).toBe(true);
553
+ expect(buffer.isColumnAllocated(countIndex)).toBe(false);
554
+ expect(buffer.getColumnIfAllocated('count')).toBeUndefined();
555
+ expect(Reflect.get(buffer, '_count_values')).toBeUndefined();
556
+ expect(Reflect.get(buffer, '_count_nulls')).toBeUndefined();
312
557
  });
313
558
 
314
- it('allocates count column on first write', () => {
559
+ it('writes without allocating another block', () => {
315
560
  const count = getMethod<[idx: number, val: number], WasmSpanBufferInstance>(buffer, 'count');
561
+ const allocsBefore = allocator.getAllocCount();
316
562
  count(0, 42);
317
-
318
- expect(buffer.isColumnAllocated(2)).toBe(true);
563
+ expect(allocator.getAllocCount()).toBe(allocsBefore);
564
+ expect(buffer.getColumnIfAllocated('count')).toBeInstanceOf(Float64Array);
565
+ expect(buffer.getNullsIfAllocated('count')).toBeInstanceOf(Uint8Array);
319
566
  });
320
567
 
321
568
  it('writes to numeric column', () => {
@@ -339,37 +586,135 @@ describe('WasmSpanBuffer', () => {
339
586
 
340
587
  describe('eager numeric columns', () => {
341
588
  it('statusCode column is pre-allocated', () => {
342
- const buffer = createTestWasmBuffer({ trace_id: 'trace-123', thread_id: 1n, span_id: 1 });
589
+ const buffer = createTestWasmBuffer();
343
590
 
344
591
  // statusCode is at index 3 and marked eager
345
592
  expect(buffer.isColumnAllocated(3)).toBe(true);
346
593
  });
347
594
  });
348
595
 
349
- describe('free()', () => {
350
- it('frees system block', () => {
351
- const buffer = createTestWasmBuffer({ trace_id: 'trace-123', thread_id: 1n, span_id: 1 });
596
+ describe('compiler-proven eager columns', () => {
597
+ it('shares exact frozen descriptor bytes and eager storage across root, child, and overflow', () => {
598
+ const descriptor = compilerEagerOp.callsitePlan.eagerColumns;
599
+ const planSchema = compilerEagerOp.callsitePlan.schema;
600
+ const expectedWords = new Array<number>(Math.ceil(planSchema._columnNames.length / 32)).fill(0);
601
+ for (const name of ['provenNumber', 'provenString']) {
602
+ const ordinal = planSchema._columnNames.indexOf(name);
603
+ if (ordinal < 0) throw new Error(`missing compiler eager schema field ${name}`);
604
+ expectedWords[ordinal >>> 5] = (expectedWords[ordinal >>> 5] | (1 << (ordinal & 31))) >>> 0;
605
+ }
606
+ while (expectedWords.length > 0 && expectedWords[expectedWords.length - 1] === 0) expectedWords.pop();
352
607
 
608
+ expect(descriptor.names).toEqual(['provenNumber', 'provenString']);
609
+ expect(descriptor.words).toEqual(expectedWords);
610
+ expect(eagerDescriptorBytes(descriptor).byteLength).toBe(descriptor.words.length * 4);
611
+ expect(Object.isFrozen(descriptor)).toBe(true);
612
+ expect(Object.isFrozen(descriptor.names)).toBe(true);
613
+ expect(Object.isFrozen(descriptor.words)).toBe(true);
614
+ expect(compilerEagerOp.callsitePlan.wasmLayout.eagerColumns).toBe(descriptor);
615
+
616
+ const root = createCompilerEagerWasmBuffer();
617
+ const child = createWasmChildSpanBuffer(
618
+ root,
619
+ { allocator, capacity: CAPACITY },
620
+ traceRoot,
621
+ EMPTY_SCOPE,
622
+ compilerEagerOp.metadata,
623
+ compilerEagerOp.metadata,
624
+ );
625
+ const overflow = createWasmOverflowBuffer(
626
+ child,
627
+ traceRoot,
628
+ EMPTY_SCOPE,
629
+ compilerEagerOp.metadata,
630
+ compilerEagerOp.metadata,
631
+ );
632
+
633
+ for (const buffer of [root, child, overflow]) {
634
+ expect(buffer._layout.eagerColumns).toBe(descriptor);
635
+ for (const name of ['provenNumber', 'provenString']) {
636
+ expect(Object.hasOwn(buffer, `_${name}_values`)).toBe(true);
637
+ expect(Object.hasOwn(buffer, `_${name}_nulls`)).toBe(true);
638
+ expect(Reflect.get(buffer, `_${name}_values`)).toBeDefined();
639
+ expect(Reflect.get(buffer, `_${name}_nulls`)).toBeInstanceOf(Uint8Array);
640
+ }
641
+ for (const name of ['lazyNumber', 'lazyString']) {
642
+ expect(Reflect.get(buffer, `_${name}_values`)).toBeUndefined();
643
+ expect(Reflect.get(buffer, `_${name}_nulls`)).toBeUndefined();
644
+ expect(buffer.getColumnIfAllocated(name)).toBeUndefined();
645
+ expect(buffer.getNullsIfAllocated(name)).toBeUndefined();
646
+ }
647
+ }
648
+ expect(child._parent).toBe(root);
649
+ expect(overflow._parent).toBe(root);
650
+ expect(overflow._identityPtr).toBe(child._identityPtr);
651
+ expect(overflow._descriptor.layout).toBe(child._descriptor.layout);
652
+
653
+ root.provenNumber(0, 73);
654
+ root.provenNumber(1, null);
655
+ root.provenString(2, 'wasm-value');
656
+ overflow.provenNumber(3, null);
657
+
658
+ const jsBuffer = createSpanBuffer(
659
+ planSchema,
660
+ createTestTraceRoot('compiler-eager-js'),
661
+ compilerEagerOp.metadata,
662
+ CAPACITY,
663
+ compilerEagerOp.callsitePlan.SpanBufferClass,
664
+ );
665
+ jsBuffer.provenNumber(0, 73);
666
+ jsBuffer.provenNumber(1, null);
667
+ jsBuffer.provenString(2, 'wasm-value');
668
+
669
+ for (const name of ['provenNumber', 'provenString']) {
670
+ const wasmValues = root.getColumnIfAllocated(name);
671
+ const jsValues = jsBuffer.getColumnIfAllocated(name);
672
+ const wasmNulls = root.getNullsIfAllocated(name);
673
+ const jsNulls = jsBuffer.getNullsIfAllocated(name);
674
+ if (!wasmValues || !jsValues || !wasmNulls || !jsNulls) {
675
+ throw new Error(`missing eager JS/WASM parity storage for ${name}`);
676
+ }
677
+ for (const row of [0, 1, 2, 3]) expect(wasmValues[row]).toEqual(jsValues[row]);
678
+ expect([...wasmNulls]).toEqual([...jsNulls]);
679
+ }
680
+
681
+ const lazyString = getMethod<[index: number, value: string], WasmSpanBufferInstance>(root, 'lazyString');
682
+ lazyString(4, 'first lazy write');
683
+ expect(root.getColumnIfAllocated('lazyString')?.[4]).toBe('first lazy write');
684
+ const lazyStringNulls = root.getNullsIfAllocated('lazyString');
685
+ if (!lazyStringNulls) throw new Error('missing lazy string null bitmap after first write');
686
+ expect(lazyStringNulls[0] & 0b00010000).toBe(0b00010000);
687
+
688
+ const schemaEager = createTestWasmBuffer();
689
+ expect(schemaEager.getColumnIfAllocated('statusCode')).toBeDefined();
690
+ expect(schemaEager.getNullsIfAllocated('statusCode')).toBeUndefined();
691
+ });
692
+ });
693
+
694
+ describe('free()', () => {
695
+ it('releases its owned superblock once', () => {
696
+ const buffer = createTestWasmBuffer();
353
697
  const freesBefore = allocator.getFreeCount();
698
+
354
699
  buffer.free();
355
700
 
356
- expect(allocator.getFreeCount()).toBeGreaterThan(freesBefore);
701
+ expect(allocator.getFreeCount() - freesBefore).toBe(1);
702
+ expect(buffer._descriptor.state).toBe('freed');
357
703
  });
358
704
 
359
- it('frees allocated column blocks', () => {
360
- const buffer = createTestWasmBuffer({ trace_id: 'trace-123', thread_id: 1n, span_id: 1 });
361
-
362
- // Write to lazy columns to allocate them
705
+ it('is idempotent and rejects every stale accessor and writer', () => {
706
+ const buffer = createTestWasmBuffer();
363
707
  const count = getMethod<[idx: number, val: number], WasmSpanBufferInstance>(buffer, 'count');
364
- const isAdmin = getMethod<[idx: number, val: boolean], WasmSpanBufferInstance>(buffer, 'isAdmin');
365
- count(0, 42);
366
- isAdmin(0, true);
708
+ buffer.free();
709
+ const freesAfterFirstRelease = allocator.getFreeCount();
367
710
 
368
- const freesBefore = allocator.getFreeCount();
369
711
  buffer.free();
370
712
 
371
- // Should free system block + statusCode (eager) + count + isAdmin
372
- expect(allocator.getFreeCount()).toBeGreaterThan(freesBefore + 1);
713
+ expect(allocator.getFreeCount()).toBe(freesAfterFirstRelease);
714
+ expect(() => buffer.timestamp).toThrow(/generation .* released/);
715
+ expect(() => buffer.entry_type).toThrow(/generation .* released/);
716
+ expect(() => buffer._identity).toThrow(/generation .* released/);
717
+ expect(() => count(0, 42)).toThrow(/generation .* released/);
373
718
  });
374
719
  });
375
720
 
@@ -378,26 +723,31 @@ describe('WasmSpanBuffer', () => {
378
723
  // thread_id is global in WASM header, shared by parent and child
379
724
  allocator.setThreadId(0, 1); // thread_id = 1n
380
725
 
381
- const parent = createTestWasmBuffer({ trace_id: 'trace-123', thread_id: 1n, span_id: 1 });
726
+ const parent = createTestWasmBuffer();
727
+ traceRoot._topology.registerRoot(parent);
382
728
 
383
729
  const child = createWasmChildSpanBuffer(
384
730
  parent,
385
- { allocator, capacity: CAPACITY, thread_id: 2n, span_id: 2 },
731
+ { allocator, capacity: CAPACITY },
386
732
  traceRoot,
387
733
  EMPTY_SCOPE,
388
734
  createTestOpMetadata(),
389
735
  createTestOpMetadata({ name: 'child-span', line: 0 }),
390
736
  );
391
737
 
392
- // Manually push to _children - SpanContext does this with possible RemappedBufferView wrapper
393
- parent._children.push(child);
738
+ traceRoot._topology.registerChild(parent, child);
394
739
 
395
740
  expect(child._parent).toBe(parent);
396
- expect(parent._children).toContain(child);
741
+ expect(Array.from(iterateSpanChildren(parent))).toEqual([child]);
397
742
  expect(child.trace_id).toBe(testTraceId('trace-123'));
398
743
  // Parent and child share thread_id from global header
399
744
  expect(child.parent_thread_id).toBe(1n);
400
745
  expect(child.parent_span_id).toBe(1);
746
+ expect(child._identityPtr).not.toBe(parent._identityPtr);
747
+ expect(child._identityOwner).toBe(true);
748
+ expect(child._descriptor.kind).toBe('child');
749
+ expect(child._descriptor.parent).toBe(parent._descriptor);
750
+ expect(child._descriptor.generation).toBeGreaterThan(parent._descriptor.generation);
401
751
  });
402
752
  });
403
753
 
@@ -406,7 +756,8 @@ describe('WasmSpanBuffer', () => {
406
756
  // thread_id is global in WASM header
407
757
  allocator.setThreadId(0, 1); // thread_id = 1n
408
758
 
409
- const buffer = createTestWasmBuffer({ trace_id: 'trace-123', thread_id: 1n, span_id: 1 });
759
+ const buffer = createTestWasmBuffer();
760
+ const allocsBefore = allocator.getAllocCount();
410
761
 
411
762
  const overflow = createWasmOverflowBuffer(
412
763
  buffer,
@@ -415,14 +766,19 @@ describe('WasmSpanBuffer', () => {
415
766
  createTestOpMetadata(),
416
767
  createTestOpMetadata(),
417
768
  );
769
+ expect(allocator.getAllocCount() - allocsBefore).toBe(1);
418
770
 
419
771
  expect(buffer._overflow).toBe(overflow);
420
772
  expect(overflow._parent).toBe(buffer._parent); // Same parent (null for root)
421
773
  expect(overflow.trace_id).toBe(testTraceId('trace-123'));
422
774
  expect(overflow.thread_id).toBe(1n);
423
- // Note: span_id is unique per identity block, not shared with original
424
- // overflow has its own span_id from allocIdentityChild
425
- expect(overflow.span_id).toBeGreaterThan(0);
775
+ expect(overflow.span_id).toBe(buffer.span_id);
776
+ expect(overflow._identityPtr).toBe(buffer._identityPtr);
777
+ expect(overflow._identityOwner).toBe(false);
778
+ expect(overflow._descriptor.kind).toBe('overflow');
779
+ expect(overflow._descriptor.parent).toBeUndefined();
780
+ expect(buffer._descriptor.overflow).toBe(overflow._descriptor);
781
+ expect(overflow._descriptor.generation).toBeGreaterThan(buffer._descriptor.generation);
426
782
  });
427
783
  });
428
784
 
@@ -431,7 +787,7 @@ describe('WasmSpanBuffer', () => {
431
787
  // thread_id is global in WASM header
432
788
  allocator.setThreadId(0, 1); // thread_id = 1n
433
789
 
434
- const buffer = createTestWasmBuffer({ trace_id: 'trace-123', thread_id: 1n, span_id: 1 });
790
+ const buffer = createTestWasmBuffer();
435
791
 
436
792
  // Get custom inspect
437
793
  const inspectSymbol = Symbol.for('nodejs.util.inspect.custom');