footprintjs 8.0.0 → 8.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CLAUDE.md +4 -1
- package/README.md +1 -1
- package/dist/esm/lib/memory/StageContext.js +4 -2
- package/dist/esm/lib/runner/FlowChartExecutor.js +157 -44
- package/dist/esm/lib/runner/checkpointSanitize.js +133 -0
- package/dist/lib/memory/StageContext.js +4 -2
- package/dist/lib/runner/FlowChartExecutor.js +157 -44
- package/dist/lib/runner/checkpointSanitize.js +138 -0
- package/dist/types/lib/memory/StageContext.d.ts +3 -1
- package/dist/types/lib/runner/FlowChartExecutor.d.ts +61 -0
- package/dist/types/lib/runner/checkpointSanitize.d.ts +44 -0
- package/package.json +6 -2
|
@@ -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==
|
|
@@ -66,7 +66,9 @@ export declare class StageContext {
|
|
|
66
66
|
useRedactedMirror(mirror: SharedMemory): void;
|
|
67
67
|
/** Returns the redacted mirror if installed, else undefined. */
|
|
68
68
|
getRedactedSharedMemory(): SharedMemory | undefined;
|
|
69
|
-
/** Lazily creates the transaction buffer
|
|
69
|
+
/** Lazily creates the transaction buffer on the stage's FIRST state access —
|
|
70
|
+
* reads included, so read-only stages currently pay the clone cost too.
|
|
71
|
+
* (Truly lazy-on-write is backlog Phase-3 #13.) */
|
|
70
72
|
getTransactionBuffer(): TransactionBuffer;
|
|
71
73
|
/** Builds an absolute path inside the shared memory (run namespace). */
|
|
72
74
|
private withNamespace;
|
|
@@ -107,6 +107,16 @@ export declare class FlowChartExecutor<TOut = any, TScope = any> {
|
|
|
107
107
|
* scope. See `test/lib/pause/cross-executor-resume.test.ts`.
|
|
108
108
|
*/
|
|
109
109
|
private _hasRunBefore;
|
|
110
|
+
/**
|
|
111
|
+
* Re-entrancy guard. `run()` and `resume()` mutate per-run instance state
|
|
112
|
+
* (traverser, runId, execution counter, checkpoint) and clear attached
|
|
113
|
+
* recorders — a second concurrent entry on the SAME executor would
|
|
114
|
+
* interleave runIds and cross-contaminate recorder/narrative state, and
|
|
115
|
+
* `getCheckpoint()` would return whichever run paused last. One executor =
|
|
116
|
+
* one in-flight execution; create an executor per concurrent run.
|
|
117
|
+
* See docs/guides/execution-model.md.
|
|
118
|
+
*/
|
|
119
|
+
private _isExecuting;
|
|
110
120
|
private readonly flowChartArgs;
|
|
111
121
|
/**
|
|
112
122
|
* Create a FlowChartExecutor.
|
|
@@ -143,6 +153,11 @@ export declare class FlowChartExecutor<TOut = any, TScope = any> {
|
|
|
143
153
|
*
|
|
144
154
|
* The checkpoint is JSON-serializable — store it in Redis, Postgres, localStorage, etc.
|
|
145
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
|
+
*
|
|
146
161
|
* @example
|
|
147
162
|
* ```typescript
|
|
148
163
|
* const result = await executor.run({ input });
|
|
@@ -198,6 +213,47 @@ export declare class FlowChartExecutor<TOut = any, TScope = any> {
|
|
|
198
213
|
* ```
|
|
199
214
|
*/
|
|
200
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;
|
|
201
257
|
/**
|
|
202
258
|
* Find a StageNode in the compiled graph by ID.
|
|
203
259
|
* Handles subflow paths by drilling into registered subflows.
|
|
@@ -395,6 +451,11 @@ export declare class FlowChartExecutor<TOut = any, TScope = any> {
|
|
|
395
451
|
*
|
|
396
452
|
* The commit log is already redacted at write-time regardless of this
|
|
397
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.
|
|
398
459
|
*/
|
|
399
460
|
getSnapshot(options?: {
|
|
400
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.
|
|
3
|
+
"version": "8.2.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": "
|
|
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",
|