footprintjs 9.44.0 → 9.44.2

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 (38) hide show
  1. package/CLAUDE.md +6 -5
  2. package/dist/esm/lib/capture/freeze.d.ts +51 -7
  3. package/dist/esm/lib/capture/freeze.js +289 -24
  4. package/dist/esm/lib/capture/valueKinds.d.ts +57 -0
  5. package/dist/esm/lib/capture/valueKinds.js +142 -0
  6. package/dist/esm/lib/memory/EventLog.d.ts +24 -6
  7. package/dist/esm/lib/memory/EventLog.js +30 -10
  8. package/dist/esm/lib/memory/admission.js +4 -3
  9. package/dist/esm/lib/memory/borrowedMutation.js +2 -2
  10. package/dist/esm/lib/memory/equality.d.ts +55 -19
  11. package/dist/esm/lib/memory/equality.js +154 -51
  12. package/dist/esm/lib/memory/logModel.js +11 -4
  13. package/dist/esm/lib/memory/redaction.d.ts +21 -0
  14. package/dist/esm/lib/memory/redaction.js +64 -7
  15. package/dist/esm/lib/runner/ExecutionRuntime.d.ts +4 -1
  16. package/dist/esm/lib/runner/ExecutionRuntime.js +15 -10
  17. package/dist/esm/lib/runner/snapshot.js +17 -3
  18. package/dist/esm/lib/slice/elementProvenance.js +2 -2
  19. package/dist/esm/lib/time-travel/chain.js +28 -26
  20. package/dist/lib/capture/freeze.js +292 -25
  21. package/dist/lib/capture/valueKinds.js +146 -0
  22. package/dist/lib/memory/EventLog.js +29 -9
  23. package/dist/lib/memory/admission.js +4 -3
  24. package/dist/lib/memory/borrowedMutation.js +2 -2
  25. package/dist/lib/memory/equality.js +154 -51
  26. package/dist/lib/memory/logModel.js +11 -4
  27. package/dist/lib/memory/redaction.js +66 -8
  28. package/dist/lib/runner/ExecutionRuntime.js +14 -9
  29. package/dist/lib/runner/snapshot.js +16 -2
  30. package/dist/lib/slice/elementProvenance.js +2 -2
  31. package/dist/lib/time-travel/chain.js +27 -25
  32. package/dist/types/lib/capture/freeze.d.ts +51 -7
  33. package/dist/types/lib/capture/valueKinds.d.ts +57 -0
  34. package/dist/types/lib/memory/EventLog.d.ts +24 -6
  35. package/dist/types/lib/memory/equality.d.ts +55 -19
  36. package/dist/types/lib/memory/redaction.d.ts +21 -0
  37. package/dist/types/lib/runner/ExecutionRuntime.d.ts +4 -1
  38. package/package.json +2 -1
