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,265 @@
1
+ "use strict";
2
+ /**
3
+ * equality.ts — structural equality of committed-state values: THE owner of
4
+ * "what counts as a change". What `TransactionBuffer`'s net-change filter, the
5
+ * admitted record's compare, the read model's writer rule and the
6
+ * borrowed-mutation guard all ask. Its one import is the classifier of value
7
+ * kinds (`capture/valueKinds.ts`, a leaf); re-exported from `utils.ts`, so no
8
+ * importer moved.
9
+ */
10
+ Object.defineProperty(exports, "__esModule", { value: true });
11
+ exports.deepEqual = void 0;
12
+ const valueKinds_js_1 = require("../capture/valueKinds.js");
13
+ /**
14
+ * Structural deep equality for committed-state values.
15
+ *
16
+ * Used by {@link TransactionBuffer} to decide whether a stage actually CHANGED
17
+ * a path or merely re-wrote / reverted it to the value it already held (a
18
+ * "no-op write"), and by `borrowedMutation.firstDifferingPath` to find where a
19
+ * borrowed read moved. Committed state must survive `structuredClone`, so the
20
+ * values it can hold are the kinds the clone keeps, and two values are equal
21
+ * when a record holds the same thing for both. Every kind has ONE arm below
22
+ * (`equalPairs`' switch over `ValueKind` — a kind added to the union does not
23
+ * compile until it is compared). Before 9.22.0 a Date, Map or Set was compared
24
+ * as an object with no own keys, and until 9.44.2 so were a RegExp, an Error,
25
+ * a boxed primitive, an ArrayBuffer, a DataView and a Blob: any two of a kind
26
+ * were "equal", so `$setValue('re', /y/g)` over `/x/g` committed no row and
27
+ * live state kept the OLD value.
28
+ *
29
+ * Semantics:
30
+ * - reference / identical-primitive short-circuits first (cheap fast path)
31
+ * - `NaN` equals `NaN` (primitive compare falls back to `Object.is`)
32
+ * - two values of different KINDS are never equal (a `Date` is never `{}`,
33
+ * an array never `{ 0: … }`)
34
+ * - `Date`: same `getTime()` (two invalid dates are equal)
35
+ * - `RegExp`: same `source` and `flags` (the clone resets `lastIndex`)
36
+ * - `Error`: the same restored kind (one of the six built-in error names,
37
+ * else `Error`), own `message`, `stack` and own `cause` (deep) — what the
38
+ * clone keeps; any other own field is dropped by it, so it never counts
39
+ * (the admitted record's law)
40
+ * - a boxed primitive: the same wrapper type and the same primitive inside
41
+ * - an `ArrayBuffer`: length, resizability and bytes; a typed array or a
42
+ * `DataView`: its type and the bytes IT views — never the rest of its
43
+ * buffer (a Node `Buffer` views a slice of a shared pool, and the record
44
+ * keeps the view's bytes only: `capture/freeze.ts · freezeRecord`)
45
+ * - `Map`: same size AND, per key of `a`, `b` holds a deep-equal value —
46
+ * keys by `Map` identity (`SameValueZero`), values deep
47
+ * - `Set`: same size AND every member of `a` is deep-equal to SOME member
48
+ * of `b` (order-insensitive — a Set has no order to compare by)
49
+ * - an OPAQUE value (a Blob, a `DOMException` — content this library cannot
50
+ * read): by the caller's {@link OpaqueRule} — equal only to itself by
51
+ * default (replacing one is always a change), or, when both sides are
52
+ * copies of one record, equal to any other of its kind
53
+ * - arrays: equal length AND deep-equal element-wise (order-sensitive; an
54
+ * array's expando keys are out of contract and not compared)
55
+ * - objects: identical set of keys that HOLD a value AND deep-equal per key.
56
+ * An own key whose value is `undefined` counts as ABSENT: it is this
57
+ * library's other spelling of a deleted key (`'full'` mode flattens
58
+ * `delete` into `key: undefined`; `'delta'` mode's `delete` verb removes
59
+ * the key), and every consumer — JSON, the fold, `commitValueAt`, the
60
+ * mirror — reads the two spellings as one state. So must the net-change
61
+ * filter (9.19.1): `{}` and `{ k: undefined }` are not a change. Arrays
62
+ * are untouched — a slot holding `undefined` is still a slot.
63
+ *
64
+ * Cost & safety:
65
+ * - Allocates only transient `Object.keys` arrays, an error's property
66
+ * descriptors and a byte view per compared buffer — no clones (but one
67
+ * probe clone per class prototype, remembered: `capture/valueKinds.ts ·
68
+ * kindOf`). It is strictly cheaper than the `structuredClone` the commit
69
+ * already performs.
70
+ * - Primitive comparisons (the bulk of state) are O(1) via the `===` /
71
+ * `Object.is` fast paths; only nested objects/arrays incur a walk, bounded
72
+ * by the value's own size.
73
+ * - Terminates on CYCLIC values (9.18.1): a state value must survive
74
+ * `structuredClone`, and `structuredClone` preserves cycles — so a
75
+ * self-referencing value is a legal value, not an out-of-contract one. A
76
+ * pair of objects already under comparison is treated as equal (the
77
+ * structural answer, lodash `isEqual` semantics); acyclic inputs see no
78
+ * change. Dev mode still warns about cycles at write time
79
+ * (`ScopeFacade.setValue`) because they surprise narrative and JSON.
80
+ */
81
+ function deepEqual(a, b, opaque = 'identity') {
82
+ return equalPairs(a, b, undefined, opaque);
83
+ }
84
+ exports.deepEqual = deepEqual;
85
+ function equalPairs(a, b, seen, opaque) {
86
+ if (a === b)
87
+ return true; // same reference or identical primitive
88
+ if (typeof a !== typeof b)
89
+ return false;
90
+ if (a === null || b === null)
91
+ return a === b; // one is null, the other isn't
92
+ if (typeof a !== 'object')
93
+ return Object.is(a, b); // NaN-safe primitive compare
94
+ const kind = (0, valueKinds_js_1.kindOf)(a);
95
+ if (kind !== (0, valueKinds_js_1.kindOf)(b))
96
+ return false;
97
+ switch (kind) {
98
+ // Atoms: their content is a slot, not their keys.
99
+ case 'date':
100
+ return Object.is(Date.prototype.getTime.call(a), Date.prototype.getTime.call(b));
101
+ case 'regexp':
102
+ return a.source === b.source && a.flags === b.flags;
103
+ case 'boxed':
104
+ return tagOf(a) === tagOf(b) && equalPairs(a.valueOf(), b.valueOf(), seen, opaque);
105
+ case 'buffer':
106
+ return equalBuffers(a, b);
107
+ case 'view':
108
+ return equalViews(a, b);
109
+ case 'opaque':
110
+ return opaque === 'copies' && tagOf(a) === tagOf(b); // `a === b` was answered above
111
+ case 'plain':
112
+ case 'array':
113
+ case 'map':
114
+ case 'set':
115
+ case 'error':
116
+ return equalContainers(a, b, kind, seen, opaque);
117
+ default:
118
+ return unknownKind(kind);
119
+ }
120
+ }
121
+ /** The kinds whose content is other values: compared part by part, under the cycle guard. */
122
+ function equalContainers(a, b, kind, seen, opaque) {
123
+ // Cycle guard — a pair we are already inside is equal by assumption.
124
+ seen ??= new WeakMap();
125
+ let partners = seen.get(a);
126
+ if (partners?.has(b))
127
+ return true;
128
+ if (!partners)
129
+ seen.set(a, (partners = new WeakSet()));
130
+ partners.add(b);
131
+ if (kind === 'map')
132
+ return equalMaps(a, b, seen, opaque);
133
+ if (kind === 'set')
134
+ return equalSets(a, b, seen, opaque);
135
+ if (kind === 'error')
136
+ return equalErrors(a, b, seen, opaque);
137
+ if (kind === 'array') {
138
+ if (a.length !== b.length)
139
+ return false;
140
+ for (let i = 0; i < a.length; i++) {
141
+ if (!equalPairs(a[i], b[i], seen, opaque))
142
+ return false;
143
+ }
144
+ return true;
145
+ }
146
+ // Objects: only keys that HOLD a value take part — an own `undefined` is a
147
+ // deleted key, the same state as an absent one (see the contract above).
148
+ let aHeld = 0;
149
+ for (const key of Object.keys(a)) {
150
+ if (a[key] === undefined)
151
+ continue;
152
+ aHeld++;
153
+ if (!Object.prototype.hasOwnProperty.call(b, key))
154
+ return false;
155
+ if (!equalPairs(a[key], b[key], seen, opaque))
156
+ return false;
157
+ }
158
+ let bHeld = 0;
159
+ for (const key of Object.keys(b))
160
+ if (b[key] !== undefined)
161
+ bHeld++;
162
+ return aHeld === bHeld;
163
+ }
164
+ function unknownKind(kind) {
165
+ throw new TypeError(`deepEqual: no comparison for the value kind '${String(kind)}'`);
166
+ }
167
+ /** `[object Number]`, `[object Uint8Array]` … — the built-in type a boxed value or a view carries. */
168
+ function tagOf(value) {
169
+ return Object.prototype.toString.call(value);
170
+ }
171
+ /** The same view type over the same bytes — the bytes the view sees, wherever they sit in its buffer. */
172
+ function equalViews(a, b) {
173
+ if (tagOf(a) !== tagOf(b) || a.byteLength !== b.byteLength)
174
+ return false;
175
+ if (a.buffer === b.buffer && a.byteOffset === b.byteOffset)
176
+ return true;
177
+ const p = new Uint8Array(a.buffer, a.byteOffset, a.byteLength);
178
+ const q = new Uint8Array(b.buffer, b.byteOffset, b.byteLength);
179
+ for (let i = 0; i < p.length; i++)
180
+ if (p[i] !== q[i])
181
+ return false;
182
+ return true;
183
+ }
184
+ /** The same length, resizability and bytes. */
185
+ function equalBuffers(a, b) {
186
+ if (a === b)
187
+ return true;
188
+ const x = a;
189
+ const y = b;
190
+ if (x.byteLength !== y.byteLength || x.resizable !== y.resizable || x.maxByteLength !== y.maxByteLength) {
191
+ return false;
192
+ }
193
+ const p = new Uint8Array(a);
194
+ const q = new Uint8Array(b);
195
+ for (let i = 0; i < p.length; i++)
196
+ if (p[i] !== q[i])
197
+ return false;
198
+ return true;
199
+ }
200
+ /** The six error names `structuredClone` restores; any other name comes back an `Error`. */
201
+ const ERROR_NAMES = new Set(['EvalError', 'RangeError', 'ReferenceError', 'SyntaxError', 'TypeError', 'URIError']);
202
+ /**
203
+ * Two errors as the clone keeps them: the restored kind, the own `message`,
204
+ * the `stack` and the own `cause` (deep). Any other own field is dropped by
205
+ * the clone, so it never counts.
206
+ */
207
+ function equalErrors(a, b, seen, opaque) {
208
+ if (errorKindOf(a) !== errorKindOf(b) || stackOf(a) !== stackOf(b))
209
+ return false;
210
+ const aMessage = ownData(a, 'message');
211
+ const bMessage = ownData(b, 'message');
212
+ if (String(aMessage?.value) !== String(bMessage?.value) || !aMessage !== !bMessage)
213
+ return false;
214
+ const aCause = ownData(a, 'cause');
215
+ const bCause = ownData(b, 'cause');
216
+ if (!aCause || !bCause)
217
+ return !aCause === !bCause;
218
+ return equalPairs(aCause.value, bCause.value, seen, opaque);
219
+ }
220
+ function errorKindOf(error) {
221
+ const name = String(error.name);
222
+ return ERROR_NAMES.has(name) ? name : 'Error';
223
+ }
224
+ function stackOf(error) {
225
+ const stack = error.stack;
226
+ return typeof stack === 'string' ? stack : undefined;
227
+ }
228
+ /** An own DATA property, boxed — how the clone reads an error's `message` and `cause`. */
229
+ function ownData(value, key) {
230
+ const descriptor = Object.getOwnPropertyDescriptor(value, key);
231
+ return descriptor !== undefined && Object.prototype.hasOwnProperty.call(descriptor, 'value')
232
+ ? { value: descriptor.value }
233
+ : undefined;
234
+ }
235
+ function equalMaps(a, b, seen, opaque) {
236
+ if (a.size !== b.size)
237
+ return false;
238
+ for (const [key, value] of a) {
239
+ if (!b.has(key))
240
+ return false;
241
+ if (!equalPairs(value, b.get(key), seen, opaque))
242
+ return false;
243
+ }
244
+ return true;
245
+ }
246
+ /**
247
+ * Order-insensitive, deep per member. Quadratic in the worst case for sets of
248
+ * objects — a set of primitives (the common shape) is one `has` per member.
249
+ */
250
+ function equalSets(a, b, seen, opaque) {
251
+ if (a.size !== b.size)
252
+ return false;
253
+ const unmatched = [...b];
254
+ for (const member of a) {
255
+ // SameValueZero first — primitives and shared references, one `has`.
256
+ // Each match retires its slot so two structurally-equal members of `a`
257
+ // cannot both claim the same member of `b`.
258
+ const at = unmatched.indexOf(member);
259
+ const found = at >= 0 ? at : unmatched.findIndex((candidate) => equalPairs(member, candidate, seen, opaque));
260
+ if (found < 0)
261
+ return false;
262
+ unmatched.splice(found, 1);
263
+ }
264
+ return true;
265
+ }
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -0,0 +1,94 @@
1
+ "use strict";
2
+ /**
3
+ * honesty.ts — the ONE vocabulary for what a reader cannot see.
4
+ *
5
+ * WHY THIS FILE EXISTS. A recording cannot always answer what it is asked, and the library says
6
+ * so in several places, each in its own words: a slice carries `HonestyNote`s and, when it has no
7
+ * answer, a `missing` reason; a fed edge and an element birth carry a `basis`; a fold carries a
8
+ * `basis` and its `redacted` paths; a stored log reports `LogGap`s; a causal node carries
9
+ * `incompleteSources` and `truncated`. A reader that wants to EXPLAIN any of them to a person (a
10
+ * why-panel, an agent tool) kept its own table of code → sentence, and tables kept apart drift.
11
+ * This file is that table, once.
12
+ *
13
+ * WHAT IT HOLDS.
14
+ * - `HONESTY_CODES` — a frozen, closed registry: code → the ONE sentence that says what the code
15
+ * means. The shape of rustc's `--explain` and LSP's `Diagnostic.code`: the code is the stable
16
+ * key a consumer BRANCHES on; the sentence is for whoever reads the screen. It is not the
17
+ * per-instance `detail` a note carries (that names the key, the budget, the rows).
18
+ * - `HonestyCode` / `RegisteredCode` — what makes the registry the owner and not a suggestion:
19
+ * each public union of these codes (`HonestyNoteCode`, `MissingSliceReason`,
20
+ * `MissingProvenanceReason`, `FedBasis`, `AttributionBasis`, `FoldBasis`, and since 9.33.0 `ValueBasis` /
21
+ * `WriterBasis` — the basis twins of `commitValueAt` / `findLastWriter`) declares its members
22
+ * through `RegisteredCode`, so a member the registry does not hold fails to COMPILE. And
23
+ * `test/architecture/honesty-vocabulary.test.ts` asks the type checker for every exported type
24
+ * alias of `foottrace` that is a union of string literals: each one is inside the
25
+ * registry or is named, with its reason, as not an honesty vocabulary.
26
+ *
27
+ * NO OBJECT GAINS A FIELD from being registered. Three signals carry no code of their own — a
28
+ * `LogGap` (`'log-gap'`), `CausalNode.incompleteSources` (`'incomplete-sources'`) and
29
+ * `FoldedState.redacted` / `redactedPaths` (`'redacted'`) — and a `truncated: { byDepth, byNodes }`
30
+ * field means `'truncated'`; they are registered so a reader explains them from the same place.
31
+ *
32
+ * NOT HERE: the two strings a redaction leaves where a value was. The engine writes those on every
33
+ * redacted run; the log's lives in its own leaf, `memory/placeholders.ts` (the scope channel's with
34
+ * the verdict, in FootPrint's `memory/redaction.ts`), so that an app which only runs charts does not carry these
35
+ * sentences in its bundle (`HONESTY_CODES` is a pure expression, so a bundler drops it wherever
36
+ * nothing reads it).
37
+ *
38
+ * WHY L0. This file imports NOTHING, and `slice/` and `time-travel/` (L3) type their codes through
39
+ * it, so it sits below them — the layer table is `scripts/layers.json`.
40
+ *
41
+ * @example
42
+ * ```typescript
43
+ * import { HONESTY_CODES } from 'foottrace';
44
+ *
45
+ * for (const { code } of forwardSlice.notes) console.log(code, '→', HONESTY_CODES[code]);
46
+ * // reads-not-recorded → This log carries no recorded read at all (the readTracking: 'off' signature), …
47
+ * HONESTY_CODES[foldedState.basis]; // 'log-only' → No initialState travelled with this log (…), …
48
+ * ```
49
+ */
50
+ Object.defineProperty(exports, "__esModule", { value: true });
51
+ exports.HONESTY_CODES = void 0;
52
+ /**
53
+ * Every honesty code the trace readers (`foottrace`) speak, with the one sentence that says
54
+ * what it means.
55
+ *
56
+ * Frozen and closed: the keys are exactly the codes below, and {@link HonestyCode} is derived from
57
+ * them. A new code is one new line here: a union declared through {@link RegisteredCode} cannot hold
58
+ * a code that is not on it, and `test/architecture/honesty-vocabulary.test.ts` fails on an exported
59
+ * type alias of `foottrace` that is a union of string literals neither inside the registry
60
+ * nor named there as not an honesty vocabulary. The values are typed `string`, not literal types,
61
+ * on purpose: the sentences are for a screen and may be reworded; a consumer branches on the KEY.
62
+ */
63
+ exports.HONESTY_CODES = Object.freeze({
64
+ // ── a slice's notes — `HonestyNote.code` (slice/) ─────────────────────────
65
+ 'conservative-fed-edges': "At least one 'fed' edge is stage-level (conservative): its write carries no per-write read provenance, so the edge may not be real — turn on writeProvenance: 'reads-prefix' for exact edges.",
66
+ 'nested-rows': "A write this answer rests on reached the key only through paths inside it (a subflow's input seed, an outputMapper merge-back, a fork child's namespace), so it changed part of the key's value, not the whole: earlier writes may account for the rest, and a reader of the key may not have read the part that write changed.",
67
+ 'pre-run-origin': 'The value being followed was absent or already there before the first write this log can see (initial state, frozen run input or a closure), so who put it there is outside the commit log.',
68
+ 'reads-not-recorded': "This log carries no recorded read at all (the readTracking: 'off' signature), so 'nothing read this value' is unknowable here, not true.",
69
+ 'unknown-key': "This log has no write and no recorded read of the key, which is most likely a typo or the wrong run's log rather than a variable with no history.",
70
+ truncated: 'A budget (maxDepth or maxNodes) cut the walk, so more of the answer exists beyond the horizon it stopped at.',
71
+ // ── why a slice has no answer — `missing` (slice/: MissingSliceReason, MissingProvenanceReason) ─
72
+ 'empty-log': 'The commit log holds no commit at all (nothing has executed, or the log handed in is empty), so there is no history to answer from.',
73
+ 'never-written': 'No commit in the range asked about wrote the key — not its whole value, not a path inside it, and not a container around it in a way that changed it (a forward slice or a timeline also found no recorded read on, inside or around it) — so there is no write to start from: any value it has came from the initial state, the frozen run input or a closure, none of which the commit log can see.',
74
+ 'not-an-array': 'The key was written, but its value at the point asked about is not an array (a scalar or object key, a deleted key, or a merge that degraded it), so element provenance does not apply: sliceForKey is the query for it.',
75
+ // ── how a fed edge was attributed — `ForwardEdge.basis` (slice/: FedBasis) ─
76
+ 'per-write': "This fed edge is exact: the downstream write carries per-write read provenance (TraceEntry.readKeys, recorded under writeProvenance: 'reads-prefix') and that list names the key, so the value was read before the write it feeds.",
77
+ stage: 'This fed edge is conservative: the downstream write carries no per-write read provenance (the writeProvenance dial was off, or the log is mixed), so all that is known is that its stage read this key and wrote that path in some order, and the edge may not be real.',
78
+ // ── how an element's birth was attributed — `ElementBirth.basis` (slice/: AttributionBasis) ─
79
+ 'append-verb': "The engine recorded the element in an append row's tail (how commitValues: 'delta' records an array's growth), so the commit that added it is known exactly, not inferred.",
80
+ 'prefix-inference': 'A whole-value write (a set or a merge) kept the previous array as its prefix, so the new tail is attributed to that commit by inference: a write that replaced the array with one that happens to begin the same way looks identical.',
81
+ 'whole-value': "This commit placed the array wholesale (there was no earlier array, or the new one does not begin with it), so every element's birth resets to this commit: exact, but coarse.",
82
+ // ── how a fold was derived — `FoldedState.basis` (time-travel/) ───────────
83
+ 'initial+log': 'The fold started from the fold base that travelled with the log (initialState) and replayed the commit log onto it, so nothing seeded before the run is missing; rows it could not read are listed apart, in skipped.',
84
+ 'log-only': 'No initialState travelled with this log (a snapshot stored before 9.17, a redacted snapshot or a hand-built log), so the fold started from an empty object: a key seeded before the run is whole only once a commit since wrote its whole value, and until then it holds just what its merges, appends and nested writes added, or nothing.',
85
+ // ── a row of a stored log that is not a commit bundle — `LogGap` ──────────
86
+ 'log-gap': 'A row of the stored log could not be read as a commit bundle, so it keeps its index but contributes no state, and any fold that crosses it is missing whatever that row wrote.',
87
+ // ── untracked reads a stage also consumed — `CausalNode.incompleteSources` ─
88
+ 'incomplete-sources': 'The stage also consumed read paths that bypass read tracking (args, env or an unshadowed silent read), so a slice through it may be incomplete: those reads produce no read-to-write edge to follow.',
89
+ // ── values scrubbed at write time — `FoldedState.redacted` / `redactedPaths`, a value basis ─
90
+ redacted: "Values at the paths listed in redactedPaths (a fold's, or a commit's in the range a key's value rests on) were scrubbed when they were written (by a redaction policy, or a write marked redacted), so where the answer holds the log's placeholder 'REDACTED', or lost a key under a container the placeholder replaced, it stands in for a value the log never recorded.",
91
+ // ── how one key's value was read off the log — `ValueBasis` (commitValueAtWithBasis) ─
92
+ deleted: "The key's last write in the range asked about left it absent (a delete of the key or of a container around it, or a set of a container that no longer holds it), so the answer is undefined because the record says the key was gone at that point, not because nothing was recorded.",
93
+ 'from-initial-state': "No set or delete of the key, or of a container around it, is in the range asked about, so the answer rests on the value the key had before the run: folded from the initialState passed in when there was one, and otherwise only what the log's merges, appends and nested writes added to it (or nothing), which is partial for a key seeded before the run.",
94
+ });
@@ -0,0 +1,242 @@
1
+ "use strict";
2
+ /**
3
+ * keyPaths.ts — which rows of a commit log touch a state key. ONE answer (F3, 9.33.0).
4
+ *
5
+ * WHY THIS FILE EXISTS. A key query (`commitValueAt`, `findLastWriter`, `causalChain`, the slice
6
+ * layer) asks "which rows wrote `key`?". Each reader used to answer with its own `row.path === key`,
7
+ * so a row written INSIDE the key — `cfg␟b`, which every subflow seed of an object, every
8
+ * outputMapper merge-back and every fork child's namespace produces — was invisible to all of
9
+ * them, while the fold (`stateAt`, the live commit) applied it. The fold said `{a:1,b:2}`; the key
10
+ * queries said "never written". Every reader now asks here.
11
+ *
12
+ * THE PATH HALF (this file, L0 — it imports only the path codec and types):
13
+ * - {@link relation} — how a row's path sits against a key's path. A segment prefix on the
14
+ * DELIM-joined path, the way RFC 6901 prefixes a JSON Pointer: `cfg` is a prefix of `cfg␟a`,
15
+ * never of `cfgX`. A row is `'exact'` (on the key), `'inside'` (below it — it wrote part of the
16
+ * key's value) or `'around'` (above it — it wrote a container that holds the key).
17
+ * - {@link buildWriterIndex} / {@link writerCandidates} — one pass over a log, then the rows that
18
+ * can have written a key, found through its top-level key instead of a scan.
19
+ *
20
+ * THE RULES (defined here; the fold is L1 and the log readers L3, so `commitLogUtils.ts` applies
21
+ * them — `writersOf`, `findLastWriter`, `commitValueAt`):
22
+ * - THE WRITER RULE — commit `c` WRITES key `K` iff `c` has a row `'exact'` or `'inside'` `K`,
23
+ * or a row `'around'` `K` across which the value at `K` differs (compared over the whole commit,
24
+ * not row by row: a value that changes and changes back inside one commit was not written).
25
+ * A top-level key has no `'around'` rows, so for it the rule is the path check alone.
26
+ * Why not every `'around'` row: `$update('cfg', { list: [3] })` is a `merge` row on `cfg`, but
27
+ * it never reaches `cfg␟a` — counting it named the wrong writer of `cfg␟a`.
28
+ * - THE VALUE RULE — the value of `K` at commit `i` is the fold of EVERY row under `K`'s top-level
29
+ * key up to `i`, anchored at that key's last `set`/`delete`, read at `K`: the fold `stateAt`
30
+ * does, restricted to one top-level key. Top-level keys never interact under the step, so the
31
+ * restriction is exact. Folding only the rows that touch `K` is NOT: an array union
32
+ * (`deepSmartMerge`) dedups the whole array, so a sibling element written beside `a␟1` changes
33
+ * where a later merge on `a` leaves `a[1]`.
34
+ *
35
+ * @example
36
+ * ```typescript
37
+ * relation('cfg\u001Fb', 'cfg'); // 'inside' — the row wrote part of cfg
38
+ * relation('cfg', 'cfg\u001Fa'); // 'around' — the row wrote the container cfg.a lives in
39
+ * relation('cfgX', 'cfg'); // undefined — a different key, not a prefix match
40
+ * ```
41
+ */
42
+ Object.defineProperty(exports, "__esModule", { value: true });
43
+ exports.writesOnlyInside = exports.ascendingUnion = exports.writerCandidates = exports.between = exports.lastBefore = exports.lowerBound = exports.subtreePositions = exports.ancestorNodes = exports.nodeAt = exports.pathsWritten = exports.buildWriterIndex = exports.rootOf = exports.relation = exports.queryWork = void 0;
44
+ const paths_js_1 = require("./paths.js");
45
+ /**
46
+ * The WORK every key query has done — rows indexed and scanned, fold steps, around-verdicts, trie and
47
+ * ancestor steps, binary searches, a causal walk's own loops — summed across calls. A deterministic cost
48
+ * measure: `test/lib/memory/boundary/key-query-scaling.test.ts` asserts a causal walk's work grows linearly
49
+ * in the log by COUNTING it (a wall-clock ratio failed on a busy machine). Never read by the library; one
50
+ * integer add per unit of work it already does. Here, at L0, so every layer that does that work can count it.
51
+ */
52
+ exports.queryWork = { units: 0 };
53
+ const DELIM_CODE = paths_js_1.DELIM.charCodeAt(0);
54
+ /**
55
+ * How the row path `rowPath` sits against the key `key` — both DELIM-joined `TraceEntry.path`
56
+ * strings (a top-level key is a one-segment path). `'exact'` when they are equal, `'inside'` when
57
+ * the row is below the key, `'around'` when the row is above it, `undefined` when neither is a
58
+ * segment prefix of the other.
59
+ */
60
+ function relation(rowPath, key) {
61
+ if (rowPath === key)
62
+ return 'exact';
63
+ if (rowPath.length > key.length) {
64
+ return rowPath.charCodeAt(key.length) === DELIM_CODE && rowPath.startsWith(key) ? 'inside' : undefined;
65
+ }
66
+ return key.charCodeAt(rowPath.length) === DELIM_CODE && key.startsWith(rowPath) ? 'around' : undefined;
67
+ }
68
+ exports.relation = relation;
69
+ /** The top-level key a DELIM-joined path belongs to — its first segment. */
70
+ function rootOf(path) {
71
+ const cut = path.indexOf(paths_js_1.DELIM);
72
+ return cut === -1 ? path : path.slice(0, cut);
73
+ }
74
+ exports.rootOf = rootOf;
75
+ function nodeOf(path) {
76
+ return { path, positions: [], totals: [], writes: [], children: new Map() };
77
+ }
78
+ /** Append `i` to an ascending list unless it is already its last element. */
79
+ function pushOnce(list, i) {
80
+ if (list[list.length - 1] !== i)
81
+ list.push(i);
82
+ }
83
+ /**
84
+ * One pass over `commitLog`: the path trie of every written path. Build it ONCE when a reader asks about
85
+ * many keys (a causal walk, a forward slice) — or let `commitLogUtils · logModel` memoise it for a frozen log.
86
+ */
87
+ function buildWriterIndex(commitLog) {
88
+ const roots = new Map();
89
+ for (let i = 0; i < commitLog.length; i++) {
90
+ const trace = commitLog[i].trace;
91
+ for (let row = 0; row < trace.length; row++) {
92
+ const { path, verb } = trace[row];
93
+ const segs = path.split(paths_js_1.DELIM);
94
+ let node = roots.get(segs[0]);
95
+ if (node === undefined) {
96
+ node = nodeOf(segs[0]);
97
+ roots.set(segs[0], node);
98
+ }
99
+ for (let s = 1; s < segs.length; s++) {
100
+ let child = node.children.get(segs[s]);
101
+ if (child === undefined) {
102
+ child = nodeOf(node.path + paths_js_1.DELIM + segs[s]);
103
+ node.children.set(segs[s], child);
104
+ }
105
+ node = child;
106
+ }
107
+ pushOnce(node.positions, i);
108
+ if (verb === 'set' || verb === 'delete')
109
+ pushOnce(node.totals, i);
110
+ if (verb !== 'delete')
111
+ pushOnce(node.writes, i);
112
+ }
113
+ }
114
+ return { roots };
115
+ }
116
+ exports.buildWriterIndex = buildWriterIndex;
117
+ /** Every path a row was written on, in trie order. */
118
+ function pathsWritten(index) {
119
+ const out = [];
120
+ const stack = [...index.roots.values()];
121
+ while (stack.length > 0) {
122
+ const node = stack.pop();
123
+ if (node.positions.length > 0)
124
+ out.push(node.path);
125
+ for (const child of node.children.values())
126
+ stack.push(child);
127
+ }
128
+ return out;
129
+ }
130
+ exports.pathsWritten = pathsWritten;
131
+ /** The node for `key`, or undefined when no row was written on it or below it. */
132
+ function nodeAt(index, key) {
133
+ const segs = key.split(paths_js_1.DELIM);
134
+ exports.queryWork.units += segs.length;
135
+ let node = index.roots.get(segs[0]);
136
+ for (let s = 1; node !== undefined && s < segs.length; s++)
137
+ node = node.children.get(segs[s]);
138
+ return node;
139
+ }
140
+ exports.nodeAt = nodeAt;
141
+ /** The nodes of `key`'s PROPER ancestors that exist in the index, top-level key first. */
142
+ function ancestorNodes(index, key) {
143
+ const segs = key.split(paths_js_1.DELIM);
144
+ const out = [];
145
+ exports.queryWork.units += segs.length;
146
+ let node = index.roots.get(segs[0]);
147
+ for (let s = 1; node !== undefined && s < segs.length; s++) {
148
+ out.push(node);
149
+ node = node.children.get(segs[s]);
150
+ }
151
+ return out;
152
+ }
153
+ exports.ancestorNodes = ancestorNodes;
154
+ /** Every commit with a row on `node`'s path or below it — ascending, memoised on the node. */
155
+ function subtreePositions(node) {
156
+ exports.queryWork.units += 1;
157
+ if (node.subtree === undefined) {
158
+ if (node.children.size === 0)
159
+ node.subtree = node.positions;
160
+ else {
161
+ const lists = [node.positions];
162
+ for (const child of node.children.values())
163
+ lists.push(subtreePositions(child));
164
+ node.subtree = ascendingUnion(lists);
165
+ exports.queryWork.units += node.subtree.length;
166
+ }
167
+ }
168
+ return node.subtree;
169
+ }
170
+ exports.subtreePositions = subtreePositions;
171
+ /** The first index in an ascending list holding a value >= `value`. */
172
+ function lowerBound(sorted, value) {
173
+ exports.queryWork.units += 1;
174
+ let lo = 0;
175
+ let hi = sorted.length;
176
+ while (lo < hi) {
177
+ const mid = (lo + hi) >>> 1;
178
+ if (sorted[mid] < value)
179
+ lo = mid + 1;
180
+ else
181
+ hi = mid;
182
+ }
183
+ return lo;
184
+ }
185
+ exports.lowerBound = lowerBound;
186
+ /** The last value in an ascending list that is < `before`, or -1. */
187
+ function lastBefore(sorted, before) {
188
+ const at = lowerBound(sorted, before);
189
+ return at > 0 ? sorted[at - 1] : -1;
190
+ }
191
+ exports.lastBefore = lastBefore;
192
+ /** The values of an ascending list in the OPEN range (`after`, `before`). */
193
+ function between(sorted, after, before) {
194
+ const out = sorted.slice(lowerBound(sorted, after + 1), lowerBound(sorted, before));
195
+ exports.queryWork.units += out.length;
196
+ return out;
197
+ }
198
+ exports.between = between;
199
+ /** The path half of the writer rule over an index: every commit that can have written `key`. `O(depth + matches)`. */
200
+ function writerCandidates(index, key) {
201
+ const node = nodeAt(index, key);
202
+ const written = node === undefined ? [] : subtreePositions(node);
203
+ const ancestors = ancestorNodes(index, key);
204
+ if (ancestors.length === 0)
205
+ return { atOrInside: written, aroundOnly: [] };
206
+ const already = new Set(written);
207
+ return {
208
+ atOrInside: written,
209
+ aroundOnly: ascendingUnion(ancestors.map((a) => a.positions)).filter((i) => !already.has(i)),
210
+ };
211
+ }
212
+ exports.writerCandidates = writerCandidates;
213
+ /** The ascending, de-duplicated union of ascending position lists. */
214
+ function ascendingUnion(lists) {
215
+ if (lists.length === 0)
216
+ return [];
217
+ if (lists.length === 1)
218
+ return [...lists[0]];
219
+ const all = new Set();
220
+ for (const list of lists)
221
+ for (const i of list)
222
+ all.add(i);
223
+ return [...all].sort((a, b) => a - b);
224
+ }
225
+ exports.ascendingUnion = ascendingUnion;
226
+ /**
227
+ * Did this commit reach `key` ONLY through rows inside it — at least one row `'inside'` the key, and no
228
+ * row on it or around it? Such a write changed part of the key's value, not the whole: the one test
229
+ * behind every `'nested-rows'` code (a slice note, a causal edge's `basis`, `findLastWriterWithBasis`).
230
+ */
231
+ function writesOnlyInside(bundle, key) {
232
+ let inside = false;
233
+ for (const t of bundle.trace) {
234
+ const r = relation(t.path, key);
235
+ if (r === 'exact' || r === 'around')
236
+ return false;
237
+ if (r === 'inside')
238
+ inside = true;
239
+ }
240
+ return inside;
241
+ }
242
+ exports.writesOnlyInside = writesOnlyInside;