foottrace 0.0.0-stage → 1.0.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.
Files changed (196) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +73 -2
  3. package/dist/esm/index.d.ts +27 -0
  4. package/dist/esm/index.js +12 -0
  5. package/dist/esm/lib/capture/freeze.d.ts +91 -0
  6. package/dist/esm/lib/capture/freeze.js +313 -0
  7. package/dist/esm/lib/capture/ownData.d.ts +18 -0
  8. package/dist/esm/lib/capture/ownData.js +35 -0
  9. package/dist/esm/lib/capture/valueKinds.d.ts +57 -0
  10. package/dist/esm/lib/capture/valueKinds.js +141 -0
  11. package/dist/esm/lib/ids/runtimeStageId.d.ts +173 -0
  12. package/dist/esm/lib/ids/runtimeStageId.js +193 -0
  13. package/dist/esm/lib/memory/EventLog.d.ts +105 -0
  14. package/dist/esm/lib/memory/EventLog.js +147 -0
  15. package/dist/esm/lib/memory/RecordFrame.d.ts +232 -0
  16. package/dist/esm/lib/memory/RecordFrame.js +295 -0
  17. package/dist/esm/lib/memory/SharedMemory.d.ts +46 -0
  18. package/dist/esm/lib/memory/SharedMemory.js +87 -0
  19. package/dist/esm/lib/memory/TransactionBuffer.d.ts +521 -0
  20. package/dist/esm/lib/memory/TransactionBuffer.js +946 -0
  21. package/dist/esm/lib/memory/admission.d.ts +81 -0
  22. package/dist/esm/lib/memory/admission.js +152 -0
  23. package/dist/esm/lib/memory/backtrack.d.ts +286 -0
  24. package/dist/esm/lib/memory/backtrack.js +456 -0
  25. package/dist/esm/lib/memory/commitLogUtils.d.ts +217 -0
  26. package/dist/esm/lib/memory/commitLogUtils.js +383 -0
  27. package/dist/esm/lib/memory/deltaEncoding.d.ts +66 -0
  28. package/dist/esm/lib/memory/deltaEncoding.js +121 -0
  29. package/dist/esm/lib/memory/equality.d.ts +93 -0
  30. package/dist/esm/lib/memory/equality.js +261 -0
  31. package/dist/esm/lib/memory/eventPosition.d.ts +18 -0
  32. package/dist/esm/lib/memory/eventPosition.js +1 -0
  33. package/dist/esm/lib/memory/honesty.d.ts +93 -0
  34. package/dist/esm/lib/memory/honesty.js +91 -0
  35. package/dist/esm/lib/memory/keyPaths.d.ts +125 -0
  36. package/dist/esm/lib/memory/keyPaths.js +226 -0
  37. package/dist/esm/lib/memory/logModel.d.ts +76 -0
  38. package/dist/esm/lib/memory/logModel.js +363 -0
  39. package/dist/esm/lib/memory/merge.d.ts +26 -0
  40. package/dist/esm/lib/memory/merge.js +51 -0
  41. package/dist/esm/lib/memory/pathOps.d.ts +118 -0
  42. package/dist/esm/lib/memory/pathOps.js +292 -0
  43. package/dist/esm/lib/memory/paths.d.ts +56 -0
  44. package/dist/esm/lib/memory/paths.js +60 -0
  45. package/dist/esm/lib/memory/placeholders.d.ts +39 -0
  46. package/dist/esm/lib/memory/placeholders.js +39 -0
  47. package/dist/esm/lib/memory/recordCommit.d.ts +45 -0
  48. package/dist/esm/lib/memory/recordCommit.js +60 -0
  49. package/dist/esm/lib/memory/scrub.d.ts +52 -0
  50. package/dist/esm/lib/memory/scrub.js +72 -0
  51. package/dist/esm/lib/memory/types.d.ts +209 -0
  52. package/dist/esm/lib/memory/types.js +9 -0
  53. package/dist/esm/lib/memory/utils.d.ts +73 -0
  54. package/dist/esm/lib/memory/utils.js +150 -0
  55. package/dist/esm/lib/memory/verbs.d.ts +333 -0
  56. package/dist/esm/lib/memory/verbs.js +426 -0
  57. package/dist/esm/lib/recorder/CommitRangeIndex.d.ts +147 -0
  58. package/dist/esm/lib/recorder/CommitRangeIndex.js +206 -0
  59. package/dist/esm/lib/slice/elementProvenance.d.ts +81 -0
  60. package/dist/esm/lib/slice/elementProvenance.js +189 -0
  61. package/dist/esm/lib/slice/forwardSliceForKey.d.ts +88 -0
  62. package/dist/esm/lib/slice/forwardSliceForKey.js +242 -0
  63. package/dist/esm/lib/slice/index.d.ts +26 -0
  64. package/dist/esm/lib/slice/index.js +25 -0
  65. package/dist/esm/lib/slice/keyIndex.d.ts +137 -0
  66. package/dist/esm/lib/slice/keyIndex.js +292 -0
  67. package/dist/esm/lib/slice/keyTimeline.d.ts +49 -0
  68. package/dist/esm/lib/slice/keyTimeline.js +112 -0
  69. package/dist/esm/lib/slice/keysReadSources.d.ts +51 -0
  70. package/dist/esm/lib/slice/keysReadSources.js +94 -0
  71. package/dist/esm/lib/slice/serialize.d.ts +78 -0
  72. package/dist/esm/lib/slice/serialize.js +277 -0
  73. package/dist/esm/lib/slice/sliceForKey.d.ts +40 -0
  74. package/dist/esm/lib/slice/sliceForKey.js +78 -0
  75. package/dist/esm/lib/slice/types.d.ts +567 -0
  76. package/dist/esm/lib/slice/types.js +24 -0
  77. package/dist/esm/lib/time-travel/axis.d.ts +119 -0
  78. package/dist/esm/lib/time-travel/axis.js +178 -0
  79. package/dist/esm/lib/time-travel/bundles.d.ts +77 -0
  80. package/dist/esm/lib/time-travel/bundles.js +135 -0
  81. package/dist/esm/lib/time-travel/chain.d.ts +93 -0
  82. package/dist/esm/lib/time-travel/chain.js +187 -0
  83. package/dist/esm/lib/time-travel/commitStops.d.ts +55 -0
  84. package/dist/esm/lib/time-travel/commitStops.js +201 -0
  85. package/dist/esm/lib/time-travel/index.d.ts +22 -0
  86. package/dist/esm/lib/time-travel/index.js +20 -0
  87. package/dist/esm/lib/time-travel/stateAt.d.ts +102 -0
  88. package/dist/esm/lib/time-travel/stateAt.js +152 -0
  89. package/dist/esm/lib/time-travel/tagStops.d.ts +72 -0
  90. package/dist/esm/lib/time-travel/tagStops.js +101 -0
  91. package/dist/esm/lib/time-travel/timeTravel.d.ts +70 -0
  92. package/dist/esm/lib/time-travel/timeTravel.js +359 -0
  93. package/dist/esm/lib/time-travel/types.d.ts +470 -0
  94. package/dist/esm/lib/time-travel/types.js +22 -0
  95. package/dist/esm/package.json +1 -0
  96. package/dist/esm/paths.d.ts +4 -0
  97. package/dist/esm/paths.js +4 -0
  98. package/dist/esm/write.d.ts +49 -0
  99. package/dist/esm/write.js +47 -0
  100. package/dist/index.js +67 -0
  101. package/dist/lib/capture/freeze.js +319 -0
  102. package/dist/lib/capture/ownData.js +40 -0
  103. package/dist/lib/capture/valueKinds.js +145 -0
  104. package/dist/lib/ids/runtimeStageId.js +209 -0
  105. package/dist/lib/memory/EventLog.js +151 -0
  106. package/dist/lib/memory/RecordFrame.js +299 -0
  107. package/dist/lib/memory/SharedMemory.js +91 -0
  108. package/dist/lib/memory/TransactionBuffer.js +950 -0
  109. package/dist/lib/memory/admission.js +158 -0
  110. package/dist/lib/memory/backtrack.js +462 -0
  111. package/dist/lib/memory/commitLogUtils.js +398 -0
  112. package/dist/lib/memory/deltaEncoding.js +127 -0
  113. package/dist/lib/memory/equality.js +265 -0
  114. package/dist/lib/memory/eventPosition.js +2 -0
  115. package/dist/lib/memory/honesty.js +94 -0
  116. package/dist/lib/memory/keyPaths.js +242 -0
  117. package/dist/lib/memory/logModel.js +371 -0
  118. package/dist/lib/memory/merge.js +55 -0
  119. package/dist/lib/memory/pathOps.js +308 -0
  120. package/dist/lib/memory/paths.js +66 -0
  121. package/dist/lib/memory/placeholders.js +42 -0
  122. package/dist/lib/memory/recordCommit.js +64 -0
  123. package/dist/lib/memory/scrub.js +77 -0
  124. package/dist/lib/memory/types.js +10 -0
  125. package/dist/lib/memory/utils.js +169 -0
  126. package/dist/lib/memory/verbs.js +443 -0
  127. package/dist/lib/recorder/CommitRangeIndex.js +210 -0
  128. package/dist/lib/slice/elementProvenance.js +194 -0
  129. package/dist/lib/slice/forwardSliceForKey.js +246 -0
  130. package/dist/lib/slice/index.js +42 -0
  131. package/dist/lib/slice/keyIndex.js +312 -0
  132. package/dist/lib/slice/keyTimeline.js +116 -0
  133. package/dist/lib/slice/keysReadSources.js +100 -0
  134. package/dist/lib/slice/serialize.js +285 -0
  135. package/dist/lib/slice/sliceForKey.js +82 -0
  136. package/dist/lib/slice/types.js +25 -0
  137. package/dist/lib/time-travel/axis.js +183 -0
  138. package/dist/lib/time-travel/bundles.js +143 -0
  139. package/dist/lib/time-travel/chain.js +192 -0
  140. package/dist/lib/time-travel/commitStops.js +205 -0
  141. package/dist/lib/time-travel/index.js +31 -0
  142. package/dist/lib/time-travel/stateAt.js +159 -0
  143. package/dist/lib/time-travel/tagStops.js +105 -0
  144. package/dist/lib/time-travel/timeTravel.js +363 -0
  145. package/dist/lib/time-travel/types.js +23 -0
  146. package/dist/paths.js +14 -0
  147. package/dist/types/index.d.ts +27 -0
  148. package/dist/types/lib/capture/freeze.d.ts +91 -0
  149. package/dist/types/lib/capture/ownData.d.ts +18 -0
  150. package/dist/types/lib/capture/valueKinds.d.ts +57 -0
  151. package/dist/types/lib/ids/runtimeStageId.d.ts +173 -0
  152. package/dist/types/lib/memory/EventLog.d.ts +105 -0
  153. package/dist/types/lib/memory/RecordFrame.d.ts +232 -0
  154. package/dist/types/lib/memory/SharedMemory.d.ts +46 -0
  155. package/dist/types/lib/memory/TransactionBuffer.d.ts +521 -0
  156. package/dist/types/lib/memory/admission.d.ts +81 -0
  157. package/dist/types/lib/memory/backtrack.d.ts +286 -0
  158. package/dist/types/lib/memory/commitLogUtils.d.ts +217 -0
  159. package/dist/types/lib/memory/deltaEncoding.d.ts +66 -0
  160. package/dist/types/lib/memory/equality.d.ts +93 -0
  161. package/dist/types/lib/memory/eventPosition.d.ts +18 -0
  162. package/dist/types/lib/memory/honesty.d.ts +93 -0
  163. package/dist/types/lib/memory/keyPaths.d.ts +125 -0
  164. package/dist/types/lib/memory/logModel.d.ts +76 -0
  165. package/dist/types/lib/memory/merge.d.ts +26 -0
  166. package/dist/types/lib/memory/pathOps.d.ts +118 -0
  167. package/dist/types/lib/memory/paths.d.ts +56 -0
  168. package/dist/types/lib/memory/placeholders.d.ts +39 -0
  169. package/dist/types/lib/memory/recordCommit.d.ts +45 -0
  170. package/dist/types/lib/memory/scrub.d.ts +52 -0
  171. package/dist/types/lib/memory/types.d.ts +209 -0
  172. package/dist/types/lib/memory/utils.d.ts +73 -0
  173. package/dist/types/lib/memory/verbs.d.ts +333 -0
  174. package/dist/types/lib/recorder/CommitRangeIndex.d.ts +147 -0
  175. package/dist/types/lib/slice/elementProvenance.d.ts +81 -0
  176. package/dist/types/lib/slice/forwardSliceForKey.d.ts +88 -0
  177. package/dist/types/lib/slice/index.d.ts +26 -0
  178. package/dist/types/lib/slice/keyIndex.d.ts +137 -0
  179. package/dist/types/lib/slice/keyTimeline.d.ts +49 -0
  180. package/dist/types/lib/slice/keysReadSources.d.ts +51 -0
  181. package/dist/types/lib/slice/serialize.d.ts +78 -0
  182. package/dist/types/lib/slice/sliceForKey.d.ts +40 -0
  183. package/dist/types/lib/slice/types.d.ts +567 -0
  184. package/dist/types/lib/time-travel/axis.d.ts +119 -0
  185. package/dist/types/lib/time-travel/bundles.d.ts +77 -0
  186. package/dist/types/lib/time-travel/chain.d.ts +93 -0
  187. package/dist/types/lib/time-travel/commitStops.d.ts +55 -0
  188. package/dist/types/lib/time-travel/index.d.ts +22 -0
  189. package/dist/types/lib/time-travel/stateAt.d.ts +102 -0
  190. package/dist/types/lib/time-travel/tagStops.d.ts +72 -0
  191. package/dist/types/lib/time-travel/timeTravel.d.ts +70 -0
  192. package/dist/types/lib/time-travel/types.d.ts +470 -0
  193. package/dist/types/paths.d.ts +4 -0
  194. package/dist/types/write.d.ts +49 -0
  195. package/dist/write.js +56 -0
  196. package/package.json +48 -4
@@ -0,0 +1,52 @@
1
+ /**
2
+ * scrub.ts — the commit log's scrub: the placeholder at every redacted path of a patch (C4, L2).
3
+ *
4
+ * The RECORD's half of a redaction. A redacted path is DATA by the time it reaches here — the
5
+ * transaction buffer's `redactedPaths`, registered by `RecordFrame · write` from the scrub it was
6
+ * handed — and this file only writes `LOG_PLACEHOLDER` (`placeholders.ts`) where such a path holds
7
+ * a value. It never decides WHAT is secret: that is the engine's verdict (`redaction.ts`, L4 —
8
+ * `RedactionRule` and the write decision), which also owns the OTHER placeholder, the scope channel's
9
+ * `SCOPE_PLACEHOLDER`. Before C4 both halves lived in `redaction.ts`, so the record imported the
10
+ * engine's policy module to write a string.
11
+ *
12
+ * THE LAW:
13
+ * 1. A path is scrubbed only where it holds a DEFINED value — scrubbing never invents a field.
14
+ * 2. Paths are scrubbed in the set's order, against the tree as scrubbed so far: a path under one
15
+ * already scrubbed finds a string, not a container, and is left alone.
16
+ * 3. The input patch is never edited. `scrubPatch` copies only the spine of each scrubbed path
17
+ * (and nothing at all when nothing is scrubbed); `redactPatch` hands back a fresh deep copy.
18
+ */
19
+ import type { MemoryPatch } from './types.js';
20
+ /**
21
+ * The commit log's scrub: `patch` with {@link LOG_PLACEHOLDER} at every path in `redactedPaths` that
22
+ * holds a defined value — the copy `recordCommit` records and feeds the redacted mirror.
23
+ *
24
+ * CLONE-FREE (9.33.0). The patch is the transaction buffer's commit-time payload, already the record's
25
+ * own copy (`TransactionBuffer · commit` clones each surviving path once; the buffer is dropped at
26
+ * commit), so the scrub copies only what it must not edit in place:
27
+ * - no path to scrub (no policy, no per-call mark — the common case) → `patch` ITSELF, no copy at all;
28
+ * - otherwise → a new root and a shallow copy of each container on a scrubbed path (`pathOps ·
29
+ * ownSpine`), every other subtree shared with `patch`. `patch` is never edited.
30
+ * The engine called the public {@link redactPatch} (a whole `structuredClone`) twice per commit before
31
+ * 9.33.0. The bytes are the same: a scrubbed path holds the placeholder, every other path the value the
32
+ * buffer cloned (pinned by the 9.18.1 / 9.19.1 redaction byte tests).
33
+ *
34
+ * Paths are scrubbed in the set's order, against the tree as scrubbed so far: a path under one already
35
+ * scrubbed finds a string, not a container, and is left alone (as before).
36
+ *
37
+ * INTERNAL — the result shares structure with `patch`. A caller outside the commit path wants
38
+ * {@link redactPatch}.
39
+ *
40
+ * @param redactedPaths DELIM-joined paths (`TransactionBuffer`'s `redactedPaths`; a bundle's
41
+ * `redactedPaths` array works too).
42
+ */
43
+ export declare function scrubPatch(patch: MemoryPatch, redactedPaths: Iterable<string>): MemoryPatch;
44
+ /**
45
+ * Private historical copy-and-scrub helper, with its contract unchanged since FootPrint 4.x:
46
+ * a fresh deep copy of `patch` (`structuredClone`) with
47
+ * {@link LOG_PLACEHOLDER} at every listed path that holds a defined value. Shares nothing with `patch`
48
+ * and never edits it. (It lived in `memory/utils.ts` until 9.33.0 and in `memory/redaction.ts` until
49
+ * C4; the live commit path uses the clone-free {@link scrubPatch}.) Kept here to preserve the
50
+ * extracted implementation and its tests; this helper has no foottrace public entry point.
51
+ */
52
+ export declare function redactPatch(patch: MemoryPatch, redactedSet: Set<string>): MemoryPatch;
@@ -0,0 +1,209 @@
1
+ /**
2
+ * types.ts — the record's types: a bundle, its rows, its patches, its encoding, and the
3
+ * execution tree as the record's readers read it.
4
+ *
5
+ * Imports nothing. The standalone record names nothing outside itself: the engine's frame
6
+ * types — the stage snapshot, flow messages and retention dials — remain in FootPrint's
7
+ * `frameTypes.ts`. The closed record graph is enforced by scripts/check-layering.mjs.
8
+ */
9
+ /** A flat key-value bag representing a state patch (overwrite or merge). */
10
+ export interface MemoryPatch {
11
+ [key: string]: any;
12
+ }
13
+ /** A single entry in the chronological operation trace. */
14
+ export interface TraceEntry {
15
+ /** Canonical path string (segments joined by DELIM). */
16
+ path: string;
17
+ /**
18
+ * Per-write read provenance (#P1) — the keys this stage had TRACKED-READ at
19
+ * the moment this path was (last) written: the temporal-prefix attribution
20
+ * that lets slices link a specific write to only the reads that could have
21
+ * fed it (a stage reading `a,b` and writing `x,y` no longer implies x←b).
22
+ * ABSENT unless the `writeProvenance: 'reads-prefix'` dial is on — charts
23
+ * that never enable it keep byte-identical commit logs. Read prefixes only
24
+ * grow during a stage, so under delta mode's one-entry-per-path dedup the
25
+ * LAST write's prefix == the union across all of that path's writes.
26
+ * Honest ceiling: a write with NO tracked reads before it records `[]` —
27
+ * "depended on no tracked reads" is information, not absence.
28
+ */
29
+ readKeys?: string[];
30
+ /**
31
+ * - `'set'` — hard overwrite; `overwrite[path]` holds the full final value.
32
+ * - `'merge'` — deep union merge; `updates[path]` holds the accumulated delta.
33
+ * - `'append'` — (#13c-B, produced only under {@link CommitValuesMode}
34
+ * `'delta'`) the path's final value is its base value plus a tail of new
35
+ * trailing elements; `overwrite[path]` holds ONLY the tail. Replay
36
+ * reconstructs by concatenation. NOT idempotent — delta-mode bundles
37
+ * carry exactly one trace entry per surviving path.
38
+ * - `'delete'` — (#13c-B, produced only under `'delta'`; absorbs backlog
39
+ * B8) the key was explicitly removed via `deleteValue()`. Replay removes
40
+ * the key. `overwrite[path]` still ENUMERATES the path (value
41
+ * `undefined`) so key-set consumers keep seeing the changed key.
42
+ *
43
+ * `overwrite` values are therefore VERB-QUALIFIED: consumers that read
44
+ * `bundle.overwrite[key]` as "the full value written" must use
45
+ * `commitValueAt(commitLog, idx, key)` (from `foottrace`) when the
46
+ * log may contain delta-mode bundles.
47
+ */
48
+ verb: 'set' | 'merge' | 'append' | 'delete';
49
+ }
50
+ /**
51
+ * RFC-003 D2 — read paths that BYPASS read tracking:
52
+ * - `'args'` — the stage called `getArgs()` / `$getArgs()` with non-empty
53
+ * run input (frozen, untracked by design)
54
+ * - `'env'` — the stage called `getEnv()` / `$getEnv()` with a non-empty
55
+ * execution environment (frozen, untracked by design)
56
+ * - `'silent'` — the stage performed a silent read (`getValueSilent` /
57
+ * `getValueDirect`) of a key it never tracked-read in the same stage.
58
+ * Silent reads SHADOWED by a tracked read of the same key in the same
59
+ * stage (TypedScope array-proxy internals, `$batchArray`) are NOT
60
+ * flagged — their read→write edge is already captured.
61
+ */
62
+ export type UntrackedSource = 'args' | 'env' | 'silent';
63
+ /** The atomic bundle produced by TransactionBuffer.commit(). */
64
+ export interface CommitBundle {
65
+ /** Auto-assigned step index (set by EventLog.record). */
66
+ idx?: number;
67
+ /** Human-readable stage name. */
68
+ stage: string;
69
+ /** Stable stage identifier (matches spec node id). */
70
+ stageId: string;
71
+ /** Unique per-execution-step identifier. Format: [subflowPath/]stageId#executionIndex */
72
+ runtimeStageId: string;
73
+ /** Chronological write log for deterministic replay. */
74
+ trace: TraceEntry[];
75
+ /** Paths that should be redacted in UI (sensitive data). */
76
+ redactedPaths: string[];
77
+ /** Hard overwrite patches. */
78
+ overwrite: MemoryPatch;
79
+ /** Deep merge patches. */
80
+ updates: MemoryPatch;
81
+ /**
82
+ * RFC-003 D2 honesty markers — untracked read paths this stage consumed
83
+ * (see {@link UntrackedSource}). ABSENT when the stage used none, so
84
+ * charts that never touch those paths keep byte-identical commit logs.
85
+ * Causal-slice consumers (`causalChain`/`formatCausalChain`) surface this
86
+ * as "slice may be incomplete here". Residual limitation (by design):
87
+ * values smuggled through JS closures are undetectable.
88
+ */
89
+ untrackedSources?: ReadonlyArray<UntrackedSource>;
90
+ /**
91
+ * Declared tags (9.21.0) — the names the author put on this stage at build
92
+ * time (`FlowChartBuilder.tag` / `options.tags`), stamped here by the
93
+ * traverser so a stored recording carries its own milestones without a
94
+ * consumer's id conventions. ABSENT when the stage declares none, so an
95
+ * untagged chart's log is byte-identical to 9.20.0 (the law
96
+ * `untrackedSources` keeps). Recorded on the FIRST bundle of each execution
97
+ * of the stage — the stage's own bundle, the one without a `phase` — so
98
+ * retry attempts stamp it once, a continuation (a fork child's `'repeat'`,
99
+ * a mount's `'exit'` after its merge-back) carries none, and a mount whose
100
+ * exit is its only bundle (no `outputMapper`) carries them on that bundle. Free strings: footprintjs assigns
101
+ * them no meaning; `tagStops` (foottrace) keeps a stop when any of
102
+ * them matches.
103
+ */
104
+ tags?: readonly string[];
105
+ /**
106
+ * Which CONTINUATION of a stage's execution this bundle is (9.39.0) —
107
+ * stamped by the WRITER. THE LAW: the FIRST bundle per `runtimeStageId` is
108
+ * the stage's own and carries NO `phase`; only a bundle after it can:
109
+ *
110
+ * - `'exit'` — a subflow mount's exit commit (`SubflowExecutor`) after its
111
+ * merge-back bundle (`outputMapper`). A mount without a merge-back (a lazy
112
+ * mount, every `parallelForEach` branch, any mount with no mapper) has
113
+ * its exit as its ONLY bundle — its own, so no `phase`; the execution
114
+ * tree names it a mount.
115
+ * - `'repeat'` — the fan-out's settle commit of a fork child's frame
116
+ * (`ChildrenExecutor`), after the child's own bundle.
117
+ *
118
+ * A reader groups a stage's bundles by `runtimeStageId` and reads this
119
+ * field to know what each continuation is; it never infers it from the
120
+ * log's shape. A log written before 9.39.0 carries no `phase` anywhere —
121
+ * see `inferLegacyPhases` (foottrace) for how it is still read.
122
+ */
123
+ phase?: CommitPhase;
124
+ }
125
+ /** The continuation a bundle records — see {@link CommitBundle.phase}. */
126
+ export type CommitPhase = 'exit' | 'repeat';
127
+ /**
128
+ * Per-write read-provenance policy (#P1) — the fourth dial of the
129
+ * readTracking/writeTracking/commitValues family, same 6-site propagation
130
+ * pattern (executor option → ExecutionRuntime.use* → root StageContext →
131
+ * createNext/createChild inheritance → SubflowExecutor duck-push).
132
+ *
133
+ * - `'off'` (default) — zero cost, byte-identical commit logs.
134
+ * - `'reads-prefix'` — every committed {@link TraceEntry} carries
135
+ * `readKeys`: the keys tracked-read BEFORE that write (temporal-prefix
136
+ * attribution). Cost: one Set-to-array copy per write. Consumed by
137
+ * `causalChain`'s `edgeAttribution: 'per-write'` and the slice layer.
138
+ */
139
+ export type WriteProvenanceMode = 'off' | 'reads-prefix';
140
+ /**
141
+ * Policy for how commit-bundle VALUES are encoded into the commit log
142
+ * (#13c-B) — completes the `readTracking`/`writeTracking` dial family.
143
+ * Unlike those two (which gate lossy snapshot bookkeeping), this dial is
144
+ * **lossless in both modes** — it changes the commit log's *encoding*,
145
+ * never its *information*; any step's full state stays exactly
146
+ * reconstructable by replay.
147
+ *
148
+ * - `'full'` (default) — every surviving `set` path stores the full final
149
+ * value, one trace entry per operation. Byte-identical to the historical
150
+ * behavior.
151
+ * - `'delta'` — two changes, both replay-covered by `applySmartMerge`:
152
+ * 1. **`append` detection**: when a path's net change is "the base array
153
+ * plus new trailing elements" (strict prefix), the bundle records ONLY
154
+ * the tail under a `verb: 'append'` trace entry — the growing-history
155
+ * commit log becomes linear in tail size instead of O(N²) retained.
156
+ * A real `verb: 'delete'` entry replaces the `set: undefined` flattening
157
+ * for `deleteValue()` (closes the documented MemoryPatch limitation).
158
+ * 2. **One trace entry per surviving path** (append is not idempotent on
159
+ * replay): the verb is resolved from the path's base→final relationship
160
+ * and op mix; entries are ordered by each path's LAST touch.
161
+ *
162
+ * Honest cost note: append detection is NEW wall work — an O(|base|)
163
+ * structural prefix compare per array-set path per commit (today's
164
+ * `deepEqual` fast-fails on length in O(1) for a grown array). On a hit the
165
+ * commit gets cheaper in both wall and heap (the O(|final|) clone shrinks to
166
+ * O(|tail|)); on a miss it pays compare + full clone. `'full'` pays zero —
167
+ * the detection branch is mode-gated.
168
+ *
169
+ * Set via `new FlowChartExecutor(chart, { commitValues })` or
170
+ * `executor.setCommitValues(mode)` (before `run()`). The active mode is
171
+ * surfaced as the snapshot discriminant `RuntimeSnapshot.commitValues`.
172
+ */
173
+ export type CommitValuesMode = 'full' | 'delta';
174
+ /**
175
+ * The execution tree as the record's readers read it (C6): the fields a reader touches and
176
+ * nothing else. `commitStops` (and so `tagStops` and every strategy built on it) reads the mount
177
+ * marker (`subflowId`) and the root's `id`; `keysReadFromExecutionTree` reads the keys of
178
+ * `stageReads`; both key on `runtimeStageId` and walk `next` and `children`.
179
+ *
180
+ * A structural type the record owns, so the readers name nothing of the engine. The engine's
181
+ * `StageSnapshot` (`getSnapshot().executionTree`) is one — a supertype of it, so every caller
182
+ * that hands a snapshot's tree over compiles unchanged — and so is a stored recording's parsed
183
+ * JSON with these fields. Every field is optional: a reader that finds one absent reads on.
184
+ *
185
+ * @example
186
+ * ```typescript
187
+ * import { type ExecutionTree, keysReadFromExecutionTree } from 'foottrace';
188
+ *
189
+ * const tree: ExecutionTree = {
190
+ * runtimeStageId: 'seed#0',
191
+ * next: { runtimeStageId: 'decide#1', stageReads: { creditTier: 'A' } },
192
+ * };
193
+ * keysReadFromExecutionTree(tree).lookup('decide#1'); // ['creditTier']
194
+ * ```
195
+ */
196
+ export interface ExecutionTree {
197
+ /** The stage's id (prefixed inside a subflow). `commitStops` reads the root's to place a subflow's seed. */
198
+ readonly id?: string;
199
+ /** `[subflowPath/]stageId#executionIndex` — what a reader keys on. A node without one is not a stage. */
200
+ readonly runtimeStageId?: string;
201
+ /** Present on a subflow mount: the stop there says `kind: 'mount'`. */
202
+ readonly subflowId?: string;
203
+ /** What the stage tracked-read, by key. Only the KEYS are read: a `'summary'` marker serves as well as a value. */
204
+ readonly stageReads?: Readonly<Record<string, unknown>>;
205
+ /** The stage that ran next. */
206
+ readonly next?: ExecutionTree;
207
+ /** The stages that ran as this one's children (a fork's branches, a decider's chosen branch). */
208
+ readonly children?: readonly ExecutionTree[];
209
+ }
@@ -0,0 +1,73 @@
1
+ /**
2
+ * utils.ts — nested-object helpers for the heap, and the memory leaves' one
3
+ * re-export surface.
4
+ *
5
+ * The helpers here (`setNestedValue`, `updateNestedValue`, `updateValue`,
6
+ * `getNestedValue`) walk a nested object by path with
7
+ * prototype-pollution protection. Everything else this file used to hold has an
8
+ * owner of its own and is re-exported, so no importer moved: the path codec
9
+ * (`paths.ts`), structural equality (`equality.ts`) and the union merge
10
+ * (`merge.ts`) at L0, and the verb law (`verbs.ts`) at L1 — `applySmartMerge`,
11
+ * `nextGeneration`, `applySmartMergeInto`, `dryFold`, `supersededByNextSet`.
12
+ * The log's scrub, `redactPatch`, moved out in 9.33.0; it is the record's own, `scrub.ts` (C4).
13
+ * The writers take an ADDRESS, a path prefix their caller computed, and never
14
+ * name one (C2: the engine decides where a stage writes, `runAddress.ts · runAddress`).
15
+ * Zero external dependencies.
16
+ */
17
+ export { deepEqual } from './equality.js';
18
+ export { deepSmartMerge } from './merge.js';
19
+ export { DELIM, normalisePath, pathSegments } from './paths.js';
20
+ export { applySmartMerge, applySmartMergeInto, dryFold, nextGeneration, supersededByNextSet } from './verbs.js';
21
+ type NestedObject = {
22
+ [key: string]: any;
23
+ };
24
+ /**
25
+ * The two places a value at `path` can sit: under `address` (`runPath`, absent
26
+ * for the root address `[]`) and at the root (`globalPath`). `address` is a
27
+ * path prefix the caller computed — for the engine, a frame's run namespace
28
+ * (`runAddress.ts · runAddress`, C2), held by its record frame (`RecordFrame · address`, C3).
29
+ */
30
+ export declare function getRunAndGlobalPaths(address?: readonly string[], path?: (string | number)[]): {
31
+ runPath: (string | number)[] | undefined;
32
+ globalPath: (string | number)[];
33
+ };
34
+ /**
35
+ * Refuse the WHOLE selector before creating a container or copying a generation.
36
+ * The address's array contract is checked even when another selector is denied.
37
+ * Payload keys are data, not selectors, and are deliberately not inspected.
38
+ * @internal Shared by the nested writers and SharedMemory's pre-copy boundary.
39
+ */
40
+ export declare function isDeniedWritePath(address: readonly string[] | undefined, path: readonly (string | number)[] | undefined, field: string | number): boolean;
41
+ /**
42
+ * Sets a value at a nested path under `address`, creating intermediate objects as needed.
43
+ * A denied address/path/field leaves `obj` unchanged, including its prototype.
44
+ */
45
+ export declare function setNestedValue<T>(obj: NestedObject, address: readonly string[], path: string[], field: string, value: T, defaultValues?: unknown): NestedObject;
46
+ /**
47
+ * Updates a value in the object at a nested path under `address`.
48
+ * A denied address/path/field leaves `obj` unchanged, including its prototype.
49
+ * - Arrays: concatenate
50
+ * - Objects: shallow merge at each level
51
+ * - Primitives: replace
52
+ */
53
+ export declare function updateNestedValue<T>(obj: any, address: readonly string[], path: (string | number)[], field: string | number, value: T, defaultValues?: unknown): any;
54
+ /**
55
+ * In-place value update with merge semantics.
56
+ * - Arrays (non-empty): concatenate onto existing
57
+ * - Arrays (empty): direct replace — writing `[]` clears the field
58
+ * - Objects (non-empty): shallow merge (spread)
59
+ * - Objects (empty): direct replace — writing `{}` clears the field
60
+ * - Primitives: direct assignment
61
+ *
62
+ * Note on empty arrays: both `value && Array.isArray(value)` and
63
+ * `Array.isArray(value)` evaluate the same for arrays — `[]` is truthy in
64
+ * JavaScript, so the `&&` guard was never the issue. The actual bug was the
65
+ * concat path: `[...cur, ...[]]` silently returned `cur` unchanged when `value`
66
+ * was `[]`, making `updateValue(obj, 'tags', [])` a no-op instead of a clear.
67
+ * The fix is the explicit `value.length === 0` early-return branch.
68
+ */
69
+ export declare function updateValue(object: any, key: string | number, value: any): void;
70
+ /**
71
+ * Gets a value at a nested path with prototype-pollution protection.
72
+ */
73
+ export declare function getNestedValue(root: any, path: (string | number)[], field?: string | number): any;