@@ -1,21 +1,42 @@
1
1
  /**
2
- * freeze.ts — the ONE deep-freeze walk (moved here from `scope/protection/readonlyInput.ts` in F3).
2
+ * freeze.ts — the ONE deep-freeze walk (moved here from `scope/protection/readonlyInput.ts` in F3),
3
+ * and the serve-time law for the commit log (9.44.2).
3
4
  *
4
5
  * WHY HERE. Saved trees freeze through this walk: the fold base served as
5
6
  * `initialState` (`ExecutionRuntime · getFoldBase`), the
6
7
  * dev-mode snapshot (`runner/snapshot.ts · servedSnapshot`), every state `stateAt` hands out
7
8
  * (`time-travel/stateAt.ts`) and, since F3, every commit bundle (`EventLog · record`). `memory/` must
8
9
  * not import `scope/` (that edge closed the memory ⇄ scope ⇄ recorder module cycle F0 removed), so the
9
- * walk lives in this leaf, which imports nothing. Args use an ownership snapshot instead
10
- * (`scope/protection/readonlyInput.ts · createFrozenArgs`): never freeze borrowed caller values.
10
+ * walk lives in this leaf folder, which imports nothing outside itself. Args use an ownership snapshot
11
+ * instead (`scope/protection/readonlyInput.ts · createFrozenArgs`): never freeze borrowed caller values.
12
+ *
13
+ * FREEZE WHAT CAN BE FROZEN, COPY WHAT CAN'T (9.44.2). `Object.freeze` cannot seal a Date's time, a
14
+ * Map's or Set's entries, a buffer's bytes, a RegExp (`compile()` rewrites a frozen one) or an Error
15
+ * (V8's own `stack` accessor writes through a frozen one). A fresh tree handed to one caller (a `stateAt`
16
+ * state, the dev-mode snapshot) is that caller's own, so freezing it is a guard and nothing more. The
17
+ * COMMIT LOG — each bundle, and the fold base — is served to every reader, so {@link freezeRecord}
18
+ * remembers that a record holds such a value, its first serve maps the containers on the way to each
19
+ * one (its open PATHS), and {@link serveRecord} hands a reader a copy of those paths only: a fresh copy
20
+ * of each such value, fresh frozen containers on the way to it, and every other part shared, frozen, as
21
+ * it is. Until 9.44.2 `snapshot.commitLog[i].overwrite.when.setTime(0)` rewrote every later snapshot,
22
+ * `stateAt`, `commitValueAt`, slice and cursor answer.
23
+ *
24
+ * Every walk here is ITERATIVE (an explicit stack): a record's depth is not bounded by the call stack.
11
25
  *
12
26
  * @example
13
27
  * ```typescript
14
- * import { deepFreeze } from './freeze.js';
28
+ * import { deepFreeze, freezeRecord, serveRecord } from './freeze.js';
15
29
  *
16
30
  * const args = deepFreeze({ order: { lines: [{ sku: 'A' }] }, bytes: new Uint8Array([1]) });
17
31
  * Object.isFrozen(args.order.lines[0]); // true
18
32
  * Object.isFrozen(args.bytes); // false — a typed array cannot be frozen; it is skipped
33
+ *
34
+ * const record = freezeRecord({ when: new Date(0), tags: ['a'] });
35
+ * const served = serveRecord(record);
36
+ * served.when.setTime(5); // changes the reader's copy
37
+ * serveRecord(record).when.getTime(); // 0 — every serve is the record as recorded
38
+ * served.tags === record.tags; // true — the sealed part is shared, not copied
39
+ * serveRecord(freezeRecord({ n: 1 })); // a record freezing sealed whole: served as itself
19
40
  * ```
20
41
  */
21
42
  /**
@@ -25,8 +46,8 @@
25
46
  * AND any expando (`arr.note = { … }`). The dev-mode snapshot relies on this contract.
26
47
  * - `'indices'` — the index elements only, so no key string is allocated per element; this is what
27
48
  * keeps freezing a 10,000-element commit inside its budget (`bench/element-writes.ts`). An object
28
- * hung on an array EXPANDO is then left unfrozen — the commit log's named hole
29
- * (`EventLog · record`).
49
+ * hung on an array EXPANDO is then left unfrozen, and unseen by {@link serveRecord} — the commit
50
+ * log's named hole (`EventLog · record`; an array expando is out of contract for state).
30
51
  */
31
52
  export type ArrayWalk = 'every-key' | 'indices';
32
53
  /**
@@ -41,7 +62,30 @@ export type ArrayWalk = 'every-key' | 'indices';
41
62
  * snapshot here.
42
63
  * - What `Object.freeze` cannot reach stays mutable — Map and Set contents, a Date's time, the
43
64
  * bytes behind a buffer. A frozen RegExp's `lastIndex` is read-only, so a `/g` or `/y` regex
44
- * taken from a frozen tree throws when `exec` or `replace` advance it.
65
+ * taken from a frozen tree throws when `exec` or `replace` advance it. The commit log goes through
66
+ * {@link freezeRecord} and {@link serveRecord} instead.
45
67
  * - Functions are not descended (they are not values the engine stores).
46
68
  */
