foottrace 1.0.0 → 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.
package/README.md CHANGED
@@ -4,7 +4,7 @@ Execution records and the tools to read them: replay, time travel, slices, and e
4
4
 
5
5
  This package extracts FootPrint's established record layer. It does not introduce a second writer or a different replay law. The first release preserves the frozen record fixtures from footprintjs 9.48.1 and includes the coordinated 9.48.2 copy-safety correction: an own `__proto__` payload field stays data, never the copy's prototype. See the [changelog](CHANGELOG.md) for the limited rich-value behavior change.
6
6
 
7
- **Release status:** extraction candidate. Version 1.0.0 is not ready to publish until review and release checks are complete.
7
+ Foottrace owns the record layer used by footprintjs 10. Older stored records remain readable without installing their original engine.
8
8
 
9
9
  ## Three entry points
10
10
 
@@ -257,6 +257,12 @@ export type KeysReadLookup = (runtimeStageId: string) => string[];
257
257
  * @returns Root CausalNode with .parents forming the DAG, or undefined if startId not found
258
258
  */
259
259
  export declare function causalChain(commitLog: CommitBundle[], startId: string, getKeysRead: KeysReadLookup, options?: CausalChainOptions): CausalNode | undefined;
260
+ /**
261
+ * Internal slice composition: keep the actual bundle selected by the writer
262
+ * query (including a nonempty continuation). This is not a public entry point.
263
+ * The read position still belongs to the execution's first commit.
264
+ */
265
+ export declare function causalChainFromCommit(commitLog: CommitBundle[], writer: CommitBundle, getKeysRead: KeysReadLookup, options?: CausalChainOptions): CausalNode | undefined;
260
266
  /**
261
267
  * Flatten the causal DAG into a BFS-ordered list of nodes.
262
268
  * Each node appears exactly once (first occurrence by BFS order).
@@ -56,7 +56,7 @@
56
56
  * console.log(formatCausalChain(dag)); // human-readable
57
57
  * ```
58
58
  */
59
- import { findLastWriter } from './commitLogUtils.js';
59
+ import { buildCommitIndex, findLastWriter } from './commitLogUtils.js';
60
60
  import { queryWork, relation, writesOnlyInside } from './keyPaths.js';
61
61
  import { logModel } from './logModel.js';
62
62
  // ── Staged optimization: writer lookup strategies ──────────────────────
@@ -148,6 +148,17 @@ function incompleteSourcesFragment(commit) {
148
148
  * @returns Root CausalNode with .parents forming the DAG, or undefined if startId not found
149
149
  */
150
150
  export function causalChain(commitLog, startId, getKeysRead, options) {
151
+ return buildCausalChain(commitLog, startId, getKeysRead, options);
152
+ }
153
+ /**
154
+ * Internal slice composition: keep the actual bundle selected by the writer
155
+ * query (including a nonempty continuation). This is not a public entry point.
156
+ * The read position still belongs to the execution's first commit.
157
+ */
158
+ export function causalChainFromCommit(commitLog, writer, getKeysRead, options) {
159
+ return buildCausalChain(commitLog, writer.runtimeStageId, getKeysRead, options, writer);
160
+ }
161
+ function buildCausalChain(commitLog, startId, getKeysRead, options, selectedWriter) {
151
162
  const maxDepth = options?.maxDepth ?? 20;
152
163
  const maxNodes = options?.maxNodes ?? 100;
153
164
  const controlDeps = options?.controlDeps;
@@ -158,16 +169,15 @@ export function causalChain(commitLog, startId, getKeysRead, options) {
158
169
  // never mistake a truncated slice for a complete one.
159
170
  let truncatedByDepth = false;
160
171
  let truncatedByNodes = false;
161
- // Build position index: runtimeStageId → array position (O(n) once)
162
- const idxMap = new Map();
163
- for (let i = 0; i < commitLog.length; i++) {
164
- idxMap.set(commitLog[i].runtimeStageId, i);
165
- }
172
+ // A continuation may write more data, but it is not another execution of
173
+ // the stage's reads. Use the shared FIRST-commit rule for every read cutoff;
174
+ // keep writer bundles separately for their write metadata and read prefixes.
175
+ const idxMap = buildCommitIndex(commitLog);
166
176
  queryWork.units += commitLog.length;
167
177
  const startIdx = idxMap.get(startId);
168
178
  if (startIdx === undefined)
169
179
  return undefined;
170
- const startCommit = commitLog[startIdx];
180
+ const startCommit = selectedWriter ?? commitLog[startIdx];
171
181
  // Pick writer lookup strategy based on log size
172
182
  const findWriter = createWriterLookup(commitLog);
173
183
  // Node dedup map: runtimeStageId → CausalNode (ensures DAG, not tree)
@@ -12,7 +12,7 @@
12
12
  * it, every surface hand-rolls the anchor step differently and their answers
13
13
  * disagree. The value of this function is that they can't.
14
14
  */
15
- import { causalChain } from '../memory/backtrack.js';
15
+ import { causalChainFromCommit } from '../memory/backtrack.js';
16
16
  import { commitValueAtWithBasis, findLastWriterWithBasis } from '../memory/commitLogUtils.js';
17
17
  import { normaliseStateKey } from '../memory/paths.js';
18
18
  import { fromInitialStateNote, nestedRowsNote, redactedValueNote } from './keyIndex.js';
@@ -69,7 +69,9 @@ export function sliceForKey(commitLog, key, keysRead, options) {
69
69
  // without `TraceEntry.readKeys` (the writeProvenance dial off) fall back
70
70
  // to stage-level expansion per node, byte-identically. Consumers can force
71
71
  // 'stage' via options.
72
- const root = causalChain(commitLog, writer.runtimeStageId, source.lookup, {
72
+ // Preserve this exact writer's metadata/read prefix, even when it is a
73
+ // continuation. Causal expansion separately uses the execution's first commit.
74
+ const root = causalChainFromCommit(commitLog, writer, source.lookup, {
73
75
  ...options,
74
76
  edgeAttribution: options?.edgeAttribution ?? 'per-write',
75
77
  rootLinkKeys: options?.rootLinkKeys ?? [normalisedKey],
@@ -58,7 +58,7 @@
58
58
  * ```
59
59
  */
60
60
  Object.defineProperty(exports, "__esModule", { value: true });
61
- exports._REVERSE_INDEX_THRESHOLD = exports.formatCausalChain = exports.flattenCausalDAG = exports.causalChain = void 0;
61
+ exports._REVERSE_INDEX_THRESHOLD = exports.formatCausalChain = exports.flattenCausalDAG = exports.causalChainFromCommit = exports.causalChain = void 0;
62
62
  const commitLogUtils_js_1 = require("./commitLogUtils.js");
63
63
  const keyPaths_js_1 = require("./keyPaths.js");
64
64
  const logModel_js_1 = require("./logModel.js");
@@ -151,6 +151,19 @@ function incompleteSourcesFragment(commit) {
151
151
  * @returns Root CausalNode with .parents forming the DAG, or undefined if startId not found
152
152
  */
153
153
  function causalChain(commitLog, startId, getKeysRead, options) {
154
+ return buildCausalChain(commitLog, startId, getKeysRead, options);
155
+ }
156
+ exports.causalChain = causalChain;
157
+ /**
158
+ * Internal slice composition: keep the actual bundle selected by the writer
159
+ * query (including a nonempty continuation). This is not a public entry point.
160
+ * The read position still belongs to the execution's first commit.
161
+ */
162
+ function causalChainFromCommit(commitLog, writer, getKeysRead, options) {
163
+ return buildCausalChain(commitLog, writer.runtimeStageId, getKeysRead, options, writer);
164
+ }
165
+ exports.causalChainFromCommit = causalChainFromCommit;
166
+ function buildCausalChain(commitLog, startId, getKeysRead, options, selectedWriter) {
154
167
  const maxDepth = options?.maxDepth ?? 20;
155
168
  const maxNodes = options?.maxNodes ?? 100;
156
169
  const controlDeps = options?.controlDeps;
@@ -161,16 +174,15 @@ function causalChain(commitLog, startId, getKeysRead, options) {
161
174
  // never mistake a truncated slice for a complete one.
162
175
  let truncatedByDepth = false;
163
176
  let truncatedByNodes = false;
164
- // Build position index: runtimeStageId → array position (O(n) once)
165
- const idxMap = new Map();
166
- for (let i = 0; i < commitLog.length; i++) {
167
- idxMap.set(commitLog[i].runtimeStageId, i);
168
- }
177
+ // A continuation may write more data, but it is not another execution of
178
+ // the stage's reads. Use the shared FIRST-commit rule for every read cutoff;
179
+ // keep writer bundles separately for their write metadata and read prefixes.
180
+ const idxMap = (0, commitLogUtils_js_1.buildCommitIndex)(commitLog);
169
181
  keyPaths_js_1.queryWork.units += commitLog.length;
170
182
  const startIdx = idxMap.get(startId);
171
183
  if (startIdx === undefined)
172
184
  return undefined;
173
- const startCommit = commitLog[startIdx];
185
+ const startCommit = selectedWriter ?? commitLog[startIdx];
174
186
  // Pick writer lookup strategy based on log size
175
187
  const findWriter = createWriterLookup(commitLog);
176
188
  // Node dedup map: runtimeStageId → CausalNode (ensures DAG, not tree)
@@ -374,7 +386,6 @@ function causalChain(commitLog, startId, getKeysRead, options) {
374
386
  }
375
387
  return root;
376
388
  }
377
- exports.causalChain = causalChain;
378
389
  // ── Utilities ──────────────────────────────────────────────────────────
379
390
  /**
380
391
  * Flatten the causal DAG into a BFS-ordered list of nodes.
@@ -72,7 +72,9 @@ function sliceForKey(commitLog, key, keysRead, options) {
72
72
  // without `TraceEntry.readKeys` (the writeProvenance dial off) fall back
73
73
  // to stage-level expansion per node, byte-identically. Consumers can force
74
74
  // 'stage' via options.
75
- const root = (0, backtrack_js_1.causalChain)(commitLog, writer.runtimeStageId, source.lookup, {
75
+ // Preserve this exact writer's metadata/read prefix, even when it is a
76
+ // continuation. Causal expansion separately uses the execution's first commit.
77
+ const root = (0, backtrack_js_1.causalChainFromCommit)(commitLog, writer, source.lookup, {
76
78
  ...options,
77
79
  edgeAttribution: options?.edgeAttribution ?? 'per-write',
78
80
  rootLinkKeys: options?.rootLinkKeys ?? [normalisedKey],
@@ -257,6 +257,12 @@ export type KeysReadLookup = (runtimeStageId: string) => string[];
257
257
  * @returns Root CausalNode with .parents forming the DAG, or undefined if startId not found
258
258
  */
259
259
  export declare function causalChain(commitLog: CommitBundle[], startId: string, getKeysRead: KeysReadLookup, options?: CausalChainOptions): CausalNode | undefined;
260
+ /**
261
+ * Internal slice composition: keep the actual bundle selected by the writer
262
+ * query (including a nonempty continuation). This is not a public entry point.
263
+ * The read position still belongs to the execution's first commit.
264
+ */
265
+ export declare function causalChainFromCommit(commitLog: CommitBundle[], writer: CommitBundle, getKeysRead: KeysReadLookup, options?: CausalChainOptions): CausalNode | undefined;
260
266
  /**
261
267
  * Flatten the causal DAG into a BFS-ordered list of nodes.
262
268
  * Each node appears exactly once (first occurrence by BFS order).
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "foottrace",
3
- "version": "1.0.0",
3
+ "version": "1.0.1",
4
4
  "type": "commonjs",
5
5
  "description": "Execution records, replay, time travel and honest attribution — independent of the engine that produced them",
6
6
  "license": "MIT",