footprintjs 9.17.0 → 9.18.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 +2 -2
- package/dist/esm/lib/time-travel/axis.d.ts +119 -0
- package/dist/esm/lib/time-travel/axis.js +179 -0
- package/dist/esm/lib/time-travel/bundles.d.ts +77 -0
- package/dist/esm/lib/time-travel/bundles.js +124 -0
- package/dist/esm/lib/time-travel/chain.d.ts +93 -0
- package/dist/esm/lib/time-travel/chain.js +186 -0
- package/dist/esm/lib/time-travel/commitStops.d.ts +8 -0
- package/dist/esm/lib/time-travel/commitStops.js +9 -1
- package/dist/esm/lib/time-travel/index.d.ts +4 -1
- package/dist/esm/lib/time-travel/index.js +3 -1
- package/dist/esm/lib/time-travel/stateAt.d.ts +39 -10
- package/dist/esm/lib/time-travel/stateAt.js +88 -44
- package/dist/esm/lib/time-travel/timeTravel.d.ts +30 -3
- package/dist/esm/lib/time-travel/timeTravel.js +132 -24
- package/dist/esm/lib/time-travel/types.d.ts +227 -45
- package/dist/esm/lib/time-travel/types.js +1 -1
- package/dist/esm/trace.d.ts +2 -2
- package/dist/esm/trace.js +2 -2
- package/dist/lib/time-travel/axis.js +184 -0
- package/dist/lib/time-travel/bundles.js +132 -0
- package/dist/lib/time-travel/chain.js +191 -0
- package/dist/lib/time-travel/commitStops.js +9 -1
- package/dist/lib/time-travel/index.js +7 -2
- package/dist/lib/time-travel/stateAt.js +91 -46
- package/dist/lib/time-travel/timeTravel.js +131 -23
- package/dist/lib/time-travel/types.js +1 -1
- package/dist/trace.js +6 -3
- package/dist/types/lib/time-travel/axis.d.ts +119 -0
- package/dist/types/lib/time-travel/bundles.d.ts +77 -0
- package/dist/types/lib/time-travel/chain.d.ts +93 -0
- package/dist/types/lib/time-travel/commitStops.d.ts +8 -0
- package/dist/types/lib/time-travel/index.d.ts +4 -1
- package/dist/types/lib/time-travel/stateAt.d.ts +39 -10
- package/dist/types/lib/time-travel/timeTravel.d.ts +30 -3
- package/dist/types/lib/time-travel/types.d.ts +227 -45
- package/dist/types/trace.d.ts +2 -2
- package/package.json +1 -1
package/CLAUDE.md
CHANGED
|
@@ -17,7 +17,7 @@ Entry points: `footprintjs` (public API) · `/recorders` (factories) · `/trace`
|
|
|
17
17
|
| decide/ | decide()/select() evidence capture |
|
|
18
18
|
| recorder/ | stores (KeyedStore/SequenceStore/BoundaryStateStore), CombinedRecorder routing, built-ins (Topology/InOut/ControlDep/Quality) |
|
|
19
19
|
| slice/ | variable-first backward slicing (triage query layer): sliceForKey (anchor at last writer → causalChain) + arrayProvenance/elementProvenance (append-fold element births — the agent mega-key fix) + KeysReadSource strategies. Pure post-hoc queries; imports memory/ ONLY (see src/lib/slice/README.md) |
|
|
20
|
-
| time-travel/ | the READER'S cursor over a finished trace (9.17.0): `timeTravel()` stops/moves/marks/`drill()`, `stateAt()` fold + honesty basis, `TimeTravelStrategy
|
|
20
|
+
| time-travel/ | the READER'S cursor over a finished trace (9.17.0): `timeTravel()` stops/moves/marks/`drill()`, `stateAt()` fold + honesty basis, `TimeTravelStrategy<TMeta>` seam (one shipped: `commitStops`). 9.18.0 composer's side: axis.ts (`splitAxis` = the ONE guard of the `[start,…stages,end]` contract; `filterStops` = filter + re-partition + `Stop.meta` vocabulary + `Stop.prologue` on an absorbing start — a FLAG, deliberately not a 5th StopKind), bundles.ts (per-bundle narrowing of `unknown[]` rows → index-holding GAPS in `FoldedState.skipped`), chain.ts (`timeTravel([paused, resumed])`: commit indices stay RUN-LOCAL + `Stop.sourceIdx`; refused unless execution indices are monotonic across legs and no id repeats). Read-time only — never a second live cursor. Imports memory/ + `engine/runtimeStageId` (see src/lib/time-travel/README.md) |
|
|
21
21
|
| pause/ | PauseSignal / FlowchartCheckpoint types + `interrupt(scope, payload)` (throws InterruptSignal; converted to PauseSignal at the StageRunner boundary, `pausedBy:'interrupt'`) |
|
|
22
22
|
| engine/ | FlowchartTraverser + handlers/; narrative/ = the whole FlowRecorder channel (dispatcher + 9 strategies), not just text; branchSegment.ts = the ONE owner of the generated-segment grammar (`<stageId>~<index>`, marker `~` RESERVED); `executeStage` (:720) = the ONE funnel every stage fn passes through — and therefore the ONE owner of the declarative `retry` loop |
|
|
23
23
|
| runner/ | FlowChartExecutor wiring (runtime, recorders, pause/resume, snapshot, DeferredObserverTier). RunContext.ts is a fluent run builder, NOT the threaded per-run context |
|
|
@@ -66,7 +66,7 @@ loop: Phase 6 sees isLoopRef → ContinuationResolver.resolveTarget (:107): id-m
|
|
|
66
66
|
end: onRunEnd (throw → onRunFailed; pause → neither) → deferred terminalFlush → getSnapshot() = {sharedState (LIVE view; dev-mode frozen clone), initialState (the fold base), commitLog (detached+frozen), executionTree, subflowResults (dual-keyed), recorders}.
|
|
67
67
|
|
|
68
68
|
## Backtracking
|
|
69
|
-
Seven mechanisms, no state rollback anywhere: **M1** TransactionBuffer staging + net-change commit — commit-on-error by design, explicitly NOT rollback (TransactionBuffer.ts:13-18; error path commits then rethrows). **M2** Pause/Resume checkpointing — the only resume-from-prior-point; TWO raise shapes, ONE checkpoint: `addPausableFunction` (resume runs `resumeFn`) and `interrupt(scope, payload)` (resume RE-RUNS THE STAGE FUNCTION FROM ITS TOP — stages are atomic; the answer returns out of the `interrupt()` call via a WeakMap keyed by the scope), discriminated by `checkpoint.pausedBy`; checkpoint is one detached structuredClone; resume rebuilds the cursor from pausedStageId+subflowPath and only overrides the LEAF subflow root (2+-deep pause re-executes outer pre-mount stages). **M3** commit-log replay (EventLog.materialise, commitValueAt — required under delta mode). **M4** loopTo re-entry — forward execution over accumulated state, bounded by maxIterations + dynamicNextHops. **M5** causalChain backward slicing (read-only analysis; honesty flags for untracked reads; `edgeAttribution: 'per-write'` refines edges via `TraceEntry.readKeys` when the writeProvenance dial recorded them — worklist, subset-of-ceiling safe). **M6 (query layer)** slice/ — variable-first triage: sliceForKey + append-fold element provenance + the ONLY safe serializations (sliceToJSON/formatSlice — never JSON.stringify a slice root). **M7 (read-time cursor)** time-travel/ — `timeTravel(snapshot)` walks stops over the finished log with `stateAt()` folding M3's replay at each one (base = `RuntimeSnapshot.initialState`, which now travels with the log); it is a READER, never a second live cursor, and `drill(mount)` opens a separate cursor over a subflow's own isolated log. Deep dive + step tables: [.claude/rules/backtracking.md](.claude/rules/backtracking.md).
|
|
69
|
+
Seven mechanisms, no state rollback anywhere: **M1** TransactionBuffer staging + net-change commit — commit-on-error by design, explicitly NOT rollback (TransactionBuffer.ts:13-18; error path commits then rethrows). **M2** Pause/Resume checkpointing — the only resume-from-prior-point; TWO raise shapes, ONE checkpoint: `addPausableFunction` (resume runs `resumeFn`) and `interrupt(scope, payload)` (resume RE-RUNS THE STAGE FUNCTION FROM ITS TOP — stages are atomic; the answer returns out of the `interrupt()` call via a WeakMap keyed by the scope), discriminated by `checkpoint.pausedBy`; checkpoint is one detached structuredClone; resume rebuilds the cursor from pausedStageId+subflowPath and only overrides the LEAF subflow root (2+-deep pause re-executes outer pre-mount stages). **M3** commit-log replay (EventLog.materialise, commitValueAt — required under delta mode). **M4** loopTo re-entry — forward execution over accumulated state, bounded by maxIterations + dynamicNextHops. **M5** causalChain backward slicing (read-only analysis; honesty flags for untracked reads; `edgeAttribution: 'per-write'` refines edges via `TraceEntry.readKeys` when the writeProvenance dial recorded them — worklist, subset-of-ceiling safe). **M6 (query layer)** slice/ — variable-first triage: sliceForKey + append-fold element provenance + the ONLY safe serializations (sliceToJSON/formatSlice — never JSON.stringify a slice root). **M7 (read-time cursor)** time-travel/ — `timeTravel(snapshot)` walks stops over the finished log with `stateAt()` folding M3's replay at each one (base = `RuntimeSnapshot.initialState`, which now travels with the log); it is a READER, never a second live cursor, and `drill(mount)` opens a separate cursor over a subflow's own isolated log. 9.18.0: `timeTravel([paused, resumed])` chains a cross-executor M2 resume into ONE axis — the resumed leg's `initialState` (= the state at the pause) restarts the fold, commit indices stay run-local (`Stop.sourceIdx` names the leg), and the chain is refused unless the never-reset execution counter is monotonic across legs. Deep dive + step tables: [.claude/rules/backtracking.md](.claude/rules/backtracking.md).
|
|
70
70
|
|
|
71
71
|
## Invariants (assumed, not stated)
|
|
72
72
|
- Committed state immutable-after-swap; first-touch views hold bare references on that guarantee — in-place mutation of SharedMemory.context corrupts every in-flight stage.
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* axis.ts — the shape of an axis, and the one safe way to narrow it.
|
|
3
|
+
*
|
|
4
|
+
* WHY THIS FILE EXISTS. `commitStops` returns `Stop[]`, and every strategy
|
|
5
|
+
* that COMPOSES it (keep the stages my domain recognises, drop the rest) rests
|
|
6
|
+
* on a shape the type never stated: `[start, …stages, end]` for a non-empty
|
|
7
|
+
* log, empty for an empty one. Each composer therefore wrote its own runtime
|
|
8
|
+
* guard against its own reading of the contract, plus a mocked-substrate test
|
|
9
|
+
* to prove the guard fires. That is one invariant, discovered three times, and
|
|
10
|
+
* the library that owns the invariant should be the one that states it.
|
|
11
|
+
*
|
|
12
|
+
* {@link splitAxis} states it. {@link filterStops} then does the whole
|
|
13
|
+
* composition — check the shape, keep what the caller keeps, RE-PARTITION the
|
|
14
|
+
* log so the dropped stages' commits are still folded somewhere, and mark the
|
|
15
|
+
* `'start'` bookend as carrying a prologue — because getting the
|
|
16
|
+
* re-partitioning right by hand is the other thing every composer had to
|
|
17
|
+
* rediscover.
|
|
18
|
+
*/
|
|
19
|
+
import type { AxisSplit, Stop } from './types.js';
|
|
20
|
+
/**
|
|
21
|
+
* Split a strategy's stops into `{ start, stages, end }`, or say why it cannot.
|
|
22
|
+
*
|
|
23
|
+
* THE INVARIANT, stated once: for a non-EMPTY log, `commitStops` returns at
|
|
24
|
+
* least the two bookends, `'start'` first and `'end'` last, with one stop per
|
|
25
|
+
* executed stage in execution order between them. For an EMPTY log it returns
|
|
26
|
+
* `[]` — no bookends around nothing, because a cursor with nowhere to stand
|
|
27
|
+
* should say `reason: 'empty'` rather than offer two positions that are the
|
|
28
|
+
* same position.
|
|
29
|
+
*
|
|
30
|
+
* The two refusals are different facts and must not be collapsed: `'empty'`
|
|
31
|
+
* means the run committed nothing, `'not-bookended'` means a strategy broke
|
|
32
|
+
* the contract. A composer that treats them alike reports a broken strategy as
|
|
33
|
+
* an empty run.
|
|
34
|
+
*
|
|
35
|
+
* @example
|
|
36
|
+
* ```typescript
|
|
37
|
+
* import { commitStops, splitAxis } from 'footprintjs/trace';
|
|
38
|
+
*
|
|
39
|
+
* const axis = splitAxis(commitStops(snapshot.commitLog, snapshot.executionTree));
|
|
40
|
+
* if (axis.ok) {
|
|
41
|
+
* axis.start.commitIdx; // -1 — the fold base
|
|
42
|
+
* axis.stages.map((s) => s.label);
|
|
43
|
+
* axis.end.lastCommitIdx; // the last bundle in the log
|
|
44
|
+
* } else {
|
|
45
|
+
* axis.reason; // 'empty' | 'not-bookended'
|
|
46
|
+
* }
|
|
47
|
+
* ```
|
|
48
|
+
*/
|
|
49
|
+
export declare function splitAxis<TMeta = unknown>(stops: readonly Stop<TMeta>[]): AxisSplit<TMeta>;
|
|
50
|
+
/**
|
|
51
|
+
* What {@link filterStops} does with one stop: drop it, keep it, or keep it
|
|
52
|
+
* carrying the strategy's own label and vocabulary.
|
|
53
|
+
*
|
|
54
|
+
* `false` / `null` / `undefined` drop. `true` keeps it unchanged. An object
|
|
55
|
+
* keeps it and overrides `label` and/or sets `meta` — the strategy's own
|
|
56
|
+
* classification, travelling ON the stop instead of being re-derived from
|
|
57
|
+
* `runtimeStageId` at every read.
|
|
58
|
+
*/
|
|
59
|
+
export type StopFilter<TMeta> = boolean | null | undefined | {
|
|
60
|
+
readonly label?: string;
|
|
61
|
+
readonly meta?: TMeta;
|
|
62
|
+
};
|
|
63
|
+
/**
|
|
64
|
+
* Build a FILTERING strategy's axis out of another strategy's stops.
|
|
65
|
+
*
|
|
66
|
+
* The rule, in one sentence: keep the stages `keep` recognises, and give the
|
|
67
|
+
* survivors the log back.
|
|
68
|
+
*
|
|
69
|
+
* **Meta is the decision's, never inherited.** The input may already be a
|
|
70
|
+
* filtered axis carrying another strategy's `meta`; a `true` (or a meta-less
|
|
71
|
+
* object) keeps the stop and DROPS that meta, so a `Stop<TMeta>` never carries
|
|
72
|
+
* a value of some other type under `TMeta`. The bookends carry none either.
|
|
73
|
+
*
|
|
74
|
+
* **The survivors re-partition the log.** A dropped stage's commits still
|
|
75
|
+
* happened, so they fold into the stop that PRECEDES them: every kept stop's
|
|
76
|
+
* `lastCommitIdx` runs to just before the next kept stop begins. Nothing is
|
|
77
|
+
* orphaned and `stateAt(stop)` stays "the state that existed when the next
|
|
78
|
+
* kept stage started".
|
|
79
|
+
*
|
|
80
|
+
* **What that costs `'start'`, said out loud.** The commits before the first
|
|
81
|
+
* survivor have nowhere else to go, so `'start'` absorbs them: its fold is the
|
|
82
|
+
* run's base PLUS that prologue — the state the first stop READ, not the raw
|
|
83
|
+
* base. The returned start therefore carries `prologue: true` whenever it
|
|
84
|
+
* actually absorbed a stage, and a renderer that means "before anything ran"
|
|
85
|
+
* checks `kind === 'start' && !prologue`. That flag is the whole reason this
|
|
86
|
+
* helper exists rather than a `.filter()` call.
|
|
87
|
+
*
|
|
88
|
+
* **A log with nothing kept** yields the two bookends and nothing between
|
|
89
|
+
* them: `'start'` folds the whole log, `'end'` folds the whole log, and
|
|
90
|
+
* `jumpTo` on any stage id refuses with `reason: 'miss'`. An axis with nowhere
|
|
91
|
+
* meaningful to stand — which is the truthful shape for a run this strategy
|
|
92
|
+
* recognises nothing in, and NOT the `[]` that says the log was empty.
|
|
93
|
+
*
|
|
94
|
+
* @throws when `stops` is non-empty but not bookended — see {@link splitAxis}.
|
|
95
|
+
* Only a strategy can cause that; a run cannot, so it is a contract error
|
|
96
|
+
* and not a condition to fold into the data.
|
|
97
|
+
*
|
|
98
|
+
* @example
|
|
99
|
+
* ```typescript
|
|
100
|
+
* import { commitStops, filterStops } from 'footprintjs/trace';
|
|
101
|
+
* import type { Stop, TimeTravelStrategy } from 'footprintjs/trace';
|
|
102
|
+
*
|
|
103
|
+
* type Milestone = { kind: 'llm-turn' | 'tool-call'; label: string };
|
|
104
|
+
* declare function milestoneFor(runtimeStageId: string): Milestone | null;
|
|
105
|
+
*
|
|
106
|
+
* const milestoneStops: TimeTravelStrategy<Milestone> = {
|
|
107
|
+
* stopsFor: (log, tree) =>
|
|
108
|
+
* filterStops<Milestone>(commitStops(log, tree), (stop) => {
|
|
109
|
+
* const m = milestoneFor(stop.runtimeStageId);
|
|
110
|
+
* return m ? { label: m.label, meta: m } : null;
|
|
111
|
+
* }),
|
|
112
|
+
* };
|
|
113
|
+
*
|
|
114
|
+
* const cursor = timeTravel(snapshot, { strategy: milestoneStops });
|
|
115
|
+
* cursor.at()?.meta?.kind; // 'llm-turn' — no re-derivation
|
|
116
|
+
* cursor.stops[0].prologue; // true when stages ran before the first turn
|
|
117
|
+
* ```
|
|
118
|
+
*/
|
|
119
|
+
export declare function filterStops<TMeta = unknown>(stops: readonly Stop<unknown>[], keep: (stop: Stop<unknown>) => StopFilter<TMeta>): Stop<TMeta>[];
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* axis.ts — the shape of an axis, and the one safe way to narrow it.
|
|
3
|
+
*
|
|
4
|
+
* WHY THIS FILE EXISTS. `commitStops` returns `Stop[]`, and every strategy
|
|
5
|
+
* that COMPOSES it (keep the stages my domain recognises, drop the rest) rests
|
|
6
|
+
* on a shape the type never stated: `[start, …stages, end]` for a non-empty
|
|
7
|
+
* log, empty for an empty one. Each composer therefore wrote its own runtime
|
|
8
|
+
* guard against its own reading of the contract, plus a mocked-substrate test
|
|
9
|
+
* to prove the guard fires. That is one invariant, discovered three times, and
|
|
10
|
+
* the library that owns the invariant should be the one that states it.
|
|
11
|
+
*
|
|
12
|
+
* {@link splitAxis} states it. {@link filterStops} then does the whole
|
|
13
|
+
* composition — check the shape, keep what the caller keeps, RE-PARTITION the
|
|
14
|
+
* log so the dropped stages' commits are still folded somewhere, and mark the
|
|
15
|
+
* `'start'` bookend as carrying a prologue — because getting the
|
|
16
|
+
* re-partitioning right by hand is the other thing every composer had to
|
|
17
|
+
* rediscover.
|
|
18
|
+
*/
|
|
19
|
+
/**
|
|
20
|
+
* Split a strategy's stops into `{ start, stages, end }`, or say why it cannot.
|
|
21
|
+
*
|
|
22
|
+
* THE INVARIANT, stated once: for a non-EMPTY log, `commitStops` returns at
|
|
23
|
+
* least the two bookends, `'start'` first and `'end'` last, with one stop per
|
|
24
|
+
* executed stage in execution order between them. For an EMPTY log it returns
|
|
25
|
+
* `[]` — no bookends around nothing, because a cursor with nowhere to stand
|
|
26
|
+
* should say `reason: 'empty'` rather than offer two positions that are the
|
|
27
|
+
* same position.
|
|
28
|
+
*
|
|
29
|
+
* The two refusals are different facts and must not be collapsed: `'empty'`
|
|
30
|
+
* means the run committed nothing, `'not-bookended'` means a strategy broke
|
|
31
|
+
* the contract. A composer that treats them alike reports a broken strategy as
|
|
32
|
+
* an empty run.
|
|
33
|
+
*
|
|
34
|
+
* @example
|
|
35
|
+
* ```typescript
|
|
36
|
+
* import { commitStops, splitAxis } from 'footprintjs/trace';
|
|
37
|
+
*
|
|
38
|
+
* const axis = splitAxis(commitStops(snapshot.commitLog, snapshot.executionTree));
|
|
39
|
+
* if (axis.ok) {
|
|
40
|
+
* axis.start.commitIdx; // -1 — the fold base
|
|
41
|
+
* axis.stages.map((s) => s.label);
|
|
42
|
+
* axis.end.lastCommitIdx; // the last bundle in the log
|
|
43
|
+
* } else {
|
|
44
|
+
* axis.reason; // 'empty' | 'not-bookended'
|
|
45
|
+
* }
|
|
46
|
+
* ```
|
|
47
|
+
*/
|
|
48
|
+
export function splitAxis(stops) {
|
|
49
|
+
const kinds = Object.freeze(stops.map((stop) => stop.kind));
|
|
50
|
+
if (stops.length === 0)
|
|
51
|
+
return { ok: false, reason: 'empty', kinds };
|
|
52
|
+
const start = stops[0];
|
|
53
|
+
const end = stops[stops.length - 1];
|
|
54
|
+
if (start.kind !== 'start' || end.kind !== 'end' || stops.length < 2) {
|
|
55
|
+
return { ok: false, reason: 'not-bookended', kinds };
|
|
56
|
+
}
|
|
57
|
+
return { ok: true, start, stages: Object.freeze(stops.slice(1, -1)), end };
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* `stop` retyped to the OUTPUT vocabulary, with any meta it already carried
|
|
61
|
+
* left behind. The input axis may itself be a filtered one (a Beat axis being
|
|
62
|
+
* narrowed into a Tag axis), and a `true` decision keeps the STOP, not the
|
|
63
|
+
* other strategy's classification: a `Stop<Tag>` whose `meta` is a Beat would
|
|
64
|
+
* be a lie the type could not catch. Only the decision may supply a meta.
|
|
65
|
+
*/
|
|
66
|
+
function retyped(stop) {
|
|
67
|
+
if (stop.meta === undefined)
|
|
68
|
+
return stop;
|
|
69
|
+
const out = {};
|
|
70
|
+
const fields = stop;
|
|
71
|
+
for (const key of Object.keys(fields))
|
|
72
|
+
if (key !== 'meta')
|
|
73
|
+
out[key] = fields[key];
|
|
74
|
+
return out;
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Build a FILTERING strategy's axis out of another strategy's stops.
|
|
78
|
+
*
|
|
79
|
+
* The rule, in one sentence: keep the stages `keep` recognises, and give the
|
|
80
|
+
* survivors the log back.
|
|
81
|
+
*
|
|
82
|
+
* **Meta is the decision's, never inherited.** The input may already be a
|
|
83
|
+
* filtered axis carrying another strategy's `meta`; a `true` (or a meta-less
|
|
84
|
+
* object) keeps the stop and DROPS that meta, so a `Stop<TMeta>` never carries
|
|
85
|
+
* a value of some other type under `TMeta`. The bookends carry none either.
|
|
86
|
+
*
|
|
87
|
+
* **The survivors re-partition the log.** A dropped stage's commits still
|
|
88
|
+
* happened, so they fold into the stop that PRECEDES them: every kept stop's
|
|
89
|
+
* `lastCommitIdx` runs to just before the next kept stop begins. Nothing is
|
|
90
|
+
* orphaned and `stateAt(stop)` stays "the state that existed when the next
|
|
91
|
+
* kept stage started".
|
|
92
|
+
*
|
|
93
|
+
* **What that costs `'start'`, said out loud.** The commits before the first
|
|
94
|
+
* survivor have nowhere else to go, so `'start'` absorbs them: its fold is the
|
|
95
|
+
* run's base PLUS that prologue — the state the first stop READ, not the raw
|
|
96
|
+
* base. The returned start therefore carries `prologue: true` whenever it
|
|
97
|
+
* actually absorbed a stage, and a renderer that means "before anything ran"
|
|
98
|
+
* checks `kind === 'start' && !prologue`. That flag is the whole reason this
|
|
99
|
+
* helper exists rather than a `.filter()` call.
|
|
100
|
+
*
|
|
101
|
+
* **A log with nothing kept** yields the two bookends and nothing between
|
|
102
|
+
* them: `'start'` folds the whole log, `'end'` folds the whole log, and
|
|
103
|
+
* `jumpTo` on any stage id refuses with `reason: 'miss'`. An axis with nowhere
|
|
104
|
+
* meaningful to stand — which is the truthful shape for a run this strategy
|
|
105
|
+
* recognises nothing in, and NOT the `[]` that says the log was empty.
|
|
106
|
+
*
|
|
107
|
+
* @throws when `stops` is non-empty but not bookended — see {@link splitAxis}.
|
|
108
|
+
* Only a strategy can cause that; a run cannot, so it is a contract error
|
|
109
|
+
* and not a condition to fold into the data.
|
|
110
|
+
*
|
|
111
|
+
* @example
|
|
112
|
+
* ```typescript
|
|
113
|
+
* import { commitStops, filterStops } from 'footprintjs/trace';
|
|
114
|
+
* import type { Stop, TimeTravelStrategy } from 'footprintjs/trace';
|
|
115
|
+
*
|
|
116
|
+
* type Milestone = { kind: 'llm-turn' | 'tool-call'; label: string };
|
|
117
|
+
* declare function milestoneFor(runtimeStageId: string): Milestone | null;
|
|
118
|
+
*
|
|
119
|
+
* const milestoneStops: TimeTravelStrategy<Milestone> = {
|
|
120
|
+
* stopsFor: (log, tree) =>
|
|
121
|
+
* filterStops<Milestone>(commitStops(log, tree), (stop) => {
|
|
122
|
+
* const m = milestoneFor(stop.runtimeStageId);
|
|
123
|
+
* return m ? { label: m.label, meta: m } : null;
|
|
124
|
+
* }),
|
|
125
|
+
* };
|
|
126
|
+
*
|
|
127
|
+
* const cursor = timeTravel(snapshot, { strategy: milestoneStops });
|
|
128
|
+
* cursor.at()?.meta?.kind; // 'llm-turn' — no re-derivation
|
|
129
|
+
* cursor.stops[0].prologue; // true when stages ran before the first turn
|
|
130
|
+
* ```
|
|
131
|
+
*/
|
|
132
|
+
export function filterStops(stops, keep) {
|
|
133
|
+
const axis = splitAxis(stops);
|
|
134
|
+
if (!axis.ok) {
|
|
135
|
+
if (axis.reason === 'empty')
|
|
136
|
+
return [];
|
|
137
|
+
throw new Error('filterStops: expected a bookended axis — [start, …stages, end] — got kinds ' +
|
|
138
|
+
`[${axis.kinds.join(', ')}]. Only a strategy can produce that shape; a run cannot.`);
|
|
139
|
+
}
|
|
140
|
+
const kept = [];
|
|
141
|
+
for (const stop of axis.stages) {
|
|
142
|
+
const decision = keep(stop);
|
|
143
|
+
if (!decision)
|
|
144
|
+
continue;
|
|
145
|
+
if (decision === true)
|
|
146
|
+
kept.push({ stop });
|
|
147
|
+
else
|
|
148
|
+
kept.push({ stop, label: decision.label, meta: decision.meta });
|
|
149
|
+
}
|
|
150
|
+
// The log's last index, taken from the `'end'` bookend rather than a length:
|
|
151
|
+
// `end` is the one stop guaranteed to fold the whole log, and this function
|
|
152
|
+
// is never handed the log itself.
|
|
153
|
+
const lastIdx = axis.end.lastCommitIdx;
|
|
154
|
+
const first = kept[0];
|
|
155
|
+
const out = [
|
|
156
|
+
{
|
|
157
|
+
...retyped(axis.start),
|
|
158
|
+
step: 0,
|
|
159
|
+
lastCommitIdx: first ? first.stop.commitIdx - 1 : lastIdx,
|
|
160
|
+
// Only when a STAGE was actually absorbed. An axis that kept everything
|
|
161
|
+
// has the same start it always had, and says nothing it need not say.
|
|
162
|
+
...(first && first.stop.step > 1 ? { prologue: true } : {}),
|
|
163
|
+
...(!first && axis.stages.length > 0 ? { prologue: true } : {}),
|
|
164
|
+
},
|
|
165
|
+
];
|
|
166
|
+
for (const [i, row] of kept.entries()) {
|
|
167
|
+
const next = kept[i + 1];
|
|
168
|
+
out.push({
|
|
169
|
+
...retyped(row.stop),
|
|
170
|
+
step: out.length,
|
|
171
|
+
lastCommitIdx: next ? next.stop.commitIdx - 1 : lastIdx,
|
|
172
|
+
...(row.label !== undefined ? { label: row.label } : {}),
|
|
173
|
+
...(row.meta !== undefined ? { meta: row.meta } : {}),
|
|
174
|
+
});
|
|
175
|
+
}
|
|
176
|
+
out.push({ ...retyped(axis.end), step: out.length });
|
|
177
|
+
return out;
|
|
178
|
+
}
|
|
179
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiYXhpcy5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uLy4uLy4uL3NyYy9saWIvdGltZS10cmF2ZWwvYXhpcy50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQTs7Ozs7Ozs7Ozs7Ozs7Ozs7R0FpQkc7QUFJSDs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7OztHQTRCRztBQUNILE1BQU0sVUFBVSxTQUFTLENBQWtCLEtBQTZCO0lBQ3RFLE1BQU0sS0FBSyxHQUF3QixNQUFNLENBQUMsTUFBTSxDQUFDLEtBQUssQ0FBQyxHQUFHLENBQUMsQ0FBQyxJQUFJLEVBQUUsRUFBRSxDQUFDLElBQUksQ0FBQyxJQUFJLENBQUMsQ0FBQyxDQUFDO0lBQ2pGLElBQUksS0FBSyxDQUFDLE1BQU0sS0FBSyxDQUFDO1FBQUUsT0FBTyxFQUFFLEVBQUUsRUFBRSxLQUFLLEVBQUUsTUFBTSxFQUFFLE9BQU8sRUFBRSxLQUFLLEVBQUUsQ0FBQztJQUNyRSxNQUFNLEtBQUssR0FBRyxLQUFLLENBQUMsQ0FBQyxDQUFDLENBQUM7SUFDdkIsTUFBTSxHQUFHLEdBQUcsS0FBSyxDQUFDLEtBQUssQ0FBQyxNQUFNLEdBQUcsQ0FBQyxDQUFDLENBQUM7SUFDcEMsSUFBSSxLQUFLLENBQUMsSUFBSSxLQUFLLE9BQU8sSUFBSSxHQUFHLENBQUMsSUFBSSxLQUFLLEtBQUssSUFBSSxLQUFLLENBQUMsTUFBTSxHQUFHLENBQUMsRUFBRSxDQUFDO1FBQ3JFLE9BQU8sRUFBRSxFQUFFLEVBQUUsS0FBSyxFQUFFLE1BQU0sRUFBRSxlQUFlLEVBQUUsS0FBSyxFQUFFLENBQUM7SUFDdkQsQ0FBQztJQUNELE9BQU8sRUFBRSxFQUFFLEVBQUUsSUFBSSxFQUFFLEtBQUssRUFBRSxNQUFNLEVBQUUsTUFBTSxDQUFDLE1BQU0sQ0FBQyxLQUFLLENBQUMsS0FBSyxDQUFDLENBQUMsRUFBRSxDQUFDLENBQUMsQ0FBQyxDQUFDLEVBQUUsR0FBRyxFQUFFLENBQUM7QUFDN0UsQ0FBQztBQWFEOzs7Ozs7R0FNRztBQUNILFNBQVMsT0FBTyxDQUFRLElBQW1CO0lBQ3pDLElBQUksSUFBSSxDQUFDLElBQUksS0FBSyxTQUFTO1FBQUUsT0FBTyxJQUFtQixDQUFDO0lBQ3hELE1BQU0sR0FBRyxHQUE0QixFQUFFLENBQUM7SUFDeEMsTUFBTSxNQUFNLEdBQUcsSUFBMEMsQ0FBQztJQUMxRCxLQUFLLE1BQU0sR0FBRyxJQUFJLE1BQU0sQ0FBQyxJQUFJLENBQUMsTUFBTSxDQUFDO1FBQUUsSUFBSSxHQUFHLEtBQUssTUFBTTtZQUFFLEdBQUcsQ0FBQyxHQUFHLENBQUMsR0FBRyxNQUFNLENBQUMsR0FBRyxDQUFDLENBQUM7SUFDbEYsT0FBTyxHQUE2QixDQUFDO0FBQ3ZDLENBQUM7QUFFRDs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7OztHQXVERztBQUNILE1BQU0sVUFBVSxXQUFXLENBQ3pCLEtBQStCLEVBQy9CLElBQWdEO0lBRWhELE1BQU0sSUFBSSxHQUFHLFNBQVMsQ0FBQyxLQUFLLENBQUMsQ0FBQztJQUM5QixJQUFJLENBQUMsSUFBSSxDQUFDLEVBQUUsRUFBRSxDQUFDO1FBQ2IsSUFBSSxJQUFJLENBQUMsTUFBTSxLQUFLLE9BQU87WUFBRSxPQUFPLEVBQUUsQ0FBQztRQUN2QyxNQUFNLElBQUksS0FBSyxDQUNiLDZFQUE2RTtZQUMzRSxJQUFJLElBQUksQ0FBQyxLQUFLLENBQUMsSUFBSSxDQUFDLElBQUksQ0FBQywwREFBMEQsQ0FDdEYsQ0FBQztJQUNKLENBQUM7SUFFRCxNQUFNLElBQUksR0FBNEQsRUFBRSxDQUFDO0lBQ3pFLEtBQUssTUFBTSxJQUFJLElBQUksSUFBSSxDQUFDLE1BQU0sRUFBRSxDQUFDO1FBQy9CLE1BQU0sUUFBUSxHQUFHLElBQUksQ0FBQyxJQUFJLENBQUMsQ0FBQztRQUM1QixJQUFJLENBQUMsUUFBUTtZQUFFLFNBQVM7UUFDeEIsSUFBSSxRQUFRLEtBQUssSUFBSTtZQUFFLElBQUksQ0FBQyxJQUFJLENBQUMsRUFBRSxJQUFJLEVBQUUsQ0FBQyxDQUFDOztZQUN0QyxJQUFJLENBQUMsSUFBSSxDQUFDLEVBQUUsSUFBSSxFQUFFLEtBQUssRUFBRSxRQUFRLENBQUMsS0FBSyxFQUFFLElBQUksRUFBRSxRQUFRLENBQUMsSUFBSSxFQUFFLENBQUMsQ0FBQztJQUN2RSxDQUFDO0lBRUQsNkVBQTZFO0lBQzdFLDRFQUE0RTtJQUM1RSxrQ0FBa0M7SUFDbEMsTUFBTSxPQUFPLEdBQUcsSUFBSSxDQUFDLEdBQUcsQ0FBQyxhQUFhLENBQUM7SUFDdkMsTUFBTSxLQUFLLEdBQUcsSUFBSSxDQUFDLENBQUMsQ0FBQyxDQUFDO0lBQ3RCLE1BQU0sR0FBRyxHQUFrQjtRQUN6QjtZQUNFLEdBQUcsT0FBTyxDQUFRLElBQUksQ0FBQyxLQUFLLENBQUM7WUFDN0IsSUFBSSxFQUFFLENBQUM7WUFDUCxhQUFhLEVBQUUsS0FBSyxDQUFDLENBQUMsQ0FBQyxLQUFLLENBQUMsSUFBSSxDQUFDLFNBQVMsR0FBRyxDQUFDLENBQUMsQ0FBQyxDQUFDLE9BQU87WUFDekQsd0VBQXdFO1lBQ3hFLHNFQUFzRTtZQUN0RSxHQUFHLENBQUMsS0FBSyxJQUFJLEtBQUssQ0FBQyxJQUFJLENBQUMsSUFBSSxHQUFHLENBQUMsQ0FBQyxDQUFDLENBQUMsRUFBRSxRQUFRLEVBQUUsSUFBYSxFQUFFLENBQUMsQ0FBQyxDQUFDLEVBQUUsQ0FBQztZQUNwRSxHQUFHLENBQUMsQ0FBQyxLQUFLLElBQUksSUFBSSxDQUFDLE1BQU0sQ0FBQyxNQUFNLEdBQUcsQ0FBQyxDQUFDLENBQUMsQ0FBQyxFQUFFLFFBQVEsRUFBRSxJQUFhLEVBQUUsQ0FBQyxDQUFDLENBQUMsRUFBRSxDQUFDO1NBQ3pFO0tBQ0YsQ0FBQztJQUVGLEtBQUssTUFBTSxDQUFDLENBQUMsRUFBRSxHQUFHLENBQUMsSUFBSSxJQUFJLENBQUMsT0FBTyxFQUFFLEVBQUUsQ0FBQztRQUN0QyxNQUFNLElBQUksR0FBRyxJQUFJLENBQUMsQ0FBQyxHQUFHLENBQUMsQ0FBQyxDQUFDO1FBQ3pCLEdBQUcsQ0FBQyxJQUFJLENBQUM7WUFDUCxHQUFHLE9BQU8sQ0FBUSxHQUFHLENBQUMsSUFBSSxDQUFDO1lBQzNCLElBQUksRUFBRSxHQUFHLENBQUMsTUFBTTtZQUNoQixhQUFhLEVBQUUsSUFBSSxDQUFDLENBQUMsQ0FBQyxJQUFJLENBQUMsSUFBSSxDQUFDLFNBQVMsR0FBRyxDQUFDLENBQUMsQ0FBQyxDQUFDLE9BQU87WUFDdkQsR0FBRyxDQUFDLEdBQUcsQ0FBQyxLQUFLLEtBQUssU0FBUyxDQUFDLENBQUMsQ0FBQyxFQUFFLEtBQUssRUFBRSxHQUFHLENBQUMsS0FBSyxFQUFFLENBQUMsQ0FBQyxDQUFDLEVBQUUsQ0FBQztZQUN4RCxHQUFHLENBQUMsR0FBRyxDQUFDLElBQUksS0FBSyxTQUFTLENBQUMsQ0FBQyxDQUFDLEVBQUUsSUFBSSxFQUFFLEdBQUcsQ0FBQyxJQUFJLEVBQUUsQ0FBQyxDQUFDLENBQUMsRUFBRSxDQUFDO1NBQ3RELENBQUMsQ0FBQztJQUNMLENBQUM7SUFFRCxHQUFHLENBQUMsSUFBSSxDQUFDLEVBQUUsR0FBRyxPQUFPLENBQVEsSUFBSSxDQUFDLEdBQUcsQ0FBQyxFQUFFLElBQUksRUFBRSxHQUFHLENBQUMsTUFBTSxFQUFFLENBQUMsQ0FBQztJQUM1RCxPQUFPLEdBQUcsQ0FBQztBQUNiLENBQUMiLCJzb3VyY2VzQ29udGVudCI6WyIvKipcbiAqIGF4aXMudHMg4oCUIHRoZSBzaGFwZSBvZiBhbiBheGlzLCBhbmQgdGhlIG9uZSBzYWZlIHdheSB0byBuYXJyb3cgaXQuXG4gKlxuICogV0hZIFRISVMgRklMRSBFWElTVFMuIGBjb21taXRTdG9wc2AgcmV0dXJucyBgU3RvcFtdYCwgYW5kIGV2ZXJ5IHN0cmF0ZWd5XG4gKiB0aGF0IENPTVBPU0VTIGl0IChrZWVwIHRoZSBzdGFnZXMgbXkgZG9tYWluIHJlY29nbmlzZXMsIGRyb3AgdGhlIHJlc3QpIHJlc3RzXG4gKiBvbiBhIHNoYXBlIHRoZSB0eXBlIG5ldmVyIHN0YXRlZDogYFtzdGFydCwg4oCmc3RhZ2VzLCBlbmRdYCBmb3IgYSBub24tZW1wdHlcbiAqIGxvZywgZW1wdHkgZm9yIGFuIGVtcHR5IG9uZS4gRWFjaCBjb21wb3NlciB0aGVyZWZvcmUgd3JvdGUgaXRzIG93biBydW50aW1lXG4gKiBndWFyZCBhZ2FpbnN0IGl0cyBvd24gcmVhZGluZyBvZiB0aGUgY29udHJhY3QsIHBsdXMgYSBtb2NrZWQtc3Vic3RyYXRlIHRlc3RcbiAqIHRvIHByb3ZlIHRoZSBndWFyZCBmaXJlcy4gVGhhdCBpcyBvbmUgaW52YXJpYW50LCBkaXNjb3ZlcmVkIHRocmVlIHRpbWVzLCBhbmRcbiAqIHRoZSBsaWJyYXJ5IHRoYXQgb3ducyB0aGUgaW52YXJpYW50IHNob3VsZCBiZSB0aGUgb25lIHRoYXQgc3RhdGVzIGl0LlxuICpcbiAqIHtAbGluayBzcGxpdEF4aXN9IHN0YXRlcyBpdC4ge0BsaW5rIGZpbHRlclN0b3BzfSB0aGVuIGRvZXMgdGhlIHdob2xlXG4gKiBjb21wb3NpdGlvbiDigJQgY2hlY2sgdGhlIHNoYXBlLCBrZWVwIHdoYXQgdGhlIGNhbGxlciBrZWVwcywgUkUtUEFSVElUSU9OIHRoZVxuICogbG9nIHNvIHRoZSBkcm9wcGVkIHN0YWdlcycgY29tbWl0cyBhcmUgc3RpbGwgZm9sZGVkIHNvbWV3aGVyZSwgYW5kIG1hcmsgdGhlXG4gKiBgJ3N0YXJ0J2AgYm9va2VuZCBhcyBjYXJyeWluZyBhIHByb2xvZ3VlIOKAlCBiZWNhdXNlIGdldHRpbmcgdGhlXG4gKiByZS1wYXJ0aXRpb25pbmcgcmlnaHQgYnkgaGFuZCBpcyB0aGUgb3RoZXIgdGhpbmcgZXZlcnkgY29tcG9zZXIgaGFkIHRvXG4gKiByZWRpc2NvdmVyLlxuICovXG5cbmltcG9ydCB0eXBlIHsgQXhpc1NwbGl0LCBTdG9wLCBTdG9wS2luZCB9IGZyb20gJy4vdHlwZXMuanMnO1xuXG4vKipcbiAqIFNwbGl0IGEgc3RyYXRlZ3kncyBzdG9wcyBpbnRvIGB7IHN0YXJ0LCBzdGFnZXMsIGVuZCB9YCwgb3Igc2F5IHdoeSBpdCBjYW5ub3QuXG4gKlxuICogVEhFIElOVkFSSUFOVCwgc3RhdGVkIG9uY2U6IGZvciBhIG5vbi1FTVBUWSBsb2csIGBjb21taXRTdG9wc2AgcmV0dXJucyBhdFxuICogbGVhc3QgdGhlIHR3byBib29rZW5kcywgYCdzdGFydCdgIGZpcnN0IGFuZCBgJ2VuZCdgIGxhc3QsIHdpdGggb25lIHN0b3AgcGVyXG4gKiBleGVjdXRlZCBzdGFnZSBpbiBleGVjdXRpb24gb3JkZXIgYmV0d2VlbiB0aGVtLiBGb3IgYW4gRU1QVFkgbG9nIGl0IHJldHVybnNcbiAqIGBbXWAg4oCUIG5vIGJvb2tlbmRzIGFyb3VuZCBub3RoaW5nLCBiZWNhdXNlIGEgY3Vyc29yIHdpdGggbm93aGVyZSB0byBzdGFuZFxuICogc2hvdWxkIHNheSBgcmVhc29uOiAnZW1wdHknYCByYXRoZXIgdGhhbiBvZmZlciB0d28gcG9zaXRpb25zIHRoYXQgYXJlIHRoZVxuICogc2FtZSBwb3NpdGlvbi5cbiAqXG4gKiBUaGUgdHdvIHJlZnVzYWxzIGFyZSBkaWZmZXJlbnQgZmFjdHMgYW5kIG11c3Qgbm90IGJlIGNvbGxhcHNlZDogYCdlbXB0eSdgXG4gKiBtZWFucyB0aGUgcnVuIGNvbW1pdHRlZCBub3RoaW5nLCBgJ25vdC1ib29rZW5kZWQnYCBtZWFucyBhIHN0cmF0ZWd5IGJyb2tlXG4gKiB0aGUgY29udHJhY3QuIEEgY29tcG9zZXIgdGhhdCB0cmVhdHMgdGhlbSBhbGlrZSByZXBvcnRzIGEgYnJva2VuIHN0cmF0ZWd5IGFzXG4gKiBhbiBlbXB0eSBydW4uXG4gKlxuICogQGV4YW1wbGVcbiAqIGBgYHR5cGVzY3JpcHRcbiAqIGltcG9ydCB7IGNvbW1pdFN0b3BzLCBzcGxpdEF4aXMgfSBmcm9tICdmb290cHJpbnRqcy90cmFjZSc7XG4gKlxuICogY29uc3QgYXhpcyA9IHNwbGl0QXhpcyhjb21taXRTdG9wcyhzbmFwc2hvdC5jb21taXRMb2csIHNuYXBzaG90LmV4ZWN1dGlvblRyZWUpKTtcbiAqIGlmIChheGlzLm9rKSB7XG4gKiAgIGF4aXMuc3RhcnQuY29tbWl0SWR4OyAgICAgICAgICAvLyAtMSDigJQgdGhlIGZvbGQgYmFzZVxuICogICBheGlzLnN0YWdlcy5tYXAoKHMpID0+IHMubGFiZWwpO1xuICogICBheGlzLmVuZC5sYXN0Q29tbWl0SWR4OyAgICAgICAgLy8gdGhlIGxhc3QgYnVuZGxlIGluIHRoZSBsb2dcbiAqIH0gZWxzZSB7XG4gKiAgIGF4aXMucmVhc29uOyAgICAgICAgICAgICAgICAgICAvLyAnZW1wdHknIHwgJ25vdC1ib29rZW5kZWQnXG4gKiB9XG4gKiBgYGBcbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIHNwbGl0QXhpczxUTWV0YSA9IHVua25vd24+KHN0b3BzOiByZWFkb25seSBTdG9wPFRNZXRhPltdKTogQXhpc1NwbGl0PFRNZXRhPiB7XG4gIGNvbnN0IGtpbmRzOiByZWFkb25seSBTdG9wS2luZFtdID0gT2JqZWN0LmZyZWV6ZShzdG9wcy5tYXAoKHN0b3ApID0+IHN0b3Aua2luZCkpO1xuICBpZiAoc3RvcHMubGVuZ3RoID09PSAwKSByZXR1cm4geyBvazogZmFsc2UsIHJlYXNvbjogJ2VtcHR5Jywga2luZHMgfTtcbiAgY29uc3Qgc3RhcnQgPSBzdG9wc1swXTtcbiAgY29uc3QgZW5kID0gc3RvcHNbc3RvcHMubGVuZ3RoIC0gMV07XG4gIGlmIChzdGFydC5raW5kICE9PSAnc3RhcnQnIHx8IGVuZC5raW5kICE9PSAnZW5kJyB8fCBzdG9wcy5sZW5ndGggPCAyKSB7XG4gICAgcmV0dXJuIHsgb2s6IGZhbHNlLCByZWFzb246ICdub3QtYm9va2VuZGVkJywga2luZHMgfTtcbiAgfVxuICByZXR1cm4geyBvazogdHJ1ZSwgc3RhcnQsIHN0YWdlczogT2JqZWN0LmZyZWV6ZShzdG9wcy5zbGljZSgxLCAtMSkpLCBlbmQgfTtcbn1cblxuLyoqXG4gKiBXaGF0IHtAbGluayBmaWx0ZXJTdG9wc30gZG9lcyB3aXRoIG9uZSBzdG9wOiBkcm9wIGl0LCBrZWVwIGl0LCBvciBrZWVwIGl0XG4gKiBjYXJyeWluZyB0aGUgc3RyYXRlZ3kncyBvd24gbGFiZWwgYW5kIHZvY2FidWxhcnkuXG4gKlxuICogYGZhbHNlYCAvIGBudWxsYCAvIGB1bmRlZmluZWRgIGRyb3AuIGB0cnVlYCBrZWVwcyBpdCB1bmNoYW5nZWQuIEFuIG9iamVjdFxuICoga2VlcHMgaXQgYW5kIG92ZXJyaWRlcyBgbGFiZWxgIGFuZC9vciBzZXRzIGBtZXRhYCDigJQgdGhlIHN0cmF0ZWd5J3Mgb3duXG4gKiBjbGFzc2lmaWNhdGlvbiwgdHJhdmVsbGluZyBPTiB0aGUgc3RvcCBpbnN0ZWFkIG9mIGJlaW5nIHJlLWRlcml2ZWQgZnJvbVxuICogYHJ1bnRpbWVTdGFnZUlkYCBhdCBldmVyeSByZWFkLlxuICovXG5leHBvcnQgdHlwZSBTdG9wRmlsdGVyPFRNZXRhPiA9IGJvb2xlYW4gfCBudWxsIHwgdW5kZWZpbmVkIHwgeyByZWFkb25seSBsYWJlbD86IHN0cmluZzsgcmVhZG9ubHkgbWV0YT86IFRNZXRhIH07XG5cbi8qKlxuICogYHN0b3BgIHJldHlwZWQgdG8gdGhlIE9VVFBVVCB2b2NhYnVsYXJ5LCB3aXRoIGFueSBtZXRhIGl0IGFscmVhZHkgY2FycmllZFxuICogbGVmdCBiZWhpbmQuIFRoZSBpbnB1dCBheGlzIG1heSBpdHNlbGYgYmUgYSBmaWx0ZXJlZCBvbmUgKGEgQmVhdCBheGlzIGJlaW5nXG4gKiBuYXJyb3dlZCBpbnRvIGEgVGFnIGF4aXMpLCBhbmQgYSBgdHJ1ZWAgZGVjaXNpb24ga2VlcHMgdGhlIFNUT1AsIG5vdCB0aGVcbiAqIG90aGVyIHN0cmF0ZWd5J3MgY2xhc3NpZmljYXRpb246IGEgYFN0b3A8VGFnPmAgd2hvc2UgYG1ldGFgIGlzIGEgQmVhdCB3b3VsZFxuICogYmUgYSBsaWUgdGhlIHR5cGUgY291bGQgbm90IGNhdGNoLiBPbmx5IHRoZSBkZWNpc2lvbiBtYXkgc3VwcGx5IGEgbWV0YS5cbiAqL1xuZnVuY3Rpb24gcmV0eXBlZDxUTWV0YT4oc3RvcDogU3RvcDx1bmtub3duPik6IFN0b3A8VE1ldGE+IHtcbiAgaWYgKHN0b3AubWV0YSA9PT0gdW5kZWZpbmVkKSByZXR1cm4gc3RvcCBhcyBTdG9wPFRNZXRhPjtcbiAgY29uc3Qgb3V0OiBSZWNvcmQ8c3RyaW5nLCB1bmtub3duPiA9IHt9O1xuICBjb25zdCBmaWVsZHMgPSBzdG9wIGFzIHVua25vd24gYXMgUmVjb3JkPHN0cmluZywgdW5rbm93bj47XG4gIGZvciAoY29uc3Qga2V5IG9mIE9iamVjdC5rZXlzKGZpZWxkcykpIGlmIChrZXkgIT09ICdtZXRhJykgb3V0W2tleV0gPSBmaWVsZHNba2V5XTtcbiAgcmV0dXJuIG91dCBhcyB1bmtub3duIGFzIFN0b3A8VE1ldGE+O1xufVxuXG4vKipcbiAqIEJ1aWxkIGEgRklMVEVSSU5HIHN0cmF0ZWd5J3MgYXhpcyBvdXQgb2YgYW5vdGhlciBzdHJhdGVneSdzIHN0b3BzLlxuICpcbiAqIFRoZSBydWxlLCBpbiBvbmUgc2VudGVuY2U6IGtlZXAgdGhlIHN0YWdlcyBga2VlcGAgcmVjb2duaXNlcywgYW5kIGdpdmUgdGhlXG4gKiBzdXJ2aXZvcnMgdGhlIGxvZyBiYWNrLlxuICpcbiAqICoqTWV0YSBpcyB0aGUgZGVjaXNpb24ncywgbmV2ZXIgaW5oZXJpdGVkLioqIFRoZSBpbnB1dCBtYXkgYWxyZWFkeSBiZSBhXG4gKiBmaWx0ZXJlZCBheGlzIGNhcnJ5aW5nIGFub3RoZXIgc3RyYXRlZ3kncyBgbWV0YWA7IGEgYHRydWVgIChvciBhIG1ldGEtbGVzc1xuICogb2JqZWN0KSBrZWVwcyB0aGUgc3RvcCBhbmQgRFJPUFMgdGhhdCBtZXRhLCBzbyBhIGBTdG9wPFRNZXRhPmAgbmV2ZXIgY2Fycmllc1xuICogYSB2YWx1ZSBvZiBzb21lIG90aGVyIHR5cGUgdW5kZXIgYFRNZXRhYC4gVGhlIGJvb2tlbmRzIGNhcnJ5IG5vbmUgZWl0aGVyLlxuICpcbiAqICoqVGhlIHN1cnZpdm9ycyByZS1wYXJ0aXRpb24gdGhlIGxvZy4qKiBBIGRyb3BwZWQgc3RhZ2UncyBjb21taXRzIHN0aWxsXG4gKiBoYXBwZW5lZCwgc28gdGhleSBmb2xkIGludG8gdGhlIHN0b3AgdGhhdCBQUkVDRURFUyB0aGVtOiBldmVyeSBrZXB0IHN0b3Anc1xuICogYGxhc3RDb21taXRJZHhgIHJ1bnMgdG8ganVzdCBiZWZvcmUgdGhlIG5leHQga2VwdCBzdG9wIGJlZ2lucy4gTm90aGluZyBpc1xuICogb3JwaGFuZWQgYW5kIGBzdGF0ZUF0KHN0b3ApYCBzdGF5cyBcInRoZSBzdGF0ZSB0aGF0IGV4aXN0ZWQgd2hlbiB0aGUgbmV4dFxuICoga2VwdCBzdGFnZSBzdGFydGVkXCIuXG4gKlxuICogKipXaGF0IHRoYXQgY29zdHMgYCdzdGFydCdgLCBzYWlkIG91dCBsb3VkLioqIFRoZSBjb21taXRzIGJlZm9yZSB0aGUgZmlyc3RcbiAqIHN1cnZpdm9yIGhhdmUgbm93aGVyZSBlbHNlIHRvIGdvLCBzbyBgJ3N0YXJ0J2AgYWJzb3JicyB0aGVtOiBpdHMgZm9sZCBpcyB0aGVcbiAqIHJ1bidzIGJhc2UgUExVUyB0aGF0IHByb2xvZ3VlIOKAlCB0aGUgc3RhdGUgdGhlIGZpcnN0IHN0b3AgUkVBRCwgbm90IHRoZSByYXdcbiAqIGJhc2UuIFRoZSByZXR1cm5lZCBzdGFydCB0aGVyZWZvcmUgY2FycmllcyBgcHJvbG9ndWU6IHRydWVgIHdoZW5ldmVyIGl0XG4gKiBhY3R1YWxseSBhYnNvcmJlZCBhIHN0YWdlLCBhbmQgYSByZW5kZXJlciB0aGF0IG1lYW5zIFwiYmVmb3JlIGFueXRoaW5nIHJhblwiXG4gKiBjaGVja3MgYGtpbmQgPT09ICdzdGFydCcgJiYgIXByb2xvZ3VlYC4gVGhhdCBmbGFnIGlzIHRoZSB3aG9sZSByZWFzb24gdGhpc1xuICogaGVscGVyIGV4aXN0cyByYXRoZXIgdGhhbiBhIGAuZmlsdGVyKClgIGNhbGwuXG4gKlxuICogKipBIGxvZyB3aXRoIG5vdGhpbmcga2VwdCoqIHlpZWxkcyB0aGUgdHdvIGJvb2tlbmRzIGFuZCBub3RoaW5nIGJldHdlZW5cbiAqIHRoZW06IGAnc3RhcnQnYCBmb2xkcyB0aGUgd2hvbGUgbG9nLCBgJ2VuZCdgIGZvbGRzIHRoZSB3aG9sZSBsb2csIGFuZFxuICogYGp1bXBUb2Agb24gYW55IHN0YWdlIGlkIHJlZnVzZXMgd2l0aCBgcmVhc29uOiAnbWlzcydgLiBBbiBheGlzIHdpdGggbm93aGVyZVxuICogbWVhbmluZ2Z1bCB0byBzdGFuZCDigJQgd2hpY2ggaXMgdGhlIHRydXRoZnVsIHNoYXBlIGZvciBhIHJ1biB0aGlzIHN0cmF0ZWd5XG4gKiByZWNvZ25pc2VzIG5vdGhpbmcgaW4sIGFuZCBOT1QgdGhlIGBbXWAgdGhhdCBzYXlzIHRoZSBsb2cgd2FzIGVtcHR5LlxuICpcbiAqIEB0aHJvd3Mgd2hlbiBgc3RvcHNgIGlzIG5vbi1lbXB0eSBidXQgbm90IGJvb2tlbmRlZCDigJQgc2VlIHtAbGluayBzcGxpdEF4aXN9LlxuICogICBPbmx5IGEgc3RyYXRlZ3kgY2FuIGNhdXNlIHRoYXQ7IGEgcnVuIGNhbm5vdCwgc28gaXQgaXMgYSBjb250cmFjdCBlcnJvclxuICogICBhbmQgbm90IGEgY29uZGl0aW9uIHRvIGZvbGQgaW50byB0aGUgZGF0YS5cbiAqXG4gKiBAZXhhbXBsZVxuICogYGBgdHlwZXNjcmlwdFxuICogaW1wb3J0IHsgY29tbWl0U3RvcHMsIGZpbHRlclN0b3BzIH0gZnJvbSAnZm9vdHByaW50anMvdHJhY2UnO1xuICogaW1wb3J0IHR5cGUgeyBTdG9wLCBUaW1lVHJhdmVsU3RyYXRlZ3kgfSBmcm9tICdmb290cHJpbnRqcy90cmFjZSc7XG4gKlxuICogdHlwZSBNaWxlc3RvbmUgPSB7IGtpbmQ6ICdsbG0tdHVybicgfCAndG9vbC1jYWxsJzsgbGFiZWw6IHN0cmluZyB9O1xuICogZGVjbGFyZSBmdW5jdGlvbiBtaWxlc3RvbmVGb3IocnVudGltZVN0YWdlSWQ6IHN0cmluZyk6IE1pbGVzdG9uZSB8IG51bGw7XG4gKlxuICogY29uc3QgbWlsZXN0b25lU3RvcHM6IFRpbWVUcmF2ZWxTdHJhdGVneTxNaWxlc3RvbmU+ID0ge1xuICogICBzdG9wc0ZvcjogKGxvZywgdHJlZSkgPT5cbiAqICAgICBmaWx0ZXJTdG9wczxNaWxlc3RvbmU+KGNvbW1pdFN0b3BzKGxvZywgdHJlZSksIChzdG9wKSA9PiB7XG4gKiAgICAgICBjb25zdCBtID0gbWlsZXN0b25lRm9yKHN0b3AucnVudGltZVN0YWdlSWQpO1xuICogICAgICAgcmV0dXJuIG0gPyB7IGxhYmVsOiBtLmxhYmVsLCBtZXRhOiBtIH0gOiBudWxsO1xuICogICAgIH0pLFxuICogfTtcbiAqXG4gKiBjb25zdCBjdXJzb3IgPSB0aW1lVHJhdmVsKHNuYXBzaG90LCB7IHN0cmF0ZWd5OiBtaWxlc3RvbmVTdG9wcyB9KTtcbiAqIGN1cnNvci5hdCgpPy5tZXRhPy5raW5kOyAgICAgICAgLy8gJ2xsbS10dXJuJyDigJQgbm8gcmUtZGVyaXZhdGlvblxuICogY3Vyc29yLnN0b3BzWzBdLnByb2xvZ3VlOyAgICAgICAvLyB0cnVlIHdoZW4gc3RhZ2VzIHJhbiBiZWZvcmUgdGhlIGZpcnN0IHR1cm5cbiAqIGBgYFxuICovXG5leHBvcnQgZnVuY3Rpb24gZmlsdGVyU3RvcHM8VE1ldGEgPSB1bmtub3duPihcbiAgc3RvcHM6IHJlYWRvbmx5IFN0b3A8dW5rbm93bj5bXSxcbiAga2VlcDogKHN0b3A6IFN0b3A8dW5rbm93bj4pID0+IFN0b3BGaWx0ZXI8VE1ldGE+LFxuKTogU3RvcDxUTWV0YT5bXSB7XG4gIGNvbnN0IGF4aXMgPSBzcGxpdEF4aXMoc3RvcHMpO1xuICBpZiAoIWF4aXMub2spIHtcbiAgICBpZiAoYXhpcy5yZWFzb24gPT09ICdlbXB0eScpIHJldHVybiBbXTtcbiAgICB0aHJvdyBuZXcgRXJyb3IoXG4gICAgICAnZmlsdGVyU3RvcHM6IGV4cGVjdGVkIGEgYm9va2VuZGVkIGF4aXMg4oCUIFtzdGFydCwg4oCmc3RhZ2VzLCBlbmRdIOKAlCBnb3Qga2luZHMgJyArXG4gICAgICAgIGBbJHtheGlzLmtpbmRzLmpvaW4oJywgJyl9XS4gT25seSBhIHN0cmF0ZWd5IGNhbiBwcm9kdWNlIHRoYXQgc2hhcGU7IGEgcnVuIGNhbm5vdC5gLFxuICAgICk7XG4gIH1cblxuICBjb25zdCBrZXB0OiB7IHN0b3A6IFN0b3A8dW5rbm93bj47IGxhYmVsPzogc3RyaW5nOyBtZXRhPzogVE1ldGEgfVtdID0gW107XG4gIGZvciAoY29uc3Qgc3RvcCBvZiBheGlzLnN0YWdlcykge1xuICAgIGNvbnN0IGRlY2lzaW9uID0ga2VlcChzdG9wKTtcbiAgICBpZiAoIWRlY2lzaW9uKSBjb250aW51ZTtcbiAgICBpZiAoZGVjaXNpb24gPT09IHRydWUpIGtlcHQucHVzaCh7IHN0b3AgfSk7XG4gICAgZWxzZSBrZXB0LnB1c2goeyBzdG9wLCBsYWJlbDogZGVjaXNpb24ubGFiZWwsIG1ldGE6IGRlY2lzaW9uLm1ldGEgfSk7XG4gIH1cblxuICAvLyBUaGUgbG9nJ3MgbGFzdCBpbmRleCwgdGFrZW4gZnJvbSB0aGUgYCdlbmQnYCBib29rZW5kIHJhdGhlciB0aGFuIGEgbGVuZ3RoOlxuICAvLyBgZW5kYCBpcyB0aGUgb25lIHN0b3AgZ3VhcmFudGVlZCB0byBmb2xkIHRoZSB3aG9sZSBsb2csIGFuZCB0aGlzIGZ1bmN0aW9uXG4gIC8vIGlzIG5ldmVyIGhhbmRlZCB0aGUgbG9nIGl0c2VsZi5cbiAgY29uc3QgbGFzdElkeCA9IGF4aXMuZW5kLmxhc3RDb21taXRJZHg7XG4gIGNvbnN0IGZpcnN0ID0ga2VwdFswXTtcbiAgY29uc3Qgb3V0OiBTdG9wPFRNZXRhPltdID0gW1xuICAgIHtcbiAgICAgIC4uLnJldHlwZWQ8VE1ldGE+KGF4aXMuc3RhcnQpLFxuICAgICAgc3RlcDogMCxcbiAgICAgIGxhc3RDb21taXRJZHg6IGZpcnN0ID8gZmlyc3Quc3RvcC5jb21taXRJZHggLSAxIDogbGFzdElkeCxcbiAgICAgIC8vIE9ubHkgd2hlbiBhIFNUQUdFIHdhcyBhY3R1YWxseSBhYnNvcmJlZC4gQW4gYXhpcyB0aGF0IGtlcHQgZXZlcnl0aGluZ1xuICAgICAgLy8gaGFzIHRoZSBzYW1lIHN0YXJ0IGl0IGFsd2F5cyBoYWQsIGFuZCBzYXlzIG5vdGhpbmcgaXQgbmVlZCBub3Qgc2F5LlxuICAgICAgLi4uKGZpcnN0ICYmIGZpcnN0LnN0b3Auc3RlcCA+IDEgPyB7IHByb2xvZ3VlOiB0cnVlIGFzIGNvbnN0IH0gOiB7fSksXG4gICAgICAuLi4oIWZpcnN0ICYmIGF4aXMuc3RhZ2VzLmxlbmd0aCA+IDAgPyB7IHByb2xvZ3VlOiB0cnVlIGFzIGNvbnN0IH0gOiB7fSksXG4gICAgfSxcbiAgXTtcblxuICBmb3IgKGNvbnN0IFtpLCByb3ddIG9mIGtlcHQuZW50cmllcygpKSB7XG4gICAgY29uc3QgbmV4dCA9IGtlcHRbaSArIDFdO1xuICAgIG91dC5wdXNoKHtcbiAgICAgIC4uLnJldHlwZWQ8VE1ldGE+KHJvdy5zdG9wKSxcbiAgICAgIHN0ZXA6IG91dC5sZW5ndGgsXG4gICAgICBsYXN0Q29tbWl0SWR4OiBuZXh0ID8gbmV4dC5zdG9wLmNvbW1pdElkeCAtIDEgOiBsYXN0SWR4LFxuICAgICAgLi4uKHJvdy5sYWJlbCAhPT0gdW5kZWZpbmVkID8geyBsYWJlbDogcm93LmxhYmVsIH0gOiB7fSksXG4gICAgICAuLi4ocm93Lm1ldGEgIT09IHVuZGVmaW5lZCA/IHsgbWV0YTogcm93Lm1ldGEgfSA6IHt9KSxcbiAgICB9KTtcbiAgfVxuXG4gIG91dC5wdXNoKHsgLi4ucmV0eXBlZDxUTWV0YT4oYXhpcy5lbmQpLCBzdGVwOiBvdXQubGVuZ3RoIH0pO1xuICByZXR1cm4gb3V0O1xufVxuIl19
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* bundles.ts — reading a log whose rows are not typed yet.
|
|
3
|
+
*
|
|
4
|
+
* WHY THIS FILE EXISTS. A LIVE snapshot hands time travel real
|
|
5
|
+
* {@link CommitBundle}s. A STORED recording hands it parsed JSON, and a
|
|
6
|
+
* careful consumer will not claim that what came back off disk is a
|
|
7
|
+
* `CommitBundle` — so it either casts (a lie with a comment on it) or cannot
|
|
8
|
+
* call the library at all. Since 9.18.0 the source takes `readonly unknown[]`
|
|
9
|
+
* and the narrowing happens here, per row, at the one place that actually
|
|
10
|
+
* reads one.
|
|
11
|
+
*
|
|
12
|
+
* A row that is not a bundle becomes a GAP rather than an exception: it KEEPS
|
|
13
|
+
* ITS INDEX — so every `commitIdx` in the axis still addresses the same
|
|
14
|
+
* position — contributes no state, gets no stop, and is reported with its
|
|
15
|
+
* index and a reason. One corrupt row in a stored recording loses that row,
|
|
16
|
+
* not the recording.
|
|
17
|
+
*/
|
|
18
|
+
import type { CommitBundle, StageSnapshot } from '../memory/types.js';
|
|
19
|
+
import type { LogGap } from './types.js';
|
|
20
|
+
/** A plain object — not null, not an array, not a primitive. */
|
|
21
|
+
export declare function isPlainish(value: unknown): value is Record<string, unknown>;
|
|
22
|
+
/**
|
|
23
|
+
* Read a source's `executionTree` as a {@link StageSnapshot}, or as absent.
|
|
24
|
+
*
|
|
25
|
+
* The tree is only ever WALKED for mount ids (`subflowId` + `runtimeStageId`
|
|
26
|
+
* on each node), so the honest narrowing a reader can do is "a plain object";
|
|
27
|
+
* anything else — a stored field that came back as a string, a number, an
|
|
28
|
+
* array — is read as no tree at all, which sends `commitStops` down the same
|
|
29
|
+
* shape-heuristic path a log handed over without its tree already takes.
|
|
30
|
+
*/
|
|
31
|
+
export declare function readTree(value: unknown): StageSnapshot | undefined;
|
|
32
|
+
/**
|
|
33
|
+
* Why `row` cannot be read as a commit bundle, or `undefined` when it can.
|
|
34
|
+
*
|
|
35
|
+
* DELIBERATELY LENIENT — but exactly as lenient as the readers are. It checks
|
|
36
|
+
* what the fold, the axis and `changedSince` actually touch:
|
|
37
|
+
*
|
|
38
|
+
* - `runtimeStageId` must be a string — the axis keys on it.
|
|
39
|
+
* - `trace` must be an ARRAY, and it must be PRESENT. It is the one field
|
|
40
|
+
* every reader WALKS (`applySmartMerge` iterates it verb by verb;
|
|
41
|
+
* `changedSince` reads its paths), so a row without one is not a bundle the
|
|
42
|
+
* fold can survive — it is the crash a gap exists to prevent. `CommitBundle`
|
|
43
|
+
* declares it required, so no live log is affected.
|
|
44
|
+
* - `updates` / `overwrite`, when present, must be plain objects. They are
|
|
45
|
+
* read only AT THE PATHS THE TRACE NAMES, so an absent one is harmless and
|
|
46
|
+
* is tolerated, exactly as the fold tolerates an absent `redactedPaths`.
|
|
47
|
+
*
|
|
48
|
+
* Being stricter than that — demanding `stage`, `stageId`, `idx` — would
|
|
49
|
+
* reject hand-built logs that have folded correctly since 9.17.0, which is a
|
|
50
|
+
* compatibility break dressed up as validation.
|
|
51
|
+
*/
|
|
52
|
+
export declare function bundleRefusal(row: unknown): string | undefined;
|
|
53
|
+
/** `true` when `row` can be read as a {@link CommitBundle}. */
|
|
54
|
+
export declare function isCommitBundle(row: unknown): row is CommitBundle;
|
|
55
|
+
/** A log read row by row: the bundles, index-aligned, plus what was refused. */
|
|
56
|
+
export interface ReadLog {
|
|
57
|
+
/** Same length and order as the input; a refused row holds a GAP stand-in. */
|
|
58
|
+
readonly log: readonly CommitBundle[];
|
|
59
|
+
/** The refused rows, in index order. Empty when the log was clean. */
|
|
60
|
+
readonly gaps: readonly LogGap[];
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Read an unvalidated array as a commit log.
|
|
64
|
+
*
|
|
65
|
+
* The clean case (a live snapshot, or a stored recording that round-tripped
|
|
66
|
+
* intact) returns the SAME array reference and no gaps: nothing is copied,
|
|
67
|
+
* nothing is allocated, and the fold behaves byte for byte as it did in
|
|
68
|
+
* 9.17.0. Only a log with a refused row is rebuilt, and only to keep the
|
|
69
|
+
* indices aligned.
|
|
70
|
+
*
|
|
71
|
+
* @example
|
|
72
|
+
* ```typescript
|
|
73
|
+
* const { log, gaps } = readLog(JSON.parse(stored).commitLog);
|
|
74
|
+
* gaps; // [{ index: 3, reason: 'not an object (null)' }]
|
|
75
|
+
* ```
|
|
76
|
+
*/
|
|
77
|
+
export declare function readLog(raw: readonly unknown[] | undefined): ReadLog;
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* bundles.ts — reading a log whose rows are not typed yet.
|
|
3
|
+
*
|
|
4
|
+
* WHY THIS FILE EXISTS. A LIVE snapshot hands time travel real
|
|
5
|
+
* {@link CommitBundle}s. A STORED recording hands it parsed JSON, and a
|
|
6
|
+
* careful consumer will not claim that what came back off disk is a
|
|
7
|
+
* `CommitBundle` — so it either casts (a lie with a comment on it) or cannot
|
|
8
|
+
* call the library at all. Since 9.18.0 the source takes `readonly unknown[]`
|
|
9
|
+
* and the narrowing happens here, per row, at the one place that actually
|
|
10
|
+
* reads one.
|
|
11
|
+
*
|
|
12
|
+
* A row that is not a bundle becomes a GAP rather than an exception: it KEEPS
|
|
13
|
+
* ITS INDEX — so every `commitIdx` in the axis still addresses the same
|
|
14
|
+
* position — contributes no state, gets no stop, and is reported with its
|
|
15
|
+
* index and a reason. One corrupt row in a stored recording loses that row,
|
|
16
|
+
* not the recording.
|
|
17
|
+
*/
|
|
18
|
+
/**
|
|
19
|
+
* The stand-in a gap leaves at its index: a commit that belongs to no stage
|
|
20
|
+
* and carries nothing.
|
|
21
|
+
*
|
|
22
|
+
* It is NOT invented evidence — every field is the empty value, and an id-less
|
|
23
|
+
* bundle is a shape the log already has (a root-context commit made before any
|
|
24
|
+
* stage ran). `commitStops` gives it no stop for exactly that reason, the fold
|
|
25
|
+
* folds nothing from it, and `changedSince` reads no keys off it. What it does
|
|
26
|
+
* is hold the index, so the arithmetic downstream never has to know.
|
|
27
|
+
*/
|
|
28
|
+
const GAP_BUNDLE = Object.freeze({
|
|
29
|
+
stage: '',
|
|
30
|
+
stageId: '',
|
|
31
|
+
runtimeStageId: '',
|
|
32
|
+
trace: Object.freeze([]),
|
|
33
|
+
redactedPaths: Object.freeze([]),
|
|
34
|
+
overwrite: Object.freeze({}),
|
|
35
|
+
updates: Object.freeze({}),
|
|
36
|
+
});
|
|
37
|
+
/** A plain object — not null, not an array, not a primitive. */
|
|
38
|
+
export function isPlainish(value) {
|
|
39
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Read a source's `executionTree` as a {@link StageSnapshot}, or as absent.
|
|
43
|
+
*
|
|
44
|
+
* The tree is only ever WALKED for mount ids (`subflowId` + `runtimeStageId`
|
|
45
|
+
* on each node), so the honest narrowing a reader can do is "a plain object";
|
|
46
|
+
* anything else — a stored field that came back as a string, a number, an
|
|
47
|
+
* array — is read as no tree at all, which sends `commitStops` down the same
|
|
48
|
+
* shape-heuristic path a log handed over without its tree already takes.
|
|
49
|
+
*/
|
|
50
|
+
export function readTree(value) {
|
|
51
|
+
return isPlainish(value) ? value : undefined;
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Why `row` cannot be read as a commit bundle, or `undefined` when it can.
|
|
55
|
+
*
|
|
56
|
+
* DELIBERATELY LENIENT — but exactly as lenient as the readers are. It checks
|
|
57
|
+
* what the fold, the axis and `changedSince` actually touch:
|
|
58
|
+
*
|
|
59
|
+
* - `runtimeStageId` must be a string — the axis keys on it.
|
|
60
|
+
* - `trace` must be an ARRAY, and it must be PRESENT. It is the one field
|
|
61
|
+
* every reader WALKS (`applySmartMerge` iterates it verb by verb;
|
|
62
|
+
* `changedSince` reads its paths), so a row without one is not a bundle the
|
|
63
|
+
* fold can survive — it is the crash a gap exists to prevent. `CommitBundle`
|
|
64
|
+
* declares it required, so no live log is affected.
|
|
65
|
+
* - `updates` / `overwrite`, when present, must be plain objects. They are
|
|
66
|
+
* read only AT THE PATHS THE TRACE NAMES, so an absent one is harmless and
|
|
67
|
+
* is tolerated, exactly as the fold tolerates an absent `redactedPaths`.
|
|
68
|
+
*
|
|
69
|
+
* Being stricter than that — demanding `stage`, `stageId`, `idx` — would
|
|
70
|
+
* reject hand-built logs that have folded correctly since 9.17.0, which is a
|
|
71
|
+
* compatibility break dressed up as validation.
|
|
72
|
+
*/
|
|
73
|
+
export function bundleRefusal(row) {
|
|
74
|
+
if (!isPlainish(row)) {
|
|
75
|
+
return `not an object (${row === null ? 'null' : Array.isArray(row) ? 'array' : typeof row})`;
|
|
76
|
+
}
|
|
77
|
+
if (typeof row.runtimeStageId !== 'string') {
|
|
78
|
+
return `runtimeStageId is ${row.runtimeStageId === undefined ? 'missing' : `a ${typeof row.runtimeStageId}`}, not a string`;
|
|
79
|
+
}
|
|
80
|
+
if (!Array.isArray(row.trace)) {
|
|
81
|
+
return `trace is ${row.trace === undefined ? 'missing' : `a ${typeof row.trace}`}, not an array`;
|
|
82
|
+
}
|
|
83
|
+
if (row.updates !== undefined && !isPlainish(row.updates))
|
|
84
|
+
return 'updates is not a plain object';
|
|
85
|
+
if (row.overwrite !== undefined && !isPlainish(row.overwrite))
|
|
86
|
+
return 'overwrite is not a plain object';
|
|
87
|
+
return undefined;
|
|
88
|
+
}
|
|
89
|
+
/** `true` when `row` can be read as a {@link CommitBundle}. */
|
|
90
|
+
export function isCommitBundle(row) {
|
|
91
|
+
return bundleRefusal(row) === undefined;
|
|
92
|
+
}
|
|
93
|
+
/** Nothing to read — shared, so the clean path allocates nothing extra. */
|
|
94
|
+
const NO_GAPS = Object.freeze([]);
|
|
95
|
+
/**
|
|
96
|
+
* Read an unvalidated array as a commit log.
|
|
97
|
+
*
|
|
98
|
+
* The clean case (a live snapshot, or a stored recording that round-tripped
|
|
99
|
+
* intact) returns the SAME array reference and no gaps: nothing is copied,
|
|
100
|
+
* nothing is allocated, and the fold behaves byte for byte as it did in
|
|
101
|
+
* 9.17.0. Only a log with a refused row is rebuilt, and only to keep the
|
|
102
|
+
* indices aligned.
|
|
103
|
+
*
|
|
104
|
+
* @example
|
|
105
|
+
* ```typescript
|
|
106
|
+
* const { log, gaps } = readLog(JSON.parse(stored).commitLog);
|
|
107
|
+
* gaps; // [{ index: 3, reason: 'not an object (null)' }]
|
|
108
|
+
* ```
|
|
109
|
+
*/
|
|
110
|
+
export function readLog(raw) {
|
|
111
|
+
if (!raw || raw.length === 0)
|
|
112
|
+
return { log: [], gaps: NO_GAPS };
|
|
113
|
+
let gaps;
|
|
114
|
+
for (let i = 0; i < raw.length; i++) {
|
|
115
|
+
const reason = bundleRefusal(raw[i]);
|
|
116
|
+
if (reason !== undefined)
|
|
117
|
+
(gaps ??= []).push({ index: i, reason });
|
|
118
|
+
}
|
|
119
|
+
if (!gaps)
|
|
120
|
+
return { log: raw, gaps: NO_GAPS };
|
|
121
|
+
const log = raw.map((row) => (isCommitBundle(row) ? row : GAP_BUNDLE));
|
|
122
|
+
return { log, gaps: Object.freeze(gaps) };
|
|
123
|
+
}
|
|
124
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiYnVuZGxlcy5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uLy4uLy4uL3NyYy9saWIvdGltZS10cmF2ZWwvYnVuZGxlcy50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQTs7Ozs7Ozs7Ozs7Ozs7OztHQWdCRztBQUtIOzs7Ozs7Ozs7R0FTRztBQUNILE1BQU0sVUFBVSxHQUFpQixNQUFNLENBQUMsTUFBTSxDQUFDO0lBQzdDLEtBQUssRUFBRSxFQUFFO0lBQ1QsT0FBTyxFQUFFLEVBQUU7SUFDWCxjQUFjLEVBQUUsRUFBRTtJQUNsQixLQUFLLEVBQUUsTUFBTSxDQUFDLE1BQU0sQ0FBQyxFQUFFLENBQXFDO0lBQzVELGFBQWEsRUFBRSxNQUFNLENBQUMsTUFBTSxDQUFDLEVBQUUsQ0FBd0I7SUFDdkQsU0FBUyxFQUFFLE1BQU0sQ0FBQyxNQUFNLENBQUMsRUFBRSxDQUFDO0lBQzVCLE9BQU8sRUFBRSxNQUFNLENBQUMsTUFBTSxDQUFDLEVBQUUsQ0FBQztDQUMzQixDQUFDLENBQUM7QUFFSCxnRUFBZ0U7QUFDaEUsTUFBTSxVQUFVLFVBQVUsQ0FBQyxLQUFjO0lBQ3ZDLE9BQU8sT0FBTyxLQUFLLEtBQUssUUFBUSxJQUFJLEtBQUssS0FBSyxJQUFJLElBQUksQ0FBQyxLQUFLLENBQUMsT0FBTyxDQUFDLEtBQUssQ0FBQyxDQUFDO0FBQzlFLENBQUM7QUFFRDs7Ozs7Ozs7R0FRRztBQUNILE1BQU0sVUFBVSxRQUFRLENBQUMsS0FBYztJQUNyQyxPQUFPLFVBQVUsQ0FBQyxLQUFLLENBQUMsQ0FBQyxDQUFDLENBQUUsS0FBa0MsQ0FBQyxDQUFDLENBQUMsU0FBUyxDQUFDO0FBQzdFLENBQUM7QUFFRDs7Ozs7Ozs7Ozs7Ozs7Ozs7OztHQW1CRztBQUNILE1BQU0sVUFBVSxhQUFhLENBQUMsR0FBWTtJQUN4QyxJQUFJLENBQUMsVUFBVSxDQUFDLEdBQUcsQ0FBQyxFQUFFLENBQUM7UUFDckIsT0FBTyxrQkFBa0IsR0FBRyxLQUFLLElBQUksQ0FBQyxDQUFDLENBQUMsTUFBTSxDQUFDLENBQUMsQ0FBQyxLQUFLLENBQUMsT0FBTyxDQUFDLEdBQUcsQ0FBQyxDQUFDLENBQUMsQ0FBQyxPQUFPLENBQUMsQ0FBQyxDQUFDLE9BQU8sR0FBRyxHQUFHLENBQUM7SUFDaEcsQ0FBQztJQUNELElBQUksT0FBTyxHQUFHLENBQUMsY0FBYyxLQUFLLFFBQVEsRUFBRSxDQUFDO1FBQzNDLE9BQU8scUJBQ0wsR0FBRyxDQUFDLGNBQWMsS0FBSyxTQUFTLENBQUMsQ0FBQyxDQUFDLFNBQVMsQ0FBQyxDQUFDLENBQUMsS0FBSyxPQUFPLEdBQUcsQ0FBQyxjQUFjLEVBQy9FLGdCQUFnQixDQUFDO0lBQ25CLENBQUM7SUFDRCxJQUFJLENBQUMsS0FBSyxDQUFDLE9BQU8sQ0FBQyxHQUFHLENBQUMsS0FBSyxDQUFDLEVBQUUsQ0FBQztRQUM5QixPQUFPLFlBQVksR0FBRyxDQUFDLEtBQUssS0FBSyxTQUFTLENBQUMsQ0FBQyxDQUFDLFNBQVMsQ0FBQyxDQUFDLENBQUMsS0FBSyxPQUFPLEdBQUcsQ0FBQyxLQUFLLEVBQUUsZ0JBQWdCLENBQUM7SUFDbkcsQ0FBQztJQUNELElBQUksR0FBRyxDQUFDLE9BQU8sS0FBSyxTQUFTLElBQUksQ0FBQyxVQUFVLENBQUMsR0FBRyxDQUFDLE9BQU8sQ0FBQztRQUFFLE9BQU8sK0JBQStCLENBQUM7SUFDbEcsSUFBSSxHQUFHLENBQUMsU0FBUyxLQUFLLFNBQVMsSUFBSSxDQUFDLFVBQVUsQ0FBQyxHQUFHLENBQUMsU0FBUyxDQUFDO1FBQUUsT0FBTyxpQ0FBaUMsQ0FBQztJQUN4RyxPQUFPLFNBQVMsQ0FBQztBQUNuQixDQUFDO0FBRUQsK0RBQStEO0FBQy9ELE1BQU0sVUFBVSxjQUFjLENBQUMsR0FBWTtJQUN6QyxPQUFPLGFBQWEsQ0FBQyxHQUFHLENBQUMsS0FBSyxTQUFTLENBQUM7QUFDMUMsQ0FBQztBQVVELDJFQUEyRTtBQUMzRSxNQUFNLE9BQU8sR0FBc0IsTUFBTSxDQUFDLE1BQU0sQ0FBQyxFQUFFLENBQUMsQ0FBQztBQUVyRDs7Ozs7Ozs7Ozs7Ozs7R0FjRztBQUNILE1BQU0sVUFBVSxPQUFPLENBQUMsR0FBbUM7SUFDekQsSUFBSSxDQUFDLEdBQUcsSUFBSSxHQUFHLENBQUMsTUFBTSxLQUFLLENBQUM7UUFBRSxPQUFPLEVBQUUsR0FBRyxFQUFFLEVBQUUsRUFBRSxJQUFJLEVBQUUsT0FBTyxFQUFFLENBQUM7SUFFaEUsSUFBSSxJQUEwQixDQUFDO0lBQy9CLEtBQUssSUFBSSxDQUFDLEdBQUcsQ0FBQyxFQUFFLENBQUMsR0FBRyxHQUFHLENBQUMsTUFBTSxFQUFFLENBQUMsRUFBRSxFQUFFLENBQUM7UUFDcEMsTUFBTSxNQUFNLEdBQUcsYUFBYSxDQUFDLEdBQUcsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDO1FBQ3JDLElBQUksTUFBTSxLQUFLLFNBQVM7WUFBRSxDQUFDLElBQUksS0FBSyxFQUFFLENBQUMsQ0FBQyxJQUFJLENBQUMsRUFBRSxLQUFLLEVBQUUsQ0FBQyxFQUFFLE1BQU0sRUFBRSxDQUFDLENBQUM7SUFDckUsQ0FBQztJQUNELElBQUksQ0FBQyxJQUFJO1FBQUUsT0FBTyxFQUFFLEdBQUcsRUFBRSxHQUE4QixFQUFFLElBQUksRUFBRSxPQUFPLEVBQUUsQ0FBQztJQUV6RSxNQUFNLEdBQUcsR0FBRyxHQUFHLENBQUMsR0FBRyxDQUFDLENBQUMsR0FBRyxFQUFFLEVBQUUsQ0FBQyxDQUFDLGNBQWMsQ0FBQyxHQUFHLENBQUMsQ0FBQyxDQUFDLENBQUMsR0FBRyxDQUFDLENBQUMsQ0FBQyxVQUFVLENBQUMsQ0FBQyxDQUFDO0lBQ3ZFLE9BQU8sRUFBRSxHQUFHLEVBQUUsSUFBSSxFQUFFLE1BQU0sQ0FBQyxNQUFNLENBQUMsSUFBSSxDQUFDLEVBQUUsQ0FBQztBQUM1QyxDQUFDIiwic291cmNlc0NvbnRlbnQiOlsiLyoqXG4gKiBidW5kbGVzLnRzIOKAlCByZWFkaW5nIGEgbG9nIHdob3NlIHJvd3MgYXJlIG5vdCB0eXBlZCB5ZXQuXG4gKlxuICogV0hZIFRISVMgRklMRSBFWElTVFMuIEEgTElWRSBzbmFwc2hvdCBoYW5kcyB0aW1lIHRyYXZlbCByZWFsXG4gKiB7QGxpbmsgQ29tbWl0QnVuZGxlfXMuIEEgU1RPUkVEIHJlY29yZGluZyBoYW5kcyBpdCBwYXJzZWQgSlNPTiwgYW5kIGFcbiAqIGNhcmVmdWwgY29uc3VtZXIgd2lsbCBub3QgY2xhaW0gdGhhdCB3aGF0IGNhbWUgYmFjayBvZmYgZGlzayBpcyBhXG4gKiBgQ29tbWl0QnVuZGxlYCDigJQgc28gaXQgZWl0aGVyIGNhc3RzIChhIGxpZSB3aXRoIGEgY29tbWVudCBvbiBpdCkgb3IgY2Fubm90XG4gKiBjYWxsIHRoZSBsaWJyYXJ5IGF0IGFsbC4gU2luY2UgOS4xOC4wIHRoZSBzb3VyY2UgdGFrZXMgYHJlYWRvbmx5IHVua25vd25bXWBcbiAqIGFuZCB0aGUgbmFycm93aW5nIGhhcHBlbnMgaGVyZSwgcGVyIHJvdywgYXQgdGhlIG9uZSBwbGFjZSB0aGF0IGFjdHVhbGx5XG4gKiByZWFkcyBvbmUuXG4gKlxuICogQSByb3cgdGhhdCBpcyBub3QgYSBidW5kbGUgYmVjb21lcyBhIEdBUCByYXRoZXIgdGhhbiBhbiBleGNlcHRpb246IGl0IEtFRVBTXG4gKiBJVFMgSU5ERVgg4oCUIHNvIGV2ZXJ5IGBjb21taXRJZHhgIGluIHRoZSBheGlzIHN0aWxsIGFkZHJlc3NlcyB0aGUgc2FtZVxuICogcG9zaXRpb24g4oCUIGNvbnRyaWJ1dGVzIG5vIHN0YXRlLCBnZXRzIG5vIHN0b3AsIGFuZCBpcyByZXBvcnRlZCB3aXRoIGl0c1xuICogaW5kZXggYW5kIGEgcmVhc29uLiBPbmUgY29ycnVwdCByb3cgaW4gYSBzdG9yZWQgcmVjb3JkaW5nIGxvc2VzIHRoYXQgcm93LFxuICogbm90IHRoZSByZWNvcmRpbmcuXG4gKi9cblxuaW1wb3J0IHR5cGUgeyBDb21taXRCdW5kbGUsIFN0YWdlU25hcHNob3QgfSBmcm9tICcuLi9tZW1vcnkvdHlwZXMuanMnO1xuaW1wb3J0IHR5cGUgeyBMb2dHYXAgfSBmcm9tICcuL3R5cGVzLmpzJztcblxuLyoqXG4gKiBUaGUgc3RhbmQtaW4gYSBnYXAgbGVhdmVzIGF0IGl0cyBpbmRleDogYSBjb21taXQgdGhhdCBiZWxvbmdzIHRvIG5vIHN0YWdlXG4gKiBhbmQgY2FycmllcyBub3RoaW5nLlxuICpcbiAqIEl0IGlzIE5PVCBpbnZlbnRlZCBldmlkZW5jZSDigJQgZXZlcnkgZmllbGQgaXMgdGhlIGVtcHR5IHZhbHVlLCBhbmQgYW4gaWQtbGVzc1xuICogYnVuZGxlIGlzIGEgc2hhcGUgdGhlIGxvZyBhbHJlYWR5IGhhcyAoYSByb290LWNvbnRleHQgY29tbWl0IG1hZGUgYmVmb3JlIGFueVxuICogc3RhZ2UgcmFuKS4gYGNvbW1pdFN0b3BzYCBnaXZlcyBpdCBubyBzdG9wIGZvciBleGFjdGx5IHRoYXQgcmVhc29uLCB0aGUgZm9sZFxuICogZm9sZHMgbm90aGluZyBmcm9tIGl0LCBhbmQgYGNoYW5nZWRTaW5jZWAgcmVhZHMgbm8ga2V5cyBvZmYgaXQuIFdoYXQgaXQgZG9lc1xuICogaXMgaG9sZCB0aGUgaW5kZXgsIHNvIHRoZSBhcml0aG1ldGljIGRvd25zdHJlYW0gbmV2ZXIgaGFzIHRvIGtub3cuXG4gKi9cbmNvbnN0IEdBUF9CVU5ETEU6IENvbW1pdEJ1bmRsZSA9IE9iamVjdC5mcmVlemUoe1xuICBzdGFnZTogJycsXG4gIHN0YWdlSWQ6ICcnLFxuICBydW50aW1lU3RhZ2VJZDogJycsXG4gIHRyYWNlOiBPYmplY3QuZnJlZXplKFtdKSBhcyB1bmtub3duIGFzIENvbW1pdEJ1bmRsZVsndHJhY2UnXSxcbiAgcmVkYWN0ZWRQYXRoczogT2JqZWN0LmZyZWV6ZShbXSkgYXMgdW5rbm93biBhcyBzdHJpbmdbXSxcbiAgb3ZlcndyaXRlOiBPYmplY3QuZnJlZXplKHt9KSxcbiAgdXBkYXRlczogT2JqZWN0LmZyZWV6ZSh7fSksXG59KTtcblxuLyoqIEEgcGxhaW4gb2JqZWN0IOKAlCBub3QgbnVsbCwgbm90IGFuIGFycmF5LCBub3QgYSBwcmltaXRpdmUuICovXG5leHBvcnQgZnVuY3Rpb24gaXNQbGFpbmlzaCh2YWx1ZTogdW5rbm93bik6IHZhbHVlIGlzIFJlY29yZDxzdHJpbmcsIHVua25vd24+IHtcbiAgcmV0dXJuIHR5cGVvZiB2YWx1ZSA9PT0gJ29iamVjdCcgJiYgdmFsdWUgIT09IG51bGwgJiYgIUFycmF5LmlzQXJyYXkodmFsdWUpO1xufVxuXG4vKipcbiAqIFJlYWQgYSBzb3VyY2UncyBgZXhlY3V0aW9uVHJlZWAgYXMgYSB7QGxpbmsgU3RhZ2VTbmFwc2hvdH0sIG9yIGFzIGFic2VudC5cbiAqXG4gKiBUaGUgdHJlZSBpcyBvbmx5IGV2ZXIgV0FMS0VEIGZvciBtb3VudCBpZHMgKGBzdWJmbG93SWRgICsgYHJ1bnRpbWVTdGFnZUlkYFxuICogb24gZWFjaCBub2RlKSwgc28gdGhlIGhvbmVzdCBuYXJyb3dpbmcgYSByZWFkZXIgY2FuIGRvIGlzIFwiYSBwbGFpbiBvYmplY3RcIjtcbiAqIGFueXRoaW5nIGVsc2Ug4oCUIGEgc3RvcmVkIGZpZWxkIHRoYXQgY2FtZSBiYWNrIGFzIGEgc3RyaW5nLCBhIG51bWJlciwgYW5cbiAqIGFycmF5IOKAlCBpcyByZWFkIGFzIG5vIHRyZWUgYXQgYWxsLCB3aGljaCBzZW5kcyBgY29tbWl0U3RvcHNgIGRvd24gdGhlIHNhbWVcbiAqIHNoYXBlLWhldXJpc3RpYyBwYXRoIGEgbG9nIGhhbmRlZCBvdmVyIHdpdGhvdXQgaXRzIHRyZWUgYWxyZWFkeSB0YWtlcy5cbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIHJlYWRUcmVlKHZhbHVlOiB1bmtub3duKTogU3RhZ2VTbmFwc2hvdCB8IHVuZGVmaW5lZCB7XG4gIHJldHVybiBpc1BsYWluaXNoKHZhbHVlKSA/ICh2YWx1ZSBhcyB1bmtub3duIGFzIFN0YWdlU25hcHNob3QpIDogdW5kZWZpbmVkO1xufVxuXG4vKipcbiAqIFdoeSBgcm93YCBjYW5ub3QgYmUgcmVhZCBhcyBhIGNvbW1pdCBidW5kbGUsIG9yIGB1bmRlZmluZWRgIHdoZW4gaXQgY2FuLlxuICpcbiAqIERFTElCRVJBVEVMWSBMRU5JRU5UIOKAlCBidXQgZXhhY3RseSBhcyBsZW5pZW50IGFzIHRoZSByZWFkZXJzIGFyZS4gSXQgY2hlY2tzXG4gKiB3aGF0IHRoZSBmb2xkLCB0aGUgYXhpcyBhbmQgYGNoYW5nZWRTaW5jZWAgYWN0dWFsbHkgdG91Y2g6XG4gKlxuICogLSBgcnVudGltZVN0YWdlSWRgIG11c3QgYmUgYSBzdHJpbmcg4oCUIHRoZSBheGlzIGtleXMgb24gaXQuXG4gKiAtIGB0cmFjZWAgbXVzdCBiZSBhbiBBUlJBWSwgYW5kIGl0IG11c3QgYmUgUFJFU0VOVC4gSXQgaXMgdGhlIG9uZSBmaWVsZFxuICogICBldmVyeSByZWFkZXIgV0FMS1MgKGBhcHBseVNtYXJ0TWVyZ2VgIGl0ZXJhdGVzIGl0IHZlcmIgYnkgdmVyYjtcbiAqICAgYGNoYW5nZWRTaW5jZWAgcmVhZHMgaXRzIHBhdGhzKSwgc28gYSByb3cgd2l0aG91dCBvbmUgaXMgbm90IGEgYnVuZGxlIHRoZVxuICogICBmb2xkIGNhbiBzdXJ2aXZlIOKAlCBpdCBpcyB0aGUgY3Jhc2ggYSBnYXAgZXhpc3RzIHRvIHByZXZlbnQuIGBDb21taXRCdW5kbGVgXG4gKiAgIGRlY2xhcmVzIGl0IHJlcXVpcmVkLCBzbyBubyBsaXZlIGxvZyBpcyBhZmZlY3RlZC5cbiAqIC0gYHVwZGF0ZXNgIC8gYG92ZXJ3cml0ZWAsIHdoZW4gcHJlc2VudCwgbXVzdCBiZSBwbGFpbiBvYmplY3RzLiBUaGV5IGFyZVxuICogICByZWFkIG9ubHkgQVQgVEhFIFBBVEhTIFRIRSBUUkFDRSBOQU1FUywgc28gYW4gYWJzZW50IG9uZSBpcyBoYXJtbGVzcyBhbmRcbiAqICAgaXMgdG9sZXJhdGVkLCBleGFjdGx5IGFzIHRoZSBmb2xkIHRvbGVyYXRlcyBhbiBhYnNlbnQgYHJlZGFjdGVkUGF0aHNgLlxuICpcbiAqIEJlaW5nIHN0cmljdGVyIHRoYW4gdGhhdCDigJQgZGVtYW5kaW5nIGBzdGFnZWAsIGBzdGFnZUlkYCwgYGlkeGAg4oCUIHdvdWxkXG4gKiByZWplY3QgaGFuZC1idWlsdCBsb2dzIHRoYXQgaGF2ZSBmb2xkZWQgY29ycmVjdGx5IHNpbmNlIDkuMTcuMCwgd2hpY2ggaXMgYVxuICogY29tcGF0aWJpbGl0eSBicmVhayBkcmVzc2VkIHVwIGFzIHZhbGlkYXRpb24uXG4gKi9cbmV4cG9ydCBmdW5jdGlvbiBidW5kbGVSZWZ1c2FsKHJvdzogdW5rbm93bik6IHN0cmluZyB8IHVuZGVmaW5lZCB7XG4gIGlmICghaXNQbGFpbmlzaChyb3cpKSB7XG4gICAgcmV0dXJuIGBub3QgYW4gb2JqZWN0ICgke3JvdyA9PT0gbnVsbCA/ICdudWxsJyA6IEFycmF5LmlzQXJyYXkocm93KSA/ICdhcnJheScgOiB0eXBlb2Ygcm93fSlgO1xuICB9XG4gIGlmICh0eXBlb2Ygcm93LnJ1bnRpbWVTdGFnZUlkICE9PSAnc3RyaW5nJykge1xuICAgIHJldHVybiBgcnVudGltZVN0YWdlSWQgaXMgJHtcbiAgICAgIHJvdy5ydW50aW1lU3RhZ2VJZCA9PT0gdW5kZWZpbmVkID8gJ21pc3NpbmcnIDogYGEgJHt0eXBlb2Ygcm93LnJ1bnRpbWVTdGFnZUlkfWBcbiAgICB9LCBub3QgYSBzdHJpbmdgO1xuICB9XG4gIGlmICghQXJyYXkuaXNBcnJheShyb3cudHJhY2UpKSB7XG4gICAgcmV0dXJuIGB0cmFjZSBpcyAke3Jvdy50cmFjZSA9PT0gdW5kZWZpbmVkID8gJ21pc3NpbmcnIDogYGEgJHt0eXBlb2Ygcm93LnRyYWNlfWB9LCBub3QgYW4gYXJyYXlgO1xuICB9XG4gIGlmIChyb3cudXBkYXRlcyAhPT0gdW5kZWZpbmVkICYmICFpc1BsYWluaXNoKHJvdy51cGRhdGVzKSkgcmV0dXJuICd1cGRhdGVzIGlzIG5vdCBhIHBsYWluIG9iamVjdCc7XG4gIGlmIChyb3cub3ZlcndyaXRlICE9PSB1bmRlZmluZWQgJiYgIWlzUGxhaW5pc2gocm93Lm92ZXJ3cml0ZSkpIHJldHVybiAnb3ZlcndyaXRlIGlzIG5vdCBhIHBsYWluIG9iamVjdCc7XG4gIHJldHVybiB1bmRlZmluZWQ7XG59XG5cbi8qKiBgdHJ1ZWAgd2hlbiBgcm93YCBjYW4gYmUgcmVhZCBhcyBhIHtAbGluayBDb21taXRCdW5kbGV9LiAqL1xuZXhwb3J0IGZ1bmN0aW9uIGlzQ29tbWl0QnVuZGxlKHJvdzogdW5rbm93bik6IHJvdyBpcyBDb21taXRCdW5kbGUge1xuICByZXR1cm4gYnVuZGxlUmVmdXNhbChyb3cpID09PSB1bmRlZmluZWQ7XG59XG5cbi8qKiBBIGxvZyByZWFkIHJvdyBieSByb3c6IHRoZSBidW5kbGVzLCBpbmRleC1hbGlnbmVkLCBwbHVzIHdoYXQgd2FzIHJlZnVzZWQuICovXG5leHBvcnQgaW50ZXJmYWNlIFJlYWRMb2cge1xuICAvKiogU2FtZSBsZW5ndGggYW5kIG9yZGVyIGFzIHRoZSBpbnB1dDsgYSByZWZ1c2VkIHJvdyBob2xkcyBhIEdBUCBzdGFuZC1pbi4gKi9cbiAgcmVhZG9ubHkgbG9nOiByZWFkb25seSBDb21taXRCdW5kbGVbXTtcbiAgLyoqIFRoZSByZWZ1c2VkIHJvd3MsIGluIGluZGV4IG9yZGVyLiBFbXB0eSB3aGVuIHRoZSBsb2cgd2FzIGNsZWFuLiAqL1xuICByZWFkb25seSBnYXBzOiByZWFkb25seSBMb2dHYXBbXTtcbn1cblxuLyoqIE5vdGhpbmcgdG8gcmVhZCDigJQgc2hhcmVkLCBzbyB0aGUgY2xlYW4gcGF0aCBhbGxvY2F0ZXMgbm90aGluZyBleHRyYS4gKi9cbmNvbnN0IE5PX0dBUFM6IHJlYWRvbmx5IExvZ0dhcFtdID0gT2JqZWN0LmZyZWV6ZShbXSk7XG5cbi8qKlxuICogUmVhZCBhbiB1bnZhbGlkYXRlZCBhcnJheSBhcyBhIGNvbW1pdCBsb2cuXG4gKlxuICogVGhlIGNsZWFuIGNhc2UgKGEgbGl2ZSBzbmFwc2hvdCwgb3IgYSBzdG9yZWQgcmVjb3JkaW5nIHRoYXQgcm91bmQtdHJpcHBlZFxuICogaW50YWN0KSByZXR1cm5zIHRoZSBTQU1FIGFycmF5IHJlZmVyZW5jZSBhbmQgbm8gZ2Fwczogbm90aGluZyBpcyBjb3BpZWQsXG4gKiBub3RoaW5nIGlzIGFsbG9jYXRlZCwgYW5kIHRoZSBmb2xkIGJlaGF2ZXMgYnl0ZSBmb3IgYnl0ZSBhcyBpdCBkaWQgaW5cbiAqIDkuMTcuMC4gT25seSBhIGxvZyB3aXRoIGEgcmVmdXNlZCByb3cgaXMgcmVidWlsdCwgYW5kIG9ubHkgdG8ga2VlcCB0aGVcbiAqIGluZGljZXMgYWxpZ25lZC5cbiAqXG4gKiBAZXhhbXBsZVxuICogYGBgdHlwZXNjcmlwdFxuICogY29uc3QgeyBsb2csIGdhcHMgfSA9IHJlYWRMb2coSlNPTi5wYXJzZShzdG9yZWQpLmNvbW1pdExvZyk7XG4gKiBnYXBzOyAvLyBbeyBpbmRleDogMywgcmVhc29uOiAnbm90IGFuIG9iamVjdCAobnVsbCknIH1dXG4gKiBgYGBcbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIHJlYWRMb2cocmF3OiByZWFkb25seSB1bmtub3duW10gfCB1bmRlZmluZWQpOiBSZWFkTG9nIHtcbiAgaWYgKCFyYXcgfHwgcmF3Lmxlbmd0aCA9PT0gMCkgcmV0dXJuIHsgbG9nOiBbXSwgZ2FwczogTk9fR0FQUyB9O1xuXG4gIGxldCBnYXBzOiBMb2dHYXBbXSB8IHVuZGVmaW5lZDtcbiAgZm9yIChsZXQgaSA9IDA7IGkgPCByYXcubGVuZ3RoOyBpKyspIHtcbiAgICBjb25zdCByZWFzb24gPSBidW5kbGVSZWZ1c2FsKHJhd1tpXSk7XG4gICAgaWYgKHJlYXNvbiAhPT0gdW5kZWZpbmVkKSAoZ2FwcyA/Pz0gW10pLnB1c2goeyBpbmRleDogaSwgcmVhc29uIH0pO1xuICB9XG4gIGlmICghZ2FwcykgcmV0dXJuIHsgbG9nOiByYXcgYXMgcmVhZG9ubHkgQ29tbWl0QnVuZGxlW10sIGdhcHM6IE5PX0dBUFMgfTtcblxuICBjb25zdCBsb2cgPSByYXcubWFwKChyb3cpID0+IChpc0NvbW1pdEJ1bmRsZShyb3cpID8gcm93IDogR0FQX0JVTkRMRSkpO1xuICByZXR1cm4geyBsb2csIGdhcHM6IE9iamVjdC5mcmVlemUoZ2FwcykgfTtcbn1cbiJdfQ==
|