footprintjs 4.17.2 → 6.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.
- package/CLAUDE.md +71 -9
- package/dist/advanced.js +2 -3
- package/dist/esm/advanced.js +2 -2
- package/dist/esm/index.js +3 -3
- package/dist/esm/lib/builder/FlowChartBuilder.js +359 -70
- package/dist/esm/lib/builder/index.js +1 -1
- package/dist/esm/lib/builder/structure/StructureRecorder.js +158 -0
- package/dist/esm/lib/builder/structure/StructureRecorderDispatcher.js +171 -0
- package/dist/esm/lib/builder/types.js +1 -1
- package/dist/esm/lib/decide/decide.js +9 -9
- package/dist/esm/lib/decide/evidence.js +2 -2
- package/dist/esm/lib/engine/errors/errorInfo.js +4 -3
- package/dist/esm/lib/engine/handlers/ChildrenExecutor.js +11 -2
- package/dist/esm/lib/engine/handlers/DeciderHandler.js +5 -7
- package/dist/esm/lib/engine/handlers/RuntimeStructureManager.js +2 -2
- package/dist/esm/lib/engine/handlers/SelectorHandler.js +9 -7
- package/dist/esm/lib/engine/handlers/SubflowExecutor.js +7 -1
- package/dist/esm/lib/engine/handlers/index.js +1 -3
- package/dist/esm/lib/engine/handlers/types.js +2 -2
- package/dist/esm/lib/engine/narrative/CombinedNarrativeRecorder.js +14 -4
- package/dist/esm/lib/engine/narrative/FlowRecorderDispatcher.js +5 -5
- package/dist/esm/lib/engine/narrative/NarrativeFlowRecorder.js +8 -1
- package/dist/esm/lib/engine/narrative/types.js +1 -1
- package/dist/esm/lib/engine/runtimeStageId.js +64 -2
- package/dist/esm/lib/engine/traversal/FlowchartTraverser.js +30 -56
- package/dist/esm/lib/engine/types.js +1 -1
- package/dist/esm/lib/engine/walkSubflowSpec.js +144 -0
- package/dist/esm/lib/memory/StageContext.js +3 -3
- package/dist/esm/lib/memory/backtrack.js +1 -1
- package/dist/esm/lib/reactive/createTypedScope.js +5 -5
- package/dist/esm/lib/reactive/types.js +7 -7
- package/dist/esm/lib/recorder/BoundaryStateStore.js +167 -0
- package/dist/esm/lib/recorder/BoundaryStateTracker.js +4 -4
- package/dist/esm/lib/recorder/CombinedRecorder.js +9 -9
- package/dist/esm/lib/recorder/CommitRangeIndex.js +207 -0
- package/dist/esm/lib/recorder/CompositeRecorder.js +6 -6
- package/dist/esm/lib/recorder/EmitRecorder.js +3 -3
- package/dist/esm/lib/recorder/InOutRecorder.js +1 -1
- package/dist/esm/lib/recorder/KeyedStore.js +113 -0
- package/dist/esm/lib/recorder/QualityRecorder.js +2 -2
- package/dist/esm/lib/recorder/SequenceRecorder.js +2 -2
- package/dist/esm/lib/recorder/SequenceStore.js +195 -0
- package/dist/esm/lib/recorder/TopologyRecorder.js +1 -1
- package/dist/esm/lib/recorder/index.js +13 -3
- package/dist/esm/lib/runner/ExecutionRuntime.js +1 -1
- package/dist/esm/lib/runner/FlowChartExecutor.js +75 -44
- package/dist/esm/lib/runner/RunContext.js +2 -2
- package/dist/esm/lib/runner/RunnableChart.js +1 -1
- package/dist/esm/lib/runner/getSubtreeSnapshot.js +3 -2
- package/dist/esm/lib/runner/runId.js +65 -0
- package/dist/esm/lib/scope/ScopeFacade.js +6 -6
- package/dist/esm/lib/scope/index.js +1 -1
- package/dist/esm/lib/scope/recorders/DebugRecorder.js +4 -4
- package/dist/esm/lib/scope/recorders/MetricRecorder.js +2 -2
- package/dist/esm/lib/scope/recorders/index.js +1 -1
- package/dist/esm/lib/scope/types.js +1 -1
- package/dist/esm/recorders.js +1 -1
- package/dist/esm/trace.js +15 -2
- package/dist/index.js +3 -3
- package/dist/lib/builder/FlowChartBuilder.js +359 -70
- package/dist/lib/builder/index.js +1 -1
- package/dist/lib/builder/structure/StructureRecorder.js +159 -0
- package/dist/lib/builder/structure/StructureRecorderDispatcher.js +175 -0
- package/dist/lib/builder/types.js +1 -1
- package/dist/lib/decide/decide.js +9 -9
- package/dist/lib/decide/evidence.js +2 -2
- package/dist/lib/engine/errors/errorInfo.js +4 -3
- package/dist/lib/engine/handlers/ChildrenExecutor.js +11 -2
- package/dist/lib/engine/handlers/DeciderHandler.js +5 -7
- package/dist/lib/engine/handlers/RuntimeStructureManager.js +2 -2
- package/dist/lib/engine/handlers/SelectorHandler.js +9 -7
- package/dist/lib/engine/handlers/SubflowExecutor.js +7 -1
- package/dist/lib/engine/handlers/index.js +2 -5
- package/dist/lib/engine/handlers/types.js +2 -2
- package/dist/lib/engine/narrative/CombinedNarrativeRecorder.js +14 -4
- package/dist/lib/engine/narrative/FlowRecorderDispatcher.js +5 -5
- package/dist/lib/engine/narrative/NarrativeFlowRecorder.js +8 -1
- package/dist/lib/engine/narrative/types.js +1 -1
- package/dist/lib/engine/runtimeStageId.js +66 -3
- package/dist/lib/engine/traversal/FlowchartTraverser.js +30 -56
- package/dist/lib/engine/types.js +1 -1
- package/dist/lib/engine/walkSubflowSpec.js +148 -0
- package/dist/lib/memory/StageContext.js +3 -3
- package/dist/lib/memory/backtrack.js +1 -1
- package/dist/lib/reactive/createTypedScope.js +5 -5
- package/dist/lib/reactive/types.js +7 -7
- package/dist/lib/recorder/BoundaryStateStore.js +171 -0
- package/dist/lib/recorder/BoundaryStateTracker.js +4 -4
- package/dist/lib/recorder/CombinedRecorder.js +9 -9
- package/dist/lib/recorder/CommitRangeIndex.js +211 -0
- package/dist/lib/recorder/CompositeRecorder.js +6 -6
- package/dist/lib/recorder/EmitRecorder.js +3 -3
- package/dist/lib/recorder/InOutRecorder.js +1 -1
- package/dist/lib/recorder/KeyedStore.js +117 -0
- package/dist/lib/recorder/QualityRecorder.js +2 -2
- package/dist/lib/recorder/SequenceRecorder.js +2 -2
- package/dist/lib/recorder/SequenceStore.js +199 -0
- package/dist/lib/recorder/TopologyRecorder.js +1 -1
- package/dist/lib/recorder/index.js +19 -6
- package/dist/lib/runner/ExecutionRuntime.js +1 -1
- package/dist/lib/runner/FlowChartExecutor.js +75 -44
- package/dist/lib/runner/RunContext.js +2 -2
- package/dist/lib/runner/RunnableChart.js +1 -1
- package/dist/lib/runner/getSubtreeSnapshot.js +3 -2
- package/dist/lib/runner/runId.js +70 -0
- package/dist/lib/scope/ScopeFacade.js +6 -6
- package/dist/lib/scope/index.js +1 -1
- package/dist/lib/scope/recorders/DebugRecorder.js +4 -4
- package/dist/lib/scope/recorders/MetricRecorder.js +2 -2
- package/dist/lib/scope/recorders/index.js +1 -1
- package/dist/lib/scope/types.js +1 -1
- package/dist/recorders.js +1 -1
- package/dist/trace.js +21 -2
- package/dist/types/advanced.d.ts +4 -4
- package/dist/types/index.d.ts +6 -4
- package/dist/types/lib/builder/FlowChartBuilder.d.ts +110 -14
- package/dist/types/lib/builder/index.d.ts +2 -1
- package/dist/types/lib/builder/structure/StructureRecorder.d.ts +349 -0
- package/dist/types/lib/builder/structure/StructureRecorderDispatcher.d.ts +77 -0
- package/dist/types/lib/builder/types.d.ts +20 -6
- package/dist/types/lib/decide/evidence.d.ts +3 -3
- package/dist/types/lib/engine/errors/errorInfo.d.ts +3 -2
- package/dist/types/lib/engine/handlers/DeciderHandler.d.ts +3 -3
- package/dist/types/lib/engine/handlers/RuntimeStructureManager.d.ts +1 -1
- package/dist/types/lib/engine/handlers/SelectorHandler.d.ts +2 -2
- package/dist/types/lib/engine/handlers/index.d.ts +1 -2
- package/dist/types/lib/engine/handlers/types.d.ts +2 -9
- package/dist/types/lib/engine/narrative/CombinedNarrativeRecorder.d.ts +1 -1
- package/dist/types/lib/engine/narrative/FlowRecorderDispatcher.d.ts +4 -4
- package/dist/types/lib/engine/narrative/types.d.ts +43 -4
- package/dist/types/lib/engine/runtimeStageId.d.ts +57 -1
- package/dist/types/lib/engine/traversal/FlowchartTraverser.d.ts +12 -6
- package/dist/types/lib/engine/types.d.ts +0 -26
- package/dist/types/lib/engine/walkSubflowSpec.d.ts +95 -0
- package/dist/types/lib/memory/StageContext.d.ts +2 -2
- package/dist/types/lib/memory/backtrack.d.ts +1 -1
- package/dist/types/lib/reactive/types.d.ts +7 -7
- package/dist/types/lib/recorder/BoundaryStateStore.d.ts +115 -0
- package/dist/types/lib/recorder/BoundaryStateTracker.d.ts +3 -3
- package/dist/types/lib/recorder/CombinedRecorder.d.ts +11 -11
- package/dist/types/lib/recorder/CommitRangeIndex.d.ts +147 -0
- package/dist/types/lib/recorder/CompositeRecorder.d.ts +8 -8
- package/dist/types/lib/recorder/EmitRecorder.d.ts +2 -2
- package/dist/types/lib/recorder/InOutRecorder.d.ts +1 -1
- package/dist/types/lib/recorder/KeyedStore.d.ts +70 -0
- package/dist/types/lib/recorder/QualityRecorder.d.ts +4 -4
- package/dist/types/lib/recorder/SequenceRecorder.d.ts +1 -1
- package/dist/types/lib/recorder/SequenceStore.d.ts +120 -0
- package/dist/types/lib/recorder/TopologyRecorder.d.ts +1 -1
- package/dist/types/lib/recorder/index.d.ts +5 -2
- package/dist/types/lib/runner/ExecutionRuntime.d.ts +1 -1
- package/dist/types/lib/runner/FlowChartExecutor.d.ts +40 -30
- package/dist/types/lib/runner/RunContext.d.ts +2 -2
- package/dist/types/lib/runner/RunnableChart.d.ts +2 -2
- package/dist/types/lib/runner/runId.d.ts +45 -0
- package/dist/types/lib/scope/ScopeFacade.d.ts +4 -4
- package/dist/types/lib/scope/index.d.ts +1 -1
- package/dist/types/lib/scope/recorders/DebugRecorder.d.ts +4 -4
- package/dist/types/lib/scope/recorders/MetricRecorder.d.ts +4 -4
- package/dist/types/lib/scope/recorders/index.d.ts +1 -1
- package/dist/types/lib/scope/types.d.ts +1 -1
- package/dist/types/recorders.d.ts +1 -1
- package/dist/types/trace.d.ts +8 -1
- package/package.json +1 -1
- package/dist/esm/lib/engine/handlers/ExtractorRunner.js +0 -122
- package/dist/lib/engine/handlers/ExtractorRunner.js +0 -126
- package/dist/types/lib/engine/handlers/ExtractorRunner.d.ts +0 -41
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CommitRangeIndex<TLabel> — interval index over commit indices.
|
|
3
|
+
*
|
|
4
|
+
* Built incrementally during traversal: `open(label, startIdx)` when a
|
|
5
|
+
* boundary begins, `close(token, endIdx)` when it ends. Query at any
|
|
6
|
+
* commit position with `enclosing(idx)` (returns ranges containing
|
|
7
|
+
* that index, ordered outer→inner) or `overlapping(start, end)`
|
|
8
|
+
* (returns ranges intersecting a slice).
|
|
9
|
+
*
|
|
10
|
+
* See `docs/design/commit-range-index.md` for the full contract. In
|
|
11
|
+
* one paragraph: this is a generic interval data structure for
|
|
12
|
+
* commit-range queries. footprintjs owns ZERO knowledge of what
|
|
13
|
+
* labels mean — consumers (agentfootprint, lens, OTel exporters)
|
|
14
|
+
* pick their own `TLabel` type. Open ranges (mid-run, no end yet)
|
|
15
|
+
* are first-class — query results carry `endIdx: undefined` for them.
|
|
16
|
+
*
|
|
17
|
+
* Pattern: incremental builder + interval query. Same "collect during
|
|
18
|
+
* traversal, never post-process" rule footprintjs's CLAUDE.md
|
|
19
|
+
* requires of every observer.
|
|
20
|
+
* Role: structural primitive for time-travel UIs and per-boundary
|
|
21
|
+
* aggregation.
|
|
22
|
+
* Channel: consumer-driven (no engine subscription).
|
|
23
|
+
*
|
|
24
|
+
* @example
|
|
25
|
+
* ```typescript
|
|
26
|
+
* import { CommitRangeIndex } from 'footprintjs/trace';
|
|
27
|
+
*
|
|
28
|
+
* const idx = new CommitRangeIndex<string>();
|
|
29
|
+
* const t = idx.open('LLMCall', executor.getCommitCount());
|
|
30
|
+
* // ... LLM call runs, scope writes happen ...
|
|
31
|
+
* idx.close(t, executor.getCommitCount());
|
|
32
|
+
*
|
|
33
|
+
* idx.enclosing(50); // → ranges containing commit 50, outer→inner
|
|
34
|
+
* idx.overlapping(40, 60); // → ranges sharing the slice [40,60]
|
|
35
|
+
* idx.clear(); // wipe (e.g., on new run)
|
|
36
|
+
* ```
|
|
37
|
+
*
|
|
38
|
+
* REDACTION NOTE: labels are stored verbatim and returned verbatim in
|
|
39
|
+
* query results — the index does NOT redact `TLabel` content. If a
|
|
40
|
+
* consumer attaches a label containing PII (user email, scope reads
|
|
41
|
+
* with sensitive keys, etc.) and then serializes the index for
|
|
42
|
+
* logging or telemetry, that data leaves the trust boundary. Use
|
|
43
|
+
* `RedactionPolicy` (or your own scrubbing) on the consumer side
|
|
44
|
+
* BEFORE attaching labels. The index follows the same contract as
|
|
45
|
+
* other footprintjs storage primitives (SequenceStore, KeyedStore):
|
|
46
|
+
* storage is verbatim; redaction is the caller's responsibility.
|
|
47
|
+
*/
|
|
48
|
+
/** Opaque token identifying an open range. Hold onto it; pass to `close()`.
|
|
49
|
+
* Index-scoped — using a token from one CommitRangeIndex on another is
|
|
50
|
+
* a silent no-op (verified by the per-index `_owner` symbol).
|
|
51
|
+
*
|
|
52
|
+
* SECURITY NOTE: the `_owner` symbol is enumerable on the token object
|
|
53
|
+
* via `Object.getOwnPropertySymbols(token)`. This means tokens are NOT
|
|
54
|
+
* adversary-safe — a malicious caller with access to ANY token from
|
|
55
|
+
* this index can recover the owner symbol and forge new tokens. The
|
|
56
|
+
* index is designed for in-process trust boundaries (cooperative
|
|
57
|
+
* recorders sharing one runner), not for hostile-input scenarios.
|
|
58
|
+
* If adversary-safety becomes a requirement, switch to a WeakMap-
|
|
59
|
+
* scoped token model (see security panel review Y2). */
|
|
60
|
+
export interface RangeToken {
|
|
61
|
+
/** Per-index sequential id. Opaque to consumers — they shouldn't read it. */
|
|
62
|
+
readonly _id: number;
|
|
63
|
+
/** Per-index identity — prevents accidental cross-index token misuse. */
|
|
64
|
+
readonly _owner: symbol;
|
|
65
|
+
}
|
|
66
|
+
/** A single range as returned by query methods. Frozen-shape — readonly fields. */
|
|
67
|
+
export interface RangeEntry<TLabel> {
|
|
68
|
+
readonly label: TLabel;
|
|
69
|
+
readonly startIdx: number;
|
|
70
|
+
/** Undefined while the range is still open (mid-run boundary). */
|
|
71
|
+
readonly endIdx?: number;
|
|
72
|
+
}
|
|
73
|
+
export declare class CommitRangeIndex<TLabel> {
|
|
74
|
+
private entries;
|
|
75
|
+
private byId;
|
|
76
|
+
private nextId;
|
|
77
|
+
/** Identity for token scoping — each index gets a fresh symbol so
|
|
78
|
+
* tokens from one index can't accidentally close ranges in another.
|
|
79
|
+
* ROTATED on `clear()` to invalidate stale tokens that survived a
|
|
80
|
+
* run reset (would otherwise hit a recycled id and silently mutate
|
|
81
|
+
* a different range — see DS+logic panel review RED #1). */
|
|
82
|
+
private owner;
|
|
83
|
+
/**
|
|
84
|
+
* Open a new range. Returns a token the caller MUST hold and pass
|
|
85
|
+
* to `close()` later. Each `open()` gets a fresh token; tokens
|
|
86
|
+
* cannot be reused or shared across indices (silent no-op if
|
|
87
|
+
* misused — see Law 2 in the design doc). Tokens from BEFORE
|
|
88
|
+
* the most recent `clear()` are also invalid (owner symbol
|
|
89
|
+
* rotates on clear).
|
|
90
|
+
*/
|
|
91
|
+
open(label: TLabel, startIdx: number): RangeToken;
|
|
92
|
+
/**
|
|
93
|
+
* Close an open range at `endIdx` (inclusive). After close, the
|
|
94
|
+
* range is queryable with both bounds. Closing an already-closed
|
|
95
|
+
* token is a no-op. Closing an unknown token (from another index,
|
|
96
|
+
* or fabricated) is a no-op.
|
|
97
|
+
*/
|
|
98
|
+
close(token: RangeToken, endIdx: number): void;
|
|
99
|
+
/**
|
|
100
|
+
* Returns ALL ranges enclosing `commitIdx`, ordered outer→inner.
|
|
101
|
+
* Includes both closed and open ranges. For a closed range to
|
|
102
|
+
* enclose: `startIdx <= commitIdx <= endIdx`. For an open range:
|
|
103
|
+
* `startIdx <= commitIdx` (no upper bound check).
|
|
104
|
+
*
|
|
105
|
+
* Ordering rule: ascending by `startIdx`, with TIES BROKEN BY
|
|
106
|
+
* descending `endIdx` (wider range = outer). Open ranges (endIdx
|
|
107
|
+
* undefined) are treated as `+Infinity` for tie-break — they
|
|
108
|
+
* always sort outer of any closed range starting at the same idx.
|
|
109
|
+
* This is the only deterministic outer→inner ordering when two
|
|
110
|
+
* boundaries open at the same commit (e.g., Parallel root +
|
|
111
|
+
* its first branch).
|
|
112
|
+
*
|
|
113
|
+
* Returns a SHALLOW IMMUTABLE COPY — caller mutations don't affect
|
|
114
|
+
* internal state.
|
|
115
|
+
*/
|
|
116
|
+
enclosing(commitIdx: number): readonly RangeEntry<TLabel>[];
|
|
117
|
+
/**
|
|
118
|
+
* Returns all ranges OVERLAPPING the slice `[startIdx, endIdx]`. A
|
|
119
|
+
* range overlaps if it shares at least one commit position with the
|
|
120
|
+
* slice. Use for parallel-branch detection, time-window queries,
|
|
121
|
+
* or "what boundaries fired during this slice."
|
|
122
|
+
*
|
|
123
|
+
* Sorted by the SAME outer→inner comparator as `enclosing()`:
|
|
124
|
+
* ascending by `startIdx`, ties broken by descending `endIdx`
|
|
125
|
+
* (wider = outer; open ranges treated as +Infinity).
|
|
126
|
+
*
|
|
127
|
+
* Returns a SHALLOW IMMUTABLE COPY.
|
|
128
|
+
*/
|
|
129
|
+
overlapping(startIdx: number, endIdx: number): readonly RangeEntry<TLabel>[];
|
|
130
|
+
/** Total range count (open + closed). */
|
|
131
|
+
get size(): number;
|
|
132
|
+
/**
|
|
133
|
+
* Wipe all ranges + reset the token counter AND rotate the owner
|
|
134
|
+
* symbol so any token from before this clear becomes invalid.
|
|
135
|
+
* Critical: without rotating the owner, a stale token whose `_id`
|
|
136
|
+
* happens to match a recycled id after clear would silently mutate
|
|
137
|
+
* the wrong entry. Owner rotation makes stale-token close a no-op
|
|
138
|
+
* via the `_owner !== this.owner` guard.
|
|
139
|
+
*
|
|
140
|
+
* Call from a consumer's runId guard when a new run starts (e.g.,
|
|
141
|
+
* agentfootprint's `observeRunId(onNewRun)` from Phase 2).
|
|
142
|
+
*/
|
|
143
|
+
clear(): void;
|
|
144
|
+
/** O(1) lookup by token id. Always returns the entry that the token
|
|
145
|
+
* references (or undefined if the id was never opened in this index). */
|
|
146
|
+
private findById;
|
|
147
|
+
}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* CompositeRecorder — fan-out a single recorder attachment to multiple child recorders.
|
|
3
3
|
*
|
|
4
|
-
* Implements both
|
|
5
|
-
* so it works with both `executor.
|
|
4
|
+
* Implements both ScopeRecorder (scope data ops) and FlowRecorder (control flow events)
|
|
5
|
+
* so it works with both `executor.attachScopeRecorder()` and `executor.attachFlowRecorder()`.
|
|
6
6
|
*
|
|
7
7
|
* The composite has a single ID for idempotent attach/detach. Child recorders
|
|
8
8
|
* keep their own IDs internally but are not individually visible to the executor.
|
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
* new DebugRecorder({ verbosity: 'minimal' }),
|
|
21
21
|
* ]);
|
|
22
22
|
*
|
|
23
|
-
* executor.
|
|
23
|
+
* executor.attachScopeRecorder(observability);
|
|
24
24
|
*
|
|
25
25
|
* // Access child recorders by type
|
|
26
26
|
* const metrics = observability.get(MetricRecorder);
|
|
@@ -39,11 +39,11 @@
|
|
|
39
39
|
* }
|
|
40
40
|
*
|
|
41
41
|
* // Consumer
|
|
42
|
-
* executor.
|
|
42
|
+
* executor.attachScopeRecorder(agentObservability());
|
|
43
43
|
* ```
|
|
44
44
|
*/
|
|
45
45
|
import type { FlowBreakEvent, FlowDecisionEvent, FlowErrorEvent, FlowForkEvent, FlowLoopEvent, FlowNextEvent, FlowRecorder, FlowSelectedEvent, FlowStageEvent, FlowSubflowEvent, FlowSubflowRegisteredEvent } from '../engine/narrative/types.js';
|
|
46
|
-
import type { CommitEvent, ErrorEvent, ReadEvent,
|
|
46
|
+
import type { CommitEvent, ErrorEvent, ReadEvent, ScopeRecorder, StageEvent, WriteEvent } from '../scope/types.js';
|
|
47
47
|
/** Snapshot format for composite recorders — wraps child snapshots. */
|
|
48
48
|
export interface CompositeSnapshot {
|
|
49
49
|
name: string;
|
|
@@ -55,10 +55,10 @@ export interface CompositeSnapshot {
|
|
|
55
55
|
}>;
|
|
56
56
|
};
|
|
57
57
|
}
|
|
58
|
-
export declare class CompositeRecorder implements
|
|
58
|
+
export declare class CompositeRecorder implements ScopeRecorder, FlowRecorder {
|
|
59
59
|
readonly id: string;
|
|
60
60
|
private readonly children;
|
|
61
|
-
constructor(id: string, children: Array<
|
|
61
|
+
constructor(id: string, children: Array<ScopeRecorder | FlowRecorder>);
|
|
62
62
|
/**
|
|
63
63
|
* Get a child recorder by class type.
|
|
64
64
|
*
|
|
@@ -69,7 +69,7 @@ export declare class CompositeRecorder implements Recorder, FlowRecorder {
|
|
|
69
69
|
*/
|
|
70
70
|
get<T>(type: new (...args: any[]) => T): T | undefined;
|
|
71
71
|
/** Get all child recorders. */
|
|
72
|
-
getChildren(): ReadonlyArray<
|
|
72
|
+
getChildren(): ReadonlyArray<ScopeRecorder | FlowRecorder>;
|
|
73
73
|
onRead(event: ReadEvent): void;
|
|
74
74
|
onWrite(event: WriteEvent): void;
|
|
75
75
|
onCommit(event: CommitEvent): void;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* EmitRecorder — the third observer channel, alongside `
|
|
2
|
+
* EmitRecorder — the third observer channel, alongside `ScopeRecorder`
|
|
3
3
|
* (scope data-flow) and `FlowRecorder` (control-flow).
|
|
4
4
|
*
|
|
5
5
|
* ## Why this exists
|
|
@@ -34,7 +34,7 @@
|
|
|
34
34
|
*
|
|
35
35
|
* | Channel | Fires when | Built-in consumers |
|
|
36
36
|
* |-----------------|------------------------------------|------------------------|
|
|
37
|
-
* | `
|
|
37
|
+
* | `ScopeRecorder` | scope read/write/commit | DebugRecorder, MetricRecorder |
|
|
38
38
|
* | `FlowRecorder` | traversal transitions | NarrativeFlowRecorder, etc. |
|
|
39
39
|
* | `EmitRecorder` | consumer calls `scope.$emit(...)` | (none ships today; Phase 3.X adds MemoryEmitRecorder) |
|
|
40
40
|
*
|
|
@@ -111,7 +111,7 @@ export interface InOutEntry {
|
|
|
111
111
|
readonly isRoot: boolean;
|
|
112
112
|
}
|
|
113
113
|
export interface InOutRecorderOptions {
|
|
114
|
-
/**
|
|
114
|
+
/** ScopeRecorder id. Defaults to `inout-N` (auto-incremented). */
|
|
115
115
|
id?: string;
|
|
116
116
|
}
|
|
117
117
|
/**
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* KeyedStore<T> — concrete, composable 1:1 storage keyed by string id.
|
|
3
|
+
*
|
|
4
|
+
* Pattern: COMPOSITION primitive. Concrete class — instantiate with
|
|
5
|
+
* `new KeyedStore<T>()` and own it as a field on your
|
|
6
|
+
* recorder. Replaces the abstract `KeyedRecorder<T>` base
|
|
7
|
+
* class for the v5 "one purpose per recorder" rule.
|
|
8
|
+
* Role: 1:1 Map keyed by `runtimeStageId` (or any string).
|
|
9
|
+
* Insertion-ordered iteration.
|
|
10
|
+
*
|
|
11
|
+
* **Contrast with `SequenceStore<T>`:** KeyedStore is 1:1 — one entry
|
|
12
|
+
* per key. Use SequenceStore for 1:N (multiple entries per
|
|
13
|
+
* runtimeStageId, ordering matters).
|
|
14
|
+
*
|
|
15
|
+
* @example
|
|
16
|
+
* ```typescript
|
|
17
|
+
* import { KeyedStore } from 'footprintjs/trace';
|
|
18
|
+
*
|
|
19
|
+
* interface TokenEntry { input: number; output: number; }
|
|
20
|
+
*
|
|
21
|
+
* // ONE PURPOSE: typed-event handler. Storage is composed in.
|
|
22
|
+
* class TokenRecorder {
|
|
23
|
+
* readonly id = 'tokens';
|
|
24
|
+
* private readonly store = new KeyedStore<TokenEntry>();
|
|
25
|
+
*
|
|
26
|
+
* onLLMCall(event: LLMCallEvent) {
|
|
27
|
+
* this.store.set(event.runtimeStageId, event.usage);
|
|
28
|
+
* }
|
|
29
|
+
*
|
|
30
|
+
* getForStep(id: string) { return this.store.get(id); }
|
|
31
|
+
* getTotalTokens() { return this.store.aggregate((s, e) => s + e.input + e.output, 0); }
|
|
32
|
+
* getTokensUpTo(keys: Set<string>) {
|
|
33
|
+
* return this.store.accumulate((s, e) => s + e.input + e.output, 0, keys);
|
|
34
|
+
* }
|
|
35
|
+
*
|
|
36
|
+
* clear() { this.store.clear(); }
|
|
37
|
+
* }
|
|
38
|
+
* ```
|
|
39
|
+
*/
|
|
40
|
+
export declare class KeyedStore<T> {
|
|
41
|
+
private readonly data;
|
|
42
|
+
/** Store a single entry. Replaces any existing entry for the same key. */
|
|
43
|
+
set(key: string, entry: T): void;
|
|
44
|
+
/** Remove an entry. Returns true if the key existed, false otherwise. */
|
|
45
|
+
delete(key: string): boolean;
|
|
46
|
+
/** O(1) lookup. */
|
|
47
|
+
get(key: string): T | undefined;
|
|
48
|
+
/** True if a value exists for the key. */
|
|
49
|
+
has(key: string): boolean;
|
|
50
|
+
/** All entries as a read-only Map (insertion-ordered). */
|
|
51
|
+
getMap(): ReadonlyMap<string, T>;
|
|
52
|
+
/** All values as an array (insertion-ordered). */
|
|
53
|
+
values(): T[];
|
|
54
|
+
/** Number of entries stored. */
|
|
55
|
+
get size(): number;
|
|
56
|
+
/** Reduce ALL entries to a single value. For dashboards, totals, summaries. */
|
|
57
|
+
aggregate<R>(fn: (acc: R, entry: T, key: string) => R, initial: R): R;
|
|
58
|
+
/**
|
|
59
|
+
* Reduce entries, optionally filtered by a set of keys.
|
|
60
|
+
* For time-travel progressive view: pass the keys visible at the
|
|
61
|
+
* current slider position. Without keys, reduces all entries (same
|
|
62
|
+
* as `aggregate`).
|
|
63
|
+
*/
|
|
64
|
+
accumulate<R>(fn: (acc: R, entry: T, key: string) => R, initial: R, keys?: ReadonlySet<string>): R;
|
|
65
|
+
/** Return entries whose keys are in the set, preserving insertion order. */
|
|
66
|
+
filterByKeys(keys: ReadonlySet<string>): T[];
|
|
67
|
+
/** Clear all stored data. Recorders typically call this from their own
|
|
68
|
+
* `clear()` method, which the executor invokes before each run. */
|
|
69
|
+
clear(): void;
|
|
70
|
+
}
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
* if (event.stageName.includes('llm')) return 0.7;
|
|
17
17
|
* return 1.0;
|
|
18
18
|
* });
|
|
19
|
-
* executor.
|
|
19
|
+
* executor.attachScopeRecorder(quality);
|
|
20
20
|
* await executor.run();
|
|
21
21
|
*
|
|
22
22
|
* // Per-step score
|
|
@@ -29,7 +29,7 @@
|
|
|
29
29
|
* quality.getLowest(); // { runtimeStageId: 'call-llm#5', entry: { score: 0.7, ... } }
|
|
30
30
|
* ```
|
|
31
31
|
*/
|
|
32
|
-
import type { ReadEvent,
|
|
32
|
+
import type { ReadEvent, ScopeRecorder, StageEvent, WriteEvent } from '../scope/types.js';
|
|
33
33
|
import { KeyedRecorder } from './KeyedRecorder.js';
|
|
34
34
|
import type { RecorderOperation } from './RecorderOperation.js';
|
|
35
35
|
/** Per-step quality data stored by QualityRecorder. */
|
|
@@ -64,12 +64,12 @@ export type QualityScoringFn = (runtimeStageId: string, context: {
|
|
|
64
64
|
};
|
|
65
65
|
/** Options for QualityRecorder. */
|
|
66
66
|
export interface QualityRecorderOptions {
|
|
67
|
-
/**
|
|
67
|
+
/** ScopeRecorder ID. Defaults to auto-increment. */
|
|
68
68
|
id?: string;
|
|
69
69
|
/** Preferred UI operation. Defaults to 'accumulate' (progressive quality). */
|
|
70
70
|
preferredOperation?: RecorderOperation;
|
|
71
71
|
}
|
|
72
|
-
export declare class QualityRecorder extends KeyedRecorder<QualityEntry> implements
|
|
72
|
+
export declare class QualityRecorder extends KeyedRecorder<QualityEntry> implements ScopeRecorder {
|
|
73
73
|
private static _counter;
|
|
74
74
|
readonly id: string;
|
|
75
75
|
readonly preferredOperation: RecorderOperation;
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Provides dual-indexed storage: a flat array preserving insertion order plus a
|
|
5
5
|
* Map<runtimeStageId, T[]> for O(1) per-step lookup. One entry type, multiple entries
|
|
6
|
-
* per step. Designed for recorders that implement BOTH
|
|
6
|
+
* per step. Designed for recorders that implement BOTH ScopeRecorder and FlowRecorder
|
|
7
7
|
* (merging data ops and control flow into a single interleaved sequence).
|
|
8
8
|
*
|
|
9
9
|
* **Contrast with KeyedRecorder<T>:**
|
|
@@ -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
|
-
/**
|
|
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
|
-
/**
|
|
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';
|