@rasputin-ai/node 0.5.0-alpha.5 → 0.5.0-alpha.7

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 (37) hide show
  1. package/dist/auto-instrumentation/instrument-compiled-esm.d.ts +2 -0
  2. package/dist/auto-instrumentation/instrument-compiled-esm.d.ts.map +1 -1
  3. package/dist/auto-instrumentation/instrumentation-manifest-delta.d.ts +2 -1
  4. package/dist/auto-instrumentation/instrumentation-manifest-delta.d.ts.map +1 -1
  5. package/dist/auto-instrumentation/instrumentation-manifest-registry.d.ts +1 -1
  6. package/dist/auto-instrumentation/instrumentation-manifest-registry.d.ts.map +1 -1
  7. package/dist/auto-instrumentation/transform-source.d.ts +6 -0
  8. package/dist/auto-instrumentation/transform-source.d.ts.map +1 -1
  9. package/dist/derivation/analyze-function.d.ts +40 -0
  10. package/dist/derivation/analyze-function.d.ts.map +1 -0
  11. package/dist/derivation/build-dependency-graph.d.ts +21 -0
  12. package/dist/derivation/build-dependency-graph.d.ts.map +1 -0
  13. package/dist/derivation/derivation-ir.d.ts +33 -0
  14. package/dist/derivation/derivation-ir.d.ts.map +1 -0
  15. package/dist/derivation/derivation-proof.d.ts +14 -0
  16. package/dist/derivation/derivation-proof.d.ts.map +1 -0
  17. package/dist/derivation/emit-capture-plan.d.ts +8 -0
  18. package/dist/derivation/emit-capture-plan.d.ts.map +1 -0
  19. package/dist/execution-recorder/automatic-runtime.d.ts +3 -1
  20. package/dist/execution-recorder/automatic-runtime.d.ts.map +1 -1
  21. package/dist/execution-recorder/execution-journal.d.ts +84 -0
  22. package/dist/execution-recorder/execution-journal.d.ts.map +1 -0
  23. package/dist/execution-recorder/execution-recorder-types.d.ts +19 -2
  24. package/dist/execution-recorder/execution-recorder-types.d.ts.map +1 -1
  25. package/dist/execution-recorder/execution-recorder.d.ts.map +1 -1
  26. package/dist/execution-recorder/recorder-memory-budget.d.ts +1 -0
  27. package/dist/execution-recorder/recorder-memory-budget.d.ts.map +1 -1
  28. package/dist/execution-recorder/safe-serialize.d.ts +1 -0
  29. package/dist/execution-recorder/safe-serialize.d.ts.map +1 -1
  30. package/dist/index.js +1058 -317
  31. package/dist/instrument/build.js +761 -71
  32. package/dist/instrument/bun.js +742 -66
  33. package/dist/instrument/node.js +742 -66
  34. package/dist/sdk-meta.d.ts +1 -1
  35. package/package.json +2 -2
  36. package/dist/execution-recorder/call-aware-event-buffer.d.ts +0 -37
  37. package/dist/execution-recorder/call-aware-event-buffer.d.ts.map +0 -1
package/dist/index.js CHANGED
@@ -133,7 +133,7 @@ var definitionFromEncodedPlan = (plan) => {
133
133
  var estimatedCallBytesFor = (functionId) => EVENT_PAIR_BASE_ESTIMATED_BYTES + Buffer.byteLength(functionId, "utf8") * 2;
134
134
 
135
135
  // src/auto-instrumentation/instrumentation-manifest-registry.ts
136
- var INSTRUMENTATION_MANIFEST_SYMBOL = "rasputin.instrumentation.manifest.v2";
136
+ var INSTRUMENTATION_MANIFEST_SYMBOL = "rasputin.instrumentation.manifest.v3";
137
137
  var registrySymbol = Symbol.for(INSTRUMENTATION_MANIFEST_SYMBOL);
138
138
  var getRegistry = () => {
139
139
  const existing = Reflect.get(globalThis, registrySymbol);
@@ -190,6 +190,7 @@ var snapshotInstrumentationManifest = (options = {}) => {
190
190
  const functions = [];
191
191
  const callSites = [];
192
192
  const destructures = [];
193
+ const derivationPlans = [];
193
194
  for (const delta of getRegistry().files.values()) {
194
195
  for (const fn of delta.functions) {
195
196
  const definition = definitionFromEncodedPlan(fn.plan);
@@ -241,6 +242,7 @@ var snapshotInstrumentationManifest = (options = {}) => {
241
242
  binding: destructure.binding
242
243
  });
243
244
  }
245
+ derivationPlans.push(...delta.derivationPlans ?? []);
244
246
  }
245
247
  const registry = getRegistry();
246
248
  if (registry.prebuilt && registry.files.size === 0) {
@@ -268,7 +270,8 @@ var snapshotInstrumentationManifest = (options = {}) => {
268
270
  } : {},
269
271
  functions,
270
272
  callSites,
271
- destructures
273
+ destructures,
274
+ derivationPlans
272
275
  };
273
276
  };
274
277
  var markInstrumentationManifestUploaded = (generation) => {
@@ -384,85 +387,392 @@ var scheduleInstrumentationManifestUpload = (options) => {
384
387
 
385
388
  // src/execution-recorder/execution-recorder.ts
386
389
  import { AsyncLocalStorage } from "node:async_hooks";
387
- import { runtimeFunctionId as runtimeFunctionId2 } from "@rasputin-ai/core";
390
+ import {
391
+ capturedValueCompleteness,
392
+ hashInstrumentationManifest,
393
+ rasputinOmitted,
394
+ runtimeFunctionId as runtimeFunctionId2
395
+ } from "@rasputin-ai/core";
388
396
 
389
- // src/execution-recorder/call-aware-event-buffer.ts
390
- var isSuppressionPlaceholder = (item) => "kind" in item && item.kind === "suppressed";
391
- var CallAwareEventBuffer = class {
397
+ // src/execution-recorder/execution-journal.ts
398
+ var KIND_ENTER = 1;
399
+ var KIND_EXIT = 2;
400
+ var KIND_THROW = 3;
401
+ var KIND_SUPPRESSED = 4;
402
+ var NO_PARENT = 4294967295;
403
+ var NO_SUPPRESSION = 4294967295;
404
+ var MAX_POOLED_JOURNALS = 8;
405
+ var MAX_POOLED_JOURNAL_CAPACITY = 4096;
406
+ var INITIAL_JOURNAL_STORAGE = 32;
407
+ var BYTES_PER_EVENT = Uint8Array.BYTES_PER_ELEMENT + Uint32Array.BYTES_PER_ELEMENT * 4 + BigInt64Array.BYTES_PER_ELEMENT * 2;
408
+ var DEFAULT_MAX_EVENTS_PER_EXECUTION = 500;
409
+ var MAX_JOURNAL_EVENTS = 16384;
410
+ var elapsedMs = (startedAtNs, endedAtNs) => Number(endedAtNs - startedAtNs) / 1e6;
411
+ var roundedMilliseconds = (nanoseconds) => Math.round(Number(nanoseconds) / 1e6 * 1e4) / 1e4;
412
+ var relativeMs = (relativeNs) => Number(relativeNs) / 1e6;
413
+ var sanitizeJournalCapacity = (value) => {
414
+ const requested = value ?? DEFAULT_MAX_EVENTS_PER_EXECUTION;
415
+ if (!Number.isFinite(requested)) return DEFAULT_MAX_EVENTS_PER_EXECUTION;
416
+ return Math.min(MAX_JOURNAL_EVENTS, Math.max(2, Math.floor(requested)));
417
+ };
418
+ var ExecutionJournal = class {
419
+ dropped = 0;
420
+ allocatedBytes = 0;
421
+ onGrow;
422
+ length = 0;
423
+ kind = new Uint8Array(0);
424
+ planId = new Uint32Array(0);
425
+ callId = new Uint32Array(0);
426
+ parentCallId = new Uint32Array(0);
427
+ timestampNs = new BigInt64Array(0);
428
+ durationNs = new BigInt64Array(0);
429
+ suppressionIndex = new Uint32Array(0);
430
+ payloads = [];
431
+ suppressions = [];
432
+ openCallIds = /* @__PURE__ */ new Set();
433
+ notedPlanIds = [];
434
+ notedPlanSet = /* @__PURE__ */ new Set();
435
+ maxCapacity;
392
436
  constructor(capacity) {
393
- this.capacity = capacity;
437
+ this.maxCapacity = sanitizeJournalCapacity(capacity);
438
+ this.reallocate(Math.min(this.maxCapacity, INITIAL_JOURNAL_STORAGE));
439
+ }
440
+ get capacity() {
441
+ return this.maxCapacity;
442
+ }
443
+ get hasOpenCalls() {
444
+ return this.openCallIds.size > 0;
445
+ }
446
+ get eventCount() {
447
+ return this.length;
448
+ }
449
+ get payloadSlotCount() {
450
+ return this.payloads.length;
394
451
  }
395
- items = [];
396
- openCallIds = /* @__PURE__ */ new Set();
397
- dropped = 0;
398
452
  canStartCall() {
399
- return this.items.length + this.openCallIds.size + 2 <= this.capacity;
453
+ if (this.length + this.openCallIds.size + 2 > this.maxCapacity) return false;
454
+ return this.ensureCapacity(this.length + 2);
400
455
  }
401
- startCall(event) {
402
- this.items.push(event);
403
- this.openCallIds.add(event.callId);
456
+ notePlan(runtimePlanId) {
457
+ if (this.notedPlanSet.has(runtimePlanId)) return;
458
+ this.notedPlanSet.add(runtimePlanId);
459
+ this.notedPlanIds.push(runtimePlanId);
404
460
  }
405
- finishCall(event) {
406
- if (event.type !== "function_exit" && event.type !== "function_throw") return;
407
- if (!this.openCallIds.delete(event.callId)) return;
408
- this.items.push(event);
461
+ registerSuppression(group) {
462
+ if (group.storageIndex >= 0) return group;
463
+ group.storageIndex = this.suppressions.length;
464
+ this.suppressions.push(group);
465
+ return group;
466
+ }
467
+ startCall(runtimePlanId, callId, parentCallId, timestampNs, args) {
468
+ if (!this.write(
469
+ KIND_ENTER,
470
+ runtimePlanId,
471
+ callId,
472
+ parentCallId,
473
+ timestampNs,
474
+ 0n,
475
+ args,
476
+ NO_SUPPRESSION
477
+ )) {
478
+ return;
479
+ }
480
+ this.openCallIds.add(callId);
481
+ }
482
+ finishCall(kind, callId, timestampNs, durationNs, payload) {
483
+ if (!this.openCallIds.delete(callId)) return;
484
+ this.write(kind, 0, callId, void 0, timestampNs, durationNs, payload, NO_SUPPRESSION);
409
485
  }
410
486
  dropCall() {
411
487
  this.dropped += 2;
412
488
  }
413
489
  appendSuppressed(group) {
414
- if (this.items.length + this.openCallIds.size + 1 > this.capacity) {
490
+ if (this.length + this.openCallIds.size + 1 > this.maxCapacity) {
491
+ this.dropped++;
492
+ return false;
493
+ }
494
+ this.registerSuppression(group);
495
+ group.retainedIndex = this.length;
496
+ if (!this.write(
497
+ KIND_SUPPRESSED,
498
+ group.runtimePlanId,
499
+ 0,
500
+ group.parentCallId,
501
+ 0n,
502
+ 0n,
503
+ void 0,
504
+ group.storageIndex
505
+ )) {
506
+ group.retainedIndex = -1;
415
507
  this.dropped++;
416
508
  return false;
417
509
  }
418
- const placeholder = { kind: "suppressed", group };
419
- this.items.push(placeholder);
420
- group.retainedSlot = placeholder;
421
510
  return true;
422
511
  }
423
- forceCompletedCall(enter, terminal) {
424
- while (this.items.length + this.openCallIds.size + 2 > this.capacity) {
512
+ forceCompletedCall(runtimePlanId, callId, parentCallId, enterTimestampNs, args, terminalKind, terminalTimestampNs, durationNs, payload) {
513
+ while (this.length + this.openCallIds.size + 2 > this.maxCapacity) {
425
514
  if (!this.evictCompletedLeafOrSummary()) {
426
515
  this.dropCall();
427
516
  return false;
428
517
  }
429
518
  }
430
- this.items.push(enter, terminal);
519
+ if (!this.ensureCapacity(this.length + 2)) {
520
+ this.dropCall();
521
+ return false;
522
+ }
523
+ this.write(
524
+ KIND_ENTER,
525
+ runtimePlanId,
526
+ callId,
527
+ parentCallId,
528
+ enterTimestampNs,
529
+ 0n,
530
+ args,
531
+ NO_SUPPRESSION
532
+ );
533
+ this.write(
534
+ terminalKind,
535
+ 0,
536
+ callId,
537
+ void 0,
538
+ terminalTimestampNs,
539
+ durationNs,
540
+ payload,
541
+ NO_SUPPRESSION
542
+ );
431
543
  return true;
432
544
  }
433
- values() {
434
- return [...this.items];
545
+ materialize(plansByRuntimeId, executionStartedAtNs) {
546
+ const events = [];
547
+ const referencedFunctionIds = /* @__PURE__ */ new Set();
548
+ for (let index = 0; index < this.length; index++) {
549
+ const event = this.eventAt(index, plansByRuntimeId, executionStartedAtNs);
550
+ if (!event) continue;
551
+ events.push(event);
552
+ if (event.type === "function_enter" || event.type === "function_calls_suppressed") {
553
+ referencedFunctionIds.add(event.functionId);
554
+ }
555
+ }
556
+ const functions = [];
557
+ const seenFunctionIds = /* @__PURE__ */ new Set();
558
+ for (const runtimePlanId of this.notedPlanIds) {
559
+ const definition = plansByRuntimeId[runtimePlanId]?.definition;
560
+ if (!definition || !referencedFunctionIds.has(definition.functionId)) continue;
561
+ if (seenFunctionIds.has(definition.functionId)) continue;
562
+ seenFunctionIds.add(definition.functionId);
563
+ functions.push(definition);
564
+ }
565
+ return { events, functions };
566
+ }
567
+ clear() {
568
+ this.length = 0;
569
+ this.dropped = 0;
570
+ this.onGrow = void 0;
571
+ this.openCallIds.clear();
572
+ this.notedPlanIds.length = 0;
573
+ this.notedPlanSet.clear();
574
+ for (let index = 0; index < this.payloads.length; index++) this.payloads[index] = void 0;
575
+ this.payloads.length = 0;
576
+ for (const group of this.suppressions) {
577
+ group.retainedIndex = -1;
578
+ group.storageIndex = -1;
579
+ }
580
+ this.suppressions.length = 0;
581
+ }
582
+ eventAt(index, plansByRuntimeId, executionStartedAtNs) {
583
+ const kind = this.kind[index];
584
+ if (kind === KIND_ENTER) {
585
+ const functionId = plansByRuntimeId[this.planId[index] ?? 0]?.functionId;
586
+ if (!functionId) return void 0;
587
+ const parentCallId = this.parentCallId[index] ?? NO_PARENT;
588
+ const payload = this.payloads[index];
589
+ return {
590
+ type: "function_enter",
591
+ callId: this.callId[index] ?? 0,
592
+ functionId,
593
+ timestampMs: relativeMs(this.timestampNs[index] ?? 0n),
594
+ args: Array.isArray(payload) ? payload : [],
595
+ ...parentCallId === NO_PARENT ? {} : { parentCallId }
596
+ };
597
+ }
598
+ if (kind === KIND_EXIT) {
599
+ return {
600
+ type: "function_exit",
601
+ callId: this.callId[index] ?? 0,
602
+ timestampMs: relativeMs(this.timestampNs[index] ?? 0n),
603
+ durationMs: relativeMs(this.durationNs[index] ?? 0n),
604
+ returnValue: this.payloads[index]
605
+ };
606
+ }
607
+ if (kind === KIND_THROW) {
608
+ return {
609
+ type: "function_throw",
610
+ callId: this.callId[index] ?? 0,
611
+ timestampMs: relativeMs(this.timestampNs[index] ?? 0n),
612
+ durationMs: relativeMs(this.durationNs[index] ?? 0n),
613
+ error: this.payloads[index]
614
+ };
615
+ }
616
+ if (kind === KIND_SUPPRESSED) {
617
+ const group = this.suppressions[this.suppressionIndex[index] ?? 0];
618
+ if (!group) return void 0;
619
+ return {
620
+ type: "function_calls_suppressed",
621
+ functionId: group.functionId,
622
+ timestampMs: elapsedMs(executionStartedAtNs, group.firstTimestampNs),
623
+ lastTimestampMs: elapsedMs(executionStartedAtNs, group.lastTimestampNs),
624
+ suppressedCallCount: group.count,
625
+ totalDurationMs: roundedMilliseconds(group.totalDurationNs),
626
+ ...group.parentCallId === void 0 ? {} : { parentCallId: group.parentCallId }
627
+ };
628
+ }
629
+ return void 0;
630
+ }
631
+ write(kind, runtimePlanId, callId, parentCallId, timestampNs, durationNs, payload, suppressionIndex) {
632
+ if (!this.ensureCapacity(this.length + 1)) return false;
633
+ const index = this.length;
634
+ this.kind[index] = kind;
635
+ this.planId[index] = runtimePlanId;
636
+ this.callId[index] = callId;
637
+ this.parentCallId[index] = parentCallId === void 0 ? NO_PARENT : parentCallId;
638
+ this.timestampNs[index] = timestampNs;
639
+ this.durationNs[index] = durationNs;
640
+ this.payloads[index] = payload;
641
+ this.suppressionIndex[index] = suppressionIndex;
642
+ this.length++;
643
+ return true;
644
+ }
645
+ ensureCapacity(needed) {
646
+ if (needed <= this.kind.length) return true;
647
+ if (needed > this.maxCapacity) return false;
648
+ let next = this.kind.length === 0 ? Math.min(INITIAL_JOURNAL_STORAGE, this.maxCapacity) : this.kind.length;
649
+ while (next < needed) {
650
+ const grown = Math.min(this.maxCapacity, next * 2);
651
+ if (grown <= next) return false;
652
+ next = grown;
653
+ }
654
+ const extraBytes = (next - this.kind.length) * BYTES_PER_EVENT;
655
+ if (this.onGrow && extraBytes > 0 && !this.onGrow(extraBytes)) return false;
656
+ this.reallocate(next);
657
+ return true;
658
+ }
659
+ reallocate(size) {
660
+ const nextKind = new Uint8Array(size);
661
+ const nextPlanId = new Uint32Array(size);
662
+ const nextCallId = new Uint32Array(size);
663
+ const nextParentCallId = new Uint32Array(size);
664
+ const nextTimestampNs = new BigInt64Array(size);
665
+ const nextDurationNs = new BigInt64Array(size);
666
+ const nextSuppressionIndex = new Uint32Array(size);
667
+ if (this.length > 0) {
668
+ nextKind.set(this.kind.subarray(0, this.length));
669
+ nextPlanId.set(this.planId.subarray(0, this.length));
670
+ nextCallId.set(this.callId.subarray(0, this.length));
671
+ nextParentCallId.set(this.parentCallId.subarray(0, this.length));
672
+ nextTimestampNs.set(this.timestampNs.subarray(0, this.length));
673
+ nextDurationNs.set(this.durationNs.subarray(0, this.length));
674
+ nextSuppressionIndex.set(this.suppressionIndex.subarray(0, this.length));
675
+ }
676
+ this.kind = nextKind;
677
+ this.planId = nextPlanId;
678
+ this.callId = nextCallId;
679
+ this.parentCallId = nextParentCallId;
680
+ this.timestampNs = nextTimestampNs;
681
+ this.durationNs = nextDurationNs;
682
+ this.suppressionIndex = nextSuppressionIndex;
683
+ this.allocatedBytes = size * BYTES_PER_EVENT;
435
684
  }
436
685
  evictCompletedLeafOrSummary() {
437
- const summaryIndex = this.items.findIndex((item) => isSuppressionPlaceholder(item));
438
- if (summaryIndex >= 0) {
439
- const summary = this.items[summaryIndex];
440
- if (summary && isSuppressionPlaceholder(summary)) summary.group.retainedSlot = void 0;
441
- this.items.splice(summaryIndex, 1);
686
+ for (let index = 0; index < this.length; index++) {
687
+ if (this.kind[index] !== KIND_SUPPRESSED) continue;
688
+ const group = this.suppressions[this.suppressionIndex[index] ?? 0];
689
+ if (group) group.retainedIndex = -1;
690
+ this.removeAt(index);
442
691
  this.dropped++;
443
692
  return true;
444
693
  }
445
694
  const parentCallIds = /* @__PURE__ */ new Set();
446
- for (const item of this.items) {
447
- const parentCallId = isSuppressionPlaceholder(item) ? item.group.parentCallId : item.type === "function_enter" ? item.parentCallId : void 0;
448
- if (parentCallId !== void 0) parentCallIds.add(parentCallId);
695
+ for (let index = 0; index < this.length; index++) {
696
+ const kind = this.kind[index];
697
+ if (kind !== KIND_ENTER && kind !== KIND_SUPPRESSED) continue;
698
+ const parentCallId = this.parentCallId[index] ?? NO_PARENT;
699
+ if (parentCallId !== NO_PARENT) parentCallIds.add(parentCallId);
449
700
  }
450
- for (let index = 0; index < this.items.length; index++) {
451
- const event = this.items[index];
452
- if (!event || isSuppressionPlaceholder(event) || event.type !== "function_enter" || this.openCallIds.has(event.callId) || parentCallIds.has(event.callId)) {
453
- continue;
701
+ for (let index = 0; index < this.length; index++) {
702
+ if (this.kind[index] !== KIND_ENTER) continue;
703
+ const eventCallId = this.callId[index] ?? 0;
704
+ if (this.openCallIds.has(eventCallId) || parentCallIds.has(eventCallId)) continue;
705
+ let terminalIndex = -1;
706
+ for (let candidate = 0; candidate < this.length; candidate++) {
707
+ const kind = this.kind[candidate];
708
+ if ((kind === KIND_EXIT || kind === KIND_THROW) && this.callId[candidate] === eventCallId) {
709
+ terminalIndex = candidate;
710
+ break;
711
+ }
454
712
  }
455
- const terminalIndex = this.items.findIndex(
456
- (candidate) => !isSuppressionPlaceholder(candidate) && (candidate.type === "function_exit" || candidate.type === "function_throw") && candidate.callId === event.callId
457
- );
458
713
  if (terminalIndex < 0) continue;
459
- this.items.splice(Math.max(index, terminalIndex), 1);
460
- this.items.splice(Math.min(index, terminalIndex), 1);
714
+ const later = Math.max(index, terminalIndex);
715
+ const earlier = Math.min(index, terminalIndex);
716
+ this.removeAt(later);
717
+ this.removeAt(earlier);
461
718
  this.dropped += 2;
462
719
  return true;
463
720
  }
464
721
  return false;
465
722
  }
723
+ removeAt(index) {
724
+ this.payloads[index] = void 0;
725
+ const next = index + 1;
726
+ if (next < this.length) {
727
+ this.kind.copyWithin(index, next, this.length);
728
+ this.planId.copyWithin(index, next, this.length);
729
+ this.callId.copyWithin(index, next, this.length);
730
+ this.parentCallId.copyWithin(index, next, this.length);
731
+ this.timestampNs.copyWithin(index, next, this.length);
732
+ this.durationNs.copyWithin(index, next, this.length);
733
+ this.suppressionIndex.copyWithin(index, next, this.length);
734
+ this.payloads.copyWithin(index, next, this.length);
735
+ }
736
+ this.payloads[this.length - 1] = void 0;
737
+ this.length--;
738
+ this.payloads.length = this.length;
739
+ for (const group of this.suppressions) {
740
+ if (group.retainedIndex > index) group.retainedIndex--;
741
+ }
742
+ }
743
+ };
744
+ var ExecutionJournalPool = class {
745
+ constructor(capacity, maxPooled = MAX_POOLED_JOURNALS) {
746
+ this.capacity = capacity;
747
+ this.maxPooled = maxPooled;
748
+ }
749
+ hits = 0;
750
+ misses = 0;
751
+ available = [];
752
+ acquire() {
753
+ const pooled = this.available.pop();
754
+ if (pooled) {
755
+ this.hits++;
756
+ return pooled;
757
+ }
758
+ this.misses++;
759
+ return new ExecutionJournal(this.capacity);
760
+ }
761
+ release(journal) {
762
+ journal.clear();
763
+ if (this.available.length >= this.maxPooled || journal.capacity > MAX_POOLED_JOURNAL_CAPACITY || journal.capacity !== this.capacity) {
764
+ return;
765
+ }
766
+ this.available.push(journal);
767
+ }
768
+ stats() {
769
+ return {
770
+ journalPoolHits: this.hits,
771
+ journalPoolMisses: this.misses,
772
+ journalPoolSize: this.available.length,
773
+ journalAllocatedCapacity: this.capacity
774
+ };
775
+ }
466
776
  };
467
777
 
468
778
  // src/execution-recorder/recorder-memory-budget.ts
@@ -540,6 +850,12 @@ var RecorderMemoryBudget = class {
540
850
  this.dropEvents(allocation, 1);
541
851
  return false;
542
852
  }
853
+ reserveStorage(allocation, bytes) {
854
+ if (allocation.released || allocation.mode === "metadata-only") return false;
855
+ if (this.reserve(allocation, bytes)) return true;
856
+ this.markPressure(allocation, "metadata-only");
857
+ return false;
858
+ }
543
859
  release(allocation) {
544
860
  if (allocation.released) return;
545
861
  allocation.released = true;
@@ -600,10 +916,7 @@ var defaults = {
600
916
  maxStringLength: 500,
601
917
  maxSerializedValueBytes: 8 * 1024
602
918
  };
603
- var TYPE_MARKER_OPEN = '{"__rasputin_type":"';
604
- var TYPE_MARKER_VALUE_MID = '","value":"';
605
- var TYPE_MARKER_CLOSE = '"}';
606
- var UNDEFINED_MARKER_BYTES = TYPE_MARKER_OPEN.length + "undefined".length + TYPE_MARKER_CLOSE.length;
919
+ var REDACTED = "[RASPUTIN_REDACTED]";
607
920
  var truncation = (reason, extra = {}) => ({
608
921
  __rasputin_truncated: true,
609
922
  __reason: reason,
@@ -613,7 +926,83 @@ var typeMarker = (type, value) => ({
613
926
  __rasputin_type: type,
614
927
  ...value === void 0 ? {} : { value }
615
928
  });
616
- var REDACTED = "[RASPUTIN_REDACTED]";
929
+ var jsonStringUtf8Bytes = (value) => {
930
+ let bytes = 2;
931
+ for (let index = 0; index < value.length; index++) {
932
+ const code = value.charCodeAt(index);
933
+ if (code === 34 || code === 92) {
934
+ bytes += 2;
935
+ continue;
936
+ }
937
+ if (code === 8 || code === 9 || code === 10 || code === 12 || code === 13) {
938
+ bytes += 2;
939
+ continue;
940
+ }
941
+ if (code < 32) {
942
+ bytes += 6;
943
+ continue;
944
+ }
945
+ if (code < 128) {
946
+ bytes += 1;
947
+ continue;
948
+ }
949
+ if (code < 2048) {
950
+ bytes += 2;
951
+ continue;
952
+ }
953
+ if (code >= 55296 && code <= 56319) {
954
+ const next = value.charCodeAt(index + 1);
955
+ if (index + 1 < value.length && next >= 56320 && next <= 57343) {
956
+ bytes += 4;
957
+ index++;
958
+ continue;
959
+ }
960
+ bytes += 6;
961
+ continue;
962
+ }
963
+ if (code >= 56320 && code <= 57343) {
964
+ bytes += 6;
965
+ continue;
966
+ }
967
+ bytes += 3;
968
+ }
969
+ return bytes;
970
+ };
971
+ var jsonLiteralUtf8Bytes = (value) => {
972
+ if (value === null) return 4;
973
+ switch (typeof value) {
974
+ case "boolean":
975
+ return value ? 4 : 5;
976
+ case "number":
977
+ if (!Number.isFinite(value)) return 4;
978
+ if (Object.is(value, -0)) return 1;
979
+ return String(value).length;
980
+ case "string":
981
+ return jsonStringUtf8Bytes(value);
982
+ case "object": {
983
+ if (Array.isArray(value)) {
984
+ let bytes2 = 2;
985
+ for (let index = 0; index < value.length; index++) {
986
+ if (index > 0) bytes2 += 1;
987
+ bytes2 += jsonLiteralUtf8Bytes(value[index]);
988
+ }
989
+ return bytes2;
990
+ }
991
+ const record = value;
992
+ let bytes = 2;
993
+ let first = true;
994
+ for (const key of Object.keys(record)) {
995
+ if (!first) bytes += 1;
996
+ first = false;
997
+ bytes += jsonStringUtf8Bytes(key) + 1;
998
+ bytes += jsonLiteralUtf8Bytes(record[key]);
999
+ }
1000
+ return bytes;
1001
+ }
1002
+ default:
1003
+ return Buffer2.byteLength(JSON.stringify(value) ?? "null", "utf8");
1004
+ }
1005
+ };
617
1006
  var compileLimits = (options) => ({
618
1007
  maxDepth: options.maxDepth ?? defaults.maxDepth,
619
1008
  maxObjectKeys: options.maxObjectKeys ?? defaults.maxObjectKeys,
@@ -625,6 +1014,13 @@ var compileRedactKeys = (options) => {
625
1014
  if (!options.redactKeys?.length) return new Set(DEFAULT_REDACT_KEYS);
626
1015
  return /* @__PURE__ */ new Set([...DEFAULT_REDACT_KEYS, ...options.redactKeys.map((key) => key.toLowerCase())]);
627
1016
  };
1017
+ var keyNeedsCaseFold = (key) => {
1018
+ for (let index = 0; index < key.length; index++) {
1019
+ const code = key.charCodeAt(index);
1020
+ if (code > 127 || code >= 65 && code <= 90) return true;
1021
+ }
1022
+ return false;
1023
+ };
628
1024
  var isJsonSafeString = (value) => {
629
1025
  for (let index = 0; index < value.length; index++) {
630
1026
  const code = value.charCodeAt(index);
@@ -639,7 +1035,6 @@ var isJsonSafeString = (value) => {
639
1035
  }
640
1036
  return true;
641
1037
  };
642
- var typeMarkerWithValueBytes = (type, jsonSafeValue) => TYPE_MARKER_OPEN.length + type.length + TYPE_MARKER_VALUE_MID.length + Buffer2.byteLength(jsonSafeValue, "utf8") + TYPE_MARKER_CLOSE.length;
643
1038
  var primitiveFastPath = (value, maxStringLength) => {
644
1039
  if (value === null) return { value: null, bytes: 4, visitedNodes: 1 };
645
1040
  if (typeof value === "boolean") return { value, bytes: value ? 4 : 5, visitedNodes: 1 };
@@ -653,176 +1048,306 @@ var primitiveFastPath = (value, maxStringLength) => {
653
1048
  return { value, bytes: 2 + Buffer2.byteLength(value, "utf8"), visitedNodes: 1 };
654
1049
  }
655
1050
  if (typeof value === "undefined") {
656
- return {
657
- value: { __rasputin_type: "undefined" },
658
- bytes: UNDEFINED_MARKER_BYTES,
659
- visitedNodes: 1
660
- };
1051
+ const serialized = typeMarker("undefined");
1052
+ return { value: serialized, bytes: jsonLiteralUtf8Bytes(serialized), visitedNodes: 1 };
661
1053
  }
662
1054
  if (typeof value === "bigint") {
663
- const digits = value.toString();
664
- return {
665
- value: { __rasputin_type: "bigint", value: digits },
666
- bytes: typeMarkerWithValueBytes("bigint", digits),
667
- visitedNodes: 1
668
- };
1055
+ const serialized = typeMarker("bigint", value.toString());
1056
+ return { value: serialized, bytes: jsonLiteralUtf8Bytes(serialized), visitedNodes: 1 };
669
1057
  }
670
1058
  if (typeof value === "symbol") {
671
1059
  const text = String(value);
672
1060
  if (!isJsonSafeString(text)) return void 0;
673
- return {
674
- value: { __rasputin_type: "symbol", value: text },
675
- bytes: typeMarkerWithValueBytes("symbol", text),
676
- visitedNodes: 1
677
- };
1061
+ const serialized = typeMarker("symbol", text);
1062
+ return { value: serialized, bytes: jsonLiteralUtf8Bytes(serialized), visitedNodes: 1 };
678
1063
  }
679
1064
  if (typeof value === "function") {
680
1065
  const name = value.name || "anonymous";
681
1066
  if (!isJsonSafeString(name)) return void 0;
682
- return {
683
- value: { __rasputin_type: "function", value: name },
684
- bytes: typeMarkerWithValueBytes("function", name),
685
- visitedNodes: 1
686
- };
1067
+ const serialized = typeMarker("function", name);
1068
+ return { value: serialized, bytes: jsonLiteralUtf8Bytes(serialized), visitedNodes: 1 };
687
1069
  }
688
1070
  return void 0;
689
1071
  };
690
1072
  var createSafeSerializer = (options = {}) => {
691
1073
  const limits = compileLimits(options);
692
1074
  const redactKeys = compileRedactKeys(options);
693
- const serializeInner = (value, depth, seen, metrics) => {
1075
+ const sharedPath = /* @__PURE__ */ new Set();
1076
+ let serializing = false;
1077
+ const isRedactedKey = (key) => {
1078
+ if (redactKeys.has(key)) return true;
1079
+ if (!keyNeedsCaseFold(key)) return false;
1080
+ return redactKeys.has(key.toLowerCase());
1081
+ };
1082
+ const addBytes = (metrics, count) => {
1083
+ metrics.bytes += count;
1084
+ if (metrics.bytes > limits.maxSerializedValueBytes) metrics.oversize = true;
1085
+ };
1086
+ const boundedOversize = (bytes, visitedNodes) => {
1087
+ const bounded = truncation("max_serialized_value_bytes", { __serialized_bytes: bytes });
1088
+ return { value: bounded, bytes: jsonLiteralUtf8Bytes(bounded), visitedNodes };
1089
+ };
1090
+ const openObject = (metrics, allocate) => {
1091
+ addBytes(metrics, 2);
1092
+ return {
1093
+ target: allocate && !metrics.oversize ? {} : void 0,
1094
+ first: true
1095
+ };
1096
+ };
1097
+ const openArray = (metrics, allocate) => {
1098
+ addBytes(metrics, 2);
1099
+ return {
1100
+ items: allocate && !metrics.oversize ? [] : void 0,
1101
+ first: true
1102
+ };
1103
+ };
1104
+ const beginField = (metrics, object, key) => {
1105
+ if (!object.first) addBytes(metrics, 1);
1106
+ object.first = false;
1107
+ addBytes(metrics, jsonStringUtf8Bytes(key) + 1);
1108
+ };
1109
+ const assignField = (object, key, fieldValue) => {
1110
+ if (object.target) object.target[key] = fieldValue;
1111
+ };
1112
+ const writeLiteralField = (metrics, object, key, jsonValue) => {
1113
+ beginField(metrics, object, key);
1114
+ addBytes(metrics, jsonLiteralUtf8Bytes(jsonValue));
1115
+ assignField(object, key, jsonValue);
1116
+ };
1117
+ const writeTypeMarker = (metrics, allocate, type, nestedValue) => {
1118
+ const object = openObject(metrics, allocate);
1119
+ writeLiteralField(metrics, object, "__rasputin_type", type);
1120
+ if (nestedValue !== void 0) writeLiteralField(metrics, object, "value", nestedValue);
1121
+ return object.target;
1122
+ };
1123
+ const writeTruncation = (metrics, allocate, reason, extra = {}) => {
1124
+ const object = openObject(metrics, allocate);
1125
+ writeLiteralField(metrics, object, "__rasputin_truncated", true);
1126
+ writeLiteralField(metrics, object, "__reason", reason);
1127
+ for (const key of Object.keys(extra)) writeLiteralField(metrics, object, key, extra[key]);
1128
+ return object.target;
1129
+ };
1130
+ const serializeInner = (value, depth, path, allocate, metrics) => {
694
1131
  metrics.visitedNodes++;
695
- if (value === null || typeof value === "boolean" || typeof value === "number") return value;
1132
+ const store = allocate && !metrics.oversize;
1133
+ if (value === null) {
1134
+ addBytes(metrics, 4);
1135
+ return store ? null : void 0;
1136
+ }
1137
+ if (typeof value === "boolean") {
1138
+ addBytes(metrics, value ? 4 : 5);
1139
+ return store ? value : void 0;
1140
+ }
1141
+ if (typeof value === "number") {
1142
+ if (!Number.isFinite(value)) addBytes(metrics, 4);
1143
+ else if (Object.is(value, -0)) addBytes(metrics, 1);
1144
+ else addBytes(metrics, String(value).length);
1145
+ return store ? value : void 0;
1146
+ }
696
1147
  if (typeof value === "string") {
697
- if (value.length <= limits.maxStringLength) return value;
698
- return truncation("max_string_length", {
1148
+ if (value.length <= limits.maxStringLength) {
1149
+ addBytes(metrics, jsonStringUtf8Bytes(value));
1150
+ return store ? value : void 0;
1151
+ }
1152
+ return writeTruncation(metrics, store, "max_string_length", {
699
1153
  __original_length: value.length,
700
1154
  value: value.slice(0, limits.maxStringLength)
701
1155
  });
702
1156
  }
703
- if (typeof value === "undefined") return typeMarker("undefined");
704
- if (typeof value === "bigint") return typeMarker("bigint", value.toString());
705
- if (typeof value === "symbol") return typeMarker("symbol", String(value));
706
- if (typeof value === "function") return typeMarker("function", value.name || "anonymous");
707
- if (depth >= limits.maxDepth) return truncation("max_depth");
708
- if (typeof value !== "object") return typeMarker(typeof value);
709
- const tracking = seen ?? /* @__PURE__ */ new WeakSet();
710
- if (tracking.has(value)) return typeMarker("circular");
711
- tracking.add(value);
1157
+ if (typeof value === "undefined") return writeTypeMarker(metrics, store, "undefined");
1158
+ if (typeof value === "bigint") {
1159
+ return writeTypeMarker(metrics, store, "bigint", value.toString());
1160
+ }
1161
+ if (typeof value === "symbol") {
1162
+ return writeTypeMarker(metrics, store, "symbol", String(value));
1163
+ }
1164
+ if (typeof value === "function") {
1165
+ return writeTypeMarker(metrics, store, "function", value.name || "anonymous");
1166
+ }
1167
+ if (depth >= limits.maxDepth) return writeTruncation(metrics, store, "max_depth");
1168
+ if (typeof value !== "object") return writeTypeMarker(metrics, store, typeof value);
1169
+ if (path.has(value)) return writeTypeMarker(metrics, store, "circular");
1170
+ path.add(value);
1171
+ const bytesBefore = metrics.bytes;
1172
+ const oversizeBefore = metrics.oversize;
712
1173
  try {
1174
+ const childAllocate = allocate && !metrics.oversize;
713
1175
  if (value instanceof Error) {
714
- return {
715
- __rasputin_type: "Error",
716
- name: value.name,
717
- message: serializeInner(value.message, depth + 1, tracking, metrics),
718
- ...value.stack ? { stack: serializeInner(value.stack, depth + 1, tracking, metrics) } : {}
719
- };
1176
+ const object2 = openObject(metrics, childAllocate);
1177
+ writeLiteralField(metrics, object2, "__rasputin_type", "Error");
1178
+ writeLiteralField(metrics, object2, "name", value.name);
1179
+ beginField(metrics, object2, "message");
1180
+ assignField(
1181
+ object2,
1182
+ "message",
1183
+ serializeInner(value.message, depth + 1, path, childAllocate, metrics)
1184
+ );
1185
+ if (value.stack) {
1186
+ beginField(metrics, object2, "stack");
1187
+ assignField(
1188
+ object2,
1189
+ "stack",
1190
+ serializeInner(value.stack, depth + 1, path, childAllocate, metrics)
1191
+ );
1192
+ }
1193
+ return object2.target;
1194
+ }
1195
+ if (value instanceof Date) {
1196
+ return writeTypeMarker(metrics, childAllocate, "Date", value.toISOString());
720
1197
  }
721
- if (value instanceof Date) return typeMarker("Date", value.toISOString());
722
1198
  if (Buffer2.isBuffer(value)) {
723
- return typeMarker("Buffer", {
1199
+ return writeTypeMarker(metrics, childAllocate, "Buffer", {
724
1200
  byteLength: value.byteLength,
725
1201
  base64: value.subarray(0, limits.maxStringLength).toString("base64"),
726
1202
  ...value.byteLength > limits.maxStringLength ? { __rasputin_truncated: true } : {}
727
1203
  });
728
1204
  }
729
- if (value instanceof Promise) return typeMarker("Promise");
1205
+ if (value instanceof Promise) return writeTypeMarker(metrics, childAllocate, "Promise");
730
1206
  if (value instanceof Map) {
731
- const entries = [];
1207
+ const object2 = openObject(metrics, childAllocate);
1208
+ writeLiteralField(metrics, object2, "__rasputin_type", "Map");
1209
+ beginField(metrics, object2, "entries");
1210
+ const entries = openArray(metrics, childAllocate);
1211
+ let counted2 = 0;
732
1212
  for (const entry of value.entries()) {
733
- if (entries.length >= limits.maxArrayElements) break;
734
- entries.push([
735
- serializeInner(entry[0], depth + 1, tracking, metrics),
736
- serializeInner(entry[1], depth + 1, tracking, metrics)
737
- ]);
1213
+ if (counted2 >= limits.maxArrayElements) break;
1214
+ if (!entries.first) addBytes(metrics, 1);
1215
+ entries.first = false;
1216
+ addBytes(metrics, 2);
1217
+ const key = serializeInner(entry[0], depth + 1, path, childAllocate, metrics);
1218
+ addBytes(metrics, 1);
1219
+ const mapped = serializeInner(entry[1], depth + 1, path, childAllocate, metrics);
1220
+ entries.items?.push([key, mapped]);
1221
+ counted2++;
1222
+ }
1223
+ assignField(object2, "entries", entries.items ?? []);
1224
+ if (value.size > limits.maxArrayElements) {
1225
+ writeLiteralField(metrics, object2, "__rasputin_truncated", true);
1226
+ writeLiteralField(metrics, object2, "__reason", "max_array_elements");
1227
+ writeLiteralField(metrics, object2, "__original_length", value.size);
738
1228
  }
739
- return {
740
- __rasputin_type: "Map",
741
- entries,
742
- ...value.size > limits.maxArrayElements ? truncation("max_array_elements", { __original_length: value.size }) : {}
743
- };
1229
+ return object2.target;
744
1230
  }
745
1231
  if (value instanceof Set) {
746
- const items = [];
1232
+ const object2 = openObject(metrics, childAllocate);
1233
+ writeLiteralField(metrics, object2, "__rasputin_type", "Set");
1234
+ beginField(metrics, object2, "values");
1235
+ const items = openArray(metrics, childAllocate);
1236
+ let counted2 = 0;
747
1237
  for (const item of value.values()) {
748
- if (items.length >= limits.maxArrayElements) break;
749
- items.push(serializeInner(item, depth + 1, tracking, metrics));
1238
+ if (counted2 >= limits.maxArrayElements) break;
1239
+ if (!items.first) addBytes(metrics, 1);
1240
+ items.first = false;
1241
+ const serializedItem = serializeInner(item, depth + 1, path, childAllocate, metrics);
1242
+ items.items?.push(serializedItem);
1243
+ counted2++;
750
1244
  }
751
- return {
752
- __rasputin_type: "Set",
753
- values: items,
754
- ...value.size > limits.maxArrayElements ? truncation("max_array_elements", { __original_length: value.size }) : {}
755
- };
1245
+ assignField(object2, "values", items.items ?? []);
1246
+ if (value.size > limits.maxArrayElements) {
1247
+ writeLiteralField(metrics, object2, "__rasputin_truncated", true);
1248
+ writeLiteralField(metrics, object2, "__reason", "max_array_elements");
1249
+ writeLiteralField(metrics, object2, "__original_length", value.size);
1250
+ }
1251
+ return object2.target;
756
1252
  }
757
1253
  if (Array.isArray(value)) {
1254
+ const items = openArray(metrics, childAllocate);
758
1255
  const maxItems = value.length > limits.maxArrayElements ? limits.maxArrayElements - 1 : value.length;
759
- const items = [];
760
1256
  for (let index = 0; index < maxItems; index++) {
761
- items.push(serializeInner(value[index], depth + 1, tracking, metrics));
1257
+ if (!items.first) addBytes(metrics, 1);
1258
+ items.first = false;
1259
+ items.items?.push(serializeInner(value[index], depth + 1, path, childAllocate, metrics));
762
1260
  }
763
1261
  if (value.length > limits.maxArrayElements) {
764
- items.push(truncation("max_array_elements", { __original_length: value.length }));
1262
+ if (!items.first) addBytes(metrics, 1);
1263
+ items.first = false;
1264
+ items.items?.push(
1265
+ writeTruncation(metrics, childAllocate, "max_array_elements", {
1266
+ __original_length: value.length
1267
+ })
1268
+ );
765
1269
  }
766
- return items;
1270
+ return items.items;
767
1271
  }
1272
+ const object = openObject(metrics, childAllocate);
768
1273
  const prototype = Object.getPrototypeOf(value);
769
- const serialized = {};
770
1274
  if (prototype && prototype !== Object.prototype) {
771
- serialized.__rasputin_type = prototype.constructor?.name ?? "instance";
1275
+ writeLiteralField(
1276
+ metrics,
1277
+ object,
1278
+ "__rasputin_type",
1279
+ prototype.constructor?.name ?? "instance"
1280
+ );
772
1281
  }
773
1282
  const keys = Object.keys(value);
774
1283
  const maxKeys = keys.length > limits.maxObjectKeys ? limits.maxObjectKeys - 1 : keys.length;
775
1284
  for (let index = 0; index < maxKeys; index++) {
776
1285
  const key = keys[index];
777
1286
  if (key === void 0) continue;
778
- if (redactKeys.has(key.toLowerCase())) {
779
- serialized[key] = REDACTED;
1287
+ if (isRedactedKey(key)) {
1288
+ writeLiteralField(metrics, object, key, REDACTED);
780
1289
  continue;
781
1290
  }
782
1291
  const descriptor = Object.getOwnPropertyDescriptor(value, key);
783
- serialized[key] = descriptor && "value" in descriptor ? serializeInner(descriptor.value, depth + 1, tracking, metrics) : typeMarker("accessor");
1292
+ if (descriptor && "value" in descriptor) {
1293
+ beginField(metrics, object, key);
1294
+ assignField(
1295
+ object,
1296
+ key,
1297
+ serializeInner(descriptor.value, depth + 1, path, childAllocate, metrics)
1298
+ );
1299
+ } else {
1300
+ beginField(metrics, object, key);
1301
+ assignField(object, key, writeTypeMarker(metrics, childAllocate, "accessor"));
1302
+ }
784
1303
  }
785
1304
  if (keys.length > limits.maxObjectKeys) {
786
- Object.assign(
787
- serialized,
788
- truncation("max_object_keys", { __original_length: keys.length })
789
- );
1305
+ writeLiteralField(metrics, object, "__rasputin_truncated", true);
1306
+ writeLiteralField(metrics, object, "__reason", "max_object_keys");
1307
+ writeLiteralField(metrics, object, "__original_length", keys.length);
790
1308
  }
791
- return serialized;
1309
+ return object.target;
792
1310
  } catch (error) {
793
- return typeMarker("unserializable", error instanceof Error ? error.message : void 0);
1311
+ metrics.bytes = bytesBefore;
1312
+ metrics.oversize = oversizeBefore;
1313
+ return writeTypeMarker(
1314
+ metrics,
1315
+ allocate && !metrics.oversize,
1316
+ "unserializable",
1317
+ error instanceof Error ? error.message : void 0
1318
+ );
794
1319
  } finally {
795
- tracking.delete(value);
1320
+ path.delete(value);
796
1321
  }
797
1322
  };
798
- const boundedOversize = (bytes, visitedNodes) => {
799
- const bounded = truncation("max_serialized_value_bytes", { __serialized_bytes: bytes });
800
- return {
801
- value: bounded,
802
- bytes: Buffer2.byteLength(JSON.stringify(bounded), "utf8"),
803
- visitedNodes
804
- };
805
- };
806
1323
  const serializeWithBytes = (value) => {
807
1324
  const fast = primitiveFastPath(value, limits.maxStringLength);
808
1325
  if (fast) {
809
1326
  return fast.bytes <= limits.maxSerializedValueBytes ? fast : boundedOversize(fast.bytes, fast.visitedNodes);
810
1327
  }
811
- const metrics = { visitedNodes: 0 };
1328
+ const metrics = { visitedNodes: 0, bytes: 0, oversize: false };
1329
+ const reentrant = serializing;
1330
+ const path = reentrant ? /* @__PURE__ */ new Set() : sharedPath;
1331
+ if (!reentrant) {
1332
+ serializing = true;
1333
+ sharedPath.clear();
1334
+ }
812
1335
  try {
813
- const serialized = serializeInner(value, 0, void 0, metrics);
814
- const bytes = Buffer2.byteLength(JSON.stringify(serialized), "utf8");
815
- if (bytes <= limits.maxSerializedValueBytes) {
816
- return { value: serialized, bytes, visitedNodes: metrics.visitedNodes };
1336
+ const serialized = serializeInner(value, 0, path, true, metrics);
1337
+ if (metrics.bytes > limits.maxSerializedValueBytes) {
1338
+ return boundedOversize(metrics.bytes, metrics.visitedNodes);
817
1339
  }
818
- return boundedOversize(bytes, metrics.visitedNodes);
1340
+ return { value: serialized, bytes: metrics.bytes, visitedNodes: metrics.visitedNodes };
819
1341
  } catch {
820
1342
  const failed = typeMarker("unserializable");
821
1343
  return {
822
1344
  value: failed,
823
- bytes: Buffer2.byteLength(JSON.stringify(failed), "utf8"),
1345
+ bytes: jsonLiteralUtf8Bytes(failed),
824
1346
  visitedNodes: metrics.visitedNodes
825
1347
  };
1348
+ } finally {
1349
+ path.clear();
1350
+ if (!reentrant) serializing = false;
826
1351
  }
827
1352
  };
828
1353
  return {
@@ -898,6 +1423,8 @@ var createSourceExclusionMatcher = (exclusions) => {
898
1423
 
899
1424
  // src/execution-recorder/execution-recorder.ts
900
1425
  var DEFAULT_MAX_ACTIVE_MEMORY_BYTES = 64 * 1024 * 1024;
1426
+ var DERIVATION_ROOT_RECORD_BYTES = 64;
1427
+ var phaseTimingEnabled = process.env.RASPUTIN_RECORDER_PHASE_TIMING === "1";
901
1428
  var manualFunctionDefinition = (value) => {
902
1429
  const located = /^(.+):([^:@]+)@(\d+):(\d+)$/.exec(value);
903
1430
  const named = located ? void 0 : /^(.+):([^:@]+)$/.exec(value);
@@ -912,8 +1439,8 @@ var manualFunctionDefinition = (value) => {
912
1439
  return { ...definition, functionId: runtimeFunctionId2(definition) };
913
1440
  };
914
1441
  var normalizeFunctionDefinition = (value) => typeof value === "string" ? manualFunctionDefinition(value) : value;
915
- var elapsedMs = (startedAtNs, endedAtNs = process.hrtime.bigint()) => Number(endedAtNs - startedAtNs) / 1e6;
916
- var roundedMilliseconds = (nanoseconds) => Math.round(Number(nanoseconds) / 1e6 * 1e4) / 1e4;
1442
+ var elapsedMs2 = (startedAtNs, endedAtNs = process.hrtime.bigint()) => Number(endedAtNs - startedAtNs) / 1e6;
1443
+ var roundedMilliseconds2 = (nanoseconds) => Math.round(Number(nanoseconds) / 1e6 * 1e4) / 1e4;
917
1444
  var addRecorderTime = (recording, nanoseconds) => {
918
1445
  if (nanoseconds > 0n) recording.inlineRecorderTimeNs += nanoseconds;
919
1446
  };
@@ -1005,7 +1532,20 @@ var emptyStats = {
1005
1532
  totalInlineWallTimeMs: 0,
1006
1533
  maxInlineWallTimeMs: 0,
1007
1534
  activeEstimatedBytes: 0,
1008
- peakActiveEstimatedBytes: 0
1535
+ peakActiveEstimatedBytes: 0,
1536
+ journalPoolHits: 0,
1537
+ journalPoolMisses: 0,
1538
+ journalPoolSize: 0,
1539
+ journalAllocatedCapacity: 0,
1540
+ derivationCandidateFunctions: 0,
1541
+ derivationElidedFunctionCalls: 0,
1542
+ derivationRootsCaptured: 0,
1543
+ derivationControlFactsCaptured: 0,
1544
+ derivationValuesAvoided: 0,
1545
+ derivationFallbackFunctions: 0,
1546
+ derivationFallbackExpressions: 0,
1547
+ derivationFallbackReasons: {},
1548
+ derivationRootBytes: 0
1009
1549
  };
1010
1550
  var noOpExecution = {
1011
1551
  createScope: () => ({
@@ -1021,17 +1561,34 @@ var noOpExecution = {
1021
1561
  prepareFunctionPlans: () => [],
1022
1562
  runPreparedFunction: (_plan, _args, callback) => callback(),
1023
1563
  trace: (_functionId, fn) => fn,
1024
- getStats: () => ({ ...emptyStats })
1564
+ getStats: () => ({ ...emptyStats, derivationFallbackReasons: {} }),
1565
+ captureDerivationRoot: (_rootId, value) => value
1025
1566
  };
1026
1567
  var mergeExecutionMetadata = (target, metadata) => {
1027
1568
  if (metadata.kind) target.kind = metadata.kind;
1028
1569
  if (metadata.name !== void 0) target.name = metadata.name;
1029
1570
  if (metadata.request) target.request = { ...target.request, ...metadata.request };
1030
1571
  };
1572
+ var newRepeatedGroup = (plan) => ({
1573
+ observedCalls: 0,
1574
+ functionId: plan.functionId,
1575
+ runtimePlanId: plan.runtimePlanId
1576
+ });
1577
+ var newSuppression = (group, parentCallId, startedAtNs, endedAtNs) => ({
1578
+ count: 1,
1579
+ firstTimestampNs: startedAtNs,
1580
+ lastTimestampNs: endedAtNs,
1581
+ totalDurationNs: endedAtNs - startedAtNs,
1582
+ functionId: group.functionId,
1583
+ runtimePlanId: group.runtimePlanId,
1584
+ ...parentCallId === void 0 ? {} : { parentCallId },
1585
+ storageIndex: -1,
1586
+ retainedIndex: -1
1587
+ });
1031
1588
  var createExecutionRecorder = (options) => {
1032
1589
  if (!options?.enabled) return noOpExecution;
1033
1590
  const storage = new AsyncLocalStorage();
1034
- const maxEventsPerExecution = Math.max(2, Math.floor(options.maxEventsPerExecution ?? 500));
1591
+ const maxEventsPerExecution = sanitizeJournalCapacity(options.maxEventsPerExecution);
1035
1592
  const maxCapturedCallsPerFunction = Math.max(
1036
1593
  1,
1037
1594
  Math.floor(options.maxCapturedCallsPerFunction ?? 3)
@@ -1046,11 +1603,14 @@ var createExecutionRecorder = (options) => {
1046
1603
  );
1047
1604
  const serializer = createSafeSerializer(options);
1048
1605
  const excludesSource = createSourceExclusionMatcher(options.excludeSources);
1606
+ const journalPool = new ExecutionJournalPool(maxEventsPerExecution);
1607
+ const plansByRuntimeId = [];
1049
1608
  let executionCounter = 0;
1050
1609
  let nextRuntimePlanId = 1;
1051
1610
  const internedPlans = /* @__PURE__ */ new Map();
1052
1611
  const preparedModules = /* @__PURE__ */ new WeakMap();
1053
1612
  const errorExecutions = /* @__PURE__ */ new WeakMap();
1613
+ const phases = phaseTimingEnabled ? { alsNs: 0n, materializeNs: 0n } : void 0;
1054
1614
  const stats = {
1055
1615
  functionCallsObserved: 0,
1056
1616
  functionCallsCaptured: 0,
@@ -1067,10 +1627,22 @@ var createExecutionRecorder = (options) => {
1067
1627
  partialErrorSnapshots: 0,
1068
1628
  measuredExecutions: 0,
1069
1629
  totalInlineWallTimeMs: 0,
1070
- maxInlineWallTimeMs: 0
1630
+ maxInlineWallTimeMs: 0,
1631
+ derivationCandidateFunctions: 0,
1632
+ derivationElidedFunctionCalls: 0,
1633
+ derivationRootsCaptured: 0,
1634
+ derivationControlFactsCaptured: 0,
1635
+ derivationValuesAvoided: 0,
1636
+ derivationFallbackFunctions: 0,
1637
+ derivationFallbackExpressions: 0,
1638
+ derivationFallbackReasons: {},
1639
+ derivationRootBytes: 0
1640
+ };
1641
+ const addPhase = (key, nanoseconds) => {
1642
+ if (phases && nanoseconds > 0n) phases[key] += nanoseconds;
1071
1643
  };
1072
1644
  const recordInlineTiming = (recording) => {
1073
- const inlineWallTimeMs = roundedMilliseconds(recording.inlineRecorderTimeNs);
1645
+ const inlineWallTimeMs = roundedMilliseconds2(recording.inlineRecorderTimeNs);
1074
1646
  stats.measuredExecutions++;
1075
1647
  stats.totalInlineWallTimeMs = Math.round((stats.totalInlineWallTimeMs + inlineWallTimeMs) * 1e4) / 1e4;
1076
1648
  stats.maxInlineWallTimeMs = Math.max(stats.maxInlineWallTimeMs, inlineWallTimeMs);
@@ -1083,16 +1655,20 @@ var createExecutionRecorder = (options) => {
1083
1655
  return serialized;
1084
1656
  };
1085
1657
  const captureValue = (recording, value) => memoryBudget.captureValue(recording.memory, () => serializeAndCount(value));
1658
+ const storePlan = (plan) => {
1659
+ plansByRuntimeId[plan.runtimePlanId] = plan;
1660
+ return plan;
1661
+ };
1086
1662
  const preparePlan = (definition) => {
1087
1663
  const existing = internedPlans.get(definition.functionId);
1088
1664
  if (existing) return existing;
1089
- const plan = {
1665
+ const plan = storePlan({
1090
1666
  runtimePlanId: nextRuntimePlanId++,
1091
1667
  definition,
1092
1668
  functionId: definition.functionId,
1093
1669
  excluded: excludesSource(definition),
1094
1670
  estimatedCallBytes: estimatedCallBytesFor(definition.functionId)
1095
- };
1671
+ });
1096
1672
  internedPlans.set(definition.functionId, plan);
1097
1673
  return plan;
1098
1674
  };
@@ -1101,62 +1677,111 @@ var createExecutionRecorder = (options) => {
1101
1677
  if (cached) return cached;
1102
1678
  const prepared = plans.map((encoded) => {
1103
1679
  const definition = definitionFromEncodedPlan(encoded);
1104
- return {
1680
+ return storePlan({
1105
1681
  runtimePlanId: nextRuntimePlanId++,
1106
1682
  definition,
1107
1683
  functionId: definition.functionId,
1108
1684
  excluded: excludesSource(definition),
1109
1685
  estimatedCallBytes: estimatedCallBytesFor(definition.functionId)
1110
- };
1686
+ });
1111
1687
  });
1112
1688
  preparedModules.set(plans, prepared);
1113
1689
  return prepared;
1114
1690
  };
1115
- const snapshotSuppressed = (group, startedAtNs) => ({
1116
- type: "function_calls_suppressed",
1117
- functionId: group.functionId,
1118
- timestampMs: elapsedMs(startedAtNs, group.firstTimestampNs),
1119
- lastTimestampMs: elapsedMs(startedAtNs, group.lastTimestampNs),
1120
- suppressedCallCount: group.count,
1121
- totalDurationMs: roundedMilliseconds(group.totalDurationNs),
1122
- ...group.parentCallId === void 0 ? {} : { parentCallId: group.parentCallId }
1123
- });
1691
+ const derivationMetrics = (recording) => {
1692
+ const manifest = snapshotInstrumentationManifest();
1693
+ const plans = manifest?.derivationPlans ?? [];
1694
+ const reasons = { ...stats.derivationFallbackReasons };
1695
+ return {
1696
+ derivationCandidateFunctions: plans.filter((plan) => !plan.fallbackReason).length,
1697
+ derivationElidedFunctionCalls: stats.derivationElidedFunctionCalls,
1698
+ derivationRootsCaptured: recording.derivationRoots.length,
1699
+ derivationControlFactsCaptured: stats.derivationControlFactsCaptured,
1700
+ derivationValuesAvoided: stats.derivationValuesAvoided,
1701
+ derivationFallbackFunctions: plans.filter((plan) => plan.fallbackReason).length,
1702
+ derivationFallbackExpressions: recording.derivationRoots.length,
1703
+ derivationFallbackReasons: reasons,
1704
+ derivationRootBytes: recording.derivationRootBytes
1705
+ };
1706
+ };
1707
+ const resolveCaptureMode = (recording) => {
1708
+ if (recording.derivationRoots.length > 0) return "derivation";
1709
+ const plans = snapshotInstrumentationManifest()?.derivationPlans ?? [];
1710
+ if (recording.sawAutomatic && plans.some(
1711
+ (plan) => !plan.fallbackReason && recording.executedFunctionIds.has(plan.functionId)
1712
+ )) {
1713
+ return "derivation";
1714
+ }
1715
+ if (recording.sawAutomatic) return "automatic-full";
1716
+ if (recording.sawManual) return "manual";
1717
+ return "scope-only";
1718
+ };
1719
+ const derivationCapture = (recording) => {
1720
+ const manifest = snapshotInstrumentationManifest();
1721
+ if (!manifest) return void 0;
1722
+ return {
1723
+ manifestHash: hashInstrumentationManifest(manifest),
1724
+ roots: recording.derivationRoots.slice(),
1725
+ controlFacts: []
1726
+ };
1727
+ };
1124
1728
  const stateFrom = (recording, endedAtNs = process.hrtime.bigint()) => {
1125
1729
  const recorderStartedAtNs = process.hrtime.bigint();
1126
- const events = recording.events.values().map(
1127
- (item) => isSuppressionPlaceholder(item) ? snapshotSuppressed(item.group, recording.startedAtNs) : item
1128
- );
1129
- const referencedFunctionIds = new Set(
1130
- events.flatMap(
1131
- (event) => event.type === "function_enter" || event.type === "function_calls_suppressed" ? [event.functionId] : []
1132
- )
1133
- );
1730
+ let events = [];
1731
+ let functions = [];
1732
+ let truncated;
1733
+ let capture;
1734
+ let derivation;
1735
+ if (recording.cachedState) {
1736
+ events = recording.cachedState.events;
1737
+ functions = recording.cachedState.functions;
1738
+ truncated = recording.cachedState.truncated;
1739
+ capture = recording.cachedState.capture;
1740
+ derivation = recording.cachedState.derivation;
1741
+ } else {
1742
+ if (recording.journal) {
1743
+ const materialized = recording.journal.materialize(plansByRuntimeId, recording.startedAtNs);
1744
+ events = materialized.events;
1745
+ functions = materialized.functions;
1746
+ truncated = recording.journal.dropped > 0 ? { eventsDropped: recording.journal.dropped } : void 0;
1747
+ }
1748
+ capture = recording.memory.underPressure ? {
1749
+ completeness: "partial",
1750
+ reason: "process_memory_budget",
1751
+ valuesDropped: recording.memory.valuesDropped,
1752
+ eventsDropped: recording.memory.eventsDropped
1753
+ } : void 0;
1754
+ derivation = derivationCapture(recording);
1755
+ if (recording.finished && recording.inFlightCalls === 0 && recording.journal) {
1756
+ recording.cachedState = { events, functions, truncated, capture, derivation };
1757
+ journalPool.release(recording.journal);
1758
+ recording.journal = void 0;
1759
+ }
1760
+ }
1134
1761
  const state = {
1135
- version: 2,
1762
+ version: 3,
1763
+ captureMode: resolveCaptureMode(recording),
1136
1764
  executionId: recording.executionId,
1137
1765
  startedAt: recording.startedAt,
1138
- durationMs: elapsedMs(recording.startedAtNs, endedAtNs),
1139
- recorder: { inlineWallTimeMs: 0 },
1766
+ durationMs: elapsedMs2(recording.startedAtNs, endedAtNs),
1767
+ recorder: { inlineWallTimeMs: 0, derivation: derivationMetrics(recording) },
1140
1768
  execution: {
1141
1769
  ...recording.execution,
1142
1770
  ...recording.execution.request ? { request: { ...recording.execution.request } } : {}
1143
1771
  },
1144
- functions: [...recording.functions.values()].filter(
1145
- (definition) => referencedFunctionIds.has(definition.functionId)
1146
- ),
1147
- events,
1148
- ...recording.events.dropped > 0 ? { truncated: { eventsDropped: recording.events.dropped } } : {},
1149
- ...recording.memory.underPressure ? {
1150
- capture: {
1151
- completeness: "partial",
1152
- reason: "process_memory_budget",
1153
- valuesDropped: recording.memory.valuesDropped,
1154
- eventsDropped: recording.memory.eventsDropped
1155
- }
1156
- } : {}
1772
+ functions: functions.slice(),
1773
+ events: events.slice(),
1774
+ ...truncated ? { truncated: { ...truncated } } : {},
1775
+ ...capture ? { capture: { ...capture } } : {},
1776
+ ...derivation ? { derivation: { ...derivation, roots: derivation.roots.slice() } } : {}
1157
1777
  };
1778
+ if (phaseTimingEnabled)
1779
+ addPhase("materializeNs", process.hrtime.bigint() - recorderStartedAtNs);
1158
1780
  addRecorderTime(recording, process.hrtime.bigint() - recorderStartedAtNs);
1159
- state.recorder = { inlineWallTimeMs: roundedMilliseconds(recording.inlineRecorderTimeNs) };
1781
+ state.recorder = {
1782
+ inlineWallTimeMs: roundedMilliseconds2(recording.inlineRecorderTimeNs),
1783
+ derivation: derivationMetrics(recording)
1784
+ };
1160
1785
  return state;
1161
1786
  };
1162
1787
  const rememberError = (error, recording) => {
@@ -1172,32 +1797,38 @@ var createExecutionRecorder = (options) => {
1172
1797
  errorExecutions.set(error, recording);
1173
1798
  };
1174
1799
  const countDroppedEvents = (recording, droppedBefore) => {
1175
- stats.truncatedEvents += recording.events.dropped - droppedBefore;
1800
+ const dropped = recording.journal?.dropped ?? droppedBefore;
1801
+ stats.truncatedEvents += dropped - droppedBefore;
1176
1802
  };
1177
1803
  const appendSuppressedPlaceholder = (recording, group) => {
1804
+ if (!recording.journal) return;
1178
1805
  if (!memoryBudget.reserveStandaloneEvent(recording.memory)) return;
1179
- const droppedBefore = recording.events.dropped;
1180
- recording.events.appendSuppressed(group);
1806
+ const droppedBefore = recording.journal.dropped;
1807
+ recording.journal.appendSuppressed(group);
1181
1808
  countDroppedEvents(recording, droppedBefore);
1182
1809
  };
1183
- const repeatedCallGroup = (recording, runtimePlanId, parentCallId) => {
1810
+ const repeatedCallGroup = (recording, plan, parentCallId) => {
1184
1811
  const slots = recording.repeatedCalls;
1185
- let slot = slots[runtimePlanId];
1812
+ let slot = slots[plan.runtimePlanId];
1186
1813
  if (!slot) {
1187
1814
  slot = {};
1188
- slots[runtimePlanId] = slot;
1815
+ slots[plan.runtimePlanId] = slot;
1189
1816
  }
1190
1817
  if (parentCallId === void 0) {
1191
- if (!slot.root) slot.root = { observedCalls: 0 };
1818
+ if (!slot.root) slot.root = newRepeatedGroup(plan);
1192
1819
  return slot.root;
1193
1820
  }
1821
+ if (slot.lastParentGroup && slot.lastParentCallId === parentCallId) {
1822
+ return slot.lastParentGroup;
1823
+ }
1194
1824
  if (!slot.byParent) slot.byParent = /* @__PURE__ */ new Map();
1195
- const byParent = slot.byParent;
1196
- let group = byParent.get(parentCallId);
1825
+ let group = slot.byParent.get(parentCallId);
1197
1826
  if (!group) {
1198
- group = { observedCalls: 0 };
1199
- byParent.set(parentCallId, group);
1827
+ group = newRepeatedGroup(plan);
1828
+ slot.byParent.set(parentCallId, group);
1200
1829
  }
1830
+ slot.lastParentCallId = parentCallId;
1831
+ slot.lastParentGroup = group;
1201
1832
  return group;
1202
1833
  };
1203
1834
  const serializedError = (recording, error, callId) => {
@@ -1220,29 +1851,30 @@ var createExecutionRecorder = (options) => {
1220
1851
  };
1221
1852
  };
1222
1853
  const captureCompletedThrow = (recording, plan, args, parentCallId, startedAtNs, error) => {
1854
+ if (!recording.journal) {
1855
+ rememberError(error, recording);
1856
+ return;
1857
+ }
1223
1858
  if (!memoryBudget.reserveCall(recording.memory, plan.estimatedCallBytes)) {
1224
1859
  rememberError(error, recording);
1225
1860
  return;
1226
1861
  }
1227
1862
  const callId = ++recording.nextCallId;
1228
1863
  const capturedError = serializedError(recording, error, callId);
1229
- const enter = {
1230
- type: "function_enter",
1231
- callId,
1232
- functionId: plan.functionId,
1233
- timestampMs: elapsedMs(recording.startedAtNs, startedAtNs),
1234
- args: Array.from(args, (argument) => captureValue(recording, argument)),
1235
- ...parentCallId === void 0 ? {} : { parentCallId }
1236
- };
1237
- const terminal = {
1238
- type: "function_throw",
1864
+ const capturedArgs = Array.from(args, (argument) => captureValue(recording, argument));
1865
+ const endedAtNs = process.hrtime.bigint();
1866
+ const droppedBefore = recording.journal.dropped;
1867
+ const retained = recording.journal.forceCompletedCall(
1868
+ plan.runtimePlanId,
1239
1869
  callId,
1240
- timestampMs: elapsedMs(recording.startedAtNs),
1241
- durationMs: elapsedMs(startedAtNs),
1242
- error: capturedError.value
1243
- };
1244
- const droppedBefore = recording.events.dropped;
1245
- const retained = recording.events.forceCompletedCall(enter, terminal);
1870
+ parentCallId,
1871
+ startedAtNs - recording.startedAtNs,
1872
+ capturedArgs,
1873
+ KIND_THROW,
1874
+ endedAtNs - recording.startedAtNs,
1875
+ endedAtNs - startedAtNs,
1876
+ capturedError.value
1877
+ );
1246
1878
  if (retained) {
1247
1879
  stats.functionCallsCaptured++;
1248
1880
  capturedError.commit();
@@ -1250,29 +1882,46 @@ var createExecutionRecorder = (options) => {
1250
1882
  countDroppedEvents(recording, droppedBefore);
1251
1883
  rememberError(error, recording);
1252
1884
  };
1253
- const recordSuppressedCall = (recording, group, functionId, parentCallId, startedAtNs, endedAtNs) => {
1885
+ const recordSuppressedCall = (recording, group, parentCallId, startedAtNs, endedAtNs) => {
1254
1886
  stats.repeatedCallsSuppressed++;
1255
- const durationNs = endedAtNs - startedAtNs;
1256
1887
  if (!group.suppressed) {
1257
- group.suppressed = {
1258
- count: 1,
1259
- firstTimestampNs: startedAtNs,
1260
- lastTimestampNs: endedAtNs,
1261
- totalDurationNs: durationNs,
1262
- functionId,
1263
- ...parentCallId === void 0 ? {} : { parentCallId }
1264
- };
1888
+ group.suppressed = newSuppression(group, parentCallId, startedAtNs, endedAtNs);
1265
1889
  appendSuppressedPlaceholder(recording, group.suppressed);
1266
1890
  return;
1267
1891
  }
1268
1892
  const suppressed = group.suppressed;
1269
1893
  suppressed.count++;
1270
1894
  suppressed.lastTimestampNs = endedAtNs;
1271
- suppressed.totalDurationNs += durationNs;
1272
- if (!suppressed.retainedSlot) {
1895
+ suppressed.totalDurationNs += endedAtNs - startedAtNs;
1896
+ if (suppressed.retainedIndex < 0) {
1273
1897
  appendSuppressedPlaceholder(recording, suppressed);
1274
1898
  }
1275
1899
  };
1900
+ const releaseJournalIfIdle = (recording) => {
1901
+ if (!recording.finished || recording.hasError || recording.inFlightCalls > 0) return;
1902
+ if (!recording.journal) return;
1903
+ journalPool.release(recording.journal);
1904
+ recording.journal = void 0;
1905
+ };
1906
+ const acquireJournal = (recording) => {
1907
+ if (recording.journal) return recording.journal;
1908
+ try {
1909
+ const journal = journalPool.acquire();
1910
+ if (!memoryBudget.reserveStorage(recording.memory, journal.allocatedBytes)) {
1911
+ journalPool.release(journal);
1912
+ return void 0;
1913
+ }
1914
+ journal.onGrow = (deltaBytes) => memoryBudget.reserveStorage(recording.memory, deltaBytes);
1915
+ recording.journal = journal;
1916
+ return journal;
1917
+ } catch {
1918
+ return void 0;
1919
+ }
1920
+ };
1921
+ const settleInFlight = (recording) => {
1922
+ if (recording.inFlightCalls > 0) recording.inFlightCalls--;
1923
+ releaseJournalIfIdle(recording);
1924
+ };
1276
1925
  const finish = (store, metadata) => {
1277
1926
  const { recording } = store;
1278
1927
  if (recording.finished) return;
@@ -1281,11 +1930,22 @@ var createExecutionRecorder = (options) => {
1281
1930
  if (metadata) mergeExecutionMetadata(recording.execution, metadata);
1282
1931
  memoryBudget.release(recording.memory);
1283
1932
  stats.activeExecutions = Math.max(0, stats.activeExecutions - 1);
1284
- if (!recording.hasError) stats.successfulExecutionsDiscarded++;
1933
+ if (!recording.hasError) {
1934
+ stats.successfulExecutionsDiscarded++;
1935
+ releaseJournalIfIdle(recording);
1936
+ }
1285
1937
  recording.finishedAtNs = process.hrtime.bigint();
1286
1938
  addRecorderTime(recording, recording.finishedAtNs - recorderStartedAtNs);
1287
1939
  recordInlineTiming(recording);
1288
1940
  };
1941
+ const runStore = (store, callback) => {
1942
+ if (!phaseTimingEnabled) return storage.run(store, callback);
1943
+ const alsStartedAtNs = process.hrtime.bigint();
1944
+ return storage.run(store, () => {
1945
+ addPhase("alsNs", process.hrtime.bigint() - alsStartedAtNs);
1946
+ return callback();
1947
+ });
1948
+ };
1289
1949
  const createScope = (metadata) => {
1290
1950
  const recorderStartedAtNs = process.hrtime.bigint();
1291
1951
  try {
@@ -1298,15 +1958,20 @@ var createExecutionRecorder = (options) => {
1298
1958
  ...metadata,
1299
1959
  ...metadata.request ? { request: { ...metadata.request } } : {}
1300
1960
  },
1301
- events: new CallAwareEventBuffer(maxEventsPerExecution),
1302
- functions: /* @__PURE__ */ new Map(),
1961
+ journal: void 0,
1303
1962
  repeatedCalls: [],
1304
1963
  capturedErrors: /* @__PURE__ */ new WeakMap(),
1305
1964
  nextCallId: 0,
1965
+ inFlightCalls: 0,
1306
1966
  finished: false,
1307
1967
  hasError: false,
1308
1968
  memory: memoryBudget.startExecution(),
1309
- partialSnapshotCounted: false
1969
+ partialSnapshotCounted: false,
1970
+ derivationRoots: [],
1971
+ derivationRootBytes: 0,
1972
+ sawAutomatic: false,
1973
+ sawManual: false,
1974
+ executedFunctionIds: /* @__PURE__ */ new Set()
1310
1975
  };
1311
1976
  stats.activeExecutions++;
1312
1977
  stats.peakActiveExecutions = Math.max(stats.peakActiveExecutions, stats.activeExecutions);
@@ -1315,7 +1980,7 @@ var createExecutionRecorder = (options) => {
1315
1980
  return {
1316
1981
  run: (callback) => {
1317
1982
  const runStartedAtNs = process.hrtime.bigint();
1318
- return storage.run(store, () => {
1983
+ return runStore(store, () => {
1319
1984
  addRecorderTime(recording, process.hrtime.bigint() - runStartedAtNs);
1320
1985
  return callback();
1321
1986
  });
@@ -1368,156 +2033,210 @@ var createExecutionRecorder = (options) => {
1368
2033
  return void 0;
1369
2034
  }
1370
2035
  };
1371
- const runPreparedFunction = (plan, args, callback) => {
2036
+ const runPreparedFunction = (plan, args, callback, omitValues) => {
1372
2037
  const store = storage.getStore();
1373
2038
  if (!store || store.recording.finished) return callback();
1374
2039
  stats.functionCallsObserved++;
1375
2040
  const recorderStartedAtNs = process.hrtime.bigint();
1376
2041
  if (store.suppressCapture || plan.excluded) {
1377
2042
  stats.functionCallsExcluded++;
1378
- return storage.run({ ...store, suppressCapture: true }, () => {
2043
+ return runStore({ ...store, suppressCapture: true }, () => {
1379
2044
  addRecorderTime(store.recording, process.hrtime.bigint() - recorderStartedAtNs);
1380
2045
  return callback();
1381
2046
  });
1382
2047
  }
2048
+ const recording = store.recording;
2049
+ recording.inFlightCalls++;
1383
2050
  const parentCallId = store.currentCallId;
1384
- const group = repeatedCallGroup(store.recording, plan.runtimePlanId, parentCallId);
2051
+ const group = repeatedCallGroup(recording, plan, parentCallId);
1385
2052
  group.observedCalls++;
1386
- if (group.observedCalls === 1) {
1387
- store.recording.functions.set(plan.functionId, plan.definition);
1388
- }
2053
+ const journal = acquireJournal(recording);
2054
+ if (group.observedCalls === 1) journal?.notePlan(plan.runtimePlanId);
1389
2055
  if (group.observedCalls > maxCapturedCallsPerFunction) {
1390
2056
  const startedAtNs2 = process.hrtime.bigint();
1391
2057
  try {
1392
2058
  const result = callback();
1393
2059
  if (isPromiseLike(result)) {
1394
- addRecorderTime(store.recording, startedAtNs2 - recorderStartedAtNs);
2060
+ addRecorderTime(recording, startedAtNs2 - recorderStartedAtNs);
1395
2061
  return result.then(
1396
2062
  (value) => {
1397
2063
  const callbackEndedAtNs2 = process.hrtime.bigint();
1398
- recordSuppressedCall(
1399
- store.recording,
1400
- group,
1401
- plan.functionId,
1402
- parentCallId,
1403
- startedAtNs2,
1404
- callbackEndedAtNs2
1405
- );
1406
- addRecorderTime(store.recording, process.hrtime.bigint() - callbackEndedAtNs2);
2064
+ recordSuppressedCall(recording, group, parentCallId, startedAtNs2, callbackEndedAtNs2);
2065
+ addRecorderTime(recording, process.hrtime.bigint() - callbackEndedAtNs2);
2066
+ settleInFlight(recording);
1407
2067
  return value;
1408
2068
  },
1409
2069
  (error) => {
1410
2070
  const callbackEndedAtNs2 = process.hrtime.bigint();
1411
- captureCompletedThrow(store.recording, plan, args, parentCallId, startedAtNs2, error);
1412
- addRecorderTime(store.recording, process.hrtime.bigint() - callbackEndedAtNs2);
2071
+ captureCompletedThrow(recording, plan, args, parentCallId, startedAtNs2, error);
2072
+ addRecorderTime(recording, process.hrtime.bigint() - callbackEndedAtNs2);
2073
+ settleInFlight(recording);
1413
2074
  throw error;
1414
2075
  }
1415
2076
  );
1416
2077
  }
1417
2078
  const callbackEndedAtNs = process.hrtime.bigint();
1418
- recordSuppressedCall(
1419
- store.recording,
1420
- group,
1421
- plan.functionId,
1422
- parentCallId,
1423
- startedAtNs2,
1424
- callbackEndedAtNs
1425
- );
1426
- addRecorderTime(store.recording, startedAtNs2 - recorderStartedAtNs);
1427
- addRecorderTime(store.recording, process.hrtime.bigint() - callbackEndedAtNs);
2079
+ recordSuppressedCall(recording, group, parentCallId, startedAtNs2, callbackEndedAtNs);
2080
+ addRecorderTime(recording, startedAtNs2 - recorderStartedAtNs);
2081
+ addRecorderTime(recording, process.hrtime.bigint() - callbackEndedAtNs);
2082
+ settleInFlight(recording);
1428
2083
  return result;
1429
2084
  } catch (error) {
1430
2085
  const callbackEndedAtNs = process.hrtime.bigint();
1431
- captureCompletedThrow(store.recording, plan, args, parentCallId, startedAtNs2, error);
1432
- addRecorderTime(store.recording, startedAtNs2 - recorderStartedAtNs);
1433
- addRecorderTime(store.recording, process.hrtime.bigint() - callbackEndedAtNs);
2086
+ captureCompletedThrow(recording, plan, args, parentCallId, startedAtNs2, error);
2087
+ addRecorderTime(recording, startedAtNs2 - recorderStartedAtNs);
2088
+ addRecorderTime(recording, process.hrtime.bigint() - callbackEndedAtNs);
2089
+ settleInFlight(recording);
1434
2090
  throw error;
1435
2091
  }
1436
2092
  }
1437
2093
  const startedAtNs = process.hrtime.bigint();
1438
- if (!store.recording.events.canStartCall()) {
2094
+ if (!journal) {
2095
+ memoryBudget.reserveCall(recording.memory, plan.estimatedCallBytes);
2096
+ return runStore(
2097
+ store,
2098
+ () => observeRecordedCallback(
2099
+ recording,
2100
+ recorderStartedAtNs,
2101
+ callback,
2102
+ () => settleInFlight(recording),
2103
+ (error) => {
2104
+ rememberError(error, recording);
2105
+ settleInFlight(recording);
2106
+ }
2107
+ )
2108
+ );
2109
+ }
2110
+ if (!journal.canStartCall()) {
1439
2111
  const captureSuccess = () => {
1440
- const droppedBefore = store.recording.events.dropped;
1441
- store.recording.events.dropCall();
1442
- countDroppedEvents(store.recording, droppedBefore);
2112
+ const droppedBefore = recording.journal?.dropped ?? 0;
2113
+ recording.journal?.dropCall();
2114
+ countDroppedEvents(recording, droppedBefore);
2115
+ settleInFlight(recording);
1443
2116
  };
1444
- return storage.run(
2117
+ return runStore(
1445
2118
  store,
1446
2119
  () => observeRecordedCallback(
1447
- store.recording,
2120
+ recording,
1448
2121
  recorderStartedAtNs,
1449
2122
  callback,
1450
2123
  captureSuccess,
1451
- (error) => captureCompletedThrow(store.recording, plan, args, parentCallId, startedAtNs, error)
2124
+ (error) => {
2125
+ captureCompletedThrow(recording, plan, args, parentCallId, startedAtNs, error);
2126
+ settleInFlight(recording);
2127
+ }
1452
2128
  )
1453
2129
  );
1454
2130
  }
1455
- if (!memoryBudget.reserveCall(store.recording.memory, plan.estimatedCallBytes)) {
2131
+ if (!memoryBudget.reserveCall(recording.memory, plan.estimatedCallBytes)) {
1456
2132
  return observeRecordedCallback(
1457
- store.recording,
2133
+ recording,
1458
2134
  recorderStartedAtNs,
1459
2135
  callback,
1460
- () => {
1461
- },
1462
- (error) => rememberError(error, store.recording)
2136
+ () => settleInFlight(recording),
2137
+ (error) => {
2138
+ rememberError(error, recording);
2139
+ settleInFlight(recording);
2140
+ }
1463
2141
  );
1464
2142
  }
1465
- const callId = ++store.recording.nextCallId;
1466
- const enter = {
1467
- type: "function_enter",
1468
- callId,
1469
- functionId: plan.functionId,
1470
- timestampMs: elapsedMs(store.recording.startedAtNs),
1471
- args: Array.from(args, (argument) => captureValue(store.recording, argument)),
1472
- ...parentCallId === void 0 ? {} : { parentCallId }
1473
- };
1474
- store.recording.events.startCall(enter);
1475
- const callStore = { recording: store.recording, currentCallId: callId };
1476
- return storage.run(
2143
+ const callId = ++recording.nextCallId;
2144
+ const enterTimestampNs = process.hrtime.bigint() - recording.startedAtNs;
2145
+ const capturedArgs = omitValues ? Array.from(args, () => rasputinOmitted()) : Array.from(args, (argument) => captureValue(recording, argument));
2146
+ journal.startCall(plan.runtimePlanId, callId, parentCallId, enterTimestampNs, capturedArgs);
2147
+ const callStore = { recording, currentCallId: callId };
2148
+ return runStore(
1477
2149
  callStore,
1478
2150
  () => observeRecordedCallback(
1479
- store.recording,
2151
+ recording,
1480
2152
  recorderStartedAtNs,
1481
2153
  callback,
1482
2154
  (value) => {
1483
- store.recording.events.finishCall({
1484
- type: "function_exit",
1485
- callId,
1486
- timestampMs: elapsedMs(store.recording.startedAtNs),
1487
- durationMs: elapsedMs(startedAtNs),
1488
- returnValue: captureValue(store.recording, value)
1489
- });
1490
- stats.functionCallsCaptured++;
2155
+ if (recording.journal) {
2156
+ const timestampNs = process.hrtime.bigint() - recording.startedAtNs;
2157
+ const durationNs = process.hrtime.bigint() - startedAtNs;
2158
+ const returnValue = omitValues ? rasputinOmitted() : captureValue(recording, value);
2159
+ recording.journal.finishCall(KIND_EXIT, callId, timestampNs, durationNs, returnValue);
2160
+ stats.functionCallsCaptured++;
2161
+ }
2162
+ settleInFlight(recording);
1491
2163
  },
1492
2164
  (error) => {
1493
- const capturedError = serializedError(store.recording, error, callId);
1494
- store.recording.events.finishCall({
1495
- type: "function_throw",
2165
+ if (!recording.journal) {
2166
+ rememberError(error, recording);
2167
+ settleInFlight(recording);
2168
+ return;
2169
+ }
2170
+ const capturedError = serializedError(recording, error, callId);
2171
+ recording.journal.finishCall(
2172
+ KIND_THROW,
1496
2173
  callId,
1497
- timestampMs: elapsedMs(store.recording.startedAtNs),
1498
- durationMs: elapsedMs(startedAtNs),
1499
- error: capturedError.value
1500
- });
2174
+ process.hrtime.bigint() - recording.startedAtNs,
2175
+ process.hrtime.bigint() - startedAtNs,
2176
+ capturedError.value
2177
+ );
1501
2178
  stats.functionCallsCaptured++;
1502
2179
  capturedError.commit();
1503
- rememberError(error, store.recording);
2180
+ rememberError(error, recording);
2181
+ settleInFlight(recording);
1504
2182
  }
1505
2183
  )
1506
2184
  );
1507
2185
  };
1508
- const runFunction = (input, args, callback) => runPreparedFunction(preparePlan(normalizeFunctionDefinition(input)), args, callback);
1509
- const runFunctionPlans = (plans, planIndex, args, callback) => {
2186
+ const runFunction = (input, args, callback) => {
2187
+ const store = storage.getStore();
2188
+ if (store && !store.recording.finished) store.recording.sawManual = true;
2189
+ return runPreparedFunction(preparePlan(normalizeFunctionDefinition(input)), args, callback);
2190
+ };
2191
+ const runFunctionPlans = (plans, planIndex, args, callback, omitValues) => {
2192
+ const store = storage.getStore();
1510
2193
  const plan = preparedPlansFor(plans)[planIndex];
1511
- return plan ? runPreparedFunction(plan, args, callback) : callback();
2194
+ if (store && !store.recording.finished) {
2195
+ store.recording.sawAutomatic = true;
2196
+ if (plan) store.recording.executedFunctionIds.add(plan.functionId);
2197
+ }
2198
+ return plan ? runPreparedFunction(plan, args, callback, omitValues) : callback();
1512
2199
  };
1513
2200
  const prepareFunctionPlans = (plans) => preparedPlansFor(plans);
1514
2201
  function trace(input, fn) {
1515
2202
  const definition = typeof input === "string" ? manualFunctionDefinition(input) : { ...input, functionId: runtimeFunctionId2(input) };
1516
2203
  const plan = preparePlan(definition);
1517
2204
  return function traced(...args) {
2205
+ const store = storage.getStore();
2206
+ if (store && !store.recording.finished) store.recording.sawManual = true;
1518
2207
  return runPreparedFunction(plan, args, () => fn.apply(this, args));
1519
2208
  };
1520
2209
  }
2210
+ const captureDerivationRoot = (rootId, value) => {
2211
+ const store = storage.getStore();
2212
+ if (!store || store.recording.finished || store.suppressCapture) return value;
2213
+ const recording = store.recording;
2214
+ if (recording.memory.mode !== "full") return value;
2215
+ if (recording.derivationRoots.length >= maxEventsPerExecution) return value;
2216
+ const recorderStartedAtNs = process.hrtime.bigint();
2217
+ try {
2218
+ const serialized = captureValue(recording, value);
2219
+ if (recording.memory.mode !== "full") return value;
2220
+ if (!memoryBudget.reserveStorage(recording.memory, DERIVATION_ROOT_RECORD_BYTES)) {
2221
+ return value;
2222
+ }
2223
+ const completeness = capturedValueCompleteness(serialized);
2224
+ const bytes = jsonLiteralUtf8Bytes(serialized);
2225
+ recording.derivationRoots.push({
2226
+ rootId,
2227
+ callId: store.currentCallId ?? 0,
2228
+ value: serialized,
2229
+ completeness
2230
+ });
2231
+ recording.derivationRootBytes += bytes;
2232
+ stats.derivationRootsCaptured++;
2233
+ stats.derivationRootBytes += bytes;
2234
+ stats.derivationFallbackExpressions++;
2235
+ return value;
2236
+ } finally {
2237
+ addRecorderTime(recording, process.hrtime.bigint() - recorderStartedAtNs);
2238
+ }
2239
+ };
1521
2240
  return {
1522
2241
  createScope,
1523
2242
  run,
@@ -1527,13 +2246,30 @@ var createExecutionRecorder = (options) => {
1527
2246
  prepareFunctionPlans,
1528
2247
  runPreparedFunction,
1529
2248
  trace,
1530
- getStats: () => ({ ...stats, ...memoryBudget.getStats() })
2249
+ captureDerivationRoot,
2250
+ getStats: () => {
2251
+ const manifest = snapshotInstrumentationManifest();
2252
+ const plans = manifest?.derivationPlans ?? [];
2253
+ return {
2254
+ ...stats,
2255
+ ...memoryBudget.getStats(),
2256
+ ...journalPool.stats(),
2257
+ derivationCandidateFunctions: plans.filter((plan) => !plan.fallbackReason).length,
2258
+ derivationFallbackFunctions: plans.filter((plan) => plan.fallbackReason).length,
2259
+ derivationFallbackReasons: { ...stats.derivationFallbackReasons },
2260
+ ...phases ? {
2261
+ phaseAlsNs: Number(phases.alsNs),
2262
+ phaseMaterializeNs: Number(phases.materializeNs)
2263
+ } : {}
2264
+ };
2265
+ }
1531
2266
  };
1532
2267
  };
1533
2268
 
1534
2269
  // src/execution-recorder/automatic-runtime.ts
1535
2270
  var AUTOMATIC_RUNTIME_SYMBOL = "rasputin.execution.runtime.v2";
1536
2271
  var runtimeSymbol = Symbol.for(AUTOMATIC_RUNTIME_SYMBOL);
2272
+ var passthroughCapture = (_rootId, value) => value;
1537
2273
  var passthrough = (_planIndex, _args, callback) => callback();
1538
2274
  var bindPassthrough = (module) => {
1539
2275
  module.cell.run = passthrough;
@@ -1544,12 +2280,13 @@ var registerModule = (registry, plans) => {
1544
2280
  const module = { plans, cell };
1545
2281
  registry.modules.push(module);
1546
2282
  registry.runtimes.at(-1)?.bind(module);
1547
- return (planIndex, args, callback) => cell.run(planIndex, args, callback);
2283
+ return (planIndex, args, callback, omitValues) => cell.run(planIndex, args, callback, omitValues);
1548
2284
  };
1549
2285
  var createRegistry = () => {
1550
2286
  const registry = {
1551
2287
  runtimes: [],
1552
2288
  modules: [],
2289
+ captureRoot: passthroughCapture,
1553
2290
  register(plans) {
1554
2291
  return registerModule(registry, plans);
1555
2292
  }
@@ -1558,7 +2295,12 @@ var createRegistry = () => {
1558
2295
  };
1559
2296
  var getAutomaticRuntimeRegistry = () => {
1560
2297
  const existing = Reflect.get(globalThis, runtimeSymbol);
1561
- if (existing && typeof existing === "object" && isRegistry(existing)) return existing;
2298
+ if (existing && typeof existing === "object" && isRegistry(existing)) {
2299
+ if (typeof existing.captureRoot !== "function") {
2300
+ existing.captureRoot = passthroughCapture;
2301
+ }
2302
+ return existing;
2303
+ }
1562
2304
  const registry = createRegistry();
1563
2305
  Reflect.set(globalThis, runtimeSymbol, registry);
1564
2306
  return registry;
@@ -1567,19 +2309,18 @@ var installAutomaticExecutionRuntime = (execution) => {
1567
2309
  const registry = getAutomaticRuntimeRegistry();
1568
2310
  const runtime = {
1569
2311
  bind(module) {
1570
- const prepared = execution.prepareFunctionPlans(module.plans);
1571
- module.cell.run = (planIndex, args, callback) => {
1572
- const plan = prepared[planIndex];
1573
- return plan ? execution.runPreparedFunction(plan, args, callback) : callback();
1574
- };
1575
- }
2312
+ module.cell.run = (planIndex, args, callback, omitValues) => execution.runFunctionPlans(module.plans, planIndex, args, callback, omitValues);
2313
+ },
2314
+ captureRoot: (rootId, value) => execution.captureDerivationRoot(rootId, value)
1576
2315
  };
1577
2316
  registry.runtimes.push(runtime);
2317
+ registry.captureRoot = runtime.captureRoot;
1578
2318
  for (const module of registry.modules) runtime.bind(module);
1579
2319
  return () => {
1580
2320
  const index = registry.runtimes.lastIndexOf(runtime);
1581
2321
  if (index >= 0) registry.runtimes.splice(index, 1);
1582
2322
  const current = registry.runtimes.at(-1);
2323
+ registry.captureRoot = current?.captureRoot ?? passthroughCapture;
1583
2324
  for (const module of registry.modules) {
1584
2325
  if (current) current.bind(module);
1585
2326
  else bindPassthrough(module);
@@ -1644,7 +2385,7 @@ import {
1644
2385
 
1645
2386
  // src/sdk-meta.ts
1646
2387
  var SDK_NAME = "@rasputin-ai/node";
1647
- var SDK_VERSION = "0.5.0-alpha.5";
2388
+ var SDK_VERSION = "0.5.0-alpha.7";
1648
2389
 
1649
2390
  // src/rasputin-init.ts
1650
2391
  var withExecution = (client, execution, installRuntime, manifest) => {