47
69
  export declare function deepFreeze<T>(obj: T, arrays?: ArrayWalk): T;
70
+ /**
71
+ * {@link deepFreeze} for a record many readers are SERVED (a commit bundle, the fold base), and two more
72
+ * things on the same walk:
73
+ *
74
+ * - it remembers THAT the record holds a value freezing cannot seal — one flag; the record's first
75
+ * serve maps WHERE (the containers on the way to each such value, its open paths), so the run pays
76
+ * nothing more and {@link serveRecord} copies those paths and nothing else;
77
+ * - a typed array or `DataView` that views a SLICE of a bigger buffer (a Node `Buffer` views its
78
+ * shared pool) is replaced, in the record, by a copy of the bytes it views — the record keeps the
79
+ * view's bytes, never the rest of the buffer (a resizable or shared buffer is kept as it is).
80
+ *
81
+ * A part frozen before is not frozen past (deepFreeze's contract) but is looked through, so a value
82
+ * freezing cannot seal inside it still opens the record. Returns `record`.
83
+ */
84
+ export declare function freezeRecord<T extends object>(record: T, arrays?: ArrayWalk): T;
85
+ /**
86
+ * The record as a reader may hold it — THE serve-time law: a record freezing sealed whole is served as
87
+ * itself (shared, no cost); any other as a copy of its open paths ({@link freezeRecord}): fresh frozen
88
+ * containers down to each value freezing cannot seal, a fresh copy of that value, and every other part
89
+ * shared, frozen, as it is. A copy is served the same way again.
90
+ */
91
+ export declare function serveRecord<T>(record: T): T;
@@ -0,0 +1,57 @@
1
+ /**
2
+ * valueKinds.ts — what KIND of value a record holds: the one classifier.
3
+ *
4
+ * WHY. Committed state is what survives `structuredClone` (an engine invariant), so the values a
5
+ * record holds are the kinds the clone keeps. Two laws need the kind, and both ask here:
6
+ *
7
+ * - what counts as a CHANGE (`memory/equality.ts · deepEqual`): two values are equal when a record
8
+ * holds the same thing for both, compared kind by kind. Until 9.44.2 only Date, Map and Set had a
9
+ * kind; a RegExp, an Error, a boxed number, a buffer, a DataView or a Blob was compared as an
10
+ * object with no own keys — equal to any other of its kind — so replacing one committed no row
11
+ * and live state kept the old value;
12
+ * - what a served record must COPY (`capture/freeze.ts · freezeRecord` / `serveRecord`):
13
+ * `Object.freeze` seals only the kinds whose content is their own properties ({@link SEALABLE}).
14
+ *
15
+ * A leaf: imports nothing (`capture/` is L0, below `memory/`).
16
+ *
17
+ * @example
18
+ * ```typescript
19
+ * kindOf(new Date(0)); // 'date'
20
+ * kindOf(new (class Point { x = 1 })()); // 'plain' — its clone is a plain object
21
+ * kindOf(new Blob(['a'])); // 'opaque' — its clone keeps content this library cannot read
22
+ * SEALABLE.date; // false — setTime writes a slot Object.freeze cannot reach
23
+ * ```
24
+ */
25
+ /** Every kind a record can hold. */
26
+ export type ValueKind =
27
+ /** An object the clone keeps as its own enumerable keys — a class instance included. */
28
+ 'plain' | 'array' | 'date' | 'regexp' | 'map' | 'set' | 'error'
29
+ /** A boxed primitive: `new Number(1)`, `new String('a')`, `new Boolean(true)`, `Object(1n)`. */
30
+ | 'boxed'
31
+ /** An `ArrayBuffer` (a `SharedArrayBuffer` too: shared memory is out of contract for state). */
32
+ | 'buffer'
33
+ /** A typed array or a `DataView`. */
34
+ | 'view'
35
+ /** Any other object the clone keeps as itself (a Blob, a File, a `DOMException`): content unread here. */
36
+ | 'opaque';
37
+ /**
38
+ * Does `Object.freeze` seal every part of a value of this kind? True only where the content is the
39
+ * value's own properties (or a slot nothing can write). A `Record`, so a new kind does not compile
40
+ * until it is decided here.
41
+ */
42
+ export declare const SEALABLE: {
43
+ readonly [K in ValueKind]: boolean;
44
+ };
45
+ /**
46
+ * The kind of an object, as a record holds it. A plain object or an array — the bulk of state —
47
+ * costs one `Array.isArray` and one `getPrototypeOf`; a built-in a tag and one brand-checked read.
48
+ * The brand is the value's own slot, so a subclass (a `Map` subclass is a `'map'`, Node's `Buffer` a
49
+ * `'view'`) and a value from another realm land on their kind.
50
+ *
51
+ * Any other object is classified by WHAT ITS CLONE IS, once per prototype: a class instance comes
52
+ * back a plain object (`'plain'`), a fake built-in (`Object.create(Date.prototype)`, or a class whose
53
+ * `Symbol.toStringTag` says `'Date'`) comes back `{}` (`'plain'`), and a host object comes back as
54
+ * itself (`'opaque'`). A prototype whose instance the clone refuses is `'opaque'` — remembered too:
55
+ * it cannot be held, and its commit will refuse it.
56
+ */
57
+ export declare function kindOf(value: object): ValueKind;
@@ -13,11 +13,17 @@
13
13
  * (`verbs.ts · foldRows` detaches, `StageContext · retainedForm` clones) — pinned by
