foottrace 0.0.0-stage → 1.0.1

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 +292 -0
  24. package/dist/esm/lib/memory/backtrack.js +466 -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 +80 -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 +473 -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 +84 -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 +292 -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,521 @@
1
+ /**
2
+ * TransactionBuffer — Per-stage STAGING buffer for state mutations
3
+ *
4
+ * What it IS: a staging buffer with read-your-writes and net-change commits.
5
+ * - Changes are staged here during stage execution and flushed to
6
+ * SharedMemory in ONE batch per stage (`commit()`) — other stages and
7
+ * parallel siblings never observe a stage's half-finished writes.
8
+ * - Read-after-write consistency within a stage — a stage sees its own
9
+ * staged writes immediately.
10
+ * - `commit()` records the stage's NET change (see {@link commit}), plus an
11
+ * operation trace for deterministic replay.
12
+ *
13
+ * What it is NOT: a rollback mechanism. Despite the name, there is no
14
+ * abort/rollback path — when a stage THROWS, the engine still commits
15
+ * everything staged so far before re-throwing (commit-on-error in
16
+ * `FlowchartTraverser`). That is deliberate: the audit trail must record
17
+ * what the failing stage changed. Do not rely on "stage failed → its
18
+ * writes vanished".
19
+ *
20
+ * The patch trees hold REFERENCES until commit (9.23.0). A `set` stores the
21
+ * caller's own value in `overwritePatch` exactly as it always did in
22
+ * `workingCopy`; the copy the record needs is taken ONCE per surviving path
23
+ * when the payload leaves the buffer (`toChangeOnlyPayload` /
24
+ * `toDeltaPayload` — every value they emit passes through `structuredClone`
25
+ * there). The law "the record never aliases a caller's object" is kept at
26
+ * the commit boundary instead of at every write, which is what makes N
27
+ * writes to one path O(N) instead of O(N × size). Consequence, by design: a
28
+ * caller who mutates its own object AFTER the write and BEFORE the stage
29
+ * ends commits the value as the stage read it back — the record agrees with
30
+ * the stage (CLAUDE.md landmine 3's first bite, closed). The one place the
31
+ * engine itself edits THROUGH a staged value — a later nested op under a
32
+ * `set` path — detaches that value first ({@link detachHeldAncestors}), so
33
+ * `workingCopy` and `overwritePatch` never share a container the engine
34
+ * mutates on one side only.
35
+ *
36
+ * COPY-ON-WRITE (9.29.0 — docs/design/2026-10-copy-on-write-commit.md). The
37
+ * buffer no longer clones the committed state it starts from. `baseSnapshot`
38
+ * IS the committed generation the stage first touched (held by reference —
39
+ * committed state is immutable-after-swap), and `workingCopy` starts as a
40
+ * copy of its ROOT only: each write copies the containers on its own path
41
+ * ({@link ownSpine}), and the first READ after the stage's first write of a
42
+ * container still shared with committed state takes a private deep copy of
43
+ * it ({@link privatise}) — what the whole-state clone used to give every
44
+ * read, so an in-place edit of a read value stays private exactly as it did.
45
+ * A read the working copy cannot answer is served from live state by the
46
+ * caller, as before; the buffer then replaces that path of its diff base
47
+ * with a private copy ({@link detachBase}), as the clone had it.
48
+ *
49
+ * THE ADMITTED RECORD (9.30.0 — docs/design/2026-10-admitted-record.md). A
50
+ * commit is admitted only if its bundle folds back to the stage's
51
+ * read-your-writes view at every path the stage touched (and at every
52
+ * container its writes created on the way there, below the stage's address).
53
+ * Both encoders commit the value the stage read: a compact row — a `merge`
54
+ * delta, an `append` tail — is kept where it provably folds back; a family of
55
+ * rows that does not is recorded as `set` rows of the values the stage read
56
+ * ({@link admit}). Before 9.30.0 the one accumulated merge delta per path was
57
+ * replayed at every `merge` row of that path, which lied across a hard write
58
+ * (`$update(k,{x}); k = {y}; $update(k,{z})` committed `{y, x, z}` for a
59
+ * read-back of `{y, z}`), an `[]` clear, a kind change or an array union
60
+ * deduplicated by reference. Verified when the stage staged a `merge` or a
61
+ * nested op; a stage that staged only `set` / `delete` of keys directly under
62
+ * its address is coherent by construction — both trees receive the same
63
+ * write — and is admitted without the fold.
64
+ */
65
+ import type { CommitValuesMode, MemoryPatch, TraceEntry } from './types.js';
66
+ export declare class TransactionBuffer {
67
+ /**
68
+ * The net-change diff base: the committed generation the stage first
69
+ * touched, by reference — except where {@link detachBase} replaced a path
70
+ * of it with a private copy (its root and the containers on the way are
71
+ * then copies too, owned by {@link baseOwned}).
72
+ */
73
+ private baseSnapshot;
74
+ private workingCopy;
75
+ private overwritePatch;
76
+ private updatePatch;
77
+ private opTrace;
78
+ private redactedPaths;
79
+ /**
80
+ * Paths whose `overwritePatch` value is the caller's own object (9.23.0):
81
+ * a `set` of a container stores the reference, and this is the ledger of
82
+ * which ones are still shared with the caller (and with `workingCopy`).
83
+ * Consulted only by the engine's nested ops — see
84
+ * {@link detachHeldAncestors}; the commit payload clones every surviving
85
+ * value anyway and never asks. Cleared with the rest at `commit()`.
86
+ */
87
+ private heldRefs;
88
+ /** Commit-value encoding policy (#13c-B). `'full'` = historical bytes. */
89
+ private readonly commitValues;
90
+ /** Per-write read-provenance source (#P1). When set (the
91
+ * `writeProvenance: 'reads-prefix'` dial), every staged op snapshots the
92
+ * keys tracked-read so far — the temporal-prefix attribution consumed by
93
+ * causal slicing. Undefined (default) = zero cost, byte-identical ops. */
94
+ private readonly readKeysProvider?;
95
+ /**
96
+ * The containers in `workingCopy` this buffer CREATED: its root copy, the
97
+ * path copies its writes made, the merge results it built, and every
98
+ * container of the private copies its reads took. A write edits these in
99
+ * place. Everything else in `workingCopy` is SHARED — with the committed
100
+ * generation the stage first touched, or with a value the stage staged —
101
+ * and is copied before a write passes through it ({@link ownSpine}).
102
+ */
103
+ private owned;
104
+ /**
105
+ * Container values the stage staged with {@link set} — its own objects,
106
+ * held by reference (9.23.0). A read hands them back as they are, exactly
107
+ * as the private working copy always did, and never privatises them: a
108
+ * private copy of a staged value would leave `workingCopy` and
109
+ * `overwritePatch` disagreeing about a value the stage wrote.
110
+ */
111
+ private staged;
112
+ /**
113
+ * Owned containers with no container still shared with committed state
114
+ * anywhere below them (a private deep copy, a merge result built on a
115
+ * private base, a path copy whose shared children were privatised). A read
116
+ * that lands on one needs no walk — see {@link privatiseBelow}.
117
+ */
118
+ private privateTrees;
119
+ /**
120
+ * The containers of {@link baseSnapshot} that {@link detachBase} created:
121
+ * its root copy and the copies on the way to a detached path. Allocated
122
+ * only by the first detach — the in-contract stage never pays for it.
123
+ */
124
+ private baseOwned?;
125
+ /**
126
+ * The private deep copies {@link detachBase} put into the diff base: nothing
127
+ * at or below one is committed state, so a later detach inside it is a no-op.
128
+ */
129
+ private detachedBase?;
130
+ /**
131
+ * Where the stage writes (9.30.0): a path prefix its frame computed and
132
+ * hands over as data (C2 — for the engine, the frame's run namespace, or
133
+ * `[]` for the root). Its containers are the stage's ADDRESS, not a value
134
+ * the stage reads (every read it makes is a key below it), so a shell a
135
+ * write leaves there is no part of the read-back the record must hold.
136
+ */
137
+ private readonly address;
138
+ /** `merge` ops staged since the last commit — see {@link admit}. */
139
+ private stagedMerges;
140
+ /** Ops staged deeper than one key below the stage's address — see {@link admit}. */
141
+ private nestedOps;
142
+ constructor(base: any, commitValues?: CommitValuesMode, readKeysProvider?: () => string[], address?: readonly string[]);
143
+ /** Stamp the current read prefix onto a staged op — only when the
144
+ * provenance dial is on (provider present), so the default path allocates
145
+ * nothing and commit bundles stay byte-identical. */
146
+ private stampReadKeys;
147
+ /** Stage an op into `opTrace`, counting what decides whether {@link admit} must fold. */
148
+ private stage;
149
+ /**
150
+ * Hard overwrite at the specified path. Stores the REFERENCE in both trees
151
+ * (9.23.0) — the record's copy is taken once, at commit; see the header.
152
+ */
153
+ set(path: (string | number)[], value: any, shouldRedact?: boolean): void;
154
+ /**
155
+ * A nested op is about to write THROUGH a path whose `overwritePatch`
156
+ * value is a held reference (9.23.0). Today's law is that the two trees
157
+ * receive the same nested `set`/`delete` mutations but ONLY `workingCopy`
158
+ * receives a nested `merge`'s result (`updatePatch` gets the delta) — so a
159
+ * container shared between the trees would leak the merged value into the
160
+ * overwrite payload, and a shared container is also the caller's own
161
+ * object, which the engine must not edit on the record's behalf. Replacing
162
+ * the held ancestor with its clone in `overwritePatch` restores exactly
163
+ * the private tree the per-write clone used to give it, paid once per
164
+ * ancestor and only on the nested-op path the public surface never takes
165
+ * (the scope proxy writes ROOT keys). `workingCopy` keeps the reference,
166
+ * as it always did.
167
+ */
168
+ private detachHeldAncestors;
169
+ /**
170
+ * Explicit key deletion at the specified path (#13c-B; absorbs backlog B8).
171
+ *
172
+ * Stages EXACTLY the same buffer mutations as `set(path, undefined)` —
173
+ * `workingCopy`/`overwritePatch` get an own `undefined` at the path (the
174
+ * historical flattening, preserving read behavior and the dedup diff base
175
+ * across modes) — but records the op verb as `'delete'`. At commit:
176
+ * `'full'` mode maps it back to a `'set'` trace entry (byte-identical to
177
+ * today); `'delta'` mode emits a real `'delete'` entry whose replay
178
+ * REMOVES the key instead of leaving `key: undefined` behind.
179
+ *
180
+ * The net-change filter reads that staged own `undefined` as ABSENT
181
+ * (`deepEqual`, 9.19.1) — so under `'delta'`, whose committed state really
182
+ * lacks the key, deleting an absent key inside a container the stage also
183
+ * re-wrote is the no-op it is, not a `set` of the whole container.
184
+ */
185
+ delete(path: (string | number)[], shouldRedact?: boolean): void;
186
+ /** Deep union merge at the specified path. `deepSmartMerge` builds fresh
187
+ * containers, so neither tree ever holds the merge INPUT by reference;
188
+ * the nested-op detach guards the ancestor it writes INTO.
189
+ *
190
+ * The value merged INTO is made private first (`privatise`), as the
191
+ * whole-state clone made it before 9.29.0. Not only for the reads that
192
+ * follow: `deepSmartMerge` unions arrays BY REFERENCE, so whether an
193
+ * element the stage passes in is "already there" depends on which objects
194
+ * the base holds — a committed array would dedup an element the stage
195
+ * read from it before its first write, where the private copy (and
196
+ * 9.28.0) appends it. */
197
+ merge(path: (string | number)[], value: any, shouldRedact?: boolean): void;
198
+ /**
199
+ * Field-level redaction (9.19.0): mark dot-paths INSIDE the value staged at
200
+ * `path` as secret, so `scrubPatch` scrubs them in the commit log and the
201
+ * mirror the same way a whole-key redaction is scrubbed. A field is
202
+ * registered as a literal key AND, when dotted, as the nested path it
203
+ * names — whichever exists in the patch is the one `scrubPatch` finds.
204
+ * Marked paths survive commit only while the op path they sit under does
205
+ * (see {@link survivingRedactedPaths}).
206
+ */
207
+ markRedactedFields(path: (string | number)[], fields: readonly string[]): void;
208
+ /**
209
+ * Does a redaction mark address an array ELEMENT right below `path`
210
+ * (`list.1.token`)? An `append` row re-bases the indices onto the tail, so
211
+ * such a path takes the whole-value `set` (`deltaEncoding · pushValueRow`).
212
+ * A mark by NAME below an array (`obj.y`) means the same thing on the tail
213
+ * as on the whole value, so the compact row stays (byte-identical).
214
+ */
215
+ private markedBelow;
216
+ /**
217
+ * The redacted paths that survive the net-change filter: a path survives
218
+ * when it IS a surviving op path (a whole-key redaction) or sits UNDER one
219
+ * (a field inside a surviving write). A dropped op takes its fields with it.
220
+ */
221
+ private survivingRedactedPaths;
222
+ /**
223
+ * Read current value at path (includes uncommitted changes). The value is
224
+ * the stage's own — private to it, as the whole-state clone made every read
225
+ * after the first write before 9.29.0 (`privatise`).
226
+ */
227
+ get(path: (string | number)[], defaultValue?: any): any;
228
+ /**
229
+ * Read the working copy at `path` WITHOUT taking a private copy — for a
230
+ * reader that only compares (the dev-mode borrowed-mutation report in
231
+ * `StageContext.commit`), never for a value handed to a stage.
232
+ */
233
+ peek(path: (string | number)[]): unknown;
234
+ /**
235
+ * Keep the diff base exact under a read the working copy cannot answer.
236
+ *
237
+ * When `workingCopy` holds nothing at `path` — the stage deleted or unset
238
+ * it, or a write replaced a container above it — `RecordFrame · read`
239
+ * serves the read from LIVE committed state, exactly as 9.28.0
240
+ * did, so the value it hands out is committed state itself. On 9.28.0 the
241
+ * diff base was a whole-state clone taken at the stage's first write, and
242
+ * an in-place edit of that value (out of contract — reads are borrowed)
243
+ * could not move it: written back, the edit was recorded. Here the diff
244
+ * base IS a committed generation, and it very often holds that same
245
+ * container at `path` — the edit would move the base with it, and the
246
+ * write-back would commit no change. So before the value goes out, the
247
+ * base's value at `path` is replaced by a private deep copy (the base's
248
+ * root and the containers on the way copied shallowly) — what 9.28.0's
249
+ * base held there. The commit's net-change test compares against it and
250
+ * the delta encoder replays from it; the value handed out stays the live
251
+ * one, so whatever else an edit of it reaches (live state, the next
252
+ * generation) behaves as on 9.28.0 too.
253
+ *
254
+ * Not `privatise`: a private copy handed to the stage would also keep the
255
+ * edit out of live state, where 9.28.0 let it in — a run-namespaced read
256
+ * that falls back to a global key would then end differently (the
257
+ * differential's NESTED family fails within 100 programs that way).
258
+ *
259
+ * Copies nothing when the base holds no container at `path`, and nothing
260
+ * twice: a path at or below an earlier detach is already private. O(path)
261
+ * otherwise, plus O(value) once per detached path.
262
+ */
263
+ detachBase(path: (string | number)[]): void;
264
+ /**
265
+ * Private reads (9.29.0): make the value at `path` the stage's own before
266
+ * a read hands it out — so a read after the stage's first write behaves
267
+ * exactly as it did when the buffer began with a deep clone of the whole
268
+ * state. An in-place edit of what the read returned (out of contract —
269
+ * reads are borrowed) stays inside the buffer, and committed state is never
270
+ * touched. Whether the RECORD keeps it is the admitted record's law
271
+ * (9.30.0, owner ruling R1 — the record keeps what the stage read back): it
272
+ * is kept when the edited value lies at or below a path the stage wrote in
273
+ * this stage — set, merged or written back alike — and lost otherwise.
274
+ *
275
+ * Walking the path from the (owned) root:
276
+ * - a container still SHARED with committed state at the same position is
277
+ * replaced by a private deep copy when it is the value read, or by a
278
+ * shallow copy when it is a container on the way to it (so a read of
279
+ * `k` under a fork child's address copies `k`, not every child's namespace);
280
+ * - a container the stage STAGED (or anything else the buffer did not
281
+ * take from committed state) is handed back by reference, as before;
282
+ * - a container the buffer owns is walked on — and when it is the value
283
+ * read, its still-shared descendants are privatised
284
+ * ({@link privatiseBelow}).
285
+ *
286
+ * Cost: O(path) per read, plus O(value) ONCE per shared container a stage
287
+ * reads after its first write. A stage that never reads after writing pays
288
+ * nothing; a read before the first write never reaches the buffer.
289
+ */
290
+ private privatise;
291
+ /**
292
+ * An OWNED container is about to be read whole (a path copy a nested write
293
+ * made, say): its descendants still shared with committed state are
294
+ * replaced by private deep copies, so nothing the read hands out is
295
+ * committed state. Walks owned containers only; marks each one finished
296
+ * ({@link privateTrees}), so a container is scanned once per stage.
297
+ */
298
+ private privatiseBelow;
299
+ /** A private deep copy of a committed container — owned through and through. */
300
+ private privateCopyOf;
301
+ /**
302
+ * Did any staged op touch `path`, or a path above or below it?
303
+ *
304
+ * The dev-mode borrowed-read check (`StageContext.commit`) asks this before
305
+ * accusing a stage of mutating a value in place: a key the stage legitimately
306
+ * WROTE is expected to differ from what it read, and writes reach the buffer
307
+ * from paths that bypass user-level write tracking too (the subflow seed,
308
+ * `outputMapper` merge-back, the resume re-seed). Dev-mode only — nothing on
309
+ * the hot path calls it.
310
+ */
311
+ wasStaged(path: (string | number)[]): boolean;
312
+ /**
313
+ * Flush all staged mutations and return the commit bundle — recording the
314
+ * stage's NET CHANGE, not its raw write log.
315
+ *
316
+ * ── WHY (the defect this fixes) ─────────────────────────────────────────
317
+ * Previously every `set`/`merge` was recorded verbatim, so the commit bundle
318
+ * was a log of *operations* rather than *changes*. Two operations produce no
319
+ * net change yet were still committed as "mutations":
320
+ *
321
+ * 1. No-op write — writing a key the value it already holds (e.g. an
322
+ * agent context slot re-emitting identical content every
323
+ * turn). base K=1, stage writes K=1.
324
+ * 2. Write-revert — changing then restoring a key within one stage.
325
+ * base K=1, stage writes K=2 then K=1.
326
+ *
327
+ * Recording these as mutations (a) bloated causal slicing / backtracking with
328
+ * spurious dependencies on intermediate values that never reach final state,
329
+ * and (b) made downstream "what changed here?" consumers light up stages that
330
+ * changed nothing — most visibly the lens highlight flagging every slot.
331
+ *
332
+ * ── HOW ─────────────────────────────────────────────────────────────────
333
+ * At commit we hold BOTH `baseSnapshot` (state when the stage began) and
334
+ * `workingCopy` (state after all its writes). For each path the stage touched
335
+ * we keep it in the bundle ONLY if its final value differs from the base
336
+ * value ({@link deepEqual}). No-op AND write-revert paths drop out, because
337
+ * both compare equal to base. This is a single net-delta diff at commit time
338
+ * — one deep compare per touched path, O(changed state), paid once per stage
339
+ * (NOT per write). A naive per-write deep-equal skip would be more expensive
340
+ * and would still miss write-revert (the intermediate write differs from the
341
+ * value present at the moment of writing).
342
+ *
343
+ * ── TWO HONEST TIERS (by design — do not "unify" them) ──────────────────
344
+ * • commit (here) = CHANGE-level — truthful net delta. Feeds the commit
345
+ * log, causal chain, narrative, and the lens highlight.
346
+ * • `onWrite` event = OP-level — fires on EVERY write attempt regardless of
347
+ * net change. Feeds metrics / behavioural observability
348
+ * (a debugger wants to see "wrote 2, then reverted").
349
+ * `onWrite` is unchanged by this method; only the COMMIT becomes change-only.
350
+ *
351
+ * ── EMPTY COMMITS ARE INTENTIONAL ───────────────────────────────────────
352
+ * A stage that nets no change commits an EMPTY patch — NOT nothing.
353
+ * {@link StageContext.commit} still records the bundle unconditionally, so
354
+ * every executed stage remains a time-travel cursor stop (its `runtimeStageId`
355
+ * marker is preserved); only its PATCH is empty. This is what keeps the
356
+ * commit-indexed slider stable while making the highlight truthful.
357
+ *
358
+ * ── KNOWN LIMITATIONS / FUTURE ──────────────────────────────────────────
359
+ * • Explicit key DELETION under the default 'full' mode is still
360
+ * flattened to set-of-`undefined` (a removed key cannot be expressed
361
+ * in MemoryPatch alone). CLOSED under `commitValues: 'delta'` (#13c-B):
362
+ * {@link delete} stages a distinct op and the bundle carries a real
363
+ * `delete` trace verb whose replay removes the key.
364
+ * • Array-merge dedup in {@link deepSmartMerge} still uses reference equality
365
+ * (`new Set`), so deep-equal *objects* in a merged array are not deduped.
366
+ * Orthogonal to this change; tracked separately.
367
+ *
368
+ * Resets the buffer to empty state after commit.
369
+ */
370
+ commit(): {
371
+ overwrite: MemoryPatch;
372
+ updates: MemoryPatch;
373
+ redactedPaths: Set<string>;
374
+ trace: TraceEntry[];
375
+ };
376
+ /**
377
+ * Rebuild overwrite / updates / trace keeping ONLY paths whose final value
378
+ * differs from the base value — i.e. the stage's net change. See
379
+ * {@link TransactionBuffer.commit} for the rationale.
380
+ *
381
+ * Paths are compared at the exact granularity they were written (each trace
382
+ * entry's path), against `workingCopy` (final) vs `baseSnapshot` (start).
383
+ * Surviving `set` paths copy their final value from `overwritePatch`;
384
+ * surviving `merge` paths copy their accumulated delta from `updatePatch`,
385
+ * keeping the set-vs-merge verb — and the result is ADMITTED ({@link admit}):
386
+ * a family of rows whose replay does not give back what the stage read is
387
+ * recorded as `set` rows of the read-back instead (9.30.0).
388
+ *
389
+ * This is the DEFAULT (`commitValues: 'full'`) payload, including the
390
+ * historical flattening of staged `delete` ops into `set`-of-`undefined`
391
+ * trace entries. The delta encoding lives in {@link toDeltaPayload}.
392
+ *
393
+ * Work, not bytes (9.22.1): the net-change verdict is paid once per PATH
394
+ * and the clone once per CONSECUTIVE run of ops on a path, not once per op
395
+ * — see the two memos inside. Pinned by
396
+ * test/lib/memory/scenario/repeated-path-byte-identity.test.ts against the
397
+ * 9.22.0 bytes. Since 9.23.0 that clone is THE clone: the patch trees hold
398
+ * the caller's references (see the class header), so this loop is the one
399
+ * place the record is detached from them.
400
+ */
401
+ private toChangeOnlyPayload;
402
+ /** The 'full' rows; a family in `lossy` is written as its read-back, at its last touch ({@link emitReadBack}). */
403
+ private changeOnlyRows;
404
+ /**
405
+ * Delta-encoded payload (`commitValues: 'delta'`, #13c-B) — same net-change
406
+ * filter as {@link toChangeOnlyPayload}, three encoding differences:
407
+ *
408
+ * 1. **One trace entry per surviving path** (the §2.5 dedup rule — `append`
409
+ * is NOT idempotent on replay, so duplicate entries would multiply
410
+ * tails). The verb is resolved from the path's op mix + base→final
411
+ * relationship; entries are ordered by each path's LAST touch,
412
+ * preserving last-writer-wins for nested/overlapping paths.
413
+ * 2. **Verb resolution per path** — for a path with NO surviving relative
414
+ * (see 3):
415
+ * - last op `'delete'` AND final value gone → `delete` (the path stays
416
+ * enumerated in `overwrite` with `undefined` for key-set consumers).
417
+ * Requires the parent to be a real container in the base — replay uses
418
+ * `nativeDelete`, which is inert on a primitive/absent parent, whereas
419
+ * the `'full'` flattening (`_set` of `undefined`) COERCES that parent
420
+ * into an object; when they would disagree we keep the flattening;
421
+ * - ONLY `'merge'` ops → `merge` with the accumulated `updatePatch`
422
+ * delta;
423
+ * - otherwise (`set`/mixed): the value the stage's 'full' rows give the
424
+ * path ({@link fullRowsValue}). If base and that value are arrays and
425
+ * base is a STRICT PREFIX → `append` storing only the tail; else `set`
426
+ * storing the full value.
427
+ * 3. **OVERLAP FAMILIES take the full-value fallback.** `overwrite` /
428
+ * `updates` are nested path TREES, so two surviving paths where one is
429
+ * an ancestor of the other (`a` and `a.p`) share storage. Delta's
430
+ * per-path encodings would clobber each other there (an `append`
431
+ * ancestor stores only a TAIL, a `delete` ancestor `undefined`), so every
432
+ * path that has a surviving ancestor/descendant is committed as a plain
433
+ * `set` of the value the 'full' rows give it, and the family's
434
+ * shallowest path (whose set covers the whole subtree) is emitted LAST.
435
+ *
436
+ * The result is ADMITTED ({@link admit}) like the 'full' payload: a compact
437
+ * row is kept only where the bundle provably folds back, and a family that
438
+ * does not is recorded as what the stage read. So both encodings commit the
439
+ * value the stage read; before 9.30.0 they shared a replay that did not
440
+ * (`replayPathVerbs` / `replayFamilyVerbs`, deleted).
441
+ */
442
+ private toDeltaPayload;
443
+ /** The delta rows; a family in `lossy` is written as its read-back, at its last touch ({@link emitReadBack}). */
444
+ private deltaRows;
445
+ /**
446
+ * THE ADMISSION — a commit is admitted only if its bundle folds back to the
447
+ * stage's read-your-writes view at every path the stage touched. `build`
448
+ * lays the rows out (one encoder: `'full'` or `'delta'`); the candidate is
449
+ * checked ({@link lossyFamilies}), and when some family's rows do not fold
450
+ * back the rows are laid out again with those families written as what the
451
+ * stage read ({@link emitReadBack}) — every other family's bytes exactly as
452
+ * the first layout made them.
453
+ */
454
+ private admit;
455
+ /**
456
+ * The families whose rows do not fold back to what the stage read —
457
+ * `undefined` when there are none (`admission.ts · lossyFamilies`: the
458
+ * candidate replayed onto the diff base by `dryFold`, every family of
459
+ * touched paths compared with the working copy at its root and on the way
460
+ * down to it).
461
+ *
462
+ * Without a fold, when the stage staged no `merge` and no nested op: its
463
+ * rows are `set` / `delete` of keys directly under its address, each
464
+ * writing the SAME value into `workingCopy` and `overwritePatch` — the
465
+ * record is the read-back by construction (the property's ROOT-ONLY arm
466
+ * pins this). That keeps the commit of the typed scope's root-key writes
467
+ * exactly as cheap as it was.
468
+ */
469
+ private lossyFamilies;
470
+ /**
471
+ * A family whose rows did not fold back, written as what the stage read:
472
+ * one `set` row per member that changed (descendants by last touch, each
473
+ * with its own read prefix), the ROOT last, and the root's read-back value
474
+ * in `overwrite` — one coherent tree, so no row can clobber another. The
475
+ * root's row is written even when the root itself did not change: it is the
476
+ * row that carries the family's read-back (and the containers L-1's dropped
477
+ * op left). A lone root under `'delta'` keeps the compact `append` when the
478
+ * value it read is its base plus a tail.
479
+ */
480
+ private emitReadBack;
481
+ /**
482
+ * The value the stage's 'full' rows give a path — what the delta encoder
483
+ * commits for a family member or a path with a `set` — read from ONE
484
+ * {@link dryFold} of those rows over the patch trees themselves, taken the
485
+ * first time it is asked. By reference, as the stage held them: an element
486
+ * a `set` and a `merge` of one path share stays one element. A stage that
487
+ * staged no `merge` and no nested op needs no fold — a path's value is its
488
+ * last staged value. (Before 9.30.0: two replicas of the verb law,
489
+ * `replayPathVerbs` and `replayFamilyVerbs`.)
490
+ */
491
+ private fullRowsValue;
492
+ /** The stage's 'full' rows: every op on a path the net-change filter keeps, a `delete` spelled `set`. */
493
+ private fullRows;
494
+ /**
495
+ * Did the stage change the value at `segments`? Base and final value are
496
+ * fixed at commit, so this is the ONE net-change verdict both payload
497
+ * encodings apply (a no-op write and a write-then-revert both fail it).
498
+ */
499
+ private changedSinceBase;
500
+ /**
501
+ * Path → its op-verb sequence, ordered by LAST touch (delete + re-insert
502
+ * moves a re-touched path to the end of the Map's insertion order —
503
+ * preserving last-writer-wins for nested/overlapping paths). Per-write
504
+ * provenance (#P1): the LAST op's readKeys is kept — read prefixes only grow
505
+ * within a stage, so last == union across the path.
506
+ */
507
+ private opsByPath;
508
+ /** The net-change filter — identical to 'full' ({@link changedSinceBase}). Survivors keep last-touch order. */
509
+ private netChangeSurvivors;
510
+ /**
511
+ * May a staged `delete` at this path commit as the `'delete'` verb?
512
+ *
513
+ * Only when removing the key and the `'full'` mode's flattening (`_set` of
514
+ * `undefined`) land in the same place. They diverge when the parent is not
515
+ * a container: `nativeDelete` walks away untouched, while `nativeSet`
516
+ * COERCES the primitive parent into an object to hold the key. Committing
517
+ * `delete` there would silently drop a state change the stage really made,
518
+ * so those (pathological) deletes keep the historical flattening.
519
+ */
520
+ private deleteReplaysAsFlattening;
521
+ }