footprintjs 4.17.2 → 5.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 (115) hide show
  1. package/CLAUDE.md +39 -1
  2. package/dist/advanced.js +1 -1
  3. package/dist/esm/advanced.js +1 -1
  4. package/dist/esm/index.js +3 -3
  5. package/dist/esm/lib/builder/FlowChartBuilder.js +32 -7
  6. package/dist/esm/lib/decide/decide.js +9 -9
  7. package/dist/esm/lib/decide/evidence.js +2 -2
  8. package/dist/esm/lib/engine/narrative/CombinedNarrativeRecorder.js +3 -3
  9. package/dist/esm/lib/engine/narrative/FlowRecorderDispatcher.js +3 -3
  10. package/dist/esm/lib/engine/narrative/types.js +1 -1
  11. package/dist/esm/lib/engine/traversal/FlowchartTraverser.js +20 -3
  12. package/dist/esm/lib/memory/StageContext.js +3 -3
  13. package/dist/esm/lib/memory/backtrack.js +1 -1
  14. package/dist/esm/lib/reactive/createTypedScope.js +5 -5
  15. package/dist/esm/lib/reactive/types.js +7 -7
  16. package/dist/esm/lib/recorder/BoundaryStateStore.js +167 -0
  17. package/dist/esm/lib/recorder/BoundaryStateTracker.js +4 -4
  18. package/dist/esm/lib/recorder/CombinedRecorder.js +9 -9
  19. package/dist/esm/lib/recorder/CommitRangeIndex.js +207 -0
  20. package/dist/esm/lib/recorder/CompositeRecorder.js +6 -6
  21. package/dist/esm/lib/recorder/EmitRecorder.js +3 -3
  22. package/dist/esm/lib/recorder/InOutRecorder.js +1 -1
  23. package/dist/esm/lib/recorder/KeyedStore.js +113 -0
  24. package/dist/esm/lib/recorder/QualityRecorder.js +2 -2
  25. package/dist/esm/lib/recorder/SequenceRecorder.js +2 -2
  26. package/dist/esm/lib/recorder/SequenceStore.js +195 -0
  27. package/dist/esm/lib/recorder/TopologyRecorder.js +1 -1
  28. package/dist/esm/lib/recorder/index.js +13 -3
  29. package/dist/esm/lib/runner/ExecutionRuntime.js +1 -1
  30. package/dist/esm/lib/runner/FlowChartExecutor.js +72 -28
  31. package/dist/esm/lib/runner/RunContext.js +2 -2
  32. package/dist/esm/lib/runner/RunnableChart.js +1 -1
  33. package/dist/esm/lib/runner/runId.js +65 -0
  34. package/dist/esm/lib/scope/ScopeFacade.js +6 -6
  35. package/dist/esm/lib/scope/index.js +1 -1
  36. package/dist/esm/lib/scope/recorders/DebugRecorder.js +4 -4
  37. package/dist/esm/lib/scope/recorders/MetricRecorder.js +2 -2
  38. package/dist/esm/lib/scope/recorders/index.js +1 -1
  39. package/dist/esm/lib/scope/types.js +1 -1
  40. package/dist/esm/recorders.js +1 -1
  41. package/dist/esm/trace.js +13 -1
  42. package/dist/index.js +3 -3
  43. package/dist/lib/builder/FlowChartBuilder.js +32 -7
  44. package/dist/lib/decide/decide.js +9 -9
  45. package/dist/lib/decide/evidence.js +2 -2
  46. package/dist/lib/engine/narrative/CombinedNarrativeRecorder.js +3 -3
  47. package/dist/lib/engine/narrative/FlowRecorderDispatcher.js +3 -3
  48. package/dist/lib/engine/narrative/types.js +1 -1
  49. package/dist/lib/engine/traversal/FlowchartTraverser.js +20 -3
  50. package/dist/lib/memory/StageContext.js +3 -3
  51. package/dist/lib/memory/backtrack.js +1 -1
  52. package/dist/lib/reactive/createTypedScope.js +5 -5
  53. package/dist/lib/reactive/types.js +7 -7
  54. package/dist/lib/recorder/BoundaryStateStore.js +171 -0
  55. package/dist/lib/recorder/BoundaryStateTracker.js +4 -4
  56. package/dist/lib/recorder/CombinedRecorder.js +9 -9
  57. package/dist/lib/recorder/CommitRangeIndex.js +211 -0
  58. package/dist/lib/recorder/CompositeRecorder.js +6 -6
  59. package/dist/lib/recorder/EmitRecorder.js +3 -3
  60. package/dist/lib/recorder/InOutRecorder.js +1 -1
  61. package/dist/lib/recorder/KeyedStore.js +117 -0
  62. package/dist/lib/recorder/QualityRecorder.js +2 -2
  63. package/dist/lib/recorder/SequenceRecorder.js +2 -2
  64. package/dist/lib/recorder/SequenceStore.js +199 -0
  65. package/dist/lib/recorder/TopologyRecorder.js +1 -1
  66. package/dist/lib/recorder/index.js +19 -6
  67. package/dist/lib/runner/ExecutionRuntime.js +1 -1
  68. package/dist/lib/runner/FlowChartExecutor.js +72 -28
  69. package/dist/lib/runner/RunContext.js +2 -2
  70. package/dist/lib/runner/RunnableChart.js +1 -1
  71. package/dist/lib/runner/runId.js +70 -0
  72. package/dist/lib/scope/ScopeFacade.js +6 -6
  73. package/dist/lib/scope/index.js +1 -1
  74. package/dist/lib/scope/recorders/DebugRecorder.js +4 -4
  75. package/dist/lib/scope/recorders/MetricRecorder.js +2 -2
  76. package/dist/lib/scope/recorders/index.js +1 -1
  77. package/dist/lib/scope/types.js +1 -1
  78. package/dist/recorders.js +1 -1
  79. package/dist/trace.js +18 -2
  80. package/dist/types/index.d.ts +4 -4
  81. package/dist/types/lib/decide/evidence.d.ts +3 -3
  82. package/dist/types/lib/engine/narrative/CombinedNarrativeRecorder.d.ts +1 -1
  83. package/dist/types/lib/engine/narrative/FlowRecorderDispatcher.d.ts +2 -2
  84. package/dist/types/lib/engine/narrative/types.d.ts +19 -2
  85. package/dist/types/lib/engine/traversal/FlowchartTraverser.d.ts +11 -0
  86. package/dist/types/lib/memory/StageContext.d.ts +2 -2
  87. package/dist/types/lib/memory/backtrack.d.ts +1 -1
  88. package/dist/types/lib/reactive/types.d.ts +7 -7
  89. package/dist/types/lib/recorder/BoundaryStateStore.d.ts +115 -0
  90. package/dist/types/lib/recorder/BoundaryStateTracker.d.ts +3 -3
  91. package/dist/types/lib/recorder/CombinedRecorder.d.ts +11 -11
  92. package/dist/types/lib/recorder/CommitRangeIndex.d.ts +147 -0
  93. package/dist/types/lib/recorder/CompositeRecorder.d.ts +8 -8
  94. package/dist/types/lib/recorder/EmitRecorder.d.ts +2 -2
  95. package/dist/types/lib/recorder/InOutRecorder.d.ts +1 -1
  96. package/dist/types/lib/recorder/KeyedStore.d.ts +70 -0
  97. package/dist/types/lib/recorder/QualityRecorder.d.ts +4 -4
  98. package/dist/types/lib/recorder/SequenceRecorder.d.ts +1 -1
  99. package/dist/types/lib/recorder/SequenceStore.d.ts +120 -0
  100. package/dist/types/lib/recorder/TopologyRecorder.d.ts +1 -1
  101. package/dist/types/lib/recorder/index.d.ts +5 -2
  102. package/dist/types/lib/runner/ExecutionRuntime.d.ts +1 -1
  103. package/dist/types/lib/runner/FlowChartExecutor.d.ts +36 -14
  104. package/dist/types/lib/runner/RunContext.d.ts +2 -2
  105. package/dist/types/lib/runner/RunnableChart.d.ts +2 -2
  106. package/dist/types/lib/runner/runId.d.ts +45 -0
  107. package/dist/types/lib/scope/ScopeFacade.d.ts +4 -4
  108. package/dist/types/lib/scope/index.d.ts +1 -1
  109. package/dist/types/lib/scope/recorders/DebugRecorder.d.ts +4 -4
  110. package/dist/types/lib/scope/recorders/MetricRecorder.d.ts +4 -4
  111. package/dist/types/lib/scope/recorders/index.d.ts +1 -1
  112. package/dist/types/lib/scope/types.d.ts +1 -1
  113. package/dist/types/recorders.d.ts +1 -1
  114. package/dist/types/trace.d.ts +5 -0
  115. package/package.json +1 -1
@@ -0,0 +1,120 @@
1
+ /**
2
+ * SequenceStore<T> — concrete, composable storage for ordered sequence data.
3
+ *
4
+ * Pattern: COMPOSITION primitive. Concrete class — instantiate with
5
+ * `new SequenceStore<T>()` and own it as a field on your
6
+ * recorder. Replaces the abstract `SequenceRecorder<T>` base
7
+ * class for the v5 "one purpose per recorder" rule:
8
+ * stores ARE storage; recorders ARE event handlers; consumers
9
+ * COMPOSE.
10
+ * Role: Dual-indexed append-only sequence: a flat array preserving
11
+ * insertion order plus a `Map<runtimeStageId, T[]>` for O(1)
12
+ * per-step lookup. Plus a precomputed range index for time-
13
+ * travel scrubbing.
14
+ *
15
+ * @example
16
+ * ```typescript
17
+ * import { SequenceStore } from 'footprintjs/trace';
18
+ * import type { ScopeRecorder } from 'footprintjs';
19
+ *
20
+ * interface AuditEntry {
21
+ * runtimeStageId?: string;
22
+ * type: 'read' | 'write' | 'decision';
23
+ * detail: string;
24
+ * }
25
+ *
26
+ * // ONE PURPOSE: scope-event handler. Storage is composed in.
27
+ * class AuditRecorder implements ScopeRecorder {
28
+ * readonly id = 'audit';
29
+ * private readonly store = new SequenceStore<AuditEntry>();
30
+ *
31
+ * onRead(event: ReadEvent) {
32
+ * this.store.push({
33
+ * runtimeStageId: event.runtimeStageId,
34
+ * type: 'read',
35
+ * detail: event.key,
36
+ * });
37
+ * }
38
+ * onWrite(event: WriteEvent) {
39
+ * this.store.push({
40
+ * runtimeStageId: event.runtimeStageId,
41
+ * type: 'write',
42
+ * detail: event.key,
43
+ * });
44
+ * }
45
+ *
46
+ * getAudit() { return this.store.getAll(); }
47
+ * getAuditUpTo(ids: ReadonlySet<string>) {
48
+ * return this.store.getEntriesUpTo(ids);
49
+ * }
50
+ *
51
+ * clear() { this.store.clear(); }
52
+ * }
53
+ * ```
54
+ *
55
+ * **Contrast with `KeyedStore<T>`:** SequenceStore stores 1:N entries
56
+ * per runtimeStageId in insertion order. Use KeyedStore for 1:1
57
+ * (one record per step — token counts, metric snapshots).
58
+ */
59
+ export declare class SequenceStore<T extends {
60
+ runtimeStageId?: string;
61
+ }> {
62
+ /** Ordered sequence of all entries (insertion order). */
63
+ private readonly entries;
64
+ /** Per-step index: runtimeStageId → entries for that step. Same objects as `entries[]`. */
65
+ private readonly byRuntimeStageId;
66
+ /** Per-step range index: runtimeStageId → [firstIdx, endIdx) in entries array.
67
+ * endIdx includes trailing keyless entries (structural markers). Maintained during push(). */
68
+ private readonly entryRanges;
69
+ /** The runtimeStageId of the most recently emitted keyed entry. Used to extend
70
+ * ranges for trailing markers (entries without runtimeStageId attached after a step). */
71
+ private lastEmittedId;
72
+ /**
73
+ * Append an entry to both the ordered sequence, keyed index, and range index.
74
+ * All three reference the SAME entry object — no duplication.
75
+ */
76
+ push(entry: T): void;
77
+ /** All entries in insertion order. Returns a shallow copy — entry objects are shared. */
78
+ getAll(): T[];
79
+ /** Number of entries in the sequence. */
80
+ get size(): number;
81
+ /** Zero-copy iteration. Avoids the `getAll()` spread when the caller just needs
82
+ * to walk the entries (e.g., aggregating, rendering). */
83
+ forEach(fn: (entry: T) => void): void;
84
+ /** O(1) lookup: all entries for a specific execution step. Returns a copy. */
85
+ getByKey(runtimeStageId: string): T[];
86
+ /** Number of distinct execution steps that have at least one entry. */
87
+ get keyCount(): number;
88
+ /**
89
+ * Pre-built range index: runtimeStageId → half-open `[firstIdx, endIdx)`
90
+ * range in the entries array. Maintained during `push()` — no rebuild
91
+ * needed. Use for O(1) per-step lookups during time-travel scrubbing.
92
+ * `endIdx` includes trailing keyless entries (structural markers
93
+ * following a step).
94
+ */
95
+ getEntryRanges(): ReadonlyMap<string, {
96
+ readonly firstIdx: number;
97
+ readonly endIdx: number;
98
+ }>;
99
+ /** Reduce ALL entries to a single value. For dashboards, totals, summaries. */
100
+ aggregate<R>(fn: (acc: R, entry: T) => R, initial: R): R;
101
+ /**
102
+ * Reduce entries, optionally filtered by a set of `runtimeStageIds`.
103
+ * For time-travel progressive view: pass the runtimeStageIds visible
104
+ * at the current slider position. Entries without `runtimeStageId`
105
+ * (structural markers) are excluded when keys are provided. Without
106
+ * keys, reduces all entries (same as `aggregate`).
107
+ */
108
+ accumulate<R>(fn: (acc: R, entry: T) => R, initial: R, keys?: ReadonlySet<string>): R;
109
+ /**
110
+ * Progressive reveal: entries whose `runtimeStageId` is in the visible
111
+ * set. Preserves insertion order. Entries without `runtimeStageId`
112
+ * (structural markers) are buffered and included only when surrounded
113
+ * by visible steps on both sides — trailing markers after the last
114
+ * visible step are discarded.
115
+ */
116
+ getEntriesUpTo(visibleIds: ReadonlySet<string>): T[];
117
+ /** Clear all stored data. Recorders typically call this from their own
118
+ * `clear()` method, which the executor invokes before each run. */
119
+ clear(): void;
120
+ }
@@ -97,7 +97,7 @@ export interface Topology {
97
97
  readonly rootId: string | null;
98
98
  }
99
99
  export interface TopologyRecorderOptions {
100
- /** Recorder id. Defaults to `topology-N` (auto-incremented). */
100
+ /** ScopeRecorder id. Defaults to `topology-N` (auto-incremented). */
101
101
  id?: string;
102
102
  }
103
103
  /**
@@ -1,9 +1,12 @@
1
+ export { BoundaryStateStore } from './BoundaryStateStore.js';
2
+ export { KeyedStore } from './KeyedStore.js';
3
+ export { SequenceStore } from './SequenceStore.js';
1
4
  export { BoundaryStateTracker } from './BoundaryStateTracker.js';
5
+ export { KeyedRecorder } from './KeyedRecorder.js';
6
+ export { SequenceRecorder } from './SequenceRecorder.js';
2
7
  export type { CombinedRecorder } from './CombinedRecorder.js';
3
8
  export { hasEmitRecorderMethods, hasFlowRecorderMethods, hasRecorderMethods, isFlowEvent } from './CombinedRecorder.js';
4
9
  export type { CompositeSnapshot } from './CompositeRecorder.js';
5
10
  export { CompositeRecorder } from './CompositeRecorder.js';
6
11
  export type { EmitEvent, EmitRecorder } from './EmitRecorder.js';
7
- export { KeyedRecorder } from './KeyedRecorder.js';
8
12
  export { RecorderOperation } from './RecorderOperation.js';
9
- export { SequenceRecorder } from './SequenceRecorder.js';
@@ -17,7 +17,7 @@ import type { CommitBundle, StageSnapshot } from '../memory/types.js';
17
17
  export interface RecorderSnapshot {
18
18
  id: string;
19
19
  name: string;
20
- /** Recorder type and pattern description (e.g., "Translator (KeyedRecorder) — per-step token usage"). */
20
+ /** ScopeRecorder type and pattern description (e.g., "Translator (KeyedRecorder) — per-step token usage"). */
21
21
  description?: string;
22
22
  /** Preferred read-time operation — hints the UI about which view to show prominently. */
23
23
  preferredOperation?: 'translate' | 'accumulate' | 'aggregate';
@@ -26,7 +26,7 @@ import type { FlowchartCheckpoint } from '../pause/types.js';
26
26
  import type { CombinedRecorder } from '../recorder/CombinedRecorder.js';
27
27
  import type { EmitRecorder } from '../recorder/EmitRecorder.js';
28
28
  import type { ScopeProtectionMode } from '../scope/protection/types.js';
29
- import type { Recorder, RedactionPolicy, RedactionReport } from '../scope/types.js';
29
+ import type { RedactionPolicy, RedactionReport, ScopeRecorder } from '../scope/types.js';
30
30
  import { type RuntimeSnapshot } from './ExecutionRuntime.js';
31
31
  /**
32
32
  * Options object for `FlowChartExecutor` — preferred over positional params.
@@ -86,6 +86,10 @@ export declare class FlowChartExecutor<TOut = any, TScope = any> {
86
86
  private traverser;
87
87
  /** Shared execution counter — survives pause/resume. Reset on fresh run(). */
88
88
  private _executionCounter;
89
+ /** Per-`run()` identifier — generated fresh per run + per resume. Threaded
90
+ * through every TraversalContext so recorders can scope state to a single
91
+ * run. See `runId.ts`. */
92
+ private _currentRunId;
89
93
  private narrativeEnabled;
90
94
  private narrativeOptions?;
91
95
  private combinedRecorder;
@@ -159,6 +163,24 @@ export declare class FlowChartExecutor<TOut = any, TScope = any> {
159
163
  getCheckpoint(): FlowchartCheckpoint | undefined;
160
164
  /** Returns `true` if the most recent run() was paused (checkpoint available). */
161
165
  isPaused(): boolean;
166
+ /**
167
+ * Number of commits in the run's commit log. O(1) — direct length
168
+ * read, no snapshot materialization. Use this to stamp commit
169
+ * indices on observer events (e.g., `BoundaryRecorder` storing
170
+ * `commitIdxBefore` / `commitIdxAfter` per domain event for
171
+ * `CommitRangeIndex` queries — see `footprintjs/trace`).
172
+ *
173
+ * Returns 0 before any run; after, returns the cumulative commit
174
+ * count across the executor's lifetime (including resumes).
175
+ *
176
+ * IMPLEMENTATION NOTE: this returns `runtime.executionHistory.length`,
177
+ * which is the same value as `getSnapshot().commitLog.length`. The
178
+ * naming asymmetry is historical — the underlying `EventLog` field
179
+ * is named `executionHistory` but stores the `CommitBundle[]` that
180
+ * `commitLog` exposes. They are the SAME array (verified by the
181
+ * "matches commitLog.length" integration test).
182
+ */
183
+ getCommitCount(): number;
162
184
  /**
163
185
  * Resume a paused flowchart from a checkpoint.
164
186
  *
@@ -201,7 +223,7 @@ export declare class FlowChartExecutor<TOut = any, TScope = any> {
201
223
  /** DFS search for a node by ID in the StageNode graph. Cycle-safe via visited set. */
202
224
  private dfsFind;
203
225
  /**
204
- * Attach a scope Recorder to observe data operations (reads, writes, commits).
226
+ * Attach a scope ScopeRecorder to observe data operations (reads, writes, commits).
205
227
  * Automatically attached to every ScopeFacade created during traversal.
206
228
  * Must be called before run().
207
229
  *
@@ -216,18 +238,18 @@ export declare class FlowChartExecutor<TOut = any, TScope = any> {
216
238
  * @example
217
239
  * ```typescript
218
240
  * // Multiple recorders with different configs — each gets a unique ID
219
- * executor.attachRecorder(new MetricRecorder());
220
- * executor.attachRecorder(new DebugRecorder({ verbosity: 'minimal' }));
241
+ * executor.attachScopeRecorder(new MetricRecorder());
242
+ * executor.attachScopeRecorder(new DebugRecorder({ verbosity: 'minimal' }));
221
243
  *
222
244
  * // Override a framework-attached recorder by passing its well-known ID
223
- * executor.attachRecorder(new MetricRecorder('metrics'));
245
+ * executor.attachScopeRecorder(new MetricRecorder('metrics'));
224
246
  *
225
247
  * // Attaching twice with same ID replaces (no double-counting)
226
- * executor.attachRecorder(new MetricRecorder('my-metrics'));
227
- * executor.attachRecorder(new MetricRecorder('my-metrics')); // replaces previous
248
+ * executor.attachScopeRecorder(new MetricRecorder('my-metrics'));
249
+ * executor.attachScopeRecorder(new MetricRecorder('my-metrics')); // replaces previous
228
250
  * ```
229
251
  */
230
- attachRecorder(recorder: Recorder): void;
252
+ attachScopeRecorder(recorder: ScopeRecorder): void;
231
253
  /**
232
254
  * Detach a child flowchart on the given driver and return a `DetachHandle`
233
255
  * the caller can `wait()` on (Promise) or read `.status` from (sync).
@@ -257,9 +279,9 @@ export declare class FlowChartExecutor<TOut = any, TScope = any> {
257
279
  */
258
280
  detachAndForget(driver: import('../detach/types.js').DetachDriver, child: import('../builder/types.js').FlowChart, input?: unknown): void;
259
281
  /** Detach all scope Recorders with the given ID. */
260
- detachRecorder(id: string): void;
282
+ detachScopeRecorder(id: string): void;
261
283
  /** Returns a defensive copy of attached scope Recorders. */
262
- getRecorders(): Recorder[];
284
+ getScopeRecorders(): ScopeRecorder[];
263
285
  /**
264
286
  * Attach a FlowRecorder to observe control flow events.
265
287
  * Automatically enables narrative if not already enabled.
@@ -277,7 +299,7 @@ export declare class FlowChartExecutor<TOut = any, TScope = any> {
277
299
  * data-flow, control-flow, or both). Detects at runtime which streams the
278
300
  * recorder has methods for and routes it to the correct internal channels.
279
301
  *
280
- * Preferred over calling `attachRecorder` and `attachFlowRecorder`
302
+ * Preferred over calling `attachScopeRecorder` and `attachFlowRecorder`
281
303
  * separately, because forgetting one of the two is a silent foot-gun —
282
304
  * half your events never fire and there is no runtime warning. With
283
305
  * `attachCombinedRecorder` the library guarantees the recorder's declared
@@ -287,7 +309,7 @@ export declare class FlowChartExecutor<TOut = any, TScope = any> {
287
309
  *
288
310
  * Idempotent by `id` across ALL channels — re-attaching with the same `id`
289
311
  * replaces the previous instance everywhere it was registered. Mixing
290
- * `attachCombinedRecorder(x)` with a prior `attachRecorder(y)` or
312
+ * `attachCombinedRecorder(x)` with a prior `attachScopeRecorder(y)` or
291
313
  * `attachFlowRecorder(y)` that share `x.id === y.id` is also safe: the
292
314
  * combined attach replaces the single-channel registration on whichever
293
315
  * channel(s) `x` has methods for. No duplicate firings occur.
@@ -330,8 +352,8 @@ export declare class FlowChartExecutor<TOut = any, TScope = any> {
330
352
  * Internally, emit recorders share the scope-recorder channel because
331
353
  * emit events fire from inside `ScopeFacade` during stage execution,
332
354
  * same timing as `onRead`/`onWrite`. This method is a convenience that
333
- * delegates to `attachRecorder` — consumers can also use
334
- * `attachRecorder` directly for a recorder that implements BOTH
355
+ * delegates to `attachScopeRecorder` — consumers can also use
356
+ * `attachScopeRecorder` directly for a recorder that implements BOTH
335
357
  * `onWrite` and `onEmit`. Either approach places the recorder on the
336
358
  * same underlying list, so `onEmit` fires exactly once per event.
337
359
  *
@@ -10,7 +10,7 @@
10
10
  import type { FlowChart } from '../builder/types.js';
11
11
  import type { FlowRecorder } from '../engine/narrative/types.js';
12
12
  import type { RunOptions } from '../engine/types.js';
13
- import type { Recorder, RedactionPolicy } from '../scope/types.js';
13
+ import type { RedactionPolicy, ScopeRecorder } from '../scope/types.js';
14
14
  /** Result from RunContext.run() — owns state and output. */
15
15
  export interface RunResult {
16
16
  /** Raw scope state after execution. */
@@ -29,7 +29,7 @@ export declare class RunContext<TOut = any, TScope = any> {
29
29
  private redactionPolicy?;
30
30
  constructor(chart: FlowChart<TOut, TScope>);
31
31
  /** Attach a recorder. Auto-detects scope vs flow recorder. Chainable. */
32
- recorder(r: Recorder | FlowRecorder): RunContext<TOut, TScope>;
32
+ recorder(r: ScopeRecorder | FlowRecorder): RunContext<TOut, TScope>;
33
33
  /** Set redaction policy for this run. Chainable. */
34
34
  redact(policy: RedactionPolicy): RunContext<TOut, TScope>;
35
35
  /** Execute the chart with accumulated config. Returns RunResult. */
@@ -9,7 +9,7 @@ import type { FlowChart } from '../builder/types.js';
9
9
  import type { JsonSchema } from '../contract/types.js';
10
10
  import type { FlowRecorder } from '../engine/narrative/types.js';
11
11
  import type { RunOptions } from '../engine/types.js';
12
- import type { Recorder, RedactionPolicy } from '../scope/types.js';
12
+ import type { RedactionPolicy, ScopeRecorder } from '../scope/types.js';
13
13
  import { type RunResult, RunContext } from './RunContext.js';
14
14
  /** OpenAPI generation options. */
15
15
  export interface ChartOpenAPIOptions {
@@ -38,7 +38,7 @@ export interface MCPToolDescription {
38
38
  */
39
39
  export interface RunnableFlowChart<TOut = any, TScope = any> extends FlowChart<TOut, TScope> {
40
40
  /** Attach a recorder for the next run. Returns a chainable RunContext. */
41
- recorder(r: Recorder | FlowRecorder): RunContext<TOut, TScope>;
41
+ recorder(r: ScopeRecorder | FlowRecorder): RunContext<TOut, TScope>;
42
42
  /** Set redaction policy for the next run. Returns a chainable RunContext. */
43
43
  redact(policy: RedactionPolicy): RunContext<TOut, TScope>;
44
44
  /** Execute the chart directly (bare run, no recorders). */
@@ -0,0 +1,45 @@
1
+ /**
2
+ * runId — per-`executor.run()` identifier generator.
3
+ *
4
+ * Pattern: monotonic counter + clock-guarded timestamp. One id per
5
+ * call to `executor.run()` (or `executor.resume()`). Stable
6
+ * for the duration of that run; unique across consecutive
7
+ * runs.
8
+ * Role: primitive that solves the "two consecutive runs of the
9
+ * same executor produce identical runtimeStageIds" class of
10
+ * bugs. Recorders that accumulate state across runs detect
11
+ * "new run" via `runId` change and reset transient
12
+ * bookkeeping.
13
+ *
14
+ * Format: `${timestamp}-${counter}`.
15
+ * - `timestamp` is `Date.now()` clamped to a monotonic-clock guard
16
+ * (never decreases — protects against NTP / system-clock
17
+ * adjustments).
18
+ * - `counter` is a process-local incrementing integer, ZERO-PADDED
19
+ * to 10 digits so lexicographic sort matches numeric order
20
+ * (`"...001"` < `"...010"` < `"...100"`). 10 digits = 10 billion
21
+ * runs in a single process — sufficient for any real workload.
22
+ *
23
+ * Lexicographic ordering of `runId` strings matches chronological
24
+ * ordering for runs that are at least 1ms apart, AND for runs that
25
+ * happen within the same millisecond (because the padded counter
26
+ * tie-breaks). The counter NEVER resets — it is process-global.
27
+ *
28
+ * Process-local only. Cross-process correlation uses
29
+ * `getEnv().traceId` (consumer-supplied), not `runId`. Documented
30
+ * in `docs/design/v5-recorder-redesign.md` Section 8.1.
31
+ */
32
+ /**
33
+ * Generate a fresh runId. Called once per `executor.run()` and once
34
+ * per `executor.resume()`. Pure (deterministic for a given clock +
35
+ * counter state); no side effects beyond advancing the counter and
36
+ * monotonic-clock guard.
37
+ */
38
+ export declare function generateRunId(): string;
39
+ /**
40
+ * Reset the runId state. Test-only. NEVER call from production code —
41
+ * runIds must be process-globally monotonic.
42
+ *
43
+ * @internal
44
+ */
45
+ export declare function _resetRunIdStateForTesting(): void;
@@ -15,7 +15,7 @@
15
15
  */
16
16
  import type { ExecutionEnv } from '../engine/types.js';
17
17
  import { StageContext } from '../memory/StageContext.js';
18
- import type { CommitEvent, Recorder, RedactionPolicy, RedactionReport } from './types.js';
18
+ import type { CommitEvent, RedactionPolicy, RedactionReport, ScopeRecorder } from './types.js';
19
19
  export declare class ScopeFacade {
20
20
  static readonly BRAND: unique symbol;
21
21
  /**
@@ -60,9 +60,9 @@ export declare class ScopeFacade {
60
60
  * Never includes actual values — only key names, field names, and patterns.
61
61
  */
62
62
  getRedactionReport(): RedactionReport;
63
- attachRecorder(recorder: Recorder): void;
64
- detachRecorder(recorderId: string): void;
65
- getRecorders(): Recorder[];
63
+ attachScopeRecorder(recorder: ScopeRecorder): void;
64
+ detachScopeRecorder(recorderId: string): void;
65
+ getScopeRecorders(): ScopeRecorder[];
66
66
  /** @internal */
67
67
  notifyStageStart(): void;
68
68
  /** @internal */
@@ -5,7 +5,7 @@
5
5
  * providers, protection, and Zod-based scope definitions.
6
6
  */
7
7
  export { ScopeFacade } from './ScopeFacade.js';
8
- export type { CommitEvent, ErrorEvent, ReadEvent, Recorder, RecorderContext, RedactionPolicy, RedactionReport, StageEvent, WriteEvent, } from './types.js';
8
+ export type { CommitEvent, ErrorEvent, ReadEvent, RecorderContext, RedactionPolicy, RedactionReport, ScopeRecorder, StageEvent, WriteEvent, } from './types.js';
9
9
  export type { DebugEntry, DebugRecorderOptions, DebugVerbosity } from './recorders/DebugRecorder.js';
10
10
  export { DebugRecorder } from './recorders/DebugRecorder.js';
11
11
  export type { AggregatedMetrics, StageMetrics } from './recorders/MetricRecorder.js';
@@ -5,7 +5,7 @@
5
5
  * and stage lifecycle events for troubleshooting.
6
6
  */
7
7
  import type { RecorderOperation } from '../../recorder/RecorderOperation.js';
8
- import type { ErrorEvent, PauseEvent, ReadEvent, Recorder, ResumeEvent, StageEvent, WriteEvent } from '../types.js';
8
+ import type { ErrorEvent, PauseEvent, ReadEvent, ResumeEvent, ScopeRecorder, StageEvent, WriteEvent } from '../types.js';
9
9
  export type DebugVerbosity = 'minimal' | 'verbose';
10
10
  export interface DebugEntry {
11
11
  type: 'read' | 'write' | 'error' | 'stageStart' | 'stageEnd' | 'pause' | 'resume';
@@ -26,15 +26,15 @@ export interface DebugRecorderOptions {
26
26
  * @example
27
27
  * ```typescript
28
28
  * // Verbose debug for development
29
- * executor.attachRecorder(new DebugRecorder({ verbosity: 'verbose' }));
29
+ * executor.attachScopeRecorder(new DebugRecorder({ verbosity: 'verbose' }));
30
30
  *
31
31
  * // Minimal debug for production (errors only)
32
- * executor.attachRecorder(new DebugRecorder({ verbosity: 'minimal' }));
32
+ * executor.attachScopeRecorder(new DebugRecorder({ verbosity: 'minimal' }));
33
33
  *
34
34
  * // Both coexist — different auto IDs
35
35
  * ```
36
36
  */
37
- export declare class DebugRecorder implements Recorder {
37
+ export declare class DebugRecorder implements ScopeRecorder {
38
38
  private static _counter;
39
39
  readonly id: string;
40
40
  readonly preferredOperation: RecorderOperation;
@@ -7,7 +7,7 @@
7
7
  * @example
8
8
  * ```typescript
9
9
  * const metric = new MetricRecorder();
10
- * executor.attachRecorder(metric);
10
+ * executor.attachScopeRecorder(metric);
11
11
  * await executor.run();
12
12
  *
13
13
  * // Per-step (time-travel):
@@ -22,7 +22,7 @@
22
22
  */
23
23
  import { KeyedRecorder } from '../../recorder/KeyedRecorder.js';
24
24
  import type { RecorderOperation } from '../../recorder/RecorderOperation.js';
25
- import type { CommitEvent, PauseEvent, ReadEvent, Recorder, StageEvent, WriteEvent } from '../types.js';
25
+ import type { CommitEvent, PauseEvent, ReadEvent, ScopeRecorder, StageEvent, WriteEvent } from '../types.js';
26
26
  /** Per-invocation metrics for a single execution step. */
27
27
  export interface StepMetrics {
28
28
  /** Human-readable stage name. */
@@ -60,14 +60,14 @@ export interface StageMetrics {
60
60
  }
61
61
  /** Options for MetricRecorder. */
62
62
  export interface MetricRecorderOptions {
63
- /** Recorder ID. Defaults to auto-increment (`metrics-1`, `metrics-2`, ...). */
63
+ /** ScopeRecorder ID. Defaults to auto-increment (`metrics-1`, `metrics-2`, ...). */
64
64
  id?: string;
65
65
  /** Filter which stages are recorded. Return `true` to record, `false` to skip. */
66
66
  stageFilter?: (stageName: string) => boolean;
67
67
  /** Preferred UI operation. Defaults to 'aggregate' (dashboard totals). */
68
68
  preferredOperation?: RecorderOperation;
69
69
  }
70
- export declare class MetricRecorder extends KeyedRecorder<StepMetrics> implements Recorder {
70
+ export declare class MetricRecorder extends KeyedRecorder<StepMetrics> implements ScopeRecorder {
71
71
  private static _counter;
72
72
  readonly id: string;
73
73
  readonly preferredOperation: RecorderOperation;
@@ -1,4 +1,4 @@
1
- export type { CommitEvent, ErrorEvent, ReadEvent, Recorder, RecorderContext, StageEvent, WriteEvent, } from '../types.js';
1
+ export type { CommitEvent, ErrorEvent, ReadEvent, RecorderContext, ScopeRecorder, StageEvent, WriteEvent, } from '../types.js';
2
2
  export type { DebugEntry, DebugRecorderOptions, DebugVerbosity } from './DebugRecorder.js';
3
3
  export { DebugRecorder } from './DebugRecorder.js';
4
4
  export type { AggregatedMetrics, StageMetrics, StepMetrics } from './MetricRecorder.js';
@@ -100,7 +100,7 @@ export interface RedactionReport {
100
100
  * If a recorder throws, the error is caught and passed to onError
101
101
  * hooks of other recorders; the scope operation continues normally.
102
102
  */
103
- export interface Recorder {
103
+ export interface ScopeRecorder {
104
104
  readonly id: string;
105
105
  onRead?(event: ReadEvent): void;
106
106
  onWrite?(event: WriteEvent): void;
@@ -37,7 +37,7 @@ import { DebugRecorder } from './lib/scope/recorders/DebugRecorder.js';
37
37
  import type { AggregatedMetrics, MetricRecorderOptions, StageMetrics } from './lib/scope/recorders/MetricRecorder.js';
38
38
  import { MetricRecorder } from './lib/scope/recorders/MetricRecorder.js';
39
39
  /**
40
- * Recorder factory for combined flow+data narrative.
40
+ * ScopeRecorder factory for combined flow+data narrative.
41
41
  *
42
42
  * The returned recorder is a `CombinedNarrativeRecorder` — attach it to a
43
43
  * chart/executor, then read structured entries via `.getEntries()` after
@@ -25,6 +25,11 @@ export { buildRuntimeStageId, createExecutionCounter, parseRuntimeStageId } from
25
25
  export { findCommit, findCommits, findLastWriter } from './lib/memory/commitLogUtils.js';
26
26
  export type { CausalChainOptions, CausalNode, KeysReadLookup } from './lib/memory/backtrack.js';
27
27
  export { causalChain, flattenCausalDAG, formatCausalChain } from './lib/memory/backtrack.js';
28
+ export { BoundaryStateStore } from './lib/recorder/BoundaryStateStore.js';
29
+ export { KeyedStore } from './lib/recorder/KeyedStore.js';
30
+ export { SequenceStore } from './lib/recorder/SequenceStore.js';
31
+ export type { RangeEntry, RangeToken } from './lib/recorder/CommitRangeIndex.js';
32
+ export { CommitRangeIndex } from './lib/recorder/CommitRangeIndex.js';
28
33
  export { KeyedRecorder } from './lib/recorder/KeyedRecorder.js';
29
34
  export { SequenceRecorder } from './lib/recorder/SequenceRecorder.js';
30
35
  export { BoundaryStateTracker } from './lib/recorder/BoundaryStateTracker.js';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "footprintjs",
3
- "version": "4.17.2",
3
+ "version": "5.0.0",
4
4
  "description": "Explainable backend flows — automatic causal traces, decision evidence, and MCP tool generation for AI agents",
5
5
  "license": "MIT",
6
6
  "author": "Sanjay Krishna Anbalagan",