@rasputin-ai/node 0.4.1 → 0.5.0-alpha.10

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 (49) hide show
  1. package/README.md +188 -162
  2. package/dist/auto-instrumentation/collect-instrumentation-delta.d.ts +2 -37
  3. package/dist/auto-instrumentation/collect-instrumentation-delta.d.ts.map +1 -1
  4. package/dist/auto-instrumentation/instrument-compiled-esm.d.ts +19 -0
  5. package/dist/auto-instrumentation/instrument-compiled-esm.d.ts.map +1 -0
  6. package/dist/auto-instrumentation/instrumentation-manifest-delta.d.ts +41 -0
  7. package/dist/auto-instrumentation/instrumentation-manifest-delta.d.ts.map +1 -0
  8. package/dist/auto-instrumentation/instrumentation-manifest-registry.d.ts +5 -2
  9. package/dist/auto-instrumentation/instrumentation-manifest-registry.d.ts.map +1 -1
  10. package/dist/auto-instrumentation/transform-source.d.ts +7 -1
  11. package/dist/auto-instrumentation/transform-source.d.ts.map +1 -1
  12. package/dist/auto-instrumentation/warn-production-runtime-transform.d.ts +8 -0
  13. package/dist/auto-instrumentation/warn-production-runtime-transform.d.ts.map +1 -0
  14. package/dist/derivation/analyze-function.d.ts +42 -0
  15. package/dist/derivation/analyze-function.d.ts.map +1 -0
  16. package/dist/derivation/build-dependency-graph.d.ts +21 -0
  17. package/dist/derivation/build-dependency-graph.d.ts.map +1 -0
  18. package/dist/derivation/derivation-ir.d.ts +33 -0
  19. package/dist/derivation/derivation-ir.d.ts.map +1 -0
  20. package/dist/derivation/derivation-proof.d.ts +14 -0
  21. package/dist/derivation/derivation-proof.d.ts.map +1 -0
  22. package/dist/derivation/emit-capture-plan.d.ts +8 -0
  23. package/dist/derivation/emit-capture-plan.d.ts.map +1 -0
  24. package/dist/execution-recorder/automatic-runtime.d.ts +21 -10
  25. package/dist/execution-recorder/automatic-runtime.d.ts.map +1 -1
  26. package/dist/execution-recorder/execution-journal.d.ts +84 -0
  27. package/dist/execution-recorder/execution-journal.d.ts.map +1 -0
  28. package/dist/execution-recorder/execution-recorder-types.d.ts +36 -0
  29. package/dist/execution-recorder/execution-recorder-types.d.ts.map +1 -1
  30. package/dist/execution-recorder/execution-recorder.d.ts.map +1 -1
  31. package/dist/execution-recorder/function-plan.d.ts +26 -0
  32. package/dist/execution-recorder/function-plan.d.ts.map +1 -0
  33. package/dist/execution-recorder/recorder-memory-budget.d.ts +8 -1
  34. package/dist/execution-recorder/recorder-memory-budget.d.ts.map +1 -1
  35. package/dist/execution-recorder/safe-serialize.d.ts +8 -0
  36. package/dist/execution-recorder/safe-serialize.d.ts.map +1 -1
  37. package/dist/index.js +1432 -418
  38. package/dist/instrument/build.d.ts +2 -0
  39. package/dist/instrument/build.d.ts.map +1 -0
  40. package/dist/instrument/build.js +1430 -0
  41. package/dist/instrument/bun.js +825 -82
  42. package/dist/instrument/node.js +825 -82
  43. package/dist/rasputin-init.d.ts +5 -5
  44. package/dist/rasputin-init.d.ts.map +1 -1
  45. package/dist/sdk-meta.d.ts +1 -1
  46. package/dist/sdk-meta.d.ts.map +1 -1
  47. package/package.json +10 -2
  48. package/dist/execution-recorder/call-aware-event-buffer.d.ts +0 -23
  49. package/dist/execution-recorder/call-aware-event-buffer.d.ts.map +0 -1
package/dist/index.js CHANGED
@@ -104,76 +104,38 @@ var runtimeLabel = () => {
104
104
 
105
105
  // src/auto-instrumentation/instrumentation-manifest-registry.ts
106
106
  import { createHash } from "node:crypto";
107
+ import { existsSync, readFileSync } from "node:fs";
107
108
  import {
108
109
  INSTRUMENTATION_MANIFEST_SCHEMA_VERSION,
109
110
  sourceLocatorFromFile as sourceLocatorFromFile2
110
111
  } from "@rasputin-ai/core";
111
112
 
112
- // src/execution-recorder/automatic-runtime.ts
113
+ // src/execution-recorder/function-plan.ts
113
114
  import {
114
115
  runtimeFunctionId,
115
116
  sourceLocatorFromFile
116
117
  } from "@rasputin-ai/core";
117
- var AUTOMATIC_RUNTIME_SYMBOL = "rasputin.execution.runtime.v2";
118
- var AUTOMATIC_SOURCE_PREFIX = "rasputin-source-v2:";
119
- var runtimeSymbol = Symbol.for(AUTOMATIC_RUNTIME_SYMBOL);
120
- var decodeAutomaticFunctionSource = (sourceToken) => {
121
- if (!sourceToken.startsWith(AUTOMATIC_SOURCE_PREFIX)) return void 0;
122
- try {
123
- const parsed = JSON.parse(
124
- sourceToken.slice(AUTOMATIC_SOURCE_PREFIX.length)
125
- );
126
- if (typeof parsed.name !== "string" || !parsed.name) return void 0;
127
- const definition = {
128
- name: parsed.name,
129
- ...parsed.source ? { source: parsed.source } : {}
130
- };
131
- return { ...definition, functionId: runtimeFunctionId(definition) };
132
- } catch {
133
- return void 0;
134
- }
135
- };
136
- var getRegistry = () => {
137
- const existing = Reflect.get(globalThis, runtimeSymbol);
138
- if (existing?.__rasputinRuntimeRegistry && Array.isArray(existing.runtimes)) {
139
- return existing;
140
- }
141
- const runtimes = [];
142
- const registry = {
143
- __rasputinRuntimeRegistry: true,
144
- runtimes,
145
- run: (sourceToken, args, callback) => {
146
- const runtime = runtimes.at(-1);
147
- return runtime ? runtime.run(sourceToken, args, callback) : callback();
148
- }
149
- };
150
- Reflect.set(globalThis, runtimeSymbol, registry);
151
- return registry;
152
- };
153
- var installAutomaticExecutionRuntime = (execution) => {
154
- const registry = getRegistry();
155
- const definitions = /* @__PURE__ */ new Map();
156
- const runtime = {
157
- run: (sourceToken, args, callback) => {
158
- let definition = definitions.get(sourceToken);
159
- if (!definitions.has(sourceToken)) {
160
- definition = decodeAutomaticFunctionSource(sourceToken);
161
- definitions.set(sourceToken, definition);
162
- }
163
- return definition ? execution.runFunction(definition, args, callback) : callback();
164
- }
165
- };
166
- registry.runtimes.push(runtime);
167
- return () => {
168
- const index = registry.runtimes.lastIndexOf(runtime);
169
- if (index >= 0) registry.runtimes.splice(index, 1);
118
+ var EVENT_PAIR_BASE_ESTIMATED_BYTES = 512;
119
+ var definitionFromEncodedPlan = (plan) => {
120
+ const [functionId, name, packageName, packageRelativePath, line, column] = plan;
121
+ const source = packageRelativePath ? {
122
+ ...packageName ? { packageName } : {},
123
+ packageRelativePath,
124
+ ...line == null ? {} : { line },
125
+ ...column == null ? {} : { column }
126
+ } : void 0;
127
+ return {
128
+ functionId,
129
+ name,
130
+ ...source ? { source } : {}
170
131
  };
171
132
  };
133
+ var estimatedCallBytesFor = (functionId) => EVENT_PAIR_BASE_ESTIMATED_BYTES + Buffer.byteLength(functionId, "utf8") * 2;
172
134
 
173
135
  // src/auto-instrumentation/instrumentation-manifest-registry.ts
174
- var INSTRUMENTATION_MANIFEST_SYMBOL = "rasputin.instrumentation.manifest.v2";
136
+ var INSTRUMENTATION_MANIFEST_SYMBOL = "rasputin.instrumentation.manifest.v3";
175
137
  var registrySymbol = Symbol.for(INSTRUMENTATION_MANIFEST_SYMBOL);
176
- var getRegistry2 = () => {
138
+ var getRegistry = () => {
177
139
  const existing = Reflect.get(globalThis, registrySymbol);
178
140
  if (existing?.__rasputinManifestRegistry && existing.files instanceof Map && typeof existing.generation === "number" && typeof existing.uploadedGeneration === "number") {
179
141
  return existing;
@@ -192,20 +154,47 @@ var sourceAt = (filePath, line, column) => {
192
154
  return source ? { ...source, line, column } : void 0;
193
155
  };
194
156
  var stableId = (kind, ...parts) => `${kind}_${createHash("sha256").update(parts.join("\0")).digest("base64url").slice(0, 24)}`;
157
+ var recordPrebuiltInstrumentationManifest = (manifest) => {
158
+ const registry = getRegistry();
159
+ registry.prebuilt = manifest;
160
+ registry.generation += 1;
161
+ registry.onDirty?.();
162
+ };
163
+ var loadInstrumentationManifestFile = (path) => {
164
+ try {
165
+ const parsed = JSON.parse(readFileSync(path, "utf8"));
166
+ if (parsed.schema_version !== INSTRUMENTATION_MANIFEST_SCHEMA_VERSION) return false;
167
+ if (!Array.isArray(parsed.functions)) return false;
168
+ recordPrebuiltInstrumentationManifest(parsed);
169
+ return true;
170
+ } catch {
171
+ return false;
172
+ }
173
+ };
174
+ var loadInstrumentationManifestIfPresent = (path) => {
175
+ if (!path || !existsSync(path)) return;
176
+ loadInstrumentationManifestFile(path);
177
+ };
195
178
  var isInstrumentationManifestDirty = () => {
196
- const registry = getRegistry2();
197
- return registry.generation !== registry.uploadedGeneration && registry.files.size > 0;
179
+ const registry = getRegistry();
180
+ return registry.generation !== registry.uploadedGeneration && (registry.files.size > 0 || registry.prebuilt !== void 0);
181
+ };
182
+ var instrumentationManifestGeneration = () => getRegistry().generation;
183
+ var instrumentationManifestModuleCount = () => {
184
+ const registry = getRegistry();
185
+ if (registry.files.size > 0) return registry.files.size;
186
+ if (!registry.prebuilt) return 0;
187
+ return new Set(registry.prebuilt.functions.map((fn) => fn.source.packageRelativePath)).size;
198
188
  };
199
- var instrumentationManifestGeneration = () => getRegistry2().generation;
200
- var instrumentationManifestModuleCount = () => getRegistry2().files.size;
201
189
  var snapshotInstrumentationManifest = (options = {}) => {
202
190
  const functions = [];
203
191
  const callSites = [];
204
192
  const destructures = [];
205
- for (const delta of getRegistry2().files.values()) {
193
+ const derivationPlans = [];
194
+ for (const delta of getRegistry().files.values()) {
206
195
  for (const fn of delta.functions) {
207
- const definition = decodeAutomaticFunctionSource(fn.sourceToken);
208
- if (!definition?.source) continue;
196
+ const definition = definitionFromEncodedPlan(fn.plan);
197
+ if (!definition.source) continue;
209
198
  functions.push({
210
199
  functionId: definition.functionId,
211
200
  source: definition.source,
@@ -218,19 +207,18 @@ var snapshotInstrumentationManifest = (options = {}) => {
218
207
  });
219
208
  }
220
209
  for (const site of delta.callSites) {
221
- const caller = decodeAutomaticFunctionSource(site.callerSourceToken);
222
210
  const source = sourceAt(site.filePath, site.line, site.column);
223
- if (!caller || !source) continue;
211
+ if (!source) continue;
224
212
  callSites.push({
225
213
  id: stableId(
226
214
  "cs",
227
- caller.functionId,
215
+ site.callerFunctionId,
228
216
  source.packageName ?? "",
229
217
  source.packageRelativePath,
230
218
  site.line,
231
219
  site.column
232
220
  ),
233
- callerFunctionId: caller.functionId,
221
+ callerFunctionId: site.callerFunctionId,
234
222
  calleeName: site.calleeName,
235
223
  calleeText: site.calleeText,
236
224
  source,
@@ -243,11 +231,10 @@ var snapshotInstrumentationManifest = (options = {}) => {
243
231
  });
244
232
  }
245
233
  for (const destructure of delta.destructures) {
246
- const caller = decodeAutomaticFunctionSource(destructure.callerSourceToken);
247
234
  const source = sourceAt(destructure.filePath, destructure.line, destructure.column);
248
- if (!caller || !source) continue;
235
+ if (!source) continue;
249
236
  destructures.push({
250
- callerFunctionId: caller.functionId,
237
+ callerFunctionId: destructure.callerFunctionId,
251
238
  sourceParam: destructure.sourceParam,
252
239
  source,
253
240
  line: destructure.line,
@@ -255,6 +242,19 @@ var snapshotInstrumentationManifest = (options = {}) => {
255
242
  binding: destructure.binding
256
243
  });
257
244
  }
245
+ derivationPlans.push(...delta.derivationPlans ?? []);
246
+ }
247
+ const registry = getRegistry();
248
+ if (registry.prebuilt && registry.files.size === 0) {
249
+ const prebuilt = registry.prebuilt;
250
+ if (!options.sourceRoot && !options.sourceRoots) return prebuilt;
251
+ return {
252
+ ...prebuilt,
253
+ sourceRoots: {
254
+ ...options.sourceRoot ? { default: options.sourceRoot } : {},
255
+ packages: { ...options.sourceRoots ?? {} }
256
+ }
257
+ };
258
258
  }
259
259
  if (functions.length === 0 && callSites.length === 0 && destructures.length === 0) {
260
260
  return void 0;
@@ -270,15 +270,16 @@ var snapshotInstrumentationManifest = (options = {}) => {
270
270
  } : {},
271
271
  functions,
272
272
  callSites,
273
- destructures
273
+ destructures,
274
+ derivationPlans
274
275
  };
275
276
  };
276
277
  var markInstrumentationManifestUploaded = (generation) => {
277
- const registry = getRegistry2();
278
+ const registry = getRegistry();
278
279
  if (registry.generation === generation) registry.uploadedGeneration = generation;
279
280
  };
280
281
  var setInstrumentationManifestOnDirty = (onDirty) => {
281
- getRegistry2().onDirty = onDirty;
282
+ getRegistry().onDirty = onDirty;
282
283
  };
283
284
 
284
285
  // src/auto-instrumentation/schedule-instrumentation-manifest-upload.ts
@@ -386,92 +387,397 @@ var scheduleInstrumentationManifestUpload = (options) => {
386
387
 
387
388
  // src/execution-recorder/execution-recorder.ts
388
389
  import { AsyncLocalStorage } from "node:async_hooks";
389
- import { runtimeFunctionId as runtimeFunctionId2 } from "@rasputin-ai/core";
390
+ import {
391
+ capturedValueCompleteness,
392
+ hashInstrumentationManifest,
393
+ rasputinOmitted,
394
+ rasputinTagKind,
395
+ runtimeFunctionId as runtimeFunctionId2
396
+ } from "@rasputin-ai/core";
390
397
 
391
- // src/execution-recorder/call-aware-event-buffer.ts
392
- var CallAwareEventBuffer = class {
398
+ // src/execution-recorder/execution-journal.ts
399
+ var KIND_ENTER = 1;
400
+ var KIND_EXIT = 2;
401
+ var KIND_THROW = 3;
402
+ var KIND_SUPPRESSED = 4;
403
+ var NO_PARENT = 4294967295;
404
+ var NO_SUPPRESSION = 4294967295;
405
+ var MAX_POOLED_JOURNALS = 8;
406
+ var MAX_POOLED_JOURNAL_CAPACITY = 4096;
407
+ var INITIAL_JOURNAL_STORAGE = 32;
408
+ var BYTES_PER_EVENT = Uint8Array.BYTES_PER_ELEMENT + Uint32Array.BYTES_PER_ELEMENT * 4 + BigInt64Array.BYTES_PER_ELEMENT * 2;
409
+ var DEFAULT_MAX_EVENTS_PER_EXECUTION = 500;
410
+ var MAX_JOURNAL_EVENTS = 16384;
411
+ var elapsedMs = (startedAtNs, endedAtNs) => Number(endedAtNs - startedAtNs) / 1e6;
412
+ var roundedMilliseconds = (nanoseconds) => Math.round(Number(nanoseconds) / 1e6 * 1e4) / 1e4;
413
+ var relativeMs = (relativeNs) => Number(relativeNs) / 1e6;
414
+ var sanitizeJournalCapacity = (value) => {
415
+ const requested = value ?? DEFAULT_MAX_EVENTS_PER_EXECUTION;
416
+ if (!Number.isFinite(requested)) return DEFAULT_MAX_EVENTS_PER_EXECUTION;
417
+ return Math.min(MAX_JOURNAL_EVENTS, Math.max(2, Math.floor(requested)));
418
+ };
419
+ var ExecutionJournal = class {
420
+ dropped = 0;
421
+ allocatedBytes = 0;
422
+ onGrow;
423
+ length = 0;
424
+ kind = new Uint8Array(0);
425
+ planId = new Uint32Array(0);
426
+ callId = new Uint32Array(0);
427
+ parentCallId = new Uint32Array(0);
428
+ timestampNs = new BigInt64Array(0);
429
+ durationNs = new BigInt64Array(0);
430
+ suppressionIndex = new Uint32Array(0);
431
+ payloads = [];
432
+ suppressions = [];
433
+ openCallIds = /* @__PURE__ */ new Set();
434
+ notedPlanIds = [];
435
+ notedPlanSet = /* @__PURE__ */ new Set();
436
+ maxCapacity;
393
437
  constructor(capacity) {
394
- this.capacity = capacity;
438
+ this.maxCapacity = sanitizeJournalCapacity(capacity);
439
+ this.reallocate(Math.min(this.maxCapacity, INITIAL_JOURNAL_STORAGE));
440
+ }
441
+ get capacity() {
442
+ return this.maxCapacity;
443
+ }
444
+ get hasOpenCalls() {
445
+ return this.openCallIds.size > 0;
446
+ }
447
+ get eventCount() {
448
+ return this.length;
449
+ }
450
+ get payloadSlotCount() {
451
+ return this.payloads.length;
395
452
  }
396
- items = [];
397
- openCallIds = /* @__PURE__ */ new Set();
398
- dropped = 0;
399
453
  canStartCall() {
400
- return this.items.length + this.openCallIds.size + 2 <= this.capacity;
454
+ if (this.length + this.openCallIds.size + 2 > this.maxCapacity) return false;
455
+ return this.ensureCapacity(this.length + 2);
456
+ }
457
+ notePlan(runtimePlanId) {
458
+ if (this.notedPlanSet.has(runtimePlanId)) return;
459
+ this.notedPlanSet.add(runtimePlanId);
460
+ this.notedPlanIds.push(runtimePlanId);
401
461
  }
402
- startCall(event) {
403
- this.items.push(event);
404
- this.openCallIds.add(event.callId);
462
+ registerSuppression(group) {
463
+ if (group.storageIndex >= 0) return group;
464
+ group.storageIndex = this.suppressions.length;
465
+ this.suppressions.push(group);
466
+ return group;
467
+ }
468
+ startCall(runtimePlanId, callId, parentCallId, timestampNs, args) {
469
+ if (!this.write(
470
+ KIND_ENTER,
471
+ runtimePlanId,
472
+ callId,
473
+ parentCallId,
474
+ timestampNs,
475
+ 0n,
476
+ args,
477
+ NO_SUPPRESSION
478
+ )) {
479
+ return;
480
+ }
481
+ this.openCallIds.add(callId);
405
482
  }
406
- finishCall(event) {
407
- if (event.type !== "function_exit" && event.type !== "function_throw") return;
408
- if (!this.openCallIds.delete(event.callId)) return;
409
- this.items.push(event);
483
+ finishCall(kind, callId, timestampNs, durationNs, payload) {
484
+ if (!this.openCallIds.delete(callId)) return;
485
+ this.write(kind, 0, callId, void 0, timestampNs, durationNs, payload, NO_SUPPRESSION);
410
486
  }
411
487
  dropCall() {
412
488
  this.dropped += 2;
413
489
  }
414
- append(event) {
415
- if (this.items.length + this.openCallIds.size + 1 > this.capacity) {
490
+ appendSuppressed(group) {
491
+ if (this.length + this.openCallIds.size + 1 > this.maxCapacity) {
492
+ this.dropped++;
493
+ return false;
494
+ }
495
+ this.registerSuppression(group);
496
+ group.retainedIndex = this.length;
497
+ if (!this.write(
498
+ KIND_SUPPRESSED,
499
+ group.runtimePlanId,
500
+ 0,
501
+ group.parentCallId,
502
+ 0n,
503
+ 0n,
504
+ void 0,
505
+ group.storageIndex
506
+ )) {
507
+ group.retainedIndex = -1;
416
508
  this.dropped++;
417
509
  return false;
418
510
  }
419
- this.items.push(event);
420
511
  return true;
421
512
  }
422
- forceCompletedCall(enter, terminal) {
423
- while (this.items.length + this.openCallIds.size + 2 > this.capacity) {
513
+ forceCompletedCall(runtimePlanId, callId, parentCallId, enterTimestampNs, args, terminalKind, terminalTimestampNs, durationNs, payload) {
514
+ while (this.length + this.openCallIds.size + 2 > this.maxCapacity) {
424
515
  if (!this.evictCompletedLeafOrSummary()) {
425
516
  this.dropCall();
426
517
  return false;
427
518
  }
428
519
  }
429
- this.items.push(enter, terminal);
520
+ if (!this.ensureCapacity(this.length + 2)) {
521
+ this.dropCall();
522
+ return false;
523
+ }
524
+ this.write(
525
+ KIND_ENTER,
526
+ runtimePlanId,
527
+ callId,
528
+ parentCallId,
529
+ enterTimestampNs,
530
+ 0n,
531
+ args,
532
+ NO_SUPPRESSION
533
+ );
534
+ this.write(
535
+ terminalKind,
536
+ 0,
537
+ callId,
538
+ void 0,
539
+ terminalTimestampNs,
540
+ durationNs,
541
+ payload,
542
+ NO_SUPPRESSION
543
+ );
544
+ return true;
545
+ }
546
+ materialize(plansByRuntimeId, executionStartedAtNs) {
547
+ const events = [];
548
+ const referencedFunctionIds = /* @__PURE__ */ new Set();
549
+ for (let index = 0; index < this.length; index++) {
550
+ const event = this.eventAt(index, plansByRuntimeId, executionStartedAtNs);
551
+ if (!event) continue;
552
+ events.push(event);
553
+ if (event.type === "function_enter" || event.type === "function_calls_suppressed") {
554
+ referencedFunctionIds.add(event.functionId);
555
+ }
556
+ }
557
+ const functions = [];
558
+ const seenFunctionIds = /* @__PURE__ */ new Set();
559
+ for (const runtimePlanId of this.notedPlanIds) {
560
+ const definition = plansByRuntimeId[runtimePlanId]?.definition;
561
+ if (!definition || !referencedFunctionIds.has(definition.functionId)) continue;
562
+ if (seenFunctionIds.has(definition.functionId)) continue;
563
+ seenFunctionIds.add(definition.functionId);
564
+ functions.push(definition);
565
+ }
566
+ return { events, functions };
567
+ }
568
+ clear() {
569
+ this.length = 0;
570
+ this.dropped = 0;
571
+ this.onGrow = void 0;
572
+ this.openCallIds.clear();
573
+ this.notedPlanIds.length = 0;
574
+ this.notedPlanSet.clear();
575
+ for (let index = 0; index < this.payloads.length; index++) this.payloads[index] = void 0;
576
+ this.payloads.length = 0;
577
+ for (const group of this.suppressions) {
578
+ group.retainedIndex = -1;
579
+ group.storageIndex = -1;
580
+ }
581
+ this.suppressions.length = 0;
582
+ }
583
+ eventAt(index, plansByRuntimeId, executionStartedAtNs) {
584
+ const kind = this.kind[index];
585
+ if (kind === KIND_ENTER) {
586
+ const functionId = plansByRuntimeId[this.planId[index] ?? 0]?.functionId;
587
+ if (!functionId) return void 0;
588
+ const parentCallId = this.parentCallId[index] ?? NO_PARENT;
589
+ const payload = this.payloads[index];
590
+ return {
591
+ type: "function_enter",
592
+ callId: this.callId[index] ?? 0,
593
+ functionId,
594
+ timestampMs: relativeMs(this.timestampNs[index] ?? 0n),
595
+ args: Array.isArray(payload) ? payload : [],
596
+ ...parentCallId === NO_PARENT ? {} : { parentCallId }
597
+ };
598
+ }
599
+ if (kind === KIND_EXIT) {
600
+ return {
601
+ type: "function_exit",
602
+ callId: this.callId[index] ?? 0,
603
+ timestampMs: relativeMs(this.timestampNs[index] ?? 0n),
604
+ durationMs: relativeMs(this.durationNs[index] ?? 0n),
605
+ returnValue: this.payloads[index]
606
+ };
607
+ }
608
+ if (kind === KIND_THROW) {
609
+ return {
610
+ type: "function_throw",
611
+ callId: this.callId[index] ?? 0,
612
+ timestampMs: relativeMs(this.timestampNs[index] ?? 0n),
613
+ durationMs: relativeMs(this.durationNs[index] ?? 0n),
614
+ error: this.payloads[index]
615
+ };
616
+ }
617
+ if (kind === KIND_SUPPRESSED) {
618
+ const group = this.suppressions[this.suppressionIndex[index] ?? 0];
619
+ if (!group) return void 0;
620
+ return {
621
+ type: "function_calls_suppressed",
622
+ functionId: group.functionId,
623
+ timestampMs: elapsedMs(executionStartedAtNs, group.firstTimestampNs),
624
+ lastTimestampMs: elapsedMs(executionStartedAtNs, group.lastTimestampNs),
625
+ suppressedCallCount: group.count,
626
+ totalDurationMs: roundedMilliseconds(group.totalDurationNs),
627
+ ...group.parentCallId === void 0 ? {} : { parentCallId: group.parentCallId }
628
+ };
629
+ }
630
+ return void 0;
631
+ }
632
+ write(kind, runtimePlanId, callId, parentCallId, timestampNs, durationNs, payload, suppressionIndex) {
633
+ if (!this.ensureCapacity(this.length + 1)) return false;
634
+ const index = this.length;
635
+ this.kind[index] = kind;
636
+ this.planId[index] = runtimePlanId;
637
+ this.callId[index] = callId;
638
+ this.parentCallId[index] = parentCallId === void 0 ? NO_PARENT : parentCallId;
639
+ this.timestampNs[index] = timestampNs;
640
+ this.durationNs[index] = durationNs;
641
+ this.payloads[index] = payload;
642
+ this.suppressionIndex[index] = suppressionIndex;
643
+ this.length++;
430
644
  return true;
431
645
  }
432
- values() {
433
- return [...this.items];
646
+ ensureCapacity(needed) {
647
+ if (needed <= this.kind.length) return true;
648
+ if (needed > this.maxCapacity) return false;
649
+ let next = this.kind.length === 0 ? Math.min(INITIAL_JOURNAL_STORAGE, this.maxCapacity) : this.kind.length;
650
+ while (next < needed) {
651
+ const grown = Math.min(this.maxCapacity, next * 2);
652
+ if (grown <= next) return false;
653
+ next = grown;
654
+ }
655
+ const extraBytes = (next - this.kind.length) * BYTES_PER_EVENT;
656
+ if (this.onGrow && extraBytes > 0 && !this.onGrow(extraBytes)) return false;
657
+ this.reallocate(next);
658
+ return true;
434
659
  }
435
- contains(target) {
436
- return this.items.includes(target);
660
+ reallocate(size) {
661
+ const nextKind = new Uint8Array(size);
662
+ const nextPlanId = new Uint32Array(size);
663
+ const nextCallId = new Uint32Array(size);
664
+ const nextParentCallId = new Uint32Array(size);
665
+ const nextTimestampNs = new BigInt64Array(size);
666
+ const nextDurationNs = new BigInt64Array(size);
667
+ const nextSuppressionIndex = new Uint32Array(size);
668
+ if (this.length > 0) {
669
+ nextKind.set(this.kind.subarray(0, this.length));
670
+ nextPlanId.set(this.planId.subarray(0, this.length));
671
+ nextCallId.set(this.callId.subarray(0, this.length));
672
+ nextParentCallId.set(this.parentCallId.subarray(0, this.length));
673
+ nextTimestampNs.set(this.timestampNs.subarray(0, this.length));
674
+ nextDurationNs.set(this.durationNs.subarray(0, this.length));
675
+ nextSuppressionIndex.set(this.suppressionIndex.subarray(0, this.length));
676
+ }
677
+ this.kind = nextKind;
678
+ this.planId = nextPlanId;
679
+ this.callId = nextCallId;
680
+ this.parentCallId = nextParentCallId;
681
+ this.timestampNs = nextTimestampNs;
682
+ this.durationNs = nextDurationNs;
683
+ this.suppressionIndex = nextSuppressionIndex;
684
+ this.allocatedBytes = size * BYTES_PER_EVENT;
437
685
  }
438
686
  evictCompletedLeafOrSummary() {
439
- const summaryIndex = this.items.findIndex(
440
- (event) => event.type === "function_calls_suppressed"
441
- );
442
- if (summaryIndex >= 0) {
443
- this.items.splice(summaryIndex, 1);
687
+ for (let index = 0; index < this.length; index++) {
688
+ if (this.kind[index] !== KIND_SUPPRESSED) continue;
689
+ const group = this.suppressions[this.suppressionIndex[index] ?? 0];
690
+ if (group) group.retainedIndex = -1;
691
+ this.removeAt(index);
444
692
  this.dropped++;
445
693
  return true;
446
694
  }
447
695
  const parentCallIds = /* @__PURE__ */ new Set();
448
- for (const event of this.items) {
449
- if ((event.type === "function_enter" || event.type === "function_calls_suppressed") && event.parentCallId !== void 0) {
450
- parentCallIds.add(event.parentCallId);
451
- }
696
+ for (let index = 0; index < this.length; index++) {
697
+ const kind = this.kind[index];
698
+ if (kind !== KIND_ENTER && kind !== KIND_SUPPRESSED) continue;
699
+ const parentCallId = this.parentCallId[index] ?? NO_PARENT;
700
+ if (parentCallId !== NO_PARENT) parentCallIds.add(parentCallId);
452
701
  }
453
- for (let index = 0; index < this.items.length; index++) {
454
- const event = this.items[index];
455
- if (event?.type !== "function_enter" || this.openCallIds.has(event.callId) || parentCallIds.has(event.callId)) {
456
- continue;
702
+ for (let index = 0; index < this.length; index++) {
703
+ if (this.kind[index] !== KIND_ENTER) continue;
704
+ const eventCallId = this.callId[index] ?? 0;
705
+ if (this.openCallIds.has(eventCallId) || parentCallIds.has(eventCallId)) continue;
706
+ let terminalIndex = -1;
707
+ for (let candidate = 0; candidate < this.length; candidate++) {
708
+ const kind = this.kind[candidate];
709
+ if ((kind === KIND_EXIT || kind === KIND_THROW) && this.callId[candidate] === eventCallId) {
710
+ terminalIndex = candidate;
711
+ break;
712
+ }
457
713
  }
458
- const terminalIndex = this.items.findIndex(
459
- (candidate) => (candidate.type === "function_exit" || candidate.type === "function_throw") && candidate.callId === event.callId
460
- );
461
714
  if (terminalIndex < 0) continue;
462
- this.items.splice(Math.max(index, terminalIndex), 1);
463
- this.items.splice(Math.min(index, terminalIndex), 1);
715
+ const later = Math.max(index, terminalIndex);
716
+ const earlier = Math.min(index, terminalIndex);
717
+ this.removeAt(later);
718
+ this.removeAt(earlier);
464
719
  this.dropped += 2;
465
720
  return true;
466
721
  }
467
722
  return false;
468
723
  }
724
+ removeAt(index) {
725
+ this.payloads[index] = void 0;
726
+ const next = index + 1;
727
+ if (next < this.length) {
728
+ this.kind.copyWithin(index, next, this.length);
729
+ this.planId.copyWithin(index, next, this.length);
730
+ this.callId.copyWithin(index, next, this.length);
731
+ this.parentCallId.copyWithin(index, next, this.length);
732
+ this.timestampNs.copyWithin(index, next, this.length);
733
+ this.durationNs.copyWithin(index, next, this.length);
734
+ this.suppressionIndex.copyWithin(index, next, this.length);
735
+ this.payloads.copyWithin(index, next, this.length);
736
+ }
737
+ this.payloads[this.length - 1] = void 0;
738
+ this.length--;
739
+ this.payloads.length = this.length;
740
+ for (const group of this.suppressions) {
741
+ if (group.retainedIndex > index) group.retainedIndex--;
742
+ }
743
+ }
744
+ };
745
+ var ExecutionJournalPool = class {
746
+ constructor(capacity, maxPooled = MAX_POOLED_JOURNALS) {
747
+ this.capacity = capacity;
748
+ this.maxPooled = maxPooled;
749
+ }
750
+ hits = 0;
751
+ misses = 0;
752
+ available = [];
753
+ acquire() {
754
+ const pooled = this.available.pop();
755
+ if (pooled) {
756
+ this.hits++;
757
+ return pooled;
758
+ }
759
+ this.misses++;
760
+ return new ExecutionJournal(this.capacity);
761
+ }
762
+ release(journal) {
763
+ journal.clear();
764
+ if (this.available.length >= this.maxPooled || journal.capacity > MAX_POOLED_JOURNAL_CAPACITY || journal.capacity !== this.capacity) {
765
+ return;
766
+ }
767
+ this.available.push(journal);
768
+ }
769
+ stats() {
770
+ return {
771
+ journalPoolHits: this.hits,
772
+ journalPoolMisses: this.misses,
773
+ journalPoolSize: this.available.length,
774
+ journalAllocatedCapacity: this.capacity
775
+ };
776
+ }
469
777
  };
470
778
 
471
779
  // src/execution-recorder/recorder-memory-budget.ts
472
- import { Buffer } from "node:buffer";
473
780
  var EXECUTION_BASE_ESTIMATED_BYTES = 1024;
474
- var EVENT_PAIR_BASE_ESTIMATED_BYTES = 512;
475
781
  var SUMMARY_EVENT_BASE_ESTIMATED_BYTES = 256;
476
782
  var VALUE_HEAP_ESTIMATE_MULTIPLIER = 2;
477
783
  var MEMORY_BUDGET_VALUE_MARKER = {
@@ -525,12 +831,11 @@ var RecorderMemoryBudget = class {
525
831
  }
526
832
  return serialized.value;
527
833
  }
528
- reserveCall(allocation, functionId) {
834
+ reserveCall(allocation, estimatedBytes) {
529
835
  if (allocation.mode === "metadata-only") {
530
836
  this.dropEvents(allocation, 2);
531
837
  return false;
532
838
  }
533
- const estimatedBytes = EVENT_PAIR_BASE_ESTIMATED_BYTES + Buffer.byteLength(functionId, "utf8") * 2;
534
839
  if (this.reserve(allocation, estimatedBytes)) return true;
535
840
  this.markPressure(allocation, "metadata-only");
536
841
  this.dropEvents(allocation, 2);
@@ -546,6 +851,12 @@ var RecorderMemoryBudget = class {
546
851
  this.dropEvents(allocation, 1);
547
852
  return false;
548
853
  }
854
+ reserveStorage(allocation, bytes) {
855
+ if (allocation.released || allocation.mode === "metadata-only") return false;
856
+ if (this.reserve(allocation, bytes)) return true;
857
+ this.markPressure(allocation, "metadata-only");
858
+ return false;
859
+ }
549
860
  release(allocation) {
550
861
  if (allocation.released) return;
551
862
  allocation.released = true;
@@ -590,7 +901,7 @@ var RecorderMemoryBudget = class {
590
901
 
591
902
  // src/execution-recorder/safe-serialize.ts
592
903
  import { Buffer as Buffer2 } from "node:buffer";
593
- var DEFAULT_REDACT_KEYS = /* @__PURE__ */ new Set([
904
+ var DEFAULT_REDACT_KEYS = [
594
905
  "password",
595
906
  "token",
596
907
  "authorization",
@@ -598,7 +909,7 @@ var DEFAULT_REDACT_KEYS = /* @__PURE__ */ new Set([
598
909
  "secret",
599
910
  "apikey",
600
911
  "creditcard"
601
- ]);
912
+ ];
602
913
  var defaults = {
603
914
  maxDepth: 3,
604
915
  maxObjectKeys: 30,
@@ -606,6 +917,7 @@ var defaults = {
606
917
  maxStringLength: 500,
607
918
  maxSerializedValueBytes: 8 * 1024
608
919
  };
920
+ var REDACTED = "[RASPUTIN_REDACTED]";
609
921
  var truncation = (reason, extra = {}) => ({
610
922
  __rasputin_truncated: true,
611
923
  __reason: reason,
@@ -615,135 +927,436 @@ var typeMarker = (type, value) => ({
615
927
  __rasputin_type: type,
616
928
  ...value === void 0 ? {} : { value }
617
929
  });
618
- var redact = (item, redactKeys, seen) => {
619
- if (!item || typeof item !== "object") return item;
620
- if (seen.has(item)) return item;
621
- seen.add(item);
622
- if (Array.isArray(item)) {
623
- return item.map((entry) => redact(entry, redactKeys, seen));
930
+ var jsonStringUtf8Bytes = (value) => {
931
+ let bytes = 2;
932
+ for (let index = 0; index < value.length; index++) {
933
+ const code = value.charCodeAt(index);
934
+ if (code === 34 || code === 92) {
935
+ bytes += 2;
936
+ continue;
937
+ }
938
+ if (code === 8 || code === 9 || code === 10 || code === 12 || code === 13) {
939
+ bytes += 2;
940
+ continue;
941
+ }
942
+ if (code < 32) {
943
+ bytes += 6;
944
+ continue;
945
+ }
946
+ if (code < 128) {
947
+ bytes += 1;
948
+ continue;
949
+ }
950
+ if (code < 2048) {
951
+ bytes += 2;
952
+ continue;
953
+ }
954
+ if (code >= 55296 && code <= 56319) {
955
+ const next = value.charCodeAt(index + 1);
956
+ if (index + 1 < value.length && next >= 56320 && next <= 57343) {
957
+ bytes += 4;
958
+ index++;
959
+ continue;
960
+ }
961
+ bytes += 6;
962
+ continue;
963
+ }
964
+ if (code >= 56320 && code <= 57343) {
965
+ bytes += 6;
966
+ continue;
967
+ }
968
+ bytes += 3;
624
969
  }
625
- for (const [key, child] of Object.entries(item)) {
626
- item[key] = redactKeys.has(key.toLowerCase()) ? "[RASPUTIN_REDACTED]" : redact(child, redactKeys, seen);
970
+ return bytes;
971
+ };
972
+ var jsonLiteralUtf8Bytes = (value) => {
973
+ if (value === null) return 4;
974
+ switch (typeof value) {
975
+ case "boolean":
976
+ return value ? 4 : 5;
977
+ case "number":
978
+ if (!Number.isFinite(value)) return 4;
979
+ if (Object.is(value, -0)) return 1;
980
+ return String(value).length;
981
+ case "string":
982
+ return jsonStringUtf8Bytes(value);
983
+ case "object": {
984
+ if (Array.isArray(value)) {
985
+ let bytes2 = 2;
986
+ for (let index = 0; index < value.length; index++) {
987
+ if (index > 0) bytes2 += 1;
988
+ bytes2 += jsonLiteralUtf8Bytes(value[index]);
989
+ }
990
+ return bytes2;
991
+ }
992
+ const record = value;
993
+ let bytes = 2;
994
+ let first = true;
995
+ for (const key of Object.keys(record)) {
996
+ if (!first) bytes += 1;
997
+ first = false;
998
+ bytes += jsonStringUtf8Bytes(key) + 1;
999
+ bytes += jsonLiteralUtf8Bytes(record[key]);
1000
+ }
1001
+ return bytes;
1002
+ }
1003
+ default:
1004
+ return Buffer2.byteLength(JSON.stringify(value) ?? "null", "utf8");
627
1005
  }
628
- return item;
629
1006
  };
630
- var errorValue = (error, depth, seen, limits) => ({
631
- __rasputin_type: "Error",
632
- name: error.name,
633
- message: serializeInner(error.message, depth + 1, seen, limits),
634
- ...error.stack ? { stack: serializeInner(error.stack, depth + 1, seen, limits) } : {}
1007
+ var compileLimits = (options) => ({
1008
+ maxDepth: options.maxDepth ?? defaults.maxDepth,
1009
+ maxObjectKeys: options.maxObjectKeys ?? defaults.maxObjectKeys,
1010
+ maxArrayElements: options.maxArrayElements ?? defaults.maxArrayElements,
1011
+ maxStringLength: options.maxStringLength ?? defaults.maxStringLength,
1012
+ maxSerializedValueBytes: options.maxSerializedValueBytes ?? defaults.maxSerializedValueBytes
635
1013
  });
636
- var serializeInner = (value, depth, seen, limits) => {
637
- if (value === null || typeof value === "boolean" || typeof value === "number") return value;
1014
+ var compileRedactKeys = (options) => {
1015
+ if (!options.redactKeys?.length) return new Set(DEFAULT_REDACT_KEYS);
1016
+ return /* @__PURE__ */ new Set([...DEFAULT_REDACT_KEYS, ...options.redactKeys.map((key) => key.toLowerCase())]);
1017
+ };
1018
+ var keyNeedsCaseFold = (key) => {
1019
+ for (let index = 0; index < key.length; index++) {
1020
+ const code = key.charCodeAt(index);
1021
+ if (code > 127 || code >= 65 && code <= 90) return true;
1022
+ }
1023
+ return false;
1024
+ };
1025
+ var isJsonSafeString = (value) => {
1026
+ for (let index = 0; index < value.length; index++) {
1027
+ const code = value.charCodeAt(index);
1028
+ if (code < 32 || code === 34 || code === 92) return false;
1029
+ if (code >= 55296 && code <= 56319) {
1030
+ const next = value.charCodeAt(index + 1);
1031
+ if (index + 1 >= value.length || next < 56320 || next > 57343) return false;
1032
+ index++;
1033
+ continue;
1034
+ }
1035
+ if (code >= 56320 && code <= 57343) return false;
1036
+ }
1037
+ return true;
1038
+ };
1039
+ var primitiveFastPath = (value, maxStringLength) => {
1040
+ if (value === null) return { value: null, bytes: 4, visitedNodes: 1 };
1041
+ if (typeof value === "boolean") return { value, bytes: value ? 4 : 5, visitedNodes: 1 };
1042
+ if (typeof value === "number") {
1043
+ if (!Number.isFinite(value)) return { value, bytes: 4, visitedNodes: 1 };
1044
+ if (Object.is(value, -0)) return { value, bytes: 1, visitedNodes: 1 };
1045
+ return { value, bytes: String(value).length, visitedNodes: 1 };
1046
+ }
638
1047
  if (typeof value === "string") {
639
- if (value.length <= limits.maxStringLength) return value;
640
- return truncation("max_string_length", {
641
- __original_length: value.length,
642
- value: value.slice(0, limits.maxStringLength)
643
- });
1048
+ if (value.length > maxStringLength || !isJsonSafeString(value)) return void 0;
1049
+ return { value, bytes: 2 + Buffer2.byteLength(value, "utf8"), visitedNodes: 1 };
644
1050
  }
645
- if (typeof value === "undefined") return typeMarker("undefined");
646
- if (typeof value === "bigint") return typeMarker("bigint", value.toString());
647
- if (typeof value === "symbol") return typeMarker("symbol", String(value));
648
- if (typeof value === "function") return typeMarker("function", value.name || "anonymous");
649
- if (depth >= limits.maxDepth) return truncation("max_depth");
650
- if (typeof value !== "object") return typeMarker(typeof value);
651
- if (seen.has(value)) return typeMarker("circular");
652
- seen.add(value);
653
- try {
654
- if (value instanceof Error) return errorValue(value, depth, seen, limits);
655
- if (value instanceof Date) return typeMarker("Date", value.toISOString());
656
- if (Buffer2.isBuffer(value)) {
657
- return typeMarker("Buffer", {
658
- byteLength: value.byteLength,
659
- base64: value.subarray(0, limits.maxStringLength).toString("base64"),
660
- ...value.byteLength > limits.maxStringLength ? { __rasputin_truncated: true } : {}
661
- });
1051
+ if (typeof value === "undefined") {
1052
+ const serialized = typeMarker("undefined");
1053
+ return { value: serialized, bytes: jsonLiteralUtf8Bytes(serialized), visitedNodes: 1 };
1054
+ }
1055
+ if (typeof value === "bigint") {
1056
+ const serialized = typeMarker("bigint", value.toString());
1057
+ return { value: serialized, bytes: jsonLiteralUtf8Bytes(serialized), visitedNodes: 1 };
1058
+ }
1059
+ if (typeof value === "symbol") {
1060
+ const text = String(value);
1061
+ if (!isJsonSafeString(text)) return void 0;
1062
+ const serialized = typeMarker("symbol", text);
1063
+ return { value: serialized, bytes: jsonLiteralUtf8Bytes(serialized), visitedNodes: 1 };
1064
+ }
1065
+ if (typeof value === "function") {
1066
+ const name = value.name || "anonymous";
1067
+ if (!isJsonSafeString(name)) return void 0;
1068
+ const serialized = typeMarker("function", name);
1069
+ return { value: serialized, bytes: jsonLiteralUtf8Bytes(serialized), visitedNodes: 1 };
1070
+ }
1071
+ return void 0;
1072
+ };
1073
+ var createSafeSerializer = (options = {}) => {
1074
+ const limits = compileLimits(options);
1075
+ const redactKeys = compileRedactKeys(options);
1076
+ const sharedPath = /* @__PURE__ */ new Set();
1077
+ let serializing = false;
1078
+ const isRedactedKey = (key) => {
1079
+ if (redactKeys.has(key)) return true;
1080
+ if (!keyNeedsCaseFold(key)) return false;
1081
+ return redactKeys.has(key.toLowerCase());
1082
+ };
1083
+ const addBytes = (metrics, count) => {
1084
+ metrics.bytes += count;
1085
+ if (metrics.bytes > limits.maxSerializedValueBytes) metrics.oversize = true;
1086
+ };
1087
+ const boundedOversize = (bytes, visitedNodes) => {
1088
+ const bounded = truncation("max_serialized_value_bytes", { __serialized_bytes: bytes });
1089
+ return { value: bounded, bytes: jsonLiteralUtf8Bytes(bounded), visitedNodes };
1090
+ };
1091
+ const openObject = (metrics, allocate) => {
1092
+ addBytes(metrics, 2);
1093
+ return {
1094
+ target: allocate && !metrics.oversize ? {} : void 0,
1095
+ first: true
1096
+ };
1097
+ };
1098
+ const openArray = (metrics, allocate) => {
1099
+ addBytes(metrics, 2);
1100
+ return {
1101
+ items: allocate && !metrics.oversize ? [] : void 0,
1102
+ first: true
1103
+ };
1104
+ };
1105
+ const beginField = (metrics, object, key) => {
1106
+ if (!object.first) addBytes(metrics, 1);
1107
+ object.first = false;
1108
+ addBytes(metrics, jsonStringUtf8Bytes(key) + 1);
1109
+ };
1110
+ const assignField = (object, key, fieldValue) => {
1111
+ if (object.target) object.target[key] = fieldValue;
1112
+ };
1113
+ const writeLiteralField = (metrics, object, key, jsonValue) => {
1114
+ beginField(metrics, object, key);
1115
+ addBytes(metrics, jsonLiteralUtf8Bytes(jsonValue));
1116
+ assignField(object, key, jsonValue);
1117
+ };
1118
+ const writeTypeMarker = (metrics, allocate, type, nestedValue) => {
1119
+ const object = openObject(metrics, allocate);
1120
+ writeLiteralField(metrics, object, "__rasputin_type", type);
1121
+ if (nestedValue !== void 0) writeLiteralField(metrics, object, "value", nestedValue);
1122
+ return object.target;
1123
+ };
1124
+ const writeTruncation = (metrics, allocate, reason, extra = {}) => {
1125
+ const object = openObject(metrics, allocate);
1126
+ writeLiteralField(metrics, object, "__rasputin_truncated", true);
1127
+ writeLiteralField(metrics, object, "__reason", reason);
1128
+ for (const key of Object.keys(extra)) writeLiteralField(metrics, object, key, extra[key]);
1129
+ return object.target;
1130
+ };
1131
+ const serializeInner = (value, depth, path, allocate, metrics) => {
1132
+ metrics.visitedNodes++;
1133
+ const store = allocate && !metrics.oversize;
1134
+ if (value === null) {
1135
+ addBytes(metrics, 4);
1136
+ return store ? null : void 0;
662
1137
  }
663
- if (value instanceof Promise) return typeMarker("Promise");
664
- if (value instanceof Map) {
665
- const entries = [];
666
- for (const entry of value.entries()) {
667
- if (entries.length >= limits.maxArrayElements) break;
668
- entries.push([
669
- serializeInner(entry[0], depth + 1, seen, limits),
670
- serializeInner(entry[1], depth + 1, seen, limits)
671
- ]);
672
- }
673
- return {
674
- __rasputin_type: "Map",
675
- entries,
676
- ...value.size > limits.maxArrayElements ? truncation("max_array_elements", { __original_length: value.size }) : {}
677
- };
1138
+ if (typeof value === "boolean") {
1139
+ addBytes(metrics, value ? 4 : 5);
1140
+ return store ? value : void 0;
1141
+ }
1142
+ if (typeof value === "number") {
1143
+ if (!Number.isFinite(value)) addBytes(metrics, 4);
1144
+ else if (Object.is(value, -0)) addBytes(metrics, 1);
1145
+ else addBytes(metrics, String(value).length);
1146
+ return store ? value : void 0;
678
1147
  }
679
- if (value instanceof Set) {
680
- const items = [];
681
- for (const item of value.values()) {
682
- if (items.length >= limits.maxArrayElements) break;
683
- items.push(serializeInner(item, depth + 1, seen, limits));
1148
+ if (typeof value === "string") {
1149
+ if (value.length <= limits.maxStringLength) {
1150
+ addBytes(metrics, jsonStringUtf8Bytes(value));
1151
+ return store ? value : void 0;
684
1152
  }
685
- return {
686
- __rasputin_type: "Set",
687
- values: items,
688
- ...value.size > limits.maxArrayElements ? truncation("max_array_elements", { __original_length: value.size }) : {}
689
- };
1153
+ return writeTruncation(metrics, store, "max_string_length", {
1154
+ __original_length: value.length,
1155
+ value: value.slice(0, limits.maxStringLength)
1156
+ });
1157
+ }
1158
+ if (typeof value === "undefined") return writeTypeMarker(metrics, store, "undefined");
1159
+ if (typeof value === "bigint") {
1160
+ return writeTypeMarker(metrics, store, "bigint", value.toString());
690
1161
  }
691
- if (Array.isArray(value)) {
692
- const maxItems = value.length > limits.maxArrayElements ? limits.maxArrayElements - 1 : value.length;
693
- const items = value.slice(0, maxItems).map((item) => serializeInner(item, depth + 1, seen, limits));
694
- if (value.length > limits.maxArrayElements) {
695
- items.push(truncation("max_array_elements", { __original_length: value.length }));
1162
+ if (typeof value === "symbol") {
1163
+ return writeTypeMarker(metrics, store, "symbol", String(value));
1164
+ }
1165
+ if (typeof value === "function") {
1166
+ return writeTypeMarker(metrics, store, "function", value.name || "anonymous");
1167
+ }
1168
+ if (depth >= limits.maxDepth) return writeTruncation(metrics, store, "max_depth");
1169
+ if (typeof value !== "object") return writeTypeMarker(metrics, store, typeof value);
1170
+ if (path.has(value)) return writeTypeMarker(metrics, store, "circular");
1171
+ path.add(value);
1172
+ const bytesBefore = metrics.bytes;
1173
+ const oversizeBefore = metrics.oversize;
1174
+ try {
1175
+ const childAllocate = allocate && !metrics.oversize;
1176
+ if (value instanceof Error) {
1177
+ const object2 = openObject(metrics, childAllocate);
1178
+ writeLiteralField(metrics, object2, "__rasputin_type", "Error");
1179
+ writeLiteralField(metrics, object2, "name", value.name);
1180
+ beginField(metrics, object2, "message");
1181
+ assignField(
1182
+ object2,
1183
+ "message",
1184
+ serializeInner(value.message, depth + 1, path, childAllocate, metrics)
1185
+ );
1186
+ if (value.stack) {
1187
+ beginField(metrics, object2, "stack");
1188
+ assignField(
1189
+ object2,
1190
+ "stack",
1191
+ serializeInner(value.stack, depth + 1, path, childAllocate, metrics)
1192
+ );
1193
+ }
1194
+ return object2.target;
1195
+ }
1196
+ if (value instanceof Date) {
1197
+ return writeTypeMarker(metrics, childAllocate, "Date", value.toISOString());
1198
+ }
1199
+ if (Buffer2.isBuffer(value)) {
1200
+ return writeTypeMarker(metrics, childAllocate, "Buffer", {
1201
+ byteLength: value.byteLength,
1202
+ base64: value.subarray(0, limits.maxStringLength).toString("base64"),
1203
+ ...value.byteLength > limits.maxStringLength ? { __rasputin_truncated: true } : {}
1204
+ });
1205
+ }
1206
+ if (value instanceof Promise) return writeTypeMarker(metrics, childAllocate, "Promise");
1207
+ if (value instanceof Map) {
1208
+ const object2 = openObject(metrics, childAllocate);
1209
+ writeLiteralField(metrics, object2, "__rasputin_type", "Map");
1210
+ beginField(metrics, object2, "entries");
1211
+ const entries = openArray(metrics, childAllocate);
1212
+ let counted2 = 0;
1213
+ for (const entry of value.entries()) {
1214
+ if (counted2 >= limits.maxArrayElements) break;
1215
+ if (!entries.first) addBytes(metrics, 1);
1216
+ entries.first = false;
1217
+ addBytes(metrics, 2);
1218
+ const key = serializeInner(entry[0], depth + 1, path, childAllocate, metrics);
1219
+ addBytes(metrics, 1);
1220
+ const mapped = serializeInner(entry[1], depth + 1, path, childAllocate, metrics);
1221
+ entries.items?.push([key, mapped]);
1222
+ counted2++;
1223
+ }
1224
+ assignField(object2, "entries", entries.items ?? []);
1225
+ if (value.size > limits.maxArrayElements) {
1226
+ writeLiteralField(metrics, object2, "__rasputin_truncated", true);
1227
+ writeLiteralField(metrics, object2, "__reason", "max_array_elements");
1228
+ writeLiteralField(metrics, object2, "__original_length", value.size);
1229
+ }
1230
+ return object2.target;
1231
+ }
1232
+ if (value instanceof Set) {
1233
+ const object2 = openObject(metrics, childAllocate);
1234
+ writeLiteralField(metrics, object2, "__rasputin_type", "Set");
1235
+ beginField(metrics, object2, "values");
1236
+ const items = openArray(metrics, childAllocate);
1237
+ let counted2 = 0;
1238
+ for (const item of value.values()) {
1239
+ if (counted2 >= limits.maxArrayElements) break;
1240
+ if (!items.first) addBytes(metrics, 1);
1241
+ items.first = false;
1242
+ const serializedItem = serializeInner(item, depth + 1, path, childAllocate, metrics);
1243
+ items.items?.push(serializedItem);
1244
+ counted2++;
1245
+ }
1246
+ assignField(object2, "values", items.items ?? []);
1247
+ if (value.size > limits.maxArrayElements) {
1248
+ writeLiteralField(metrics, object2, "__rasputin_truncated", true);
1249
+ writeLiteralField(metrics, object2, "__reason", "max_array_elements");
1250
+ writeLiteralField(metrics, object2, "__original_length", value.size);
1251
+ }
1252
+ return object2.target;
1253
+ }
1254
+ if (Array.isArray(value)) {
1255
+ const items = openArray(metrics, childAllocate);
1256
+ const maxItems = value.length > limits.maxArrayElements ? limits.maxArrayElements - 1 : value.length;
1257
+ for (let index = 0; index < maxItems; index++) {
1258
+ if (!items.first) addBytes(metrics, 1);
1259
+ items.first = false;
1260
+ items.items?.push(serializeInner(value[index], depth + 1, path, childAllocate, metrics));
1261
+ }
1262
+ if (value.length > limits.maxArrayElements) {
1263
+ if (!items.first) addBytes(metrics, 1);
1264
+ items.first = false;
1265
+ items.items?.push(
1266
+ writeTruncation(metrics, childAllocate, "max_array_elements", {
1267
+ __original_length: value.length
1268
+ })
1269
+ );
1270
+ }
1271
+ return items.items;
1272
+ }
1273
+ const object = openObject(metrics, childAllocate);
1274
+ const prototype = Object.getPrototypeOf(value);
1275
+ if (prototype && prototype !== Object.prototype) {
1276
+ writeLiteralField(
1277
+ metrics,
1278
+ object,
1279
+ "__rasputin_type",
1280
+ prototype.constructor?.name ?? "instance"
1281
+ );
696
1282
  }
697
- return items;
1283
+ const keys = Object.keys(value);
1284
+ const maxKeys = keys.length > limits.maxObjectKeys ? limits.maxObjectKeys - 1 : keys.length;
1285
+ for (let index = 0; index < maxKeys; index++) {
1286
+ const key = keys[index];
1287
+ if (key === void 0) continue;
1288
+ if (isRedactedKey(key)) {
1289
+ writeLiteralField(metrics, object, key, REDACTED);
1290
+ continue;
1291
+ }
1292
+ const descriptor = Object.getOwnPropertyDescriptor(value, key);
1293
+ if (descriptor && "value" in descriptor) {
1294
+ beginField(metrics, object, key);
1295
+ assignField(
1296
+ object,
1297
+ key,
1298
+ serializeInner(descriptor.value, depth + 1, path, childAllocate, metrics)
1299
+ );
1300
+ } else {
1301
+ beginField(metrics, object, key);
1302
+ assignField(object, key, writeTypeMarker(metrics, childAllocate, "accessor"));
1303
+ }
1304
+ }
1305
+ if (keys.length > limits.maxObjectKeys) {
1306
+ writeLiteralField(metrics, object, "__rasputin_truncated", true);
1307
+ writeLiteralField(metrics, object, "__reason", "max_object_keys");
1308
+ writeLiteralField(metrics, object, "__original_length", keys.length);
1309
+ }
1310
+ return object.target;
1311
+ } catch (error) {
1312
+ metrics.bytes = bytesBefore;
1313
+ metrics.oversize = oversizeBefore;
1314
+ return writeTypeMarker(
1315
+ metrics,
1316
+ allocate && !metrics.oversize,
1317
+ "unserializable",
1318
+ error instanceof Error ? error.message : void 0
1319
+ );
1320
+ } finally {
1321
+ path.delete(value);
698
1322
  }
699
- const prototype = Object.getPrototypeOf(value);
700
- const serialized = {};
701
- if (prototype && prototype !== Object.prototype) {
702
- serialized.__rasputin_type = prototype.constructor?.name ?? "instance";
1323
+ };
1324
+ const serializeWithBytes = (value) => {
1325
+ const fast = primitiveFastPath(value, limits.maxStringLength);
1326
+ if (fast) {
1327
+ return fast.bytes <= limits.maxSerializedValueBytes ? fast : boundedOversize(fast.bytes, fast.visitedNodes);
703
1328
  }
704
- const keys = Object.keys(value);
705
- const maxKeys = keys.length > limits.maxObjectKeys ? limits.maxObjectKeys - 1 : keys.length;
706
- for (const key of keys.slice(0, maxKeys)) {
707
- const descriptor = Object.getOwnPropertyDescriptor(value, key);
708
- serialized[key] = descriptor && "value" in descriptor ? serializeInner(descriptor.value, depth + 1, seen, limits) : typeMarker("accessor");
1329
+ const metrics = { visitedNodes: 0, bytes: 0, oversize: false };
1330
+ const reentrant = serializing;
1331
+ const path = reentrant ? /* @__PURE__ */ new Set() : sharedPath;
1332
+ if (!reentrant) {
1333
+ serializing = true;
1334
+ sharedPath.clear();
709
1335
  }
710
- if (keys.length > limits.maxObjectKeys) {
711
- Object.assign(serialized, truncation("max_object_keys", { __original_length: keys.length }));
1336
+ try {
1337
+ const serialized = serializeInner(value, 0, path, true, metrics);
1338
+ if (metrics.bytes > limits.maxSerializedValueBytes) {
1339
+ return boundedOversize(metrics.bytes, metrics.visitedNodes);
1340
+ }
1341
+ return { value: serialized, bytes: metrics.bytes, visitedNodes: metrics.visitedNodes };
1342
+ } catch {
1343
+ const failed = typeMarker("unserializable");
1344
+ return {
1345
+ value: failed,
1346
+ bytes: jsonLiteralUtf8Bytes(failed),
1347
+ visitedNodes: metrics.visitedNodes
1348
+ };
1349
+ } finally {
1350
+ path.clear();
1351
+ if (!reentrant) serializing = false;
712
1352
  }
713
- return serialized;
714
- } catch (error) {
715
- return typeMarker("unserializable", error instanceof Error ? error.message : void 0);
716
- } finally {
717
- seen.delete(value);
718
- }
719
- };
720
- var safeSerializeWithBytes = (value, options = {}) => {
721
- const limits = {
722
- maxDepth: options.maxDepth ?? defaults.maxDepth,
723
- maxObjectKeys: options.maxObjectKeys ?? defaults.maxObjectKeys,
724
- maxArrayElements: options.maxArrayElements ?? defaults.maxArrayElements,
725
- maxStringLength: options.maxStringLength ?? defaults.maxStringLength,
726
- maxSerializedValueBytes: options.maxSerializedValueBytes ?? defaults.maxSerializedValueBytes
727
1353
  };
728
- try {
729
- const redactKeys = /* @__PURE__ */ new Set([
730
- ...DEFAULT_REDACT_KEYS,
731
- ...(options.redactKeys ?? []).map((key) => key.toLowerCase())
732
- ]);
733
- const serialized = serializeInner(value, 0, /* @__PURE__ */ new WeakSet(), limits);
734
- const redacted = redact(serialized, redactKeys, /* @__PURE__ */ new WeakSet());
735
- const bytes = Buffer2.byteLength(JSON.stringify(redacted), "utf8");
736
- const bounded = bytes <= limits.maxSerializedValueBytes ? redacted : truncation("max_serialized_value_bytes", { __serialized_bytes: bytes });
737
- return {
738
- value: bounded,
739
- bytes: bounded === redacted ? bytes : Buffer2.byteLength(JSON.stringify(bounded), "utf8")
740
- };
741
- } catch {
742
- const value2 = typeMarker("unserializable");
743
- return { value: value2, bytes: Buffer2.byteLength(JSON.stringify(value2), "utf8") };
744
- }
1354
+ return {
1355
+ serialize: (value) => serializeWithBytes(value).value,
1356
+ serializeWithBytes
1357
+ };
745
1358
  };
746
- var safeSerialize = (value, options = {}) => safeSerializeWithBytes(value, options).value;
1359
+ var defaultSerializer = createSafeSerializer();
747
1360
 
748
1361
  // src/execution-recorder/source-exclusions.ts
749
1362
  var sourcePathFrom = (definition) => definition.source?.packageRelativePath.replaceAll("\\", "/") ?? definition.name;
@@ -811,6 +1424,8 @@ var createSourceExclusionMatcher = (exclusions) => {
811
1424
 
812
1425
  // src/execution-recorder/execution-recorder.ts
813
1426
  var DEFAULT_MAX_ACTIVE_MEMORY_BYTES = 64 * 1024 * 1024;
1427
+ var DERIVATION_ROOT_RECORD_BYTES = 64;
1428
+ var phaseTimingEnabled = process.env.RASPUTIN_RECORDER_PHASE_TIMING === "1";
814
1429
  var manualFunctionDefinition = (value) => {
815
1430
  const located = /^(.+):([^:@]+)@(\d+):(\d+)$/.exec(value);
816
1431
  const named = located ? void 0 : /^(.+):([^:@]+)$/.exec(value);
@@ -825,8 +1440,8 @@ var manualFunctionDefinition = (value) => {
825
1440
  return { ...definition, functionId: runtimeFunctionId2(definition) };
826
1441
  };
827
1442
  var normalizeFunctionDefinition = (value) => typeof value === "string" ? manualFunctionDefinition(value) : value;
828
- var elapsedMs = (startedAtNs, endedAtNs = process.hrtime.bigint()) => Number(endedAtNs - startedAtNs) / 1e6;
829
- var roundedMilliseconds = (nanoseconds) => Math.round(Number(nanoseconds) / 1e6 * 1e4) / 1e4;
1443
+ var elapsedMs2 = (startedAtNs, endedAtNs = process.hrtime.bigint()) => Number(endedAtNs - startedAtNs) / 1e6;
1444
+ var roundedMilliseconds2 = (nanoseconds) => Math.round(Number(nanoseconds) / 1e6 * 1e4) / 1e4;
830
1445
  var addRecorderTime = (recording, nanoseconds) => {
831
1446
  if (nanoseconds > 0n) recording.inlineRecorderTimeNs += nanoseconds;
832
1447
  };
@@ -896,6 +1511,43 @@ var observeRecordedCallback = (recording, recorderStartedAtNs, callback, onRetur
896
1511
  recordExitWork(callbackEndedAtNs);
897
1512
  return result;
898
1513
  };
1514
+ var emptyStats = {
1515
+ functionCallsObserved: 0,
1516
+ functionCallsCaptured: 0,
1517
+ functionCallsExcluded: 0,
1518
+ serializedValues: 0,
1519
+ serializedValueNodes: 0,
1520
+ serializedValueBytes: 0,
1521
+ errorSnapshots: 0,
1522
+ successfulExecutionsDiscarded: 0,
1523
+ truncatedEvents: 0,
1524
+ repeatedCallsSuppressed: 0,
1525
+ activeExecutions: 0,
1526
+ peakActiveExecutions: 0,
1527
+ memoryPressureExecutions: 0,
1528
+ memoryPressureDegradations: 0,
1529
+ valuesDroppedByMemoryBudget: 0,
1530
+ eventsDroppedByMemoryBudget: 0,
1531
+ partialErrorSnapshots: 0,
1532
+ measuredExecutions: 0,
1533
+ totalInlineWallTimeMs: 0,
1534
+ maxInlineWallTimeMs: 0,
1535
+ activeEstimatedBytes: 0,
1536
+ peakActiveEstimatedBytes: 0,
1537
+ journalPoolHits: 0,
1538
+ journalPoolMisses: 0,
1539
+ journalPoolSize: 0,
1540
+ journalAllocatedCapacity: 0,
1541
+ derivationCandidateFunctions: 0,
1542
+ derivationElidedFunctionCalls: 0,
1543
+ derivationRootsCaptured: 0,
1544
+ derivationControlFactsCaptured: 0,
1545
+ derivationValuesAvoided: 0,
1546
+ derivationFallbackFunctions: 0,
1547
+ derivationFallbackExpressions: 0,
1548
+ derivationFallbackReasons: {},
1549
+ derivationRootBytes: 0
1550
+ };
899
1551
  var noOpExecution = {
900
1552
  createScope: () => ({
901
1553
  run: (callback) => callback(),
@@ -906,35 +1558,38 @@ var noOpExecution = {
906
1558
  run: (_metadata, callback) => callback(),
907
1559
  getErrorState: () => void 0,
908
1560
  runFunction: (_functionId, _args, callback) => callback(),
1561
+ runFunctionPlans: (_plans, _planIndex, _args, callback) => callback(),
1562
+ prepareFunctionPlans: () => [],
1563
+ runPreparedFunction: (_plan, _args, callback) => callback(),
909
1564
  trace: (_functionId, fn) => fn,
910
- getStats: () => ({
911
- errorSnapshots: 0,
912
- successfulExecutionsDiscarded: 0,
913
- truncatedEvents: 0,
914
- repeatedCallsSuppressed: 0,
915
- activeExecutions: 0,
916
- peakActiveExecutions: 0,
917
- activeEstimatedBytes: 0,
918
- peakActiveEstimatedBytes: 0,
919
- memoryPressureExecutions: 0,
920
- memoryPressureDegradations: 0,
921
- valuesDroppedByMemoryBudget: 0,
922
- eventsDroppedByMemoryBudget: 0,
923
- partialErrorSnapshots: 0,
924
- measuredExecutions: 0,
925
- totalInlineWallTimeMs: 0,
926
- maxInlineWallTimeMs: 0
927
- })
1565
+ getStats: () => ({ ...emptyStats, derivationFallbackReasons: {} }),
1566
+ captureDerivationRoot: (_rootId, value) => value
928
1567
  };
929
1568
  var mergeExecutionMetadata = (target, metadata) => {
930
1569
  if (metadata.kind) target.kind = metadata.kind;
931
1570
  if (metadata.name !== void 0) target.name = metadata.name;
932
1571
  if (metadata.request) target.request = { ...target.request, ...metadata.request };
933
1572
  };
1573
+ var newRepeatedGroup = (plan) => ({
1574
+ observedCalls: 0,
1575
+ functionId: plan.functionId,
1576
+ runtimePlanId: plan.runtimePlanId
1577
+ });
1578
+ var newSuppression = (group, parentCallId, startedAtNs, endedAtNs) => ({
1579
+ count: 1,
1580
+ firstTimestampNs: startedAtNs,
1581
+ lastTimestampNs: endedAtNs,
1582
+ totalDurationNs: endedAtNs - startedAtNs,
1583
+ functionId: group.functionId,
1584
+ runtimePlanId: group.runtimePlanId,
1585
+ ...parentCallId === void 0 ? {} : { parentCallId },
1586
+ storageIndex: -1,
1587
+ retainedIndex: -1
1588
+ });
934
1589
  var createExecutionRecorder = (options) => {
935
1590
  if (!options?.enabled) return noOpExecution;
936
1591
  const storage = new AsyncLocalStorage();
937
- const maxEventsPerExecution = Math.max(2, Math.floor(options.maxEventsPerExecution ?? 500));
1592
+ const maxEventsPerExecution = sanitizeJournalCapacity(options.maxEventsPerExecution);
938
1593
  const maxCapturedCallsPerFunction = Math.max(
939
1594
  1,
940
1595
  Math.floor(options.maxCapturedCallsPerFunction ?? 3)
@@ -947,10 +1602,23 @@ var createExecutionRecorder = (options) => {
947
1602
  maxActiveMemoryBytes,
948
1603
  options.maxSerializedValueBytes ?? 8 * 1024
949
1604
  );
1605
+ const serializer = createSafeSerializer(options);
950
1606
  const excludesSource = createSourceExclusionMatcher(options.excludeSources);
1607
+ const journalPool = new ExecutionJournalPool(maxEventsPerExecution);
1608
+ const plansByRuntimeId = [];
951
1609
  let executionCounter = 0;
1610
+ let nextRuntimePlanId = 1;
1611
+ const internedPlans = /* @__PURE__ */ new Map();
1612
+ const preparedModules = /* @__PURE__ */ new WeakMap();
952
1613
  const errorExecutions = /* @__PURE__ */ new WeakMap();
1614
+ const phases = phaseTimingEnabled ? { alsNs: 0n, materializeNs: 0n } : void 0;
953
1615
  const stats = {
1616
+ functionCallsObserved: 0,
1617
+ functionCallsCaptured: 0,
1618
+ functionCallsExcluded: 0,
1619
+ serializedValues: 0,
1620
+ serializedValueNodes: 0,
1621
+ serializedValueBytes: 0,
954
1622
  errorSnapshots: 0,
955
1623
  successfulExecutionsDiscarded: 0,
956
1624
  truncatedEvents: 0,
@@ -960,49 +1628,185 @@ var createExecutionRecorder = (options) => {
960
1628
  partialErrorSnapshots: 0,
961
1629
  measuredExecutions: 0,
962
1630
  totalInlineWallTimeMs: 0,
963
- maxInlineWallTimeMs: 0
1631
+ maxInlineWallTimeMs: 0,
1632
+ derivationCandidateFunctions: 0,
1633
+ derivationElidedFunctionCalls: 0,
1634
+ derivationRootsCaptured: 0,
1635
+ derivationControlFactsCaptured: 0,
1636
+ derivationValuesAvoided: 0,
1637
+ derivationFallbackFunctions: 0,
1638
+ derivationFallbackExpressions: 0,
1639
+ derivationFallbackReasons: {},
1640
+ derivationRootBytes: 0
1641
+ };
1642
+ const addPhase = (key, nanoseconds) => {
1643
+ if (phases && nanoseconds > 0n) phases[key] += nanoseconds;
964
1644
  };
965
1645
  const recordInlineTiming = (recording) => {
966
- const inlineWallTimeMs = roundedMilliseconds(recording.inlineRecorderTimeNs);
1646
+ const inlineWallTimeMs = roundedMilliseconds2(recording.inlineRecorderTimeNs);
967
1647
  stats.measuredExecutions++;
968
1648
  stats.totalInlineWallTimeMs = Math.round((stats.totalInlineWallTimeMs + inlineWallTimeMs) * 1e4) / 1e4;
969
1649
  stats.maxInlineWallTimeMs = Math.max(stats.maxInlineWallTimeMs, inlineWallTimeMs);
970
1650
  };
971
- const captureValue = (recording, value) => memoryBudget.captureValue(recording.memory, () => safeSerializeWithBytes(value, options));
1651
+ const serializeAndCount = (value) => {
1652
+ const serialized = serializer.serializeWithBytes(value);
1653
+ stats.serializedValues++;
1654
+ stats.serializedValueNodes += serialized.visitedNodes;
1655
+ stats.serializedValueBytes += serialized.bytes;
1656
+ return serialized;
1657
+ };
1658
+ const captureValue = (recording, value) => memoryBudget.captureValue(recording.memory, () => serializeAndCount(value));
1659
+ const storePlan = (plan) => {
1660
+ plansByRuntimeId[plan.runtimePlanId] = plan;
1661
+ return plan;
1662
+ };
1663
+ const preparePlan = (definition) => {
1664
+ const existing = internedPlans.get(definition.functionId);
1665
+ if (existing) return existing;
1666
+ const plan = storePlan({
1667
+ runtimePlanId: nextRuntimePlanId++,
1668
+ definition,
1669
+ functionId: definition.functionId,
1670
+ excluded: excludesSource(definition),
1671
+ estimatedCallBytes: estimatedCallBytesFor(definition.functionId)
1672
+ });
1673
+ internedPlans.set(definition.functionId, plan);
1674
+ return plan;
1675
+ };
1676
+ const preparedPlansFor = (plans) => {
1677
+ const cached = preparedModules.get(plans);
1678
+ if (cached) return cached;
1679
+ const prepared = plans.map((encoded) => {
1680
+ const definition = definitionFromEncodedPlan(encoded);
1681
+ return storePlan({
1682
+ runtimePlanId: nextRuntimePlanId++,
1683
+ definition,
1684
+ functionId: definition.functionId,
1685
+ excluded: excludesSource(definition),
1686
+ estimatedCallBytes: estimatedCallBytesFor(definition.functionId)
1687
+ });
1688
+ });
1689
+ preparedModules.set(plans, prepared);
1690
+ return prepared;
1691
+ };
1692
+ const derivationMetrics = (recording) => {
1693
+ const manifest = snapshotInstrumentationManifest();
1694
+ const plans = manifest?.derivationPlans ?? [];
1695
+ const reasons = { ...stats.derivationFallbackReasons };
1696
+ return {
1697
+ derivationCandidateFunctions: plans.filter((plan) => !plan.fallbackReason).length,
1698
+ derivationElidedFunctionCalls: stats.derivationElidedFunctionCalls,
1699
+ derivationRootsCaptured: recording.derivationRoots.length,
1700
+ derivationControlFactsCaptured: stats.derivationControlFactsCaptured,
1701
+ derivationValuesAvoided: stats.derivationValuesAvoided,
1702
+ derivationFallbackFunctions: plans.filter((plan) => plan.fallbackReason).length,
1703
+ derivationFallbackExpressions: recording.derivationRoots.length,
1704
+ derivationFallbackReasons: reasons,
1705
+ derivationRootBytes: recording.derivationRootBytes
1706
+ };
1707
+ };
1708
+ const eventsHaveOmittedValues = (events) => {
1709
+ for (const event of events) {
1710
+ if (event.type === "function_enter" && event.args.some((arg) => rasputinTagKind(arg) === "omitted")) {
1711
+ return true;
1712
+ }
1713
+ if (event.type === "function_exit" && rasputinTagKind(event.returnValue) === "omitted") {
1714
+ return true;
1715
+ }
1716
+ }
1717
+ return false;
1718
+ };
1719
+ const resolveCaptureProfile = (recording, events, truncated, capture, derivation) => {
1720
+ const source = recording.sawAutomatic ? "automatic" : recording.sawManual ? "manual" : "scope-only";
1721
+ if (source === "scope-only") {
1722
+ return {
1723
+ source,
1724
+ functionBoundaries: "none",
1725
+ functionValues: "none",
1726
+ timings: "none",
1727
+ derivation: derivation ? "available" : "none"
1728
+ };
1729
+ }
1730
+ const droppedEvents = Boolean(truncated?.eventsDropped || capture?.eventsDropped);
1731
+ return {
1732
+ source,
1733
+ functionBoundaries: droppedEvents ? "partial" : "full",
1734
+ functionValues: eventsHaveOmittedValues(events) ? "selective" : "full",
1735
+ timings: droppedEvents ? "partial" : "full",
1736
+ derivation: derivation ? "available" : "none"
1737
+ };
1738
+ };
1739
+ const derivationCapture = (recording, functions) => {
1740
+ const manifest = snapshotInstrumentationManifest();
1741
+ if (!manifest) return void 0;
1742
+ const plans = manifest.derivationPlans ?? [];
1743
+ const recordedIds = new Set(functions.map((fn) => fn.functionId));
1744
+ if (!plans.some((plan) => recordedIds.has(plan.functionId))) {
1745
+ return void 0;
1746
+ }
1747
+ return {
1748
+ manifestHash: hashInstrumentationManifest(manifest),
1749
+ roots: recording.derivationRoots.slice(),
1750
+ controlFacts: []
1751
+ };
1752
+ };
972
1753
  const stateFrom = (recording, endedAtNs = process.hrtime.bigint()) => {
973
1754
  const recorderStartedAtNs = process.hrtime.bigint();
974
- const events = recording.events.values().map((event) => event.type === "function_calls_suppressed" ? { ...event } : event);
975
- const referencedFunctionIds = new Set(
976
- events.flatMap(
977
- (event) => event.type === "function_enter" || event.type === "function_calls_suppressed" ? [event.functionId] : []
978
- )
979
- );
1755
+ let events = [];
1756
+ let functions = [];
1757
+ let truncated;
1758
+ let capture;
1759
+ let derivation;
1760
+ if (recording.cachedState) {
1761
+ events = recording.cachedState.events;
1762
+ functions = recording.cachedState.functions;
1763
+ truncated = recording.cachedState.truncated;
1764
+ capture = recording.cachedState.capture;
1765
+ derivation = recording.cachedState.derivation;
1766
+ } else {
1767
+ if (recording.journal) {
1768
+ const materialized = recording.journal.materialize(plansByRuntimeId, recording.startedAtNs);
1769
+ events = materialized.events;
1770
+ functions = materialized.functions;
1771
+ truncated = recording.journal.dropped > 0 ? { eventsDropped: recording.journal.dropped } : void 0;
1772
+ }
1773
+ capture = recording.memory.underPressure ? {
1774
+ completeness: "partial",
1775
+ reason: "process_memory_budget",
1776
+ valuesDropped: recording.memory.valuesDropped,
1777
+ eventsDropped: recording.memory.eventsDropped
1778
+ } : void 0;
1779
+ derivation = derivationCapture(recording, functions);
1780
+ if (recording.finished && recording.inFlightCalls === 0 && recording.journal) {
1781
+ recording.cachedState = { events, functions, truncated, capture, derivation };
1782
+ journalPool.release(recording.journal);
1783
+ recording.journal = void 0;
1784
+ }
1785
+ }
980
1786
  const state = {
981
- version: 2,
1787
+ version: 4,
1788
+ captureProfile: resolveCaptureProfile(recording, events, truncated, capture, derivation),
982
1789
  executionId: recording.executionId,
983
1790
  startedAt: recording.startedAt,
984
- durationMs: elapsedMs(recording.startedAtNs, endedAtNs),
985
- recorder: { inlineWallTimeMs: 0 },
1791
+ durationMs: elapsedMs2(recording.startedAtNs, endedAtNs),
1792
+ recorder: { inlineWallTimeMs: 0, derivation: derivationMetrics(recording) },
986
1793
  execution: {
987
1794
  ...recording.execution,
988
1795
  ...recording.execution.request ? { request: { ...recording.execution.request } } : {}
989
1796
  },
990
- functions: [...recording.functions.values()].filter(
991
- (definition) => referencedFunctionIds.has(definition.functionId)
992
- ),
993
- events,
994
- ...recording.events.dropped > 0 ? { truncated: { eventsDropped: recording.events.dropped } } : {},
995
- ...recording.memory.underPressure ? {
996
- capture: {
997
- completeness: "partial",
998
- reason: "process_memory_budget",
999
- valuesDropped: recording.memory.valuesDropped,
1000
- eventsDropped: recording.memory.eventsDropped
1001
- }
1002
- } : {}
1797
+ functions: functions.slice(),
1798
+ events: events.slice(),
1799
+ ...truncated ? { truncated: { ...truncated } } : {},
1800
+ ...capture ? { capture: { ...capture } } : {},
1801
+ ...derivation ? { derivation: { ...derivation, roots: derivation.roots.slice() } } : {}
1003
1802
  };
1803
+ if (phaseTimingEnabled)
1804
+ addPhase("materializeNs", process.hrtime.bigint() - recorderStartedAtNs);
1004
1805
  addRecorderTime(recording, process.hrtime.bigint() - recorderStartedAtNs);
1005
- state.recorder = { inlineWallTimeMs: roundedMilliseconds(recording.inlineRecorderTimeNs) };
1806
+ state.recorder = {
1807
+ inlineWallTimeMs: roundedMilliseconds2(recording.inlineRecorderTimeNs),
1808
+ derivation: derivationMetrics(recording)
1809
+ };
1006
1810
  return state;
1007
1811
  };
1008
1812
  const rememberError = (error, recording) => {
@@ -1018,25 +1822,38 @@ var createExecutionRecorder = (options) => {
1018
1822
  errorExecutions.set(error, recording);
1019
1823
  };
1020
1824
  const countDroppedEvents = (recording, droppedBefore) => {
1021
- stats.truncatedEvents += recording.events.dropped - droppedBefore;
1825
+ const dropped = recording.journal?.dropped ?? droppedBefore;
1826
+ stats.truncatedEvents += dropped - droppedBefore;
1022
1827
  };
1023
- const appendStandaloneEvent = (recording, event) => {
1828
+ const appendSuppressedPlaceholder = (recording, group) => {
1829
+ if (!recording.journal) return;
1024
1830
  if (!memoryBudget.reserveStandaloneEvent(recording.memory)) return;
1025
- const droppedBefore = recording.events.dropped;
1026
- recording.events.append(event);
1831
+ const droppedBefore = recording.journal.dropped;
1832
+ recording.journal.appendSuppressed(group);
1027
1833
  countDroppedEvents(recording, droppedBefore);
1028
1834
  };
1029
- const repeatedCallGroup = (recording, functionId, parentCallId) => {
1030
- let byParent = recording.repeatedCalls.get(functionId);
1031
- if (!byParent) {
1032
- byParent = /* @__PURE__ */ new Map();
1033
- recording.repeatedCalls.set(functionId, byParent);
1835
+ const repeatedCallGroup = (recording, plan, parentCallId) => {
1836
+ const slots = recording.repeatedCalls;
1837
+ let slot = slots[plan.runtimePlanId];
1838
+ if (!slot) {
1839
+ slot = {};
1840
+ slots[plan.runtimePlanId] = slot;
1841
+ }
1842
+ if (parentCallId === void 0) {
1843
+ if (!slot.root) slot.root = newRepeatedGroup(plan);
1844
+ return slot.root;
1845
+ }
1846
+ if (slot.lastParentGroup && slot.lastParentCallId === parentCallId) {
1847
+ return slot.lastParentGroup;
1034
1848
  }
1035
- let group = byParent.get(parentCallId);
1849
+ if (!slot.byParent) slot.byParent = /* @__PURE__ */ new Map();
1850
+ let group = slot.byParent.get(parentCallId);
1036
1851
  if (!group) {
1037
- group = { observedCalls: 0 };
1038
- byParent.set(parentCallId, group);
1852
+ group = newRepeatedGroup(plan);
1853
+ slot.byParent.set(parentCallId, group);
1039
1854
  }
1855
+ slot.lastParentCallId = parentCallId;
1856
+ slot.lastParentGroup = group;
1040
1857
  return group;
1041
1858
  };
1042
1859
  const serializedError = (recording, error, callId) => {
@@ -1052,60 +1869,83 @@ var createExecutionRecorder = (options) => {
1052
1869
  return {
1053
1870
  // Keep the bounded thrown value even after surrounding capture degrades. A partial
1054
1871
  // snapshot without the exception itself would not be useful for investigation.
1055
- value: safeSerialize(error, options),
1872
+ value: serializeAndCount(error).value,
1056
1873
  commit: () => {
1057
1874
  if (key) recording.capturedErrors.set(key, callId);
1058
1875
  }
1059
1876
  };
1060
1877
  };
1061
- const captureCompletedThrow = (recording, functionId, args, parentCallId, startedAtNs, startedAtMs, error) => {
1062
- if (!memoryBudget.reserveCall(recording.memory, functionId)) {
1878
+ const captureCompletedThrow = (recording, plan, args, parentCallId, startedAtNs, error) => {
1879
+ if (!recording.journal) {
1880
+ rememberError(error, recording);
1881
+ return;
1882
+ }
1883
+ if (!memoryBudget.reserveCall(recording.memory, plan.estimatedCallBytes)) {
1063
1884
  rememberError(error, recording);
1064
1885
  return;
1065
1886
  }
1066
1887
  const callId = ++recording.nextCallId;
1067
1888
  const capturedError = serializedError(recording, error, callId);
1068
- const enter = {
1069
- type: "function_enter",
1889
+ const capturedArgs = Array.from(args, (argument) => captureValue(recording, argument));
1890
+ const endedAtNs = process.hrtime.bigint();
1891
+ const droppedBefore = recording.journal.dropped;
1892
+ const retained = recording.journal.forceCompletedCall(
1893
+ plan.runtimePlanId,
1070
1894
  callId,
1071
- functionId,
1072
- timestampMs: startedAtMs,
1073
- args: Array.from(args, (argument) => captureValue(recording, argument)),
1074
- ...parentCallId === void 0 ? {} : { parentCallId }
1075
- };
1076
- const terminal = {
1077
- type: "function_throw",
1078
- callId,
1079
- timestampMs: elapsedMs(recording.startedAtNs),
1080
- durationMs: elapsedMs(startedAtNs),
1081
- error: capturedError.value
1082
- };
1083
- const droppedBefore = recording.events.dropped;
1084
- const retained = recording.events.forceCompletedCall(enter, terminal);
1085
- if (retained) capturedError.commit();
1895
+ parentCallId,
1896
+ startedAtNs - recording.startedAtNs,
1897
+ capturedArgs,
1898
+ KIND_THROW,
1899
+ endedAtNs - recording.startedAtNs,
1900
+ endedAtNs - startedAtNs,
1901
+ capturedError.value
1902
+ );
1903
+ if (retained) {
1904
+ stats.functionCallsCaptured++;
1905
+ capturedError.commit();
1906
+ }
1086
1907
  countDroppedEvents(recording, droppedBefore);
1087
1908
  rememberError(error, recording);
1088
1909
  };
1089
- const recordSuppressedCall = (recording, group, functionId, parentCallId, startedAtMs, durationMs) => {
1910
+ const recordSuppressedCall = (recording, group, parentCallId, startedAtNs, endedAtNs) => {
1090
1911
  stats.repeatedCallsSuppressed++;
1091
- const endedAtMs = startedAtMs + durationMs;
1092
- if (!group.summary) {
1093
- group.summary = {
1094
- type: "function_calls_suppressed",
1095
- functionId,
1096
- timestampMs: startedAtMs,
1097
- lastTimestampMs: endedAtMs,
1098
- suppressedCallCount: 1,
1099
- totalDurationMs: durationMs,
1100
- ...parentCallId === void 0 ? {} : { parentCallId }
1101
- };
1102
- appendStandaloneEvent(recording, group.summary);
1912
+ if (!group.suppressed) {
1913
+ group.suppressed = newSuppression(group, parentCallId, startedAtNs, endedAtNs);
1914
+ appendSuppressedPlaceholder(recording, group.suppressed);
1103
1915
  return;
1104
1916
  }
1105
- group.summary.lastTimestampMs = endedAtMs;
1106
- group.summary.suppressedCallCount++;
1107
- group.summary.totalDurationMs = Math.round((group.summary.totalDurationMs + durationMs) * 1e4) / 1e4;
1108
- if (!recording.events.contains(group.summary)) appendStandaloneEvent(recording, group.summary);
1917
+ const suppressed = group.suppressed;
1918
+ suppressed.count++;
1919
+ suppressed.lastTimestampNs = endedAtNs;
1920
+ suppressed.totalDurationNs += endedAtNs - startedAtNs;
1921
+ if (suppressed.retainedIndex < 0) {
1922
+ appendSuppressedPlaceholder(recording, suppressed);
1923
+ }
1924
+ };
1925
+ const releaseJournalIfIdle = (recording) => {
1926
+ if (!recording.finished || recording.hasError || recording.inFlightCalls > 0) return;
1927
+ if (!recording.journal) return;
1928
+ journalPool.release(recording.journal);
1929
+ recording.journal = void 0;
1930
+ };
1931
+ const acquireJournal = (recording) => {
1932
+ if (recording.journal) return recording.journal;
1933
+ try {
1934
+ const journal = journalPool.acquire();
1935
+ if (!memoryBudget.reserveStorage(recording.memory, journal.allocatedBytes)) {
1936
+ journalPool.release(journal);
1937
+ return void 0;
1938
+ }
1939
+ journal.onGrow = (deltaBytes) => memoryBudget.reserveStorage(recording.memory, deltaBytes);
1940
+ recording.journal = journal;
1941
+ return journal;
1942
+ } catch {
1943
+ return void 0;
1944
+ }
1945
+ };
1946
+ const settleInFlight = (recording) => {
1947
+ if (recording.inFlightCalls > 0) recording.inFlightCalls--;
1948
+ releaseJournalIfIdle(recording);
1109
1949
  };
1110
1950
  const finish = (store, metadata) => {
1111
1951
  const { recording } = store;
@@ -1115,11 +1955,22 @@ var createExecutionRecorder = (options) => {
1115
1955
  if (metadata) mergeExecutionMetadata(recording.execution, metadata);
1116
1956
  memoryBudget.release(recording.memory);
1117
1957
  stats.activeExecutions = Math.max(0, stats.activeExecutions - 1);
1118
- if (!recording.hasError) stats.successfulExecutionsDiscarded++;
1958
+ if (!recording.hasError) {
1959
+ stats.successfulExecutionsDiscarded++;
1960
+ releaseJournalIfIdle(recording);
1961
+ }
1119
1962
  recording.finishedAtNs = process.hrtime.bigint();
1120
1963
  addRecorderTime(recording, recording.finishedAtNs - recorderStartedAtNs);
1121
1964
  recordInlineTiming(recording);
1122
1965
  };
1966
+ const runStore = (store, callback) => {
1967
+ if (!phaseTimingEnabled) return storage.run(store, callback);
1968
+ const alsStartedAtNs = process.hrtime.bigint();
1969
+ return storage.run(store, () => {
1970
+ addPhase("alsNs", process.hrtime.bigint() - alsStartedAtNs);
1971
+ return callback();
1972
+ });
1973
+ };
1123
1974
  const createScope = (metadata) => {
1124
1975
  const recorderStartedAtNs = process.hrtime.bigint();
1125
1976
  try {
@@ -1132,15 +1983,19 @@ var createExecutionRecorder = (options) => {
1132
1983
  ...metadata,
1133
1984
  ...metadata.request ? { request: { ...metadata.request } } : {}
1134
1985
  },
1135
- events: new CallAwareEventBuffer(maxEventsPerExecution),
1136
- functions: /* @__PURE__ */ new Map(),
1137
- repeatedCalls: /* @__PURE__ */ new Map(),
1986
+ journal: void 0,
1987
+ repeatedCalls: [],
1138
1988
  capturedErrors: /* @__PURE__ */ new WeakMap(),
1139
1989
  nextCallId: 0,
1990
+ inFlightCalls: 0,
1140
1991
  finished: false,
1141
1992
  hasError: false,
1142
1993
  memory: memoryBudget.startExecution(),
1143
- partialSnapshotCounted: false
1994
+ partialSnapshotCounted: false,
1995
+ derivationRoots: [],
1996
+ derivationRootBytes: 0,
1997
+ sawAutomatic: false,
1998
+ sawManual: false
1144
1999
  };
1145
2000
  stats.activeExecutions++;
1146
2001
  stats.peakActiveExecutions = Math.max(stats.peakActiveExecutions, stats.activeExecutions);
@@ -1149,7 +2004,7 @@ var createExecutionRecorder = (options) => {
1149
2004
  return {
1150
2005
  run: (callback) => {
1151
2006
  const runStartedAtNs = process.hrtime.bigint();
1152
- return storage.run(store, () => {
2007
+ return runStore(store, () => {
1153
2008
  addRecorderTime(recording, process.hrtime.bigint() - runStartedAtNs);
1154
2009
  return callback();
1155
2010
  });
@@ -1202,140 +2057,295 @@ var createExecutionRecorder = (options) => {
1202
2057
  return void 0;
1203
2058
  }
1204
2059
  };
1205
- const runFunction = (input, args, callback) => {
2060
+ const runPreparedFunction = (plan, args, callback, omitValues) => {
1206
2061
  const store = storage.getStore();
1207
2062
  if (!store || store.recording.finished) return callback();
1208
- const definition = normalizeFunctionDefinition(input);
1209
- const functionId = definition.functionId;
2063
+ stats.functionCallsObserved++;
1210
2064
  const recorderStartedAtNs = process.hrtime.bigint();
1211
- if (store.suppressCapture || excludesSource(definition)) {
1212
- return storage.run({ ...store, suppressCapture: true }, () => {
2065
+ if (store.suppressCapture || plan.excluded) {
2066
+ stats.functionCallsExcluded++;
2067
+ return runStore({ ...store, suppressCapture: true }, () => {
1213
2068
  addRecorderTime(store.recording, process.hrtime.bigint() - recorderStartedAtNs);
1214
2069
  return callback();
1215
2070
  });
1216
2071
  }
1217
- store.recording.functions.set(functionId, definition);
2072
+ const recording = store.recording;
2073
+ recording.inFlightCalls++;
1218
2074
  const parentCallId = store.currentCallId;
1219
- const group = repeatedCallGroup(store.recording, functionId, parentCallId);
2075
+ const group = repeatedCallGroup(recording, plan, parentCallId);
1220
2076
  group.observedCalls++;
2077
+ const journal = acquireJournal(recording);
2078
+ if (group.observedCalls === 1) journal?.notePlan(plan.runtimePlanId);
1221
2079
  if (group.observedCalls > maxCapturedCallsPerFunction) {
1222
2080
  const startedAtNs2 = process.hrtime.bigint();
1223
- const startedAtMs = elapsedMs(store.recording.startedAtNs, startedAtNs2);
1224
- const captureSuccess = () => recordSuppressedCall(
1225
- store.recording,
1226
- group,
1227
- functionId,
1228
- parentCallId,
1229
- startedAtMs,
1230
- elapsedMs(startedAtNs2)
1231
- );
1232
- return observeRecordedCallback(
1233
- store.recording,
1234
- recorderStartedAtNs,
1235
- callback,
1236
- captureSuccess,
1237
- (error) => captureCompletedThrow(
1238
- store.recording,
1239
- functionId,
1240
- args,
1241
- parentCallId,
1242
- startedAtNs2,
1243
- startedAtMs,
1244
- error
2081
+ try {
2082
+ const result = callback();
2083
+ if (isPromiseLike(result)) {
2084
+ addRecorderTime(recording, startedAtNs2 - recorderStartedAtNs);
2085
+ return result.then(
2086
+ (value) => {
2087
+ const callbackEndedAtNs2 = process.hrtime.bigint();
2088
+ recordSuppressedCall(recording, group, parentCallId, startedAtNs2, callbackEndedAtNs2);
2089
+ addRecorderTime(recording, process.hrtime.bigint() - callbackEndedAtNs2);
2090
+ settleInFlight(recording);
2091
+ return value;
2092
+ },
2093
+ (error) => {
2094
+ const callbackEndedAtNs2 = process.hrtime.bigint();
2095
+ captureCompletedThrow(recording, plan, args, parentCallId, startedAtNs2, error);
2096
+ addRecorderTime(recording, process.hrtime.bigint() - callbackEndedAtNs2);
2097
+ settleInFlight(recording);
2098
+ throw error;
2099
+ }
2100
+ );
2101
+ }
2102
+ const callbackEndedAtNs = process.hrtime.bigint();
2103
+ recordSuppressedCall(recording, group, parentCallId, startedAtNs2, callbackEndedAtNs);
2104
+ addRecorderTime(recording, startedAtNs2 - recorderStartedAtNs);
2105
+ addRecorderTime(recording, process.hrtime.bigint() - callbackEndedAtNs);
2106
+ settleInFlight(recording);
2107
+ return result;
2108
+ } catch (error) {
2109
+ const callbackEndedAtNs = process.hrtime.bigint();
2110
+ captureCompletedThrow(recording, plan, args, parentCallId, startedAtNs2, error);
2111
+ addRecorderTime(recording, startedAtNs2 - recorderStartedAtNs);
2112
+ addRecorderTime(recording, process.hrtime.bigint() - callbackEndedAtNs);
2113
+ settleInFlight(recording);
2114
+ throw error;
2115
+ }
2116
+ }
2117
+ const startedAtNs = process.hrtime.bigint();
2118
+ if (!journal) {
2119
+ memoryBudget.reserveCall(recording.memory, plan.estimatedCallBytes);
2120
+ return runStore(
2121
+ store,
2122
+ () => observeRecordedCallback(
2123
+ recording,
2124
+ recorderStartedAtNs,
2125
+ callback,
2126
+ () => settleInFlight(recording),
2127
+ (error) => {
2128
+ rememberError(error, recording);
2129
+ settleInFlight(recording);
2130
+ }
1245
2131
  )
1246
2132
  );
1247
2133
  }
1248
- const startedAtNs = process.hrtime.bigint();
1249
- if (!store.recording.events.canStartCall()) {
1250
- const startedAtMs = elapsedMs(store.recording.startedAtNs, startedAtNs);
2134
+ if (!journal.canStartCall()) {
1251
2135
  const captureSuccess = () => {
1252
- const droppedBefore = store.recording.events.dropped;
1253
- store.recording.events.dropCall();
1254
- countDroppedEvents(store.recording, droppedBefore);
2136
+ const droppedBefore = recording.journal?.dropped ?? 0;
2137
+ recording.journal?.dropCall();
2138
+ countDroppedEvents(recording, droppedBefore);
2139
+ settleInFlight(recording);
1255
2140
  };
1256
- return storage.run(
2141
+ return runStore(
1257
2142
  store,
1258
2143
  () => observeRecordedCallback(
1259
- store.recording,
2144
+ recording,
1260
2145
  recorderStartedAtNs,
1261
2146
  callback,
1262
2147
  captureSuccess,
1263
- (error) => captureCompletedThrow(
1264
- store.recording,
1265
- functionId,
1266
- args,
1267
- parentCallId,
1268
- startedAtNs,
1269
- startedAtMs,
1270
- error
1271
- )
2148
+ (error) => {
2149
+ captureCompletedThrow(recording, plan, args, parentCallId, startedAtNs, error);
2150
+ settleInFlight(recording);
2151
+ }
1272
2152
  )
1273
2153
  );
1274
2154
  }
1275
- if (!memoryBudget.reserveCall(store.recording.memory, functionId)) {
2155
+ if (!memoryBudget.reserveCall(recording.memory, plan.estimatedCallBytes)) {
1276
2156
  return observeRecordedCallback(
1277
- store.recording,
2157
+ recording,
1278
2158
  recorderStartedAtNs,
1279
2159
  callback,
1280
- () => {
1281
- },
1282
- (error) => rememberError(error, store.recording)
2160
+ () => settleInFlight(recording),
2161
+ (error) => {
2162
+ rememberError(error, recording);
2163
+ settleInFlight(recording);
2164
+ }
1283
2165
  );
1284
2166
  }
1285
- const callId = ++store.recording.nextCallId;
1286
- const enter = {
1287
- type: "function_enter",
1288
- callId,
1289
- functionId,
1290
- timestampMs: elapsedMs(store.recording.startedAtNs),
1291
- args: Array.from(args, (argument) => captureValue(store.recording, argument)),
1292
- ...parentCallId === void 0 ? {} : { parentCallId }
1293
- };
1294
- store.recording.events.startCall(enter);
1295
- const callStore = { recording: store.recording, currentCallId: callId };
1296
- return storage.run(
2167
+ const callId = ++recording.nextCallId;
2168
+ const enterTimestampNs = process.hrtime.bigint() - recording.startedAtNs;
2169
+ const capturedArgs = omitValues ? Array.from(args, () => rasputinOmitted()) : Array.from(args, (argument) => captureValue(recording, argument));
2170
+ journal.startCall(plan.runtimePlanId, callId, parentCallId, enterTimestampNs, capturedArgs);
2171
+ const callStore = { recording, currentCallId: callId };
2172
+ return runStore(
1297
2173
  callStore,
1298
2174
  () => observeRecordedCallback(
1299
- store.recording,
2175
+ recording,
1300
2176
  recorderStartedAtNs,
1301
2177
  callback,
1302
2178
  (value) => {
1303
- store.recording.events.finishCall({
1304
- type: "function_exit",
1305
- callId,
1306
- timestampMs: elapsedMs(store.recording.startedAtNs),
1307
- durationMs: elapsedMs(startedAtNs),
1308
- returnValue: captureValue(store.recording, value)
1309
- });
2179
+ if (recording.journal) {
2180
+ const timestampNs = process.hrtime.bigint() - recording.startedAtNs;
2181
+ const durationNs = process.hrtime.bigint() - startedAtNs;
2182
+ const returnValue = omitValues ? rasputinOmitted() : captureValue(recording, value);
2183
+ recording.journal.finishCall(KIND_EXIT, callId, timestampNs, durationNs, returnValue);
2184
+ stats.functionCallsCaptured++;
2185
+ }
2186
+ settleInFlight(recording);
1310
2187
  },
1311
2188
  (error) => {
1312
- const capturedError = serializedError(store.recording, error, callId);
1313
- store.recording.events.finishCall({
1314
- type: "function_throw",
2189
+ if (!recording.journal) {
2190
+ rememberError(error, recording);
2191
+ settleInFlight(recording);
2192
+ return;
2193
+ }
2194
+ const capturedError = serializedError(recording, error, callId);
2195
+ recording.journal.finishCall(
2196
+ KIND_THROW,
1315
2197
  callId,
1316
- timestampMs: elapsedMs(store.recording.startedAtNs),
1317
- durationMs: elapsedMs(startedAtNs),
1318
- error: capturedError.value
1319
- });
2198
+ process.hrtime.bigint() - recording.startedAtNs,
2199
+ process.hrtime.bigint() - startedAtNs,
2200
+ capturedError.value
2201
+ );
2202
+ stats.functionCallsCaptured++;
1320
2203
  capturedError.commit();
1321
- rememberError(error, store.recording);
2204
+ rememberError(error, recording);
2205
+ settleInFlight(recording);
1322
2206
  }
1323
2207
  )
1324
2208
  );
1325
2209
  };
2210
+ const runFunction = (input, args, callback) => {
2211
+ const store = storage.getStore();
2212
+ if (store && !store.recording.finished) store.recording.sawManual = true;
2213
+ return runPreparedFunction(preparePlan(normalizeFunctionDefinition(input)), args, callback);
2214
+ };
2215
+ const runFunctionPlans = (plans, planIndex, args, callback, omitValues) => {
2216
+ const store = storage.getStore();
2217
+ const plan = preparedPlansFor(plans)[planIndex];
2218
+ if (store && !store.recording.finished) store.recording.sawAutomatic = true;
2219
+ return plan ? runPreparedFunction(plan, args, callback, omitValues) : callback();
2220
+ };
2221
+ const prepareFunctionPlans = (plans) => preparedPlansFor(plans);
1326
2222
  function trace(input, fn) {
1327
2223
  const definition = typeof input === "string" ? manualFunctionDefinition(input) : { ...input, functionId: runtimeFunctionId2(input) };
2224
+ const plan = preparePlan(definition);
1328
2225
  return function traced(...args) {
1329
- return runFunction(definition, args, () => fn.apply(this, args));
2226
+ const store = storage.getStore();
2227
+ if (store && !store.recording.finished) store.recording.sawManual = true;
2228
+ return runPreparedFunction(plan, args, () => fn.apply(this, args));
1330
2229
  };
1331
2230
  }
2231
+ const captureDerivationRoot = (rootId, value) => {
2232
+ const store = storage.getStore();
2233
+ if (!store || store.recording.finished || store.suppressCapture) return value;
2234
+ const recording = store.recording;
2235
+ if (recording.memory.mode !== "full") return value;
2236
+ if (recording.derivationRoots.length >= maxEventsPerExecution) return value;
2237
+ const recorderStartedAtNs = process.hrtime.bigint();
2238
+ try {
2239
+ const serialized = captureValue(recording, value);
2240
+ if (recording.memory.mode !== "full") return value;
2241
+ if (!memoryBudget.reserveStorage(recording.memory, DERIVATION_ROOT_RECORD_BYTES)) {
2242
+ return value;
2243
+ }
2244
+ const completeness = capturedValueCompleteness(serialized);
2245
+ const bytes = jsonLiteralUtf8Bytes(serialized);
2246
+ recording.derivationRoots.push({
2247
+ rootId,
2248
+ callId: store.currentCallId ?? 0,
2249
+ value: serialized,
2250
+ completeness
2251
+ });
2252
+ recording.derivationRootBytes += bytes;
2253
+ stats.derivationRootsCaptured++;
2254
+ stats.derivationRootBytes += bytes;
2255
+ stats.derivationFallbackExpressions++;
2256
+ return value;
2257
+ } finally {
2258
+ addRecorderTime(recording, process.hrtime.bigint() - recorderStartedAtNs);
2259
+ }
2260
+ };
1332
2261
  return {
1333
2262
  createScope,
1334
2263
  run,
1335
2264
  getErrorState,
1336
2265
  runFunction,
2266
+ runFunctionPlans,
2267
+ prepareFunctionPlans,
2268
+ runPreparedFunction,
1337
2269
  trace,
1338
- getStats: () => ({ ...stats, ...memoryBudget.getStats() })
2270
+ captureDerivationRoot,
2271
+ getStats: () => {
2272
+ const manifest = snapshotInstrumentationManifest();
2273
+ const plans = manifest?.derivationPlans ?? [];
2274
+ return {
2275
+ ...stats,
2276
+ ...memoryBudget.getStats(),
2277
+ ...journalPool.stats(),
2278
+ derivationCandidateFunctions: plans.filter((plan) => !plan.fallbackReason).length,
2279
+ derivationFallbackFunctions: plans.filter((plan) => plan.fallbackReason).length,
2280
+ derivationFallbackReasons: { ...stats.derivationFallbackReasons },
2281
+ ...phases ? {
2282
+ phaseAlsNs: Number(phases.alsNs),
2283
+ phaseMaterializeNs: Number(phases.materializeNs)
2284
+ } : {}
2285
+ };
2286
+ }
2287
+ };
2288
+ };
2289
+
2290
+ // src/execution-recorder/automatic-runtime.ts
2291
+ var AUTOMATIC_RUNTIME_SYMBOL = "rasputin.execution.runtime.v2";
2292
+ var runtimeSymbol = Symbol.for(AUTOMATIC_RUNTIME_SYMBOL);
2293
+ var passthroughCapture = (_rootId, value) => value;
2294
+ var passthrough = (_planIndex, _args, callback) => callback();
2295
+ var bindPassthrough = (module) => {
2296
+ module.cell.run = passthrough;
2297
+ };
2298
+ var isRegistry = (value) => Array.isArray(Reflect.get(value, "runtimes")) && Array.isArray(Reflect.get(value, "modules")) && typeof Reflect.get(value, "register") === "function";
2299
+ var registerModule = (registry, plans) => {
2300
+ const cell = { run: passthrough };
2301
+ const module = { plans, cell };
2302
+ registry.modules.push(module);
2303
+ registry.runtimes.at(-1)?.bind(module);
2304
+ return (planIndex, args, callback, omitValues) => cell.run(planIndex, args, callback, omitValues);
2305
+ };
2306
+ var createRegistry = () => {
2307
+ const registry = {
2308
+ runtimes: [],
2309
+ modules: [],
2310
+ captureRoot: passthroughCapture,
2311
+ register(plans) {
2312
+ return registerModule(registry, plans);
2313
+ }
2314
+ };
2315
+ return registry;
2316
+ };
2317
+ var getAutomaticRuntimeRegistry = () => {
2318
+ const existing = Reflect.get(globalThis, runtimeSymbol);
2319
+ if (existing && typeof existing === "object" && isRegistry(existing)) {
2320
+ if (typeof existing.captureRoot !== "function") {
2321
+ existing.captureRoot = passthroughCapture;
2322
+ }
2323
+ return existing;
2324
+ }
2325
+ const registry = createRegistry();
2326
+ Reflect.set(globalThis, runtimeSymbol, registry);
2327
+ return registry;
2328
+ };
2329
+ var installAutomaticExecutionRuntime = (execution) => {
2330
+ const registry = getAutomaticRuntimeRegistry();
2331
+ const runtime = {
2332
+ bind(module) {
2333
+ module.cell.run = (planIndex, args, callback, omitValues) => execution.runFunctionPlans(module.plans, planIndex, args, callback, omitValues);
2334
+ },
2335
+ captureRoot: (rootId, value) => execution.captureDerivationRoot(rootId, value)
2336
+ };
2337
+ registry.runtimes.push(runtime);
2338
+ registry.captureRoot = runtime.captureRoot;
2339
+ for (const module of registry.modules) runtime.bind(module);
2340
+ return () => {
2341
+ const index = registry.runtimes.lastIndexOf(runtime);
2342
+ if (index >= 0) registry.runtimes.splice(index, 1);
2343
+ const current = registry.runtimes.at(-1);
2344
+ registry.captureRoot = current?.captureRoot ?? passthroughCapture;
2345
+ for (const module of registry.modules) {
2346
+ if (current) current.bind(module);
2347
+ else bindPassthrough(module);
2348
+ }
1339
2349
  };
1340
2350
  };
1341
2351
 
@@ -1388,6 +2398,7 @@ var installGlobalHandlers = (client, options = {}) => {
1388
2398
  };
1389
2399
 
1390
2400
  // src/rasputin-init.ts
2401
+ import { join } from "node:path";
1391
2402
  import {
1392
2403
  createClient,
1393
2404
  isClientEnabled
@@ -1395,7 +2406,7 @@ import {
1395
2406
 
1396
2407
  // src/sdk-meta.ts
1397
2408
  var SDK_NAME = "@rasputin-ai/node";
1398
- var SDK_VERSION = "0.4.1";
2409
+ var SDK_VERSION = "0.5.0-alpha.10";
1399
2410
 
1400
2411
  // src/rasputin-init.ts
1401
2412
  var withExecution = (client, execution, installRuntime, manifest) => {
@@ -1436,6 +2447,9 @@ var RasputinInit = (options) => {
1436
2447
  ...options.executionRecorder,
1437
2448
  enabled: recorderEnabled
1438
2449
  });
2450
+ loadInstrumentationManifestIfPresent(
2451
+ options.instrumentationManifestPath ?? process.env.RASPUTIN_INSTRUMENTATION_MANIFEST ?? join(process.cwd(), ".rasputin", "instrumentation-manifest.json")
2452
+ );
1439
2453
  const wrappedClient = withExecution(client, execution, recorderEnabled, {
1440
2454
  projectApiKey: options.projectApiKey,
1441
2455
  release: options.release,