footprintjs 8.1.0 → 8.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,138 @@
1
+ "use strict";
2
+ /**
3
+ * checkpointSanitize — clone-resilience helpers for `buildPauseCheckpoint`.
4
+ *
5
+ * The pause checkpoint is detached via one `structuredClone` of the assembled
6
+ * checkpoint. The JSON-safe contract governs what CONSUMERS put into a
7
+ * checkpoint (pauseData, shared state) — but the execution tree's diagnostic
8
+ * bags (`logs`/`errors`/`metrics`/`evals`) accept ANY value at write time
9
+ * without cloning (`$debug`/`$error`/`$metric`/`$eval` route through
10
+ * `DiagnosticCollector`, which stores raw references). A `$debug`'d function
11
+ * in any stage of a pausing run would make the whole-checkpoint clone throw
12
+ * `DataCloneError` — swallowing the pause.
13
+ *
14
+ * That violates the library's error-isolation grain: observability side-bags
15
+ * never abort traversal anywhere else. These helpers restore the grain:
16
+ *
17
+ * - `sanitizeDiagnosticBags` — replace non-cloneable diagnostic values with
18
+ * marker strings (`'[non-serializable: function]'`) so the pause survives.
19
+ * - `describeCheckpointCloneFailure` — when the clone STILL fails after
20
+ * sanitization (the non-cloneable lives in consumer-owned data, e.g.
21
+ * `pauseData`), name the offending checkpoint field(s) and point at the
22
+ * JSON-safe contract instead of letting a naked `DataCloneError` escape.
23
+ *
24
+ * Both run ONLY on the clone-failure path of a pause — never on the hot path.
25
+ */
26
+ Object.defineProperty(exports, "__esModule", { value: true });
27
+ exports.describeCheckpointCloneFailure = exports.sanitizeDiagnosticBags = void 0;
28
+ /** The StageSnapshot fields written by `$debug`/`$error`/`$metric`/`$eval`. */
29
+ const DIAGNOSTIC_BAGS = ['logs', 'errors', 'metrics', 'evals'];
30
+ /** `true` when `structuredClone` accepts the value as-is. */
31
+ function isCloneable(value) {
32
+ try {
33
+ structuredClone(value);
34
+ return true;
35
+ }
36
+ catch {
37
+ return false;
38
+ }
39
+ }
40
+ /** Human-readable kind for the `[non-serializable: …]` marker. */
41
+ function describeKind(value) {
42
+ if (value === null)
43
+ return 'null';
44
+ if (typeof value !== 'object')
45
+ return typeof value;
46
+ return value.constructor?.name ?? 'object';
47
+ }
48
+ /** Plain data container we can rebuild entry-by-entry without lying about the type. */
49
+ function isPlainObject(value) {
50
+ const proto = Object.getPrototypeOf(value);
51
+ return proto === Object.prototype || proto === null;
52
+ }
53
+ /**
54
+ * Deep-replace non-cloneable values with `'[non-serializable: <kind>]'`
55
+ * marker strings, preserving everything `structuredClone` accepts.
56
+ *
57
+ * Fast path: a cloneable value is returned AS-IS (no copy — the caller
58
+ * clones the whole checkpoint right after). Only containers that actually
59
+ * hold a non-cloneable leaf are rebuilt, and only KNOWN container shapes
60
+ * (array / Map / Set / plain object) are rebuilt entry-by-entry — exotic
61
+ * non-cloneables (Promise, WeakMap, class instances holding a function, …)
62
+ * become a typed marker rather than a misleading empty shell. Pure cycles
63
+ * pass the fast path untouched (`structuredClone` supports them); a cycle
64
+ * is only broken — with a marker — when it shares a container with a
65
+ * non-cloneable value.
66
+ */
67
+ function sanitizeValue(value, seen) {
68
+ if (isCloneable(value))
69
+ return value;
70
+ if (value !== null && typeof value === 'object') {
71
+ if (seen.has(value))
72
+ return '[non-serializable: circular]';
73
+ seen.add(value);
74
+ if (Array.isArray(value)) {
75
+ return value.map((v) => sanitizeValue(v, seen));
76
+ }
77
+ if (value instanceof Map) {
78
+ return new Map([...value].map(([k, v]) => [sanitizeValue(k, seen), sanitizeValue(v, seen)]));
79
+ }
80
+ if (value instanceof Set) {
81
+ return new Set([...value].map((v) => sanitizeValue(v, seen)));
82
+ }
83
+ if (isPlainObject(value)) {
84
+ return Object.fromEntries(Object.entries(value).map(([k, v]) => [k, sanitizeValue(v, seen)]));
85
+ }
86
+ }
87
+ return `[non-serializable: ${describeKind(value)}]`;
88
+ }
89
+ /**
90
+ * Walk a `StageSnapshot` tree (via `next` + `children`) and sanitize the four
91
+ * diagnostic bags on every node IN PLACE.
92
+ *
93
+ * In-place is safe and intentional: `StageContext.getSnapshot()` builds fresh
94
+ * node objects on every call, but the bag fields on those fresh nodes ALIAS
95
+ * the live `DiagnosticCollector` bags. We replace the node's bag REFERENCE
96
+ * with a sanitized copy — the live engine bags are never mutated, so a
97
+ * same-executor resume keeps the original diagnostic values.
98
+ */
99
+ function sanitizeDiagnosticBags(tree) {
100
+ const seen = new WeakSet();
101
+ const visit = (node) => {
102
+ for (const bag of DIAGNOSTIC_BAGS) {
103
+ const bagValue = node[bag];
104
+ if (bagValue !== undefined && !isCloneable(bagValue)) {
105
+ node[bag] = sanitizeValue(bagValue, seen);
106
+ }
107
+ }
108
+ if (node.next)
109
+ visit(node.next);
110
+ if (node.children)
111
+ for (const child of node.children)
112
+ visit(child);
113
+ };
114
+ visit(tree);
115
+ return tree;
116
+ }
117
+ exports.sanitizeDiagnosticBags = sanitizeDiagnosticBags;
118
+ /**
119
+ * Build the DESCRIPTIVE error for a checkpoint that still cannot be cloned
120
+ * after diagnostic-bag sanitization — i.e. the non-cloneable value lives in
121
+ * consumer-owned data (a genuine JSON-safe contract violation). Probes each
122
+ * top-level checkpoint field individually so the message names the offending
123
+ * field family. Never lets a naked `DataCloneError` escape.
124
+ */
125
+ function describeCheckpointCloneFailure(checkpoint, cause) {
126
+ const failing = Object.entries(checkpoint)
127
+ .filter(([, value]) => !isCloneable(value))
128
+ .map(([field]) => field);
129
+ const fields = failing.length > 0 ? failing.join(', ') : 'unknown';
130
+ return new Error('FlowChartExecutor: cannot build the pause checkpoint — non-serializable value(s) in ' +
131
+ `checkpoint field(s): ${fields}. The checkpoint contract is JSON-safe (no functions, no ` +
132
+ "class instances). Check the pauseData returned by the pausable stage's execute(), and any " +
133
+ 'subflow state captured at the pause. Diagnostic values from $debug/$metric/$error/$eval ' +
134
+ 'are sanitized automatically and never cause this error. ' +
135
+ 'See docs/guides/execution-model.md ("Pause / resume — what a checkpoint captures").', { cause });
136
+ }
137
+ exports.describeCheckpointCloneFailure = describeCheckpointCloneFailure;
138
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY2hlY2twb2ludFNhbml0aXplLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vLi4vc3JjL2xpYi9ydW5uZXIvY2hlY2twb2ludFNhbml0aXplLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiI7QUFBQTs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7R0F1Qkc7OztBQUlILCtFQUErRTtBQUMvRSxNQUFNLGVBQWUsR0FBRyxDQUFDLE1BQU0sRUFBRSxRQUFRLEVBQUUsU0FBUyxFQUFFLE9BQU8sQ0FBVSxDQUFDO0FBRXhFLDZEQUE2RDtBQUM3RCxTQUFTLFdBQVcsQ0FBQyxLQUFjO0lBQ2pDLElBQUksQ0FBQztRQUNILGVBQWUsQ0FBQyxLQUFLLENBQUMsQ0FBQztRQUN2QixPQUFPLElBQUksQ0FBQztJQUNkLENBQUM7SUFBQyxNQUFNLENBQUM7UUFDUCxPQUFPLEtBQUssQ0FBQztJQUNmLENBQUM7QUFDSCxDQUFDO0FBRUQsa0VBQWtFO0FBQ2xFLFNBQVMsWUFBWSxDQUFDLEtBQWM7SUFDbEMsSUFBSSxLQUFLLEtBQUssSUFBSTtRQUFFLE9BQU8sTUFBTSxDQUFDO0lBQ2xDLElBQUksT0FBTyxLQUFLLEtBQUssUUFBUTtRQUFFLE9BQU8sT0FBTyxLQUFLLENBQUM7SUFDbkQsT0FBTyxLQUFLLENBQUMsV0FBVyxFQUFFLElBQUksSUFBSSxRQUFRLENBQUM7QUFDN0MsQ0FBQztBQUVELHVGQUF1RjtBQUN2RixTQUFTLGFBQWEsQ0FBQyxLQUFhO0lBQ2xDLE1BQU0sS0FBSyxHQUFHLE1BQU0sQ0FBQyxjQUFjLENBQUMsS0FBSyxDQUFrQixDQUFDO0lBQzVELE9BQU8sS0FBSyxLQUFLLE1BQU0sQ0FBQyxTQUFTLElBQUksS0FBSyxLQUFLLElBQUksQ0FBQztBQUN0RCxDQUFDO0FBRUQ7Ozs7Ozs7Ozs7Ozs7R0FhRztBQUNILFNBQVMsYUFBYSxDQUFDLEtBQWMsRUFBRSxJQUFxQjtJQUMxRCxJQUFJLFdBQVcsQ0FBQyxLQUFLLENBQUM7UUFBRSxPQUFP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Q0FBQyxDQUFDLEVBQUUsS0FBSyxDQUFDLEVBQUUsRUFBRSxDQUFDLENBQUMsV0FBVyxDQUFDLEtBQUssQ0FBQyxDQUFDO1NBQzFDLEdBQUcsQ0FBQyxDQUFDLENBQUMsS0FBSyxDQUFDLEVBQUUsRUFBRSxDQUFDLEtBQUssQ0FBQyxDQUFDO0lBQzNCLE1BQU0sTUFBTSxHQUFHLE9BQU8sQ0FBQyxNQUFNLEdBQUcsQ0FBQyxDQUFDLENBQUMsQ0FBQyxPQUFPLENBQUMsSUFBSSxDQUFDLElBQUksQ0FBQyxDQUFDLENBQUMsQ0FBQyxTQUFTLENBQUM7SUFDbkUsT0FBTyxJQUFJLEtBQUssQ0FDZCxzRkFBc0Y7UUFDcEYsd0JBQXdCLE1BQU0sMkRBQTJEO1FBQ3pGLDRGQUE0RjtRQUM1RiwwRkFBMEY7UUFDMUYsMERBQTBEO1FBQzFELHFGQUFxRixFQUN2RixFQUFFLEtBQUssRUFBRSxDQUNWLENBQUM7QUFDSixDQUFDO0FBZEQsd0VBY0MiLCJzb3VyY2VzQ29udGVudCI6WyIvKipcbiAqIGNoZWNrcG9pbnRTYW5pdGl6ZSDigJQgY2xvbmUtcmVzaWxpZW5jZSBoZWxwZXJzIGZvciBgYnVpbGRQYXVzZUNoZWNrcG9pbnRgLlxuICpcbiAqIFRoZSBwYXVzZSBjaGVja3BvaW50IGlzIGRldGFjaGVkIHZpYSBvbmUgYHN0cnVjdHVyZWRDbG9uZWAgb2YgdGhlIGFzc2VtYmxlZFxuICogY2hlY2twb2ludC4gVGhlIEpTT04tc2FmZSBjb250cmFjdCBnb3Zlcm5zIHdoYXQgQ09OU1VNRVJTIHB1dCBpbnRvIGFcbiAqIGNoZWNrcG9pbnQgKHBhdXNlRGF0YSwgc2hhcmVkIHN0YXRlKSDigJQgYnV0IHRoZSBleGVjdXRpb24gdHJlZSdzIGRpYWdub3N0aWNcbiAqIGJhZ3MgKGBsb2dzYC9gZXJyb3JzYC9gbWV0cmljc2AvYGV2YWxzYCkgYWNjZXB0IEFOWSB2YWx1ZSBhdCB3cml0ZSB0aW1lXG4gKiB3aXRob3V0IGNsb25pbmcgKGAkZGVidWdgL2AkZXJyb3JgL2AkbWV0cmljYC9gJGV2YWxgIHJvdXRlIHRocm91Z2hcbiAqIGBEaWFnbm9zdGljQ29sbGVjdG9yYCwgd2hpY2ggc3RvcmVzIHJhdyByZWZlcmVuY2VzKS4gQSBgJGRlYnVnYCdkIGZ1bmN0aW9uXG4gKiBpbiBhbnkgc3RhZ2Ugb2YgYSBwYXVzaW5nIHJ1biB3b3VsZCBtYWtlIHRoZSB3aG9sZS1jaGVja3BvaW50IGNsb25lIHRocm93XG4gKiBgRGF0YUNsb25lRXJyb3JgIOKAlCBzd2FsbG93aW5nIHRoZSBwYXVzZS5cbiAqXG4gKiBUaGF0IHZpb2xhdGVzIHRoZSBsaWJyYXJ5J3MgZXJyb3ItaXNvbGF0aW9uIGdyYWluOiBvYnNlcnZhYmlsaXR5IHNpZGUtYmFnc1xuICogbmV2ZXIgYWJvcnQgdHJhdmVyc2FsIGFueXdoZXJlIGVsc2UuIFRoZXNlIGhlbHBlcnMgcmVzdG9yZSB0aGUgZ3JhaW46XG4gKlxuICogICAtIGBzYW5pdGl6ZURpYWdub3N0aWNCYWdzYCDigJQgcmVwbGFjZSBub24tY2xvbmVhYmxlIGRpYWdub3N0aWMgdmFsdWVzIHdpdGhcbiAqICAgICBtYXJrZXIgc3RyaW5ncyAoYCdbbm9uLXNlcmlhbGl6YWJsZTogZnVuY3Rpb25dJ2ApIHNvIHRoZSBwYXVzZSBzdXJ2aXZlcy5cbiAqICAgLSBgZGVzY3JpYmVDaGVja3BvaW50Q2xvbmVGYWlsdXJlYCDigJQgd2hlbiB0aGUgY2xvbmUgU1RJTEwgZmFpbHMgYWZ0ZXJcbiAqICAgICBzYW5pdGl6YXRpb24gKHRoZSBub24tY2xvbmVhYmxlIGxpdmVzIGluIGNvbnN1bWVyLW93bmVkIGRhdGEsIGUuZy5cbiAqICAgICBgcGF1c2VEYXRhYCksIG5hbWUgdGhlIG9mZmVuZGluZyBjaGVja3BvaW50IGZpZWxkKHMpIGFuZCBwb2ludCBhdCB0aGVcbiAqICAgICBKU09OLXNhZmUgY29udHJhY3QgaW5zdGVhZCBvZiBsZXR0aW5nIGEgbmFrZWQgYERhdGFDbG9uZUVycm9yYCBlc2NhcGUuXG4gKlxuICogQm90aCBydW4gT05MWSBvbiB0aGUgY2xvbmUtZmFpbHVyZSBwYXRoIG9mIGEgcGF1c2Ug4oCUIG5ldmVyIG9uIHRoZSBob3QgcGF0aC5cbiAqL1xuXG5pbXBvcnQgdHlwZSB7IFN0YWdlU25hcHNob3QgfSBmcm9tICcuLi9tZW1vcnkvdHlwZXMuanMnO1xuXG4vKiogVGhlIFN0YWdlU25hcHNob3QgZmllbGRzIHdyaXR0ZW4gYnkgYCRkZWJ1Z2AvYCRlcnJvcmAvYCRtZXRyaWNgL2AkZXZhbGAuICovXG5jb25zdCBESUFHTk9TVElDX0JBR1MgPSBbJ2xvZ3MnLCAnZXJyb3JzJywgJ21ldHJpY3MnLCAnZXZhbHMnXSBhcyBjb25zdDtcblxuLyoqIGB0cnVlYCB3aGVuIGBzdHJ1Y3R1cmVkQ2xvbmVgIGFjY2VwdHMgdGhlIHZhbHVlIGFzLWlzLiAqL1xuZnVuY3Rpb24gaXNDbG9uZWFibGUodmFsdWU6IHVua25vd24pOiBib29sZWFuIHtcbiAgdHJ5IHtcbiAgICBzdHJ1Y3R1cmVkQ2xvbmUodmFsdWUpO1xuICAgIHJldHVybiB0cnVlO1xuICB9IGNhdGNoIHtcbiAgICByZXR1cm4gZmFsc2U7XG4gIH1cbn1cblxuLyoqIEh1bWFuLXJlYWRhYmxlIGtpbmQgZm9yIHRoZSBgW25vbi1zZXJpYWxpemFibGU6IOKApl1gIG1hcmtlci4gKi9cbmZ1bmN0aW9uIGRlc2NyaWJlS2luZCh2YWx1ZTogdW5rbm93bik6IHN0cmluZyB7XG4gIGlmICh2YWx1ZSA9PT0gbnVsbCkgcmV0dXJuICdudWxsJztcbiAgaWYgKHR5cGVvZiB2YWx1ZSAhPT0gJ29iamVjdCcpIHJldHVybiB0eXBlb2YgdmFsdWU7XG4gIHJldHVybiB2YWx1ZS5jb25zdHJ1Y3Rvcj8ubmFtZSA/PyAnb2JqZWN0Jztcbn1cblxuLyoqIFBsYWluIGRhdGEgY29udGFpbmVyIHdlIGNhbiByZWJ1aWxkIGVudHJ5LWJ5LWVudHJ5IHdpdGhvdXQgbHlpbmcgYWJvdXQgdGhlIHR5cGUuICovXG5mdW5jdGlvbiBpc1BsYWluT2JqZWN0KHZhbHVlOiBvYmplY3QpOiBib29sZWFuIHtcbiAgY29uc3QgcHJvdG8gPSBPYmplY3QuZ2V0UHJvdG90eXBlT2YodmFsdWUpIGFzIG9iamVjdCB8IG51bGw7XG4gIHJldHVybiBwcm90byA9PT0gT2JqZWN0LnByb3RvdHlwZSB8fCBwcm90byA9PT0gbnVsbDtcbn1cblxuLyoqXG4gKiBEZWVwLXJlcGxhY2Ugbm9uLWNsb25lYWJsZSB2YWx1ZXMgd2l0aCBgJ1tub24tc2VyaWFsaXphYmxlOiA8a2luZD5dJ2BcbiAqIG1hcmtlciBzdHJpbmdzLCBwcmVzZXJ2aW5nIGV2ZXJ5dGhpbmcgYHN0cnVjdHVyZWRDbG9uZWAgYWNjZXB0cy5cbiAqXG4gKiBGYXN0IHBhdGg6IGEgY2xvbmVhYmxlIHZhbHVlIGlzIHJldHVybmVkIEFTLUlTIChubyBjb3B5IOKAlCB0aGUgY2FsbGVyXG4gKiBjbG9uZXMgdGhlIHdob2xlIGNoZWNrcG9pbnQgcmlnaHQgYWZ0ZXIpLiBPbmx5IGNvbnRhaW5lcnMgdGhhdCBhY3R1YWxseVxuICogaG9sZCBhIG5vbi1jbG9uZWFibGUgbGVhZiBhcmUgcmVidWlsdCwgYW5kIG9ubHkgS05PV04gY29udGFpbmVyIHNoYXBlc1xuICogKGFycmF5IC8gTWFwIC8gU2V0IC8gcGxhaW4gb2JqZWN0KSBhcmUgcmVidWlsdCBlbnRyeS1ieS1lbnRyeSDigJQgZXhvdGljXG4gKiBub24tY2xvbmVhYmxlcyAoUHJvbWlzZSwgV2Vha01hcCwgY2xhc3MgaW5zdGFuY2VzIGhvbGRpbmcgYSBmdW5jdGlvbiwg4oCmKVxuICogYmVjb21lIGEgdHlwZWQgbWFya2VyIHJhdGhlciB0aGFuIGEgbWlzbGVhZGluZyBlbXB0eSBzaGVsbC4gUHVyZSBjeWNsZXNcbiAqIHBhc3MgdGhlIGZhc3QgcGF0aCB1bnRvdWNoZWQgKGBzdHJ1Y3R1cmVkQ2xvbmVgIHN1cHBvcnRzIHRoZW0pOyBhIGN5Y2xlXG4gKiBpcyBvbmx5IGJyb2tlbiDigJQgd2l0aCBhIG1hcmtlciDigJQgd2hlbiBpdCBzaGFyZXMgYSBjb250YWluZXIgd2l0aCBhXG4gKiBub24tY2xvbmVhYmxlIHZhbHVlLlxuICovXG5mdW5jdGlvbiBzYW5pdGl6ZVZhbHVlKHZhbHVlOiB1bmtub3duLCBzZWVuOiBXZWFrU2V0PG9iamVjdD4pOiB1bmtub3duIHtcbiAgaWYgKGlzQ2xvbmVhYmxlKHZhbHVlKSkgcmV0dXJuIHZhbHVlO1xuICBpZiAodmFsdWUgIT09IG51bGwgJiYgdHlwZW9mIHZhbHVlID09PSAnb2JqZWN0Jykge1xuICAgIGlmIChzZWVuLmhhcyh2YWx1ZSkpIHJldHVybiAnW25vbi1zZXJpYWxpemFibGU6IGNpcmN1bGFyXSc7XG4gICAgc2Vlbi5hZGQodmFsdWUpO1xuICAgIGlmIChBcnJheS5pc0FycmF5KHZhbHVlKSkge1xuICAgICAgcmV0dXJuIHZhbHVlLm1hcCgodikgPT4gc2FuaXRpemVWYWx1ZSh2LCBzZWVuKSk7XG4gICAgfVxuICAgIGlmICh2YWx1ZSBpbnN0YW5jZW9mIE1hcCkge1xuICAgICAgcmV0dXJuIG5ldyBNYXAoWy4uLnZhbHVlXS5tYXAoKFtrLCB2XSkgPT4gW3Nhbml0aXplVmFsdWUoaywgc2VlbiksIHNhbml0aXplVmFsdWUodiwgc2VlbildKSk7XG4gICAgfVxuICAgIGlmICh2YWx1ZSBpbnN0YW5jZW9mIFNldCkge1xuICAgICAgcmV0dXJuIG5ldyBTZXQoWy4uLnZhbHVlXS5tYXAoKHYpID0+IHNhbml0aXplVmFsdWUodiwgc2VlbikpKTtcbiAgICB9XG4gICAgaWYgKGlzUGxhaW5PYmplY3QodmFsdWUpKSB7XG4gICAgICByZXR1cm4gT2JqZWN0LmZyb21FbnRyaWVzKE9iamVjdC5lbnRyaWVzKHZhbHVlKS5tYXAoKFtrLCB2XSkgPT4gW2ssIHNhbml0aXplVmFsdWUodiwgc2VlbildKSk7XG4gICAgfVxuICB9XG4gIHJldHVybiBgW25vbi1zZXJpYWxpemFibGU6ICR7ZGVzY3JpYmVLaW5kKHZhbHVlKX1dYDtcbn1cblxuLyoqXG4gKiBXYWxrIGEgYFN0YWdlU25hcHNob3RgIHRyZWUgKHZpYSBgbmV4dGAgKyBgY2hpbGRyZW5gKSBhbmQgc2FuaXRpemUgdGhlIGZvdXJcbiAqIGRpYWdub3N0aWMgYmFncyBvbiBldmVyeSBub2RlIElOIFBMQUNFLlxuICpcbiAqIEluLXBsYWNlIGlzIHNhZmUgYW5kIGludGVudGlvbmFsOiBgU3RhZ2VDb250ZXh0LmdldFNuYXBzaG90KClgIGJ1aWxkcyBmcmVzaFxuICogbm9kZSBvYmplY3RzIG9uIGV2ZXJ5IGNhbGwsIGJ1dCB0aGUgYmFnIGZpZWxkcyBvbiB0aG9zZSBmcmVzaCBub2RlcyBBTElBU1xuICogdGhlIGxpdmUgYERpYWdub3N0aWNDb2xsZWN0b3JgIGJhZ3MuIFdlIHJlcGxhY2UgdGhlIG5vZGUncyBiYWcgUkVGRVJFTkNFXG4gKiB3aXRoIGEgc2FuaXRpemVkIGNvcHkg4oCUIHRoZSBsaXZlIGVuZ2luZSBiYWdzIGFyZSBuZXZlciBtdXRhdGVkLCBzbyBhXG4gKiBzYW1lLWV4ZWN1dG9yIHJlc3VtZSBrZWVwcyB0aGUgb3JpZ2luYWwgZGlhZ25vc3RpYyB2YWx1ZXMuXG4gKi9cbmV4cG9ydCBmdW5jdGlvbiBzYW5pdGl6ZURpYWdub3N0aWNCYWdzKHRyZWU6IFN0YWdlU25hcHNob3QpOiBTdGFnZVNuYXBzaG90IHtcbiAgY29uc3Qgc2VlbiA9IG5ldyBXZWFrU2V0PG9iamVjdD4oKTtcbiAgY29uc3QgdmlzaXQgPSAobm9kZTogU3RhZ2VTbmFwc2hvdCk6IHZvaWQgPT4ge1xuICAgIGZvciAoY29uc3QgYmFnIG9mIERJQUdOT1NUSUNfQkFHUykge1xuICAgICAgY29uc3QgYmFnVmFsdWUgPSBub2RlW2JhZ107XG4gICAgICBpZiAoYmFnVmFsdWUgIT09IHVuZGVmaW5lZCAmJiAhaXNDbG9uZWFibGUoYmFnVmFsdWUpKSB7XG4gICAgICAgIG5vZGVbYmFnXSA9IHNhbml0aXplVmFsdWUoYmFnVmFsdWUsIHNlZW4pIGFzIFJlY29yZDxzdHJpbmcsIHVua25vd24+O1xuICAgICAgfVxuICAgIH1cbiAgICBpZiAobm9kZS5uZXh0KSB2aXNpdChub2RlLm5leHQpO1xuICAgIGlmIChub2RlLmNoaWxkcmVuKSBmb3IgKGNvbnN0IGNoaWxkIG9mIG5vZGUuY2hpbGRyZW4pIHZpc2l0KGNoaWxkKTtcbiAgfTtcbiAgdmlzaXQodHJlZSk7XG4gIHJldHVybiB0cmVlO1xufVxuXG4vKipcbiAqIEJ1aWxkIHRoZSBERVNDUklQVElWRSBlcnJvciBmb3IgYSBjaGVja3BvaW50IHRoYXQgc3RpbGwgY2Fubm90IGJlIGNsb25lZFxuICogYWZ0ZXIgZGlhZ25vc3RpYy1iYWcgc2FuaXRpemF0aW9uIOKAlCBpLmUuIHRoZSBub24tY2xvbmVhYmxlIHZhbHVlIGxpdmVzIGluXG4gKiBjb25zdW1lci1vd25lZCBkYXRhIChhIGdlbnVpbmUgSlNPTi1zYWZlIGNvbnRyYWN0IHZpb2xhdGlvbikuIFByb2JlcyBlYWNoXG4gKiB0b3AtbGV2ZWwgY2hlY2twb2ludCBmaWVsZCBpbmRpdmlkdWFsbHkgc28gdGhlIG1lc3NhZ2UgbmFtZXMgdGhlIG9mZmVuZGluZ1xuICogZmllbGQgZmFtaWx5LiBOZXZlciBsZXRzIGEgbmFrZWQgYERhdGFDbG9uZUVycm9yYCBlc2NhcGUuXG4gKi9cbmV4cG9ydCBmdW5jdGlvbiBkZXNjcmliZUNoZWNrcG9pbnRDbG9uZUZhaWx1cmUoY2hlY2twb2ludDogUmVjb3JkPHN0cmluZywgdW5rbm93bj4sIGNhdXNlOiB1bmtub3duKTogRXJyb3Ige1xuICBjb25zdCBmYWlsaW5nID0gT2JqZWN0LmVudHJpZXMoY2hlY2twb2ludClcbiAgICAuZmlsdGVyKChbLCB2YWx1ZV0pID0+ICFpc0Nsb25lYWJsZSh2YWx1ZSkpXG4gICAgLm1hcCgoW2ZpZWxkXSkgPT4gZmllbGQpO1xuICBjb25zdCBmaWVsZHMgPSBmYWlsaW5nLmxlbmd0aCA+IDAgPyBmYWlsaW5nLmpvaW4oJywgJykgOiAndW5rbm93bic7XG4gIHJldHVybiBuZXcgRXJyb3IoXG4gICAgJ0Zsb3dDaGFydEV4ZWN1dG9yOiBjYW5ub3QgYnVpbGQgdGhlIHBhdXNlIGNoZWNrcG9pbnQg4oCUIG5vbi1zZXJpYWxpemFibGUgdmFsdWUocykgaW4gJyArXG4gICAgICBgY2hlY2twb2ludCBmaWVsZChzKTogJHtmaWVsZHN9LiBUaGUgY2hlY2twb2ludCBjb250cmFjdCBpcyBKU09OLXNhZmUgKG5vIGZ1bmN0aW9ucywgbm8gYCArXG4gICAgICBcImNsYXNzIGluc3RhbmNlcykuIENoZWNrIHRoZSBwYXVzZURhdGEgcmV0dXJuZWQgYnkgdGhlIHBhdXNhYmxlIHN0YWdlJ3MgZXhlY3V0ZSgpLCBhbmQgYW55IFwiICtcbiAgICAgICdzdWJmbG93IHN0YXRlIGNhcHR1cmVkIGF0IHRoZSBwYXVzZS4gRGlhZ25vc3RpYyB2YWx1ZXMgZnJvbSAkZGVidWcvJG1ldHJpYy8kZXJyb3IvJGV2YWwgJyArXG4gICAgICAnYXJlIHNhbml0aXplZCBhdXRvbWF0aWNhbGx5IGFuZCBuZXZlciBjYXVzZSB0aGlzIGVycm9yLiAnICtcbiAgICAgICdTZWUgZG9jcy9ndWlkZXMvZXhlY3V0aW9uLW1vZGVsLm1kIChcIlBhdXNlIC8gcmVzdW1lIOKAlCB3aGF0IGEgY2hlY2twb2ludCBjYXB0dXJlc1wiKS4nLFxuICAgIHsgY2F1c2UgfSxcbiAgKTtcbn1cbiJdfQ==
@@ -11,7 +11,15 @@ import type { HandlerDeps } from '../types.js';
11
11
  export declare class NodeResolver<TOut = any, TScope = any> {
12
12
  private deps;
13
13
  private readonly nodeIdMap;
14
- constructor(deps: HandlerDeps<TOut, TScope>, nodeIdMap?: Map<string, StageNode<TOut, TScope>>);
14
+ private readonly getEffectiveChildren?;
15
+ constructor(deps: HandlerDeps<TOut, TScope>, nodeIdMap?: Map<string, StageNode<TOut, TScope>>,
16
+ /**
17
+ * Overlay-aware children accessor injected by the traverser. The DFS
18
+ * fallback resolves loop targets against the LIVE runtime shape — a node
19
+ * patched by a dynamic StageNode return carries its children in the
20
+ * traverser-local overlay, not on the shared built-chart node.
21
+ */
22
+ getEffectiveChildren?: (node: StageNode<TOut, TScope>) => StageNode<TOut, TScope>[] | undefined);
15
23
  /**
16
24
  * O(1) node lookup via pre-built ID map.
17
25
  * Falls back to DFS from startNode (for dynamic nodes added at runtime
@@ -21,7 +21,7 @@
21
21
  import type { ScopeProtectionMode } from '../../scope/protection/types.js';
22
22
  import { FlowRecorderDispatcher } from '../narrative/FlowRecorderDispatcher.js';
23
23
  import type { FlowRecorder, IControlFlowNarrative } from '../narrative/types.js';
24
- import type { IExecutionRuntime, ILogger, ScopeFactory, SerializedPipelineStructure, StageFunction, StageNode, StreamHandlers, SubflowResult, TraversalResult } from '../types.js';
24
+ import type { IExecutionRuntime, ILogger, ScopeFactory, Selector, SerializedPipelineStructure, StageFunction, StageNode, StreamHandlers, SubflowMountOptions, SubflowResult, TraversalResult } from '../types.js';
25
25
  export interface TraverserOptions<TOut = any, TScope = any> {
26
26
  root: StageNode<TOut, TScope>;
27
27
  stageMap: Map<string, StageFunction<TOut, TScope>>;
@@ -78,6 +78,37 @@ export interface TraverserOptions<TOut = any, TScope = any> {
78
78
  */
79
79
  runId: string;
80
80
  }
81
+ /**
82
+ * Traverser-local overlay entry for a node whose stage function returned a
83
+ * StageNode (dynamic continuation). Holds the dynamic values that earlier
84
+ * versions wrote DIRECTLY onto the shared built-chart node — which leaked the
85
+ * dynamic shape into every later run of the same built chart and raced
86
+ * concurrent executors. The overlay keeps the built graph immutable: patches
87
+ * live in a per-traverser Map keyed by `node.id` and die with the run.
88
+ *
89
+ * `next` is intentionally ABSENT: a dynamic `next` only ever applies to the
90
+ * visit that produced it (the old code wrote `node.next` and restored it
91
+ * before anything could observe the write), so it stays a local variable in
92
+ * `executeNode` and is routed through `ContinuationResolver` directly.
93
+ */
94
+ export interface DynamicNodePatch<TOut = any, TScope = any> {
95
+ /**
96
+ * Subflow mount metadata from a dynamic-subflow return. Grouped so the
97
+ * merged view reproduces the old field-wise overwrite exactly — including
98
+ * `subflowName`/`subflowMountOptions` becoming undefined when the dynamic
99
+ * return omitted them.
100
+ */
101
+ subflowMeta?: {
102
+ isSubflowRoot: true;
103
+ subflowId: string;
104
+ subflowName: string | undefined;
105
+ subflowMountOptions: SubflowMountOptions | undefined;
106
+ };
107
+ /** Dynamic fork children (replaces the built node's children for this run). */
108
+ children?: StageNode<TOut, TScope>[];
109
+ /** Dynamic output-based selector accompanying dynamic children. */
110
+ nextNodeSelector?: Selector;
111
+ }
81
112
  export declare class FlowchartTraverser<TOut = any, TScope = any> {
82
113
  private readonly root;
83
114
  private stageMap;
@@ -111,6 +142,21 @@ export declare class FlowchartTraverser<TOut = any, TScope = any> {
111
142
  * resolver before another traverser has finished using it.
112
143
  */
113
144
  private readonly resolvedLazySubflows;
145
+ /**
146
+ * Per-traverser overlay of dynamic StageNode returns, keyed by `node.id`.
147
+ * Phase 4 writes patches HERE instead of mutating the shared built-chart
148
+ * node objects (same isolation convention as `resolvedLazySubflows`).
149
+ * All engine reads of the patched fields go through the `eff*` accessors
150
+ * below. The map dies with the traverser — one run, one overlay — so a
151
+ * fresh executor over the same built chart always sees the original graph.
152
+ *
153
+ * Keyed by the node OBJECT (WeakMap), not `node.id`: a dynamic child that
154
+ * reuses a built node's id must NOT make the built node inherit the patch
155
+ * (id-keyed lookup caused phantom double-execution). `patchCount` is the
156
+ * fast-path check — WeakMap has no `size`.
157
+ */
158
+ private readonly dynamicPatches;
159
+ private patchCount;
114
160
  /**
115
161
  * Recursion depth counter for executeNode.
116
162
  * Each recursive executeNode call increments this; decrements on exit (try/finally).
@@ -203,6 +249,24 @@ export declare class FlowchartTraverser<TOut = any, TScope = any> {
203
249
  */
204
250
  private buildNodeIdMap;
205
251
  private getStageFn;
252
+ private getPatch;
253
+ private getOrCreatePatch;
254
+ /** Effective children: dynamic patch first, then the built node's children. */
255
+ private effChildren;
256
+ /** Effective output-based selector: dynamic patch first, then the built node's. */
257
+ private effSelector;
258
+ /** Effective subflow-root marker (true when a dynamic subflow was patched on). */
259
+ private effIsSubflowRoot;
260
+ /** Effective subflow id (patched verbatim by a dynamic subflow return). */
261
+ private effSubflowId;
262
+ /**
263
+ * Materialize the effective view of a node — field-identical to what the
264
+ * pre-overlay code produced by mutating the shared node. Used where a node
265
+ * is handed to a helper executor (NodeResolver / SubflowExecutor /
266
+ * ChildrenExecutor) so helpers never read stale built fields. Returns the
267
+ * node itself (no allocation) when it carries no patch.
268
+ */
269
+ private effNode;
206
270
  private executeStage;
207
271
  /**
208
272
  * Pre-order DFS traversal — the core algorithm.
@@ -153,6 +153,11 @@ export declare class FlowChartExecutor<TOut = any, TScope = any> {
153
153
  *
154
154
  * The checkpoint is JSON-serializable — store it in Redis, Postgres, localStorage, etc.
155
155
  *
156
+ * It is fully DETACHED from engine state: every field was deep-copied at
157
+ * pause time (see `buildPauseCheckpoint`). Holding, mutating, or persisting
158
+ * it cannot affect the executor, and a later same-executor resume cannot
159
+ * mutate a checkpoint you already stored.
160
+ *
156
161
  * @example
157
162
  * ```typescript
158
163
  * const result = await executor.run({ input });
@@ -208,6 +213,47 @@ export declare class FlowChartExecutor<TOut = any, TScope = any> {
208
213
  * ```
209
214
  */
210
215
  resume(checkpoint: FlowchartCheckpoint, resumeInput?: unknown, options?: Pick<RunOptions, 'signal' | 'env' | 'maxDepth'>): Promise<ExecutorResult>;
216
+ /**
217
+ * Build a fully DETACHED checkpoint from a caught PauseSignal.
218
+ *
219
+ * Every field is deep-copied via one `structuredClone` of the assembled
220
+ * checkpoint, because the raw pieces alias live engine state:
221
+ *
222
+ * - `sharedState` IS `SharedMemory`'s internal context object — the alias
223
+ * only detaches at the next commit (`applySmartMerge` rebuilds it), and
224
+ * after a pause there is no next commit until resume.
225
+ * - `executionTree` nodes are fresh, but their `logs`/`errors`/`metrics`/
226
+ * `evals`/`stageReads`/`flowMessages` fields reference live
227
+ * `DiagnosticCollector` bags that keep accumulating on same-executor
228
+ * resume.
229
+ * - `subflowStates` values are shallow copies whose NESTED objects alias
230
+ * subflow memory, and they get seeded back into live runtimes on resume.
231
+ * - `subflowResults` values stay referenced by the traverser's results map.
232
+ *
233
+ * The checkpoint is persisted by contract ("store in Redis/Postgres") — it
234
+ * must never share structure with the engine. Pause is not a hot path; the
235
+ * clone cost is irrelevant.
236
+ *
237
+ * The JSON-safe checkpoint contract (no functions, no class instances)
238
+ * governs CONSUMER-owned data — but the executionTree's diagnostic bags
239
+ * accept ANY value at write time without cloning ($debug/$error/$metric/
240
+ * $eval store raw references), so a contract-compliant run can still carry
241
+ * a non-cloneable diagnostic. Observability side-bags never abort traversal
242
+ * anywhere else in the library, so they must not abort the pause either:
243
+ * on clone failure we sanitize the diagnostic bags (non-cloneable values
244
+ * become '[non-serializable: …]' markers — the live engine bags are never
245
+ * touched) and retry. If the retry STILL fails, the violation is in
246
+ * consumer-owned data (realistically `pauseData` — a function in shared
247
+ * state already rejects at stage entry when TransactionBuffer clones the
248
+ * context) and we throw a DESCRIPTIVE contract error naming the offending
249
+ * checkpoint field(s). A naked DataCloneError never escapes.
250
+ *
251
+ * Subflow scope capture (`subflowStates`) survives ONLY on the signal — the
252
+ * nested runtimes are GC'd as the stack unwinds. Promoting it onto the
253
+ * checkpoint here lets cross-executor resume restore pre-pause subflow
254
+ * scope (e.g. an Agent's `scope.history`). Empty `{}` for root-level pauses.
255
+ */
256
+ private buildPauseCheckpoint;
211
257
  /**
212
258
  * Find a StageNode in the compiled graph by ID.
213
259
  * Handles subflow paths by drilling into registered subflows.
@@ -405,6 +451,11 @@ export declare class FlowChartExecutor<TOut = any, TScope = any> {
405
451
  *
406
452
  * The commit log is already redacted at write-time regardless of this
407
453
  * flag, and the execution tree carries only structural metadata.
454
+ *
455
+ * **Treat `sharedState` as READ-ONLY.** In production it is a live view of
456
+ * the engine's working memory (zero copy cost) — mutating it corrupts
457
+ * engine state. In dev mode (`enableDevMode()`) it is a deep-frozen CLONE,
458
+ * so any consumer mutation throws loudly instead of corrupting silently.
408
459
  */
409
460
  getSnapshot(options?: {
410
461
  redact?: boolean;
@@ -0,0 +1,44 @@
1
+ /**
2
+ * checkpointSanitize — clone-resilience helpers for `buildPauseCheckpoint`.
3
+ *
4
+ * The pause checkpoint is detached via one `structuredClone` of the assembled
5
+ * checkpoint. The JSON-safe contract governs what CONSUMERS put into a
6
+ * checkpoint (pauseData, shared state) — but the execution tree's diagnostic
7
+ * bags (`logs`/`errors`/`metrics`/`evals`) accept ANY value at write time
8
+ * without cloning (`$debug`/`$error`/`$metric`/`$eval` route through
9
+ * `DiagnosticCollector`, which stores raw references). A `$debug`'d function
10
+ * in any stage of a pausing run would make the whole-checkpoint clone throw
11
+ * `DataCloneError` — swallowing the pause.
12
+ *
13
+ * That violates the library's error-isolation grain: observability side-bags
14
+ * never abort traversal anywhere else. These helpers restore the grain:
15
+ *
16
+ * - `sanitizeDiagnosticBags` — replace non-cloneable diagnostic values with
17
+ * marker strings (`'[non-serializable: function]'`) so the pause survives.
18
+ * - `describeCheckpointCloneFailure` — when the clone STILL fails after
19
+ * sanitization (the non-cloneable lives in consumer-owned data, e.g.
20
+ * `pauseData`), name the offending checkpoint field(s) and point at the
21
+ * JSON-safe contract instead of letting a naked `DataCloneError` escape.
22
+ *
23
+ * Both run ONLY on the clone-failure path of a pause — never on the hot path.
24
+ */
25
+ import type { StageSnapshot } from '../memory/types.js';
26
+ /**
27
+ * Walk a `StageSnapshot` tree (via `next` + `children`) and sanitize the four
28
+ * diagnostic bags on every node IN PLACE.
29
+ *
30
+ * In-place is safe and intentional: `StageContext.getSnapshot()` builds fresh
31
+ * node objects on every call, but the bag fields on those fresh nodes ALIAS
32
+ * the live `DiagnosticCollector` bags. We replace the node's bag REFERENCE
33
+ * with a sanitized copy — the live engine bags are never mutated, so a
34
+ * same-executor resume keeps the original diagnostic values.
35
+ */
36
+ export declare function sanitizeDiagnosticBags(tree: StageSnapshot): StageSnapshot;
37
+ /**
38
+ * Build the DESCRIPTIVE error for a checkpoint that still cannot be cloned
39
+ * after diagnostic-bag sanitization — i.e. the non-cloneable value lives in
40
+ * consumer-owned data (a genuine JSON-safe contract violation). Probes each
41
+ * top-level checkpoint field individually so the message names the offending
42
+ * field family. Never lets a naked `DataCloneError` escape.
43
+ */
44
+ export declare function describeCheckpointCloneFailure(checkpoint: Record<string, unknown>, cause: unknown): Error;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "footprintjs",
3
- "version": "8.1.0",
3
+ "version": "8.3.0",
4
4
  "description": "Explainable backend flows — automatic causal traces, decision evidence, and MCP tool generation for AI agents",
5
5
  "license": "MIT",
6
6
  "author": "Sanjay Krishna Anbalagan",
@@ -53,7 +53,11 @@
53
53
  "lint:fix": "eslint '{src,test}/**/*.ts' --ext .ts --fix",
54
54
  "format:fix": "prettier --config .prettierrc.js --write '{src,test}/**/*.ts'",
55
55
  "format": "prettier --config .prettierrc.js --list-different '{src,test}/**/*.ts'",
56
- "bench": "npx tsx bench/run.ts",
56
+ "bench": "npm run bench:micro && npm run bench:baseline && npm run bench:depth",
57
+ "bench:micro": "npx tsx bench/run.ts",
58
+ "bench:baseline": "npx tsx bench/baseline.ts",
59
+ "bench:depth": "npx tsx bench/depth-probe.ts",
60
+ "bench:typecheck": "tsc -p bench/tsconfig.json",
57
61
  "docs": "typedoc",
58
62
  "docs:serve": "typedoc && npx serve docs",
59
63
  "test:coverage": "vitest run --coverage --coverage.reporter=json-summary --coverage.reporter=text-summary --coverage.reportsDirectory=coverage",