14
14
  * test/lib/memory/property/record-reachability.property.test.ts.
15
15
  *
16
- * Named holes — what `Object.freeze` cannot reach stays mutable: Map and Set contents, a Date's
17
- * time, the bytes of a typed array (skipped: a non-empty one cannot be frozen), and an object hung
18
- * on an array EXPANDO (arrays are walked by index — `deepFreeze`'s `'indices'` walk, which keeps
19
- * the freeze inside its budget). A frozen RegExp's `lastIndex` is read-only, so a `/g` or `/y`
20
- * regex read from a bundle throws when `exec` or `replace` advance it.
16
+ * What `Object.freeze` cannot seal — a Date's time, a Map's or Set's entries, a buffer's bytes, a
17
+ * RegExp, an Error's stack — is never handed out to a reader (9.44.2): `record` remembers that a
18
+ * bundle holds such a value (`capture/freeze.ts · freezeRecord`), and `list()` — like the public
19
+ * snapshot, `runner/snapshot.ts · servedSnapshot` — serves that bundle as a copy of its open paths
20
+ * (`serveRecord`, mapped at its first serve): fresh frozen containers down to each such value, a
21
+ * fresh copy of it, every other part the log's own. A reader edits only its own copy; every later
22
+ * serve reads the record as recorded. `recorded()` is the engine's own, unserved read. Named holes: an object hung on an array EXPANDO (arrays are walked
23
+ * by index — the `'indices'` walk, which keeps the freeze inside its budget; an expando is out of
24
+ * contract for state) and a `SharedArrayBuffer` (every copy shares its memory). A frozen RegExp's
25
+ * `lastIndex` is read-only, so a `/g` or `/y` regex read from a bundle throws when `exec` or
26
+ * `replace` advance it.
21
27
  */
22
28
  import type { EmitSourcePosition, LogAddress } from './eventPosition.js';
23
29
  import type { CommitBundle } from './types.js';
@@ -72,8 +78,20 @@ export declare class EventLog {
72
78
  * will no longer edit — its own object is the one frozen.
73
79
  */
74
80
  record(bundle: CommitBundle): void;
75
- /** Gets all recorded commit bundles — the live array; every bundle in it is frozen. */
81
+ /**
82
+ * Every recorded commit bundle, AS SERVED (9.44.2): a new array each call; a bundle freezing sealed
83
+ * whole is the log's own (frozen) object, one holding a Date, Map, buffer … is a copy of its open
84
+ * paths (`capture/freeze.ts · serveRecord`) — the reader's own, so no edit reaches the log.
85
+ */
76
86
  list(): CommitBundle[];
87
+ /**
88
+ * @internal The bundles as the log holds them — each frozen, NOT served — in a new array, so a holder's
89
+ * `splice` reaches only its own. For the engine's own snapshot (`ExecutionRuntime · getSnapshot`, which
90
+ * a subflow mount takes once per mount), so the run serves nothing; a reader is served by `list()` and
91
+ * `getSnapshot().commitLog`. A known open door (CHANGELOG 9.44.2): a holder of these bundles can still
92
+ * edit a Date, Map or buffer inside them.
93
+ */
94
+ recorded(): CommitBundle[];
77
95
  /** Number of recorded commits. */
78
96
  get length(): number;
79
97
  /** Immutable identity of this log, absent until the engine binds it. */
@@ -1,8 +1,10 @@
1
1
  /**
2
- * equality.ts — structural equality of committed-state values. A leaf (L0,
3
- * imports nothing): what `TransactionBuffer`'s net-change filter, the admitted
4
- * record's compare and the borrowed-mutation guard all ask. Re-exported from
5
- * `utils.ts`, so no importer moved.
2
+ * equality.ts — structural equality of committed-state values: THE owner of
3
+ * "what counts as a change". What `TransactionBuffer`'s net-change filter, the
4
+ * admitted record's compare, the read model's writer rule and the
5
+ * borrowed-mutation guard all ask. Its one import is the classifier of value
6
+ * kinds (`capture/valueKinds.ts`, a leaf); re-exported from `utils.ts`, so no
7
+ * importer moved.
6
8
  */
7
9
  /**
8
10
  * Structural deep equality for committed-state values.
@@ -10,26 +12,42 @@
10
12
  * Used by {@link TransactionBuffer} to decide whether a stage actually CHANGED
11
13
  * a path or merely re-wrote / reverted it to the value it already held (a
12
14
  * "no-op write"), and by `borrowedMutation.firstDifferingPath` to find where a
13
- * borrowed read moved. Committed state must survive `structuredClone`, and
14
- * `structuredClone` keeps `Date`, `Map` and `Set` — so those are legal state
15
- * values (a `$setValue` bypasses the proxy's JSON round-trip) and this must
16
- * tell two of them apart. Before 9.22.0 it compared them as plain objects, and
17
- * a `Date`/`Map`/`Set` has no own enumerable keys: every pair was "equal", so
18
- * `$setValue('k', new Date(1999))` over a 2020 date was dropped as a no-op —
19
- * no trace row, state unchanged — and a `setFullYear` in place slipped past
20
- * the dev-mode guard.
15
+ * borrowed read moved. Committed state must survive `structuredClone`, so the
16
+ * values it can hold are the kinds the clone keeps, and two values are equal
17
+ * when a record holds the same thing for both. Every kind has ONE arm below
18
+ * (`equalPairs`' switch over `ValueKind` — a kind added to the union does not
19
+ * compile until it is compared). Before 9.22.0 a Date, Map or Set was compared
20
+ * as an object with no own keys, and until 9.44.2 so were a RegExp, an Error,
21
+ * a boxed primitive, an ArrayBuffer, a DataView and a Blob: any two of a kind
22
+ * were "equal", so `$setValue('re', /y/g)` over `/x/g` committed no row and
23
+ * live state kept the OLD value.
21
24
  *
22
25
  * Semantics:
23
26
  * - reference / identical-primitive short-circuits first (cheap fast path)
24
27
  * - `NaN` equals `NaN` (primitive compare falls back to `Object.is`)
28
+ * - two values of different KINDS are never equal (a `Date` is never `{}`,
29
+ * an array never `{ 0: … }`)
25
30
  * - `Date`: same `getTime()` (two invalid dates are equal)
31
+ * - `RegExp`: same `source` and `flags` (the clone resets `lastIndex`)
32
+ * - `Error`: the same restored kind (one of the six built-in error names,
33
+ * else `Error`), own `message`, `stack` and own `cause` (deep) — what the
34
+ * clone keeps; any other own field is dropped by it, so it never counts
35
+ * (the admitted record's law)
36
+ * - a boxed primitive: the same wrapper type and the same primitive inside
37
+ * - an `ArrayBuffer`: length, resizability and bytes; a typed array or a
38
+ * `DataView`: its type and the bytes IT views — never the rest of its
39
+ * buffer (a Node `Buffer` views a slice of a shared pool, and the record
40
+ * keeps the view's bytes only: `capture/freeze.ts · freezeRecord`)
26
41
  * - `Map`: same size AND, per key of `a`, `b` holds a deep-equal value —
27
42
  * keys by `Map` identity (`SameValueZero`), values deep
28
43
  * - `Set`: same size AND every member of `a` is deep-equal to SOME member
29
44
  * of `b` (order-insensitive — a Set has no order to compare by)
30
- * - a typed value against a plain value, or two different typed kinds → not
31
- * equal (a `Date` is never `{}`)
32
- * - arrays: equal length AND deep-equal element-wise (order-sensitive)
45
+ * - an OPAQUE value (a Blob, a `DOMException` — content this library cannot
46
+ * read): by the caller's {@link OpaqueRule} — equal only to itself by
47
+ * default (replacing one is always a change), or, when both sides are
48
+ * copies of one record, equal to any other of its kind
49
+ * - arrays: equal length AND deep-equal element-wise (order-sensitive; an
50
+ * array's expando keys are out of contract and not compared)
33
51
  * - objects: identical set of keys that HOLD a value AND deep-equal per key.
34
52
  * An own key whose value is `undefined` counts as ABSENT: it is this
35
53
  * library's other spelling of a deleted key (`'full'` mode flattens
@@ -38,11 +56,13 @@
38
56
  * mirror — reads the two spellings as one state. So must the net-change
39
57
  * filter (9.19.1): `{}` and `{ k: undefined }` are not a change. Arrays
40
58
  * are untouched — a slot holding `undefined` is still a slot.
41
- * - mismatched kinds (array vs object, object vs null) → not equal
42
59
  *
43
60
  * Cost & safety:
44
- * - Allocates NOTHING but transient `Object.keys` arrays — no clones. It is
45
- * strictly cheaper than the `structuredClone` the commit already performs.
61
+ * - Allocates only transient `Object.keys` arrays, an error's property
62
+ * descriptors and a byte view per compared buffer — no clones (but one
63
+ * probe clone per class prototype, remembered: `capture/valueKinds.ts ·
64
+ * kindOf`). It is strictly cheaper than the `structuredClone` the commit
65
+ * already performs.
46
66
  * - Primitive comparisons (the bulk of state) are O(1) via the `===` /
47
67
  * `Object.is` fast paths; only nested objects/arrays incur a walk, bounded
48
68
  * by the value's own size.
@@ -54,4 +74,20 @@
54
74
  * change. Dev mode still warns about cycles at write time
55
75
  * (`ScopeFacade.setValue`) because they surprise narrative and JSON.
56
76
  */
57
- export declare function deepEqual(a: any, b: any): boolean;
77
+ export declare function deepEqual(a: any, b: any, opaque?: OpaqueRule): boolean;
78
+ /**
79
+ * How {@link deepEqual} compares two OPAQUE values (a Blob, a `DOMException` —
80
+ * content this library cannot read):
81
+ *
82
+ * - `'identity'` (the default) — equal only to itself. The answer when one
83
+ * side is a value a stage handed in: the net-change filter
84
+ * (`TransactionBuffer`), the append check (`deltaEncoding`). A new object
85
+ * is a change, because nothing can say it is not.
86
+ * - `'copies'` — equal to any other of its kind. The answer when BOTH sides
87
+ * are copies of one recorded value — a fold's generations (`logModel`,
88
+ * `elementProvenance`), a retained read against live state
89
+ * (`borrowedMutation`), the record replayed against the read-back
90
+ * (`admission`): copies never share identity, so identity would call every
91
+ * opaque value changed.
92
+ */
93
+ export type OpaqueRule = 'identity' | 'copies';
@@ -402,8 +402,29 @@ export declare function useWalkLimit(limit: number): () => void;
402
402
  * inside one segment — and an ancestor whose NAME matches is masked whole
403
403
  * before the walk goes below it, so the name decides. Conservative: `true`
404
404
  * whenever in doubt.
405
+ *
406
+ * Linear in the length of `pattern.source`: every scan here is one forward
407
+ * pass ({@link classBodies}) or a regex that does constant work per position.
405
408
  */
406
409
  export declare function needsPath(pattern: RegExp): boolean;
410
+ /**
411
+ * The body of each character class in `source`, left to right: the text
412
+ * between a `[` and the first `]` after it that no `\` escapes (a `\` takes
413
+ * the character after it along). ONE forward pass.
414
+ *
415
+ * It replaces `source.matchAll(/\[((?:\\[\s\S]|[^\]\\])*)\]/g)` and gives the
416
+ * same bodies for every string (pinned against that regex as the control,
417
+ * test/lib/memory/security/needs-path-linear.security.test.ts). The regex was
418
+ * quadratic: after a `[` that never closed it retried at every later `[`,
419
+ * each retry rescanning to the end — `\[` repeated 32,000 times took 0.7 s,
420
+ * and `setPolicy` runs once per stage's scope (CodeQL js/polynomial-redos).
421
+ * The pass stops at the first `[` that never closes instead: a later `[`
422
+ * starts its body where the failed scan was also between two tokens, so it
423
+ * reads the same tail the same way and closes nowhere either.
424
+ *
425
+ * @internal Exported for that test only — not on any public door.
426
+ */
427
+ export declare function classBodies(source: string): string[];
407
428
  /**
408
429
  * THE TAINT RULE at a subflow mapper boundary (an `inputMapper` seeding a
409
430
  * subflow, an `outputMapper` merging it back): a key the mapper writes
@@ -187,7 +187,10 @@ export declare class ExecutionRuntime {
187
187
  * The frozen, detached fold base served as {@link RuntimeSnapshot.initialState}.
188
188
  * Computed once (the base never changes after construction) and shared —
189
189
  * safe because it is deeply frozen, so no holder can mutate it or reach the
190
- * engine through it.
190
+ * engine through it; a base holding a Date, Map, buffer … (what freezing
191
+ * cannot seal) is served by the public snapshot as a copy of its open paths
192
+ * (`runner/snapshot.ts · servedSnapshot`, `capture/freeze.ts · serveRecord`,
193
+ * 9.44.2).
191
194
  */
192
195
  private getFoldBase;
193
196
  getPipelines(): string[];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "footprintjs",
3
- "version": "9.44.0",
3
+ "version": "9.44.2",
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,6 +53,7 @@
53
53
  "check:layering": "node scripts/check-layering.mjs",
54
54
  "check:doc-snippets": "node scripts/check-doc-snippets.mjs",
55
55
  "audit:family": "node scripts/audit-family-versions.mjs",
56
+ "audit:consumers": "node scripts/audit-consumers.mjs",
56
57
  "lint": "eslint '{src,test}/**/*.ts' --ext .ts",
57
58
  "lint:fix": "eslint '{src,test}/**/*.ts' --ext .ts --fix",
58
59
  "format:fix": "prettier --config .prettierrc.js --write '{src,test}/**/*.ts'",