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,42 @@
1
+ "use strict";
2
+ /**
3
+ * slice/ — variable-first slicing, both directions (the triage query layer).
4
+ *
5
+ * One variable, one contract, every surface — asked identically by UI panels
6
+ * (explainable-ui / lens), LLM triage tools (trace toolpacks), and offline
7
+ * autopsy agents:
8
+ *
9
+ * - BACKWARD — "why is this VARIABLE what it is?" (`sliceForKey`,
10
+ * `arrayProvenance` / `elementProvenance`).
11
+ * - FORWARD — "who READ it, and what did it FEED?" (`forwardSliceForKey`),
12
+ * plus the flat chronological view (`keyTimeline`).
13
+ *
14
+ * See README.md for the algorithms (thin-slice composition, append-fold
15
+ * provenance, live-range walking), the honesty model, serialization rules,
16
+ * and the evolution path.
17
+ *
18
+ * DAG position: memory ← slice (leaf-adjacent; imports memory/ only).
19
+ */
20
+ Object.defineProperty(exports, "__esModule", { value: true });
21
+ exports.normaliseStateKey = exports.sliceForKey = exports.sliceToJSON = exports.forwardSliceToJSON = exports.formatTimeline = exports.formatSlice = exports.formatForwardSlice = exports.keyTimeline = exports.resolveKeysReadSource = exports.keysReadFromMap = exports.keysReadFromExecutionTree = exports.forwardSliceForKey = exports.elementProvenance = exports.arrayProvenance = void 0;
22
+ var elementProvenance_js_1 = require("./elementProvenance.js");
23
+ Object.defineProperty(exports, "arrayProvenance", { enumerable: true, get: function () { return elementProvenance_js_1.arrayProvenance; } });
24
+ Object.defineProperty(exports, "elementProvenance", { enumerable: true, get: function () { return elementProvenance_js_1.elementProvenance; } });
25
+ var forwardSliceForKey_js_1 = require("./forwardSliceForKey.js");
26
+ Object.defineProperty(exports, "forwardSliceForKey", { enumerable: true, get: function () { return forwardSliceForKey_js_1.forwardSliceForKey; } });
27
+ var keysReadSources_js_1 = require("./keysReadSources.js");
28
+ Object.defineProperty(exports, "keysReadFromExecutionTree", { enumerable: true, get: function () { return keysReadSources_js_1.keysReadFromExecutionTree; } });
29
+ Object.defineProperty(exports, "keysReadFromMap", { enumerable: true, get: function () { return keysReadSources_js_1.keysReadFromMap; } });
30
+ Object.defineProperty(exports, "resolveKeysReadSource", { enumerable: true, get: function () { return keysReadSources_js_1.resolveKeysReadSource; } });
31
+ var keyTimeline_js_1 = require("./keyTimeline.js");
32
+ Object.defineProperty(exports, "keyTimeline", { enumerable: true, get: function () { return keyTimeline_js_1.keyTimeline; } });
33
+ var serialize_js_1 = require("./serialize.js");
34
+ Object.defineProperty(exports, "formatForwardSlice", { enumerable: true, get: function () { return serialize_js_1.formatForwardSlice; } });
35
+ Object.defineProperty(exports, "formatSlice", { enumerable: true, get: function () { return serialize_js_1.formatSlice; } });
36
+ Object.defineProperty(exports, "formatTimeline", { enumerable: true, get: function () { return serialize_js_1.formatTimeline; } });
37
+ Object.defineProperty(exports, "forwardSliceToJSON", { enumerable: true, get: function () { return serialize_js_1.forwardSliceToJSON; } });
38
+ Object.defineProperty(exports, "sliceToJSON", { enumerable: true, get: function () { return serialize_js_1.sliceToJSON; } });
39
+ var sliceForKey_js_1 = require("./sliceForKey.js");
40
+ Object.defineProperty(exports, "sliceForKey", { enumerable: true, get: function () { return sliceForKey_js_1.sliceForKey; } });
41
+ var paths_js_1 = require("../memory/paths.js");
42
+ Object.defineProperty(exports, "normaliseStateKey", { enumerable: true, get: function () { return paths_js_1.normaliseStateKey; } });
@@ -0,0 +1,312 @@
1
+ "use strict";
2
+ /**
3
+ * slice/keyIndex.ts — the one pass over the log both FORWARD queries share.
4
+ *
5
+ * WHY it exists: a backward slice only ever asks "who wrote key k before
6
+ * idx i" — `findLastWriter` answers that with a backward scan. A forward
7
+ * query asks the opposite, and the opposite is not symmetric: reads are NOT
8
+ * in the commit log, and `KeysReadSource` is a LOOKUP (runtimeStageId →
9
+ * keys), not an enumerable collection. So "who read k" cannot be looked up —
10
+ * it has to be inverted.
11
+ *
12
+ * THE INVERSION (and the law it rests on): the engine commits exactly ONE
13
+ * bundle per executed stage, so the commit log IS the list of execution
14
+ * steps. Calling the reads lookup once per bundle turns the strategy into a
15
+ * key → [commit indices that read it] index — and gives every read moment a
16
+ * commit position, the join key the whole library is addressed by.
17
+ *
18
+ * Consequence worth stating (documented, not silently absorbed): a reads
19
+ * provider naming a step that has no commit in THIS log contributes nothing.
20
+ * That is the same log↔reads pairing law the backward side has — a subflow
21
+ * runs in an isolated runtime, so its log and its tree must come from the
22
+ * same scope (see README.md § Subflow boundaries).
23
+ *
24
+ * Cost: one lookup call per bundle, once per query — O(N + total read keys).
25
+ * Both forward queries then run off sorted index arrays.
26
+ *
27
+ * DAG position: internal to slice/ (not exported from the barrel).
28
+ */
29
+ Object.defineProperty(exports, "__esModule", { value: true });
30
+ exports.writtenPaths = exports.truncatedNote = exports.conservativeEdgesNote = exports.preRunOriginNote = exports.readsNotRecordedNote = exports.fromInitialStateNote = exports.redactedValueNote = exports.nestedRowsNote = exports.NESTED_WRITES_LISTED = exports.nestedOnlyWrites = exports.writeEntry = exports.lastTraceEntry = exports.firstIndexAfter = exports.lastIndexBefore = exports.indicesInRange = exports.hasRecordedReads = exports.unknownKeyNote = exports.buildKeyIndex = exports.KNOWN_KEYS_LISTED = void 0;
31
+ const keyPaths_js_1 = require("../memory/keyPaths.js");
32
+ const logModel_js_1 = require("../memory/logModel.js");
33
+ const paths_js_1 = require("../memory/paths.js");
34
+ /** How many known keys an unknown-key refusal lists before it says "+N more". */
35
+ exports.KNOWN_KEYS_LISTED = 10;
36
+ /** Push into a Map<string, number[]>, creating the bucket on first touch. */
37
+ function push(map, key, idx) {
38
+ const arr = map.get(key);
39
+ if (arr)
40
+ arr.push(idx);
41
+ else
42
+ map.set(key, [idx]);
43
+ }
44
+ /**
45
+ * Build the index. Indices go in ascending order by construction (we iterate
46
+ * the log forwards), which is what lets every range query below be a pair of
47
+ * binary searches instead of a scan.
48
+ */
49
+ function buildKeyIndex(commitLog, lookup) {
50
+ const model = (0, logModel_js_1.logModel)(commitLog);
51
+ const writers = model.index;
52
+ const readsByKey = new Map();
53
+ const readKeysByRoot = new Map();
54
+ for (let i = 0; i < commitLog.length; i++) {
55
+ const bundle = commitLog[i];
56
+ // Reads: one lookup per execution step (see THE INVERSION above).
57
+ // A provider that throws is a consumer bug, not a reason to lose the
58
+ // query — the same error-isolation posture recorder callbacks get.
59
+ let keys = [];
60
+ try {
61
+ keys = lookup(bundle.runtimeStageId) ?? [];
62
+ }
63
+ catch {
64
+ /* provider threw — treat this step as "no recorded reads" */
65
+ }
66
+ for (const k of new Set(keys)) {
67
+ if (!readsByKey.has(k)) {
68
+ const names = readKeysByRoot.get((0, keyPaths_js_1.rootOf)(k));
69
+ if (names)
70
+ names.push(k);
71
+ else
72
+ readKeysByRoot.set((0, keyPaths_js_1.rootOf)(k), [k]);
73
+ }
74
+ push(readsByKey, k, i);
75
+ }
76
+ }
77
+ const writesOf = (key) => model.writersOf(key);
78
+ const closing = new Map();
79
+ const closingWritesOf = (key) => {
80
+ let positions = closing.get(key);
81
+ if (positions === undefined) {
82
+ const nested = new Set(nestedOnlyWrites(commitLog, key, writesOf(key)));
83
+ positions = writesOf(key).filter((c) => !nested.has(c));
84
+ closing.set(key, positions);
85
+ }
86
+ return positions;
87
+ };
88
+ const reads = new Map();
89
+ const readsOf = (key) => {
90
+ let positions = reads.get(key);
91
+ if (positions === undefined) {
92
+ const names = readKeysByRoot.get((0, keyPaths_js_1.rootOf)(key)) ?? [];
93
+ positions = (0, keyPaths_js_1.ascendingUnion)(names.filter((name) => (0, keyPaths_js_1.relation)(name, key) !== undefined).map((name) => readsByKey.get(name)));
94
+ reads.set(key, positions);
95
+ }
96
+ return positions;
97
+ };
98
+ const knownKeys = new Set([...(0, keyPaths_js_1.pathsWritten)(writers), ...readsByKey.keys()]);
99
+ const isKnown = (key) => writesOf(key).length > 0 || readsOf(key).length > 0;
100
+ return { writers, writesOf, closingWritesOf, readsByKey, readsOf, knownKeys, isKnown };
101
+ }
102
+ exports.buildKeyIndex = buildKeyIndex;
103
+ /**
104
+ * LAW — an unknown key is NAMED as unknown; it never renders as "no history".
105
+ *
106
+ * The failure this prevents: `forwardSliceForKey(log, 'recipId')` (typo) must
107
+ * not come back looking like a real variable that nothing happened to. The
108
+ * answer says so out loud AND lists the keys the log does know — bounded, so
109
+ * a 400-key agent state is a hint, not a wall.
110
+ *
111
+ * Why a NOTE and not a throw: the module's absence convention is a RESULT,
112
+ * not an exception (`VariableSlice.missing`) — one absence vocabulary across
113
+ * both doors. The loudness comes from the note + `missing: 'never-written'`,
114
+ * not from a stack trace a triage tool would have to catch.
115
+ */
116
+ function unknownKeyNote(index, key) {
117
+ const all = [...index.knownKeys].sort();
118
+ const shown = all.slice(0, exports.KNOWN_KEYS_LISTED);
119
+ const more = all.length - shown.length;
120
+ const known = shown.length > 0 ? `${shown.join(', ')}${more > 0 ? ` (+${more} more)` : ''}` : '(none)';
121
+ return {
122
+ code: 'unknown-key',
123
+ detail: `unknown key '${key}' — this commit log has no write and no recorded read of it. ` +
124
+ `Known keys: ${known}. ` +
125
+ 'If the key is real, check that the commit log and the reads provider come from the SAME scope ' +
126
+ '(a subflow has its own).',
127
+ };
128
+ }
129
+ exports.unknownKeyNote = unknownKeyNote;
130
+ /**
131
+ * Does this log carry ANY recorded read? The provider-agnostic version of
132
+ * `ReadsCoverage.stepsWithReads > 0` (map / custom-fn strategies carry no
133
+ * coverage), and the switch that decides whether "nobody read it" is a
134
+ * FINDING or a BLIND SPOT.
135
+ */
136
+ function hasRecordedReads(index) {
137
+ return index.readsByKey.size > 0;
138
+ }
139
+ exports.hasRecordedReads = hasRecordedReads;
140
+ /** First position in the ascending array holding a value >= `value`. */
141
+ function lowerBound(sorted, value) {
142
+ let lo = 0;
143
+ let hi = sorted.length;
144
+ while (lo < hi) {
145
+ const mid = (lo + hi) >>> 1;
146
+ if (sorted[mid] < value)
147
+ lo = mid + 1;
148
+ else
149
+ hi = mid;
150
+ }
151
+ return lo;
152
+ }
153
+ /**
154
+ * Ascending indices in the INCLUSIVE range [from, to]. `to === undefined`
155
+ * means "to the end of the log".
156
+ */
157
+ function indicesInRange(sorted, from, to) {
158
+ if (!sorted || sorted.length === 0)
159
+ return [];
160
+ const start = lowerBound(sorted, from);
161
+ const end = to === undefined ? sorted.length : lowerBound(sorted, to + 1);
162
+ return sorted.slice(start, end);
163
+ }
164
+ exports.indicesInRange = indicesInRange;
165
+ /** The largest index in the ascending array that is < `before`, or undefined. */
166
+ function lastIndexBefore(sorted, before) {
167
+ if (!sorted || sorted.length === 0)
168
+ return undefined;
169
+ const end = before === undefined ? sorted.length : lowerBound(sorted, before);
170
+ return end > 0 ? sorted[end - 1] : undefined;
171
+ }
172
+ exports.lastIndexBefore = lastIndexBefore;
173
+ /** The smallest index in the ascending array that is > `after`, or undefined. */
174
+ function firstIndexAfter(sorted, after) {
175
+ if (!sorted || sorted.length === 0)
176
+ return undefined;
177
+ const at = lowerBound(sorted, after + 1);
178
+ return at < sorted.length ? sorted[at] : undefined;
179
+ }
180
+ exports.firstIndexAfter = firstIndexAfter;
181
+ /**
182
+ * The LAST trace entry for `path` in a bundle — the one that decides both the
183
+ * committed verb and (under the writeProvenance dial) the widest read prefix.
184
+ * Full mode can record several ops on one path in one commit; read prefixes
185
+ * only grow during a stage, so the last entry is the honest one to attribute
186
+ * with.
187
+ */
188
+ function lastTraceEntry(bundle, path) {
189
+ for (let i = bundle.trace.length - 1; i >= 0; i--) {
190
+ if (bundle.trace[i].path === path)
191
+ return bundle.trace[i];
192
+ }
193
+ return undefined;
194
+ }
195
+ exports.lastTraceEntry = lastTraceEntry;
196
+ /**
197
+ * The trace entry that decides a WRITE of `key` in `bundle` — its verb, and the read prefix a
198
+ * fed edge is judged by: the LAST entry on the exact path when there is one (as before 9.33.0),
199
+ * otherwise the last entry inside or around the key (the commit wrote it through a nested row).
200
+ */
201
+ function writeEntry(bundle, key) {
202
+ const exact = lastTraceEntry(bundle, key);
203
+ if (exact !== undefined)
204
+ return exact;
205
+ for (let i = bundle.trace.length - 1; i >= 0; i--) {
206
+ if ((0, keyPaths_js_1.relation)(bundle.trace[i].path, key) !== undefined)
207
+ return bundle.trace[i];
208
+ }
209
+ return undefined;
210
+ }
211
+ exports.writeEntry = writeEntry;
212
+ /**
213
+ * The commits in `writes` that reached `key` ONLY through rows inside it — no row on the key and
214
+ * none around it — so each changed part of its value, not the whole. What a `'nested-rows'` note
215
+ * names.
216
+ */
217
+ function nestedOnlyWrites(commitLog, key, writes) {
218
+ return writes.filter((i) => (0, keyPaths_js_1.writesOnlyInside)(commitLog[i], key));
219
+ }
220
+ exports.nestedOnlyWrites = nestedOnlyWrites;
221
+ /** How many commit positions a `'nested-rows'` note lists before it says "+N more". */
222
+ exports.NESTED_WRITES_LISTED = 5;
223
+ /** A key as a person reads it: its segments, not the engine's delimiter. */
224
+ function shownKey(key) {
225
+ return (0, paths_js_1.pathSegments)(key).join(' › ');
226
+ }
227
+ /** A write the answer rests on reached the key only through paths inside it. */
228
+ function nestedRowsNote(key, commits) {
229
+ const shown = commits.slice(0, exports.NESTED_WRITES_LISTED);
230
+ const more = commits.length - shown.length;
231
+ const at = `commit${commits.length === 1 ? '' : 's'} ${shown.join(', ')}${more > 0 ? ` (+${more} more)` : ''}`;
232
+ return {
233
+ code: 'nested-rows',
234
+ detail: `'${shownKey(key)}' was written only through paths inside it at ${at} — such a write changed part of ` +
235
+ 'its value, not the whole: earlier writes may account for the rest, and a reader of the key may ' +
236
+ 'not have read the part it changed.',
237
+ };
238
+ }
239
+ exports.nestedRowsNote = nestedRowsNote;
240
+ /** The value a backward slice explains holds, or lost the key under, the log's redaction placeholder. */
241
+ function redactedValueNote(key) {
242
+ return {
243
+ code: 'redacted',
244
+ detail: `'${shownKey(key)}' was redacted when it was written — where its value shows the log's placeholder (or ` +
245
+ 'the key is missing under a placeholder that replaced its container), the real value was never recorded.',
246
+ };
247
+ }
248
+ exports.redactedValueNote = redactedValueNote;
249
+ /** No set or delete of the key, or of a container around it, is in range: the value rests on the pre-run state. */
250
+ function fromInitialStateNote(key) {
251
+ return {
252
+ code: 'from-initial-state',
253
+ detail: `'${shownKey(key)}' has no write of its whole value in range — its value rests on what it held before the ` +
254
+ 'run, which the commit log cannot see, so the writes here may be only part of it.',
255
+ };
256
+ }
257
+ exports.fromInitialStateNote = fromInitialStateNote;
258
+ // ── Shared honesty notes (one sentence per code, one place) ───────────────
259
+ //
260
+ // LAW: every forward answer states what it could not see, in the SAME words
261
+ // whichever query produced it. Codes are what a consumer branches on; the
262
+ // detail is what a human or an LLM reads.
263
+ /** The `readTracking: 'off'` signature — see the HonestyNoteCode docs. */
264
+ function readsNotRecordedNote() {
265
+ return {
266
+ code: 'reads-not-recorded',
267
+ detail: "reads were not recorded (readTracking may be 'off') — 'nothing read this value' is " +
268
+ 'UNKNOWABLE here, not true.',
269
+ };
270
+ }
271
+ exports.readsNotRecordedNote = readsNotRecordedNote;
272
+ /** The value predates every write this log can see. */
273
+ function preRunOriginNote(key) {
274
+ return {
275
+ code: 'pre-run-origin',
276
+ detail: `'${key}' has no write before this point — the value came from initial state, frozen run input ` +
277
+ '(args), or a closure. The reads listed did see it; who put it there is outside the commit log.',
278
+ };
279
+ }
280
+ exports.preRunOriginNote = preRunOriginNote;
281
+ /** At least one fed edge rests on stage-level co-occurrence only. */
282
+ function conservativeEdgesNote(key) {
283
+ return {
284
+ code: 'conservative-fed-edges',
285
+ detail: "some 'fed' edges are CONSERVATIVE (stage-level): those writes carry no per-write read provenance, " +
286
+ `so a stage that read '${key}' and wrote another key may not actually have used it. Run with ` +
287
+ "writeProvenance: 'reads-prefix' to get exact edges.",
288
+ };
289
+ }
290
+ exports.conservativeEdgesNote = conservativeEdgesNote;
291
+ /** A budget cut the walk — stated, never silent. */
292
+ function truncatedNote(byDepth, byNodes) {
293
+ const causes = [byDepth && 'maxDepth reached', byNodes && 'maxNodes reached'].filter(Boolean).join(', ');
294
+ return {
295
+ code: 'truncated',
296
+ detail: `walk truncated (${causes}) — more consumers of this value exist beyond this horizon.`,
297
+ };
298
+ }
299
+ exports.truncatedNote = truncatedNote;
300
+ /** Distinct written paths of a bundle, in first-touch order. */
301
+ function writtenPaths(bundle) {
302
+ const seen = new Set();
303
+ const out = [];
304
+ for (const t of bundle.trace) {
305
+ if (!seen.has(t.path)) {
306
+ seen.add(t.path);
307
+ out.push(t.path);
308
+ }
309
+ }
310
+ return out;
311
+ }
312
+ exports.writtenPaths = writtenPaths;
@@ -0,0 +1,116 @@
1
+ "use strict";
2
+ /**
3
+ * slice/keyTimeline.ts — the whole life of ONE key, in commit order.
4
+ *
5
+ * The flat companion to {@link forwardSliceForKey}: no graph, no budgets,
6
+ * no anchor — every write of the key and every recorded read of it, as one
7
+ * chronological list. This is what a UI timeline row and an LLM's "walk me
8
+ * through what happened to `total`" both want, and what makes a forward
9
+ * slice checkable: the same facts, unfolded instead of walked.
10
+ *
11
+ * Every moment carries BOTH universal join keys — `runtimeStageId` (to
12
+ * recorders, execution trees, UIs) and `commitIdx` (to the commit log).
13
+ *
14
+ * ### LAW — read attribution is the walk's, exactly
15
+ *
16
+ * A read moment's `fromWriteIdx` is the write whose live range it sits in:
17
+ * the last write at a STRICTLY earlier commit index. That is the same
18
+ * `(writeIdx, nextWriteIdx]` rule `forwardSliceForKey` walks (reads fire
19
+ * PRE-commit), so the two doors can never disagree about which value a read
20
+ * saw — pinned by a cross-door property test. A read before ANY write has no
21
+ * `fromWriteIdx` and raises the `'pre-run-origin'` note: it saw initial
22
+ * state, frozen run `input`, or a closure.
23
+ *
24
+ * DAG position: memory ← slice. Imports memory/ only (see README.md).
25
+ */
26
+ Object.defineProperty(exports, "__esModule", { value: true });
27
+ exports.keyTimeline = void 0;
28
+ const keyIndex_js_1 = require("./keyIndex.js");
29
+ const keysReadSources_js_1 = require("./keysReadSources.js");
30
+ const paths_js_1 = require("../memory/paths.js");
31
+ /**
32
+ * The life of one key as a chronological list of writes and reads.
33
+ *
34
+ * @param commitLog Ordered commit bundles — `getSnapshot().commitLog`.
35
+ * @param key A {@link StateKey} — normalised through the same
36
+ * `normaliseStateKey` both slice doors use.
37
+ * @param keysRead A {@link KeysReadSource} or bare lookup (reads are not in
38
+ * the commit log).
39
+ * @returns Always a {@link KeyTimeline}; absence carries its reason, and an
40
+ * unknown key is NAMED as unknown with a bounded list of known keys.
41
+ */
42
+ function keyTimeline(commitLog, key, keysRead, options) {
43
+ const source = (0, keysReadSources_js_1.resolveKeysReadSource)(keysRead);
44
+ const normalisedKey = (0, paths_js_1.normaliseStateKey)(key);
45
+ const base = {
46
+ key: normalisedKey,
47
+ ...(options?.before !== undefined && { before: options.before }),
48
+ keysReadKind: source.kind,
49
+ ...(source.coverage !== undefined && { readsCoverage: source.coverage }),
50
+ };
51
+ if (commitLog.length === 0)
52
+ return { ...base, missing: 'empty-log', notes: [] };
53
+ const index = (0, keyIndex_js_1.buildKeyIndex)(commitLog, source.lookup);
54
+ const readsRecorded = (0, keyIndex_js_1.hasRecordedReads)(index);
55
+ const notes = [];
56
+ // Same typo guard as the forward walk — one rule, both doors.
57
+ if (!index.isKnown(normalisedKey)) {
58
+ notes.push((0, keyIndex_js_1.unknownKeyNote)(index, normalisedKey));
59
+ if (readsRecorded)
60
+ return { ...base, missing: 'never-written', notes };
61
+ }
62
+ if (!readsRecorded)
63
+ notes.push((0, keyIndex_js_1.readsNotRecordedNote)());
64
+ const limit = options?.before ?? commitLog.length;
65
+ // Writes under the writer rule (on the key, inside it, or around it and
66
+ // changing it); reads on the key, inside it or around it (keyIndex.ts).
67
+ const allWrites = index.writesOf(normalisedKey);
68
+ const writes = allWrites.filter((i) => i < limit);
69
+ const reads = index.readsOf(normalisedKey).filter((i) => i < limit);
70
+ // Merge two ascending index lists. At the SAME commit index a read comes
71
+ // first: reads fire before their stage's commit (the engine's event order,
72
+ // and the reason the live range is closed at the next write).
73
+ const moments = [];
74
+ let w = 0;
75
+ let r = 0;
76
+ let sawPreRunRead = false;
77
+ while (w < writes.length || r < reads.length) {
78
+ const takeRead = r < reads.length && (w >= writes.length || reads[r] <= writes[w]);
79
+ if (takeRead) {
80
+ const idx = reads[r++];
81
+ const bundle = commitLog[idx];
82
+ // The live range this read sits in: the last write STRICTLY earlier.
83
+ const fromWriteIdx = (0, keyIndex_js_1.lastIndexBefore)(allWrites, idx);
84
+ if (fromWriteIdx === undefined)
85
+ sawPreRunRead = true;
86
+ moments.push({
87
+ kind: 'read',
88
+ commitIdx: idx,
89
+ runtimeStageId: bundle.runtimeStageId,
90
+ stageId: bundle.stageId,
91
+ stageName: bundle.stage,
92
+ ...(fromWriteIdx !== undefined && { fromWriteIdx }),
93
+ });
94
+ }
95
+ else {
96
+ const idx = writes[w++];
97
+ const bundle = commitLog[idx];
98
+ const entry = (0, keyIndex_js_1.writeEntry)(bundle, normalisedKey);
99
+ moments.push({
100
+ kind: 'write',
101
+ commitIdx: idx,
102
+ runtimeStageId: bundle.runtimeStageId,
103
+ stageId: bundle.stageId,
104
+ stageName: bundle.stage,
105
+ ...(entry !== undefined && { verb: entry.verb }),
106
+ });
107
+ }
108
+ }
109
+ if (sawPreRunRead)
110
+ notes.push((0, keyIndex_js_1.preRunOriginNote)(normalisedKey));
111
+ const nested = (0, keyIndex_js_1.nestedOnlyWrites)(commitLog, normalisedKey, writes);
112
+ if (nested.length > 0)
113
+ notes.push((0, keyIndex_js_1.nestedRowsNote)(normalisedKey, nested));
114
+ return { ...base, moments, notes };
115
+ }
116
+ exports.keyTimeline = keyTimeline;
@@ -0,0 +1,100 @@
1
+ "use strict";
2
+ /**
3
+ * slice/keysReadSources.ts — the shipped {@link KeysReadSource} strategies.
4
+ *
5
+ * The canonical strategy list and rationale live on the KeysReadSource type
6
+ * (types.ts) — the doc consumers hover. This file holds the implementations.
7
+ *
8
+ * Deliberately NOT here: a QualityRecorder adapter. Importing recorder/ would
9
+ * drag this leaf library up the dependency DAG. The adapter is a one-liner at
10
+ * the call site instead: `(id) => recorder.getByKey(id)?.keysRead ?? []`.
11
+ */
12
+ Object.defineProperty(exports, "__esModule", { value: true });
13
+ exports.resolveKeysReadSource = exports.keysReadFromMap = exports.keysReadFromExecutionTree = void 0;
14
+ /**
15
+ * Post-hoc reads from a finished run's execution tree — ZERO setup.
16
+ *
17
+ * WHY this works: a stage's `stageReads` records the keys it
18
+ * tracked-read whenever the `readTracking` dial ≠ 'off' ('full' is the
19
+ * engine default; 'summary' replaces VALUES with markers but keeps the
20
+ * KEYS — and keys are all a slice needs). Under 'off' this source returns
21
+ * empty read-sets — detectable via `coverage.stepsWithReads === 0`, which
22
+ * `sliceForKey` copies onto the slice so tools can say "reads were not
23
+ * recorded" instead of the lie "no dependencies".
24
+ *
25
+ * SUBFLOW PAIRING (important): a subflow runs in an ISOLATED runtime — its
26
+ * commits live in `snapshot.subflowResults[sfId].commitLog`, its reads in
27
+ * `snapshot.subflowResults[sfId].executionTree`. Trees and logs must be
28
+ * paired from the SAME scope: to slice a key inside a subflow, re-anchor
29
+ * there —
30
+ *
31
+ * ```ts
32
+ * const sf = snapshot.subflowResults['sf-tools'];
33
+ * sliceForKey(sf.commitLog, key, keysReadFromExecutionTree(sf.executionTree));
34
+ * ```
35
+ *
36
+ * Passing multiple trees widens READ resolution only (useful when one log
37
+ * genuinely spans them); it does not let a root-log slice cross a subflow
38
+ * mount — see README.md § Subflow boundaries.
39
+ */
40
+ function keysReadFromExecutionTree(tree) {
41
+ const byStep = new Map();
42
+ let steps = 0;
43
+ // The tree is acyclic by construction (next/children), but this walker is
44
+ // also handed CONSUMER-provided data — a visited set makes a malformed or
45
+ // hand-built tree a non-event instead of an infinite loop.
46
+ const visited = new Set();
47
+ const stack = [tree].flat(); // one root, or several (subflow trees); a hole is skipped
48
+ while (stack.length > 0) {
49
+ const node = stack.pop();
50
+ if (visited.has(node))
51
+ continue;
52
+ visited.add(node);
53
+ if (node.runtimeStageId) {
54
+ steps++;
55
+ if (node.stageReads) {
56
+ const keys = Object.keys(node.stageReads);
57
+ if (keys.length > 0)
58
+ byStep.set(node.runtimeStageId, keys);
59
+ }
60
+ }
61
+ if (node.next)
62
+ stack.push(node.next);
63
+ if (node.children)
64
+ for (const c of node.children)
65
+ stack.push(c);
66
+ }
67
+ return {
68
+ kind: 'execution-tree',
69
+ lookup: (runtimeStageId) => byStep.get(runtimeStageId) ?? [],
70
+ coverage: { steps, stepsWithReads: byStep.size },
71
+ };
72
+ }
73
+ exports.keysReadFromExecutionTree = keysReadFromExecutionTree;
74
+ /**
75
+ * Reads from a prebuilt map — e.g. collected live from
76
+ * `ScopeRecorder.onRead` events, or deserialized from a stored trace.
77
+ */
78
+ function keysReadFromMap(map) {
79
+ // Own-property guard on the object form: runtimeStageIds are consumer-
80
+ // influenced strings; without it, an id like 'constructor' would read
81
+ // through the prototype chain (the same hardening posture as the engine's
82
+ // nativeGet/prototype-pollution guards).
83
+ const get = map instanceof Map
84
+ ? (id) => map.get(id)
85
+ : (id) => Object.prototype.hasOwnProperty.call(map, id) ? map[id] : undefined;
86
+ return {
87
+ kind: 'map',
88
+ lookup: (runtimeStageId) => [...(get(runtimeStageId) ?? [])],
89
+ };
90
+ }
91
+ exports.keysReadFromMap = keysReadFromMap;
92
+ /**
93
+ * Normalize the ergonomic union: callers may pass a full strategy object or
94
+ * a bare lookup function (wrapped as kind 'custom-fn' so the honesty
95
+ * breadcrumb still says where reads came from).
96
+ */
97
+ function resolveKeysReadSource(src) {
98
+ return typeof src === 'function' ? { kind: 'custom-fn', lookup: src } : src;
99
+ }
100
+ exports.resolveKeysReadSource = resolveKeysReadSource;