foottrace 1.0.3 → 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (128) hide show
  1. package/README.md +137 -4
  2. package/dist/cause.js +14 -0
  3. package/dist/esm/cause.d.ts +8 -0
  4. package/dist/esm/cause.js +5 -0
  5. package/dist/esm/index.d.ts +7 -2
  6. package/dist/esm/index.js +2 -1
  7. package/dist/esm/lib/cause/admission.d.ts +27 -0
  8. package/dist/esm/lib/cause/admission.js +59 -0
  9. package/dist/esm/lib/cause/explain.d.ts +5 -0
  10. package/dist/esm/lib/cause/explain.js +114 -0
  11. package/dist/esm/lib/cause/ranking.d.ts +6 -0
  12. package/dist/esm/lib/cause/ranking.js +10 -0
  13. package/dist/esm/lib/cause/recordAdapter.d.ts +9 -0
  14. package/dist/esm/lib/cause/recordAdapter.js +49 -0
  15. package/dist/esm/lib/cause/recordChildren.d.ts +4 -0
  16. package/dist/esm/lib/cause/recordChildren.js +79 -0
  17. package/dist/esm/lib/cause/recordControl.d.ts +13 -0
  18. package/dist/esm/lib/cause/recordControl.js +66 -0
  19. package/dist/esm/lib/cause/recordCoverage.d.ts +24 -0
  20. package/dist/esm/lib/cause/recordCoverage.js +87 -0
  21. package/dist/esm/lib/cause/recordIdentity.d.ts +9 -0
  22. package/dist/esm/lib/cause/recordIdentity.js +67 -0
  23. package/dist/esm/lib/cause/recordLoading.d.ts +7 -0
  24. package/dist/esm/lib/cause/recordLoading.js +116 -0
  25. package/dist/esm/lib/cause/recordParents.d.ts +4 -0
  26. package/dist/esm/lib/cause/recordParents.js +86 -0
  27. package/dist/esm/lib/cause/recordScope.d.ts +8 -0
  28. package/dist/esm/lib/cause/recordScope.js +60 -0
  29. package/dist/esm/lib/cause/recordTypes.d.ts +67 -0
  30. package/dist/esm/lib/cause/recordTypes.js +1 -0
  31. package/dist/esm/lib/cause/recordValidation.d.ts +33 -0
  32. package/dist/esm/lib/cause/recordValidation.js +198 -0
  33. package/dist/esm/lib/cause/session.d.ts +30 -0
  34. package/dist/esm/lib/cause/session.js +161 -0
  35. package/dist/esm/lib/cause/storedRecordAdapter.d.ts +11 -0
  36. package/dist/esm/lib/cause/storedRecordAdapter.js +57 -0
  37. package/dist/esm/lib/cause/strategies.d.ts +3 -0
  38. package/dist/esm/lib/cause/strategies.js +21 -0
  39. package/dist/esm/lib/cause/types.d.ts +90 -0
  40. package/dist/esm/lib/cause/types.js +1 -0
  41. package/dist/esm/lib/cause/validation.d.ts +24 -0
  42. package/dist/esm/lib/cause/validation.js +134 -0
  43. package/dist/esm/lib/cause/walk.d.ts +12 -0
  44. package/dist/esm/lib/cause/walk.js +174 -0
  45. package/dist/esm/lib/ids/attemptId.d.ts +63 -0
  46. package/dist/esm/lib/ids/attemptId.js +91 -0
  47. package/dist/esm/lib/memory/EventLog.d.ts +89 -3
  48. package/dist/esm/lib/memory/EventLog.js +242 -3
  49. package/dist/esm/lib/memory/honesty.d.ts +6 -4
  50. package/dist/esm/lib/memory/honesty.js +7 -4
  51. package/dist/esm/lib/memory/sink.d.ts +148 -0
  52. package/dist/esm/lib/memory/sink.js +92 -0
  53. package/dist/esm/lib/memory/types.d.ts +4 -1
  54. package/dist/esm/lib/memory/types.js +1 -1
  55. package/dist/esm/lib/time-travel/bundles.d.ts +2 -0
  56. package/dist/esm/lib/time-travel/bundles.js +16 -0
  57. package/dist/esm/lib/time-travel/commitStops.js +21 -2
  58. package/dist/esm/lib/time-travel/index.d.ts +2 -0
  59. package/dist/esm/lib/time-travel/index.js +1 -0
  60. package/dist/esm/lib/time-travel/storedRecord.d.ts +195 -0
  61. package/dist/esm/lib/time-travel/storedRecord.js +494 -0
  62. package/dist/esm/lib/time-travel/timeTravel.js +11 -0
  63. package/dist/esm/lib/time-travel/types.d.ts +4 -1
  64. package/dist/esm/write.d.ts +7 -0
  65. package/dist/esm/write.js +6 -0
  66. package/dist/index.js +6 -2
  67. package/dist/lib/cause/admission.js +67 -0
  68. package/dist/lib/cause/explain.js +119 -0
  69. package/dist/lib/cause/ranking.js +14 -0
  70. package/dist/lib/cause/recordAdapter.js +55 -0
  71. package/dist/lib/cause/recordChildren.js +83 -0
  72. package/dist/lib/cause/recordControl.js +74 -0
  73. package/dist/lib/cause/recordCoverage.js +99 -0
  74. package/dist/lib/cause/recordIdentity.js +73 -0
  75. package/dist/lib/cause/recordLoading.js +121 -0
  76. package/dist/lib/cause/recordParents.js +90 -0
  77. package/dist/lib/cause/recordScope.js +66 -0
  78. package/dist/lib/cause/recordTypes.js +2 -0
  79. package/dist/lib/cause/recordValidation.js +213 -0
  80. package/dist/lib/cause/session.js +165 -0
  81. package/dist/lib/cause/storedRecordAdapter.js +61 -0
  82. package/dist/lib/cause/strategies.js +26 -0
  83. package/dist/lib/cause/types.js +2 -0
  84. package/dist/lib/cause/validation.js +150 -0
  85. package/dist/lib/cause/walk.js +180 -0
  86. package/dist/lib/ids/attemptId.js +96 -0
  87. package/dist/lib/memory/EventLog.js +242 -3
  88. package/dist/lib/memory/honesty.js +7 -4
  89. package/dist/lib/memory/sink.js +98 -0
  90. package/dist/lib/memory/types.js +1 -1
  91. package/dist/lib/time-travel/bundles.js +18 -1
  92. package/dist/lib/time-travel/commitStops.js +21 -2
  93. package/dist/lib/time-travel/index.js +3 -1
  94. package/dist/lib/time-travel/storedRecord.js +498 -0
  95. package/dist/lib/time-travel/timeTravel.js +11 -0
  96. package/dist/types/cause.d.ts +8 -0
  97. package/dist/types/index.d.ts +7 -2
  98. package/dist/types/lib/cause/admission.d.ts +27 -0
  99. package/dist/types/lib/cause/explain.d.ts +5 -0
  100. package/dist/types/lib/cause/ranking.d.ts +6 -0
  101. package/dist/types/lib/cause/recordAdapter.d.ts +9 -0
  102. package/dist/types/lib/cause/recordChildren.d.ts +4 -0
  103. package/dist/types/lib/cause/recordControl.d.ts +13 -0
  104. package/dist/types/lib/cause/recordCoverage.d.ts +24 -0
  105. package/dist/types/lib/cause/recordIdentity.d.ts +9 -0
  106. package/dist/types/lib/cause/recordLoading.d.ts +7 -0
  107. package/dist/types/lib/cause/recordParents.d.ts +4 -0
  108. package/dist/types/lib/cause/recordScope.d.ts +8 -0
  109. package/dist/types/lib/cause/recordTypes.d.ts +67 -0
  110. package/dist/types/lib/cause/recordValidation.d.ts +33 -0
  111. package/dist/types/lib/cause/session.d.ts +30 -0
  112. package/dist/types/lib/cause/storedRecordAdapter.d.ts +11 -0
  113. package/dist/types/lib/cause/strategies.d.ts +3 -0
  114. package/dist/types/lib/cause/types.d.ts +90 -0
  115. package/dist/types/lib/cause/validation.d.ts +24 -0
  116. package/dist/types/lib/cause/walk.d.ts +12 -0
  117. package/dist/types/lib/ids/attemptId.d.ts +63 -0
  118. package/dist/types/lib/memory/EventLog.d.ts +89 -3
  119. package/dist/types/lib/memory/honesty.d.ts +6 -4
  120. package/dist/types/lib/memory/sink.d.ts +148 -0
  121. package/dist/types/lib/memory/types.d.ts +4 -1
  122. package/dist/types/lib/time-travel/bundles.d.ts +2 -0
  123. package/dist/types/lib/time-travel/index.d.ts +2 -0
  124. package/dist/types/lib/time-travel/storedRecord.d.ts +195 -0
  125. package/dist/types/lib/time-travel/types.d.ts +4 -1
  126. package/dist/types/write.d.ts +7 -0
  127. package/dist/write.js +8 -1
  128. package/package.json +67 -13
@@ -10,6 +10,7 @@
10
10
  */
11
11
  import { isExecutionKey, isWithinSubflow, parseRuntimeStageId, stageIdOf } from '../ids/runtimeStageId.js';
12
12
  import { buildCommitIndex, inferLegacyPhases, recordsPhases } from '../memory/commitLogUtils.js';
13
+ import { isPlainish } from './bundles.js';
13
14
  /**
14
15
  * Every `runtimeStageId` in the execution tree that the engine marked as a
15
16
  * subflow mount. Authoritative, as is a bundle's recorded `phase: 'exit'`
@@ -89,7 +90,7 @@ export function commitStops(commitLog, executionTree, subflowResults) {
89
90
  // the MOUNT's commit, made before any of them ran. It gets no stop of its
90
91
  // own; it belongs to the `'start'` bookend, which is where a reader looks
91
92
  // for "the input this chart began with" anyway. See {@link seedCommits}.
92
- const seeds = seedCommits(commitLog, executionTree);
93
+ const seeds = seedCommits(commitLog, executionTree, subflowResults);
93
94
  const firsts = [];
94
95
  for (const [id, i] of buildCommitIndex(commitLog))
95
96
  if (id && i >= seeds)
@@ -159,6 +160,22 @@ function phasesOf(commitLog, haveTree) {
159
160
  return commitLog.map((bundle) => bundle.phase);
160
161
  return haveTree ? [] : inferLegacyPhases(commitLog);
161
162
  }
163
+ /**
164
+ * True when the results map's entry for `head` IS this log — its history opens with this very bundle.
165
+ * The log is then that mount's own, and its head run is the mount's seed (R13: a seed carries its
166
+ * mount's runtimeStageId). This is what a TREE-LESS cursor over a mount log reads in place of the
167
+ * tree's root — a stored log read back by `readStoredRecord`, or a log whose stage after the seed was
168
+ * lost — so the seed opens `start` instead of standing as a stop (marked as its own mount).
169
+ */
170
+ function opensOwnMount(subflowResults, head, first) {
171
+ if (!isPlainish(subflowResults))
172
+ return false;
173
+ const result = subflowResults[head];
174
+ if (!isPlainish(result) || !isPlainish(result.treeContext))
175
+ return false;
176
+ const history = result.treeContext.history;
177
+ return Array.isArray(history) && history[0] === first;
178
+ }
162
179
  /** `commitStops` as a {@link TimeTravelStrategy} — the default for `timeTravel`. */
163
180
  export const commitStopsStrategy = { stopsFor: commitStops };
164
181
  /**
@@ -179,7 +196,7 @@ export const commitStopsStrategy = { stopsFor: commitStops };
179
196
  * whose id carries the same subflow path. Without a tree such a log reads as
180
197
  * one stop.
181
198
  */
182
- function seedCommits(commitLog, executionTree) {
199
+ function seedCommits(commitLog, executionTree, subflowResults) {
183
200
  const head = commitLog[0]?.runtimeStageId;
184
201
  if (head === undefined)
185
202
  return 0;
@@ -192,6 +209,8 @@ function seedCommits(commitLog, executionTree) {
192
209
  let next = 1;
193
210
  while (next < commitLog.length && commitLog[next].runtimeStageId === head)
194
211
  next++;
212
+ if (executionTree === undefined && opensOwnMount(subflowResults, head, commitLog[0]))
213
+ return next;
195
214
  const address = stageIdOf(head);
196
215
  if (next >= commitLog.length) {
197
216
  const rootId = executionTree?.id;
@@ -17,6 +17,8 @@ export { filterStops, splitAxis } from './axis.js';
17
17
  export { isCommitBundle } from './bundles.js';
18
18
  export { commitStops, commitStopsStrategy } from './commitStops.js';
19
19
  export { stateAt } from './stateAt.js';
20
+ export type { StoredLog, StoredNote, StoredRecord, StoredRecordLimits } from './storedRecord.js';
21
+ export { readStoredRecord } from './storedRecord.js';
20
22
  export { tagStops } from './tagStops.js';
21
23
  export { timeTravel } from './timeTravel.js';
22
24
  export type { AxisRefusal, AxisSplit, BookendedAxis, FoldBasis, FoldedState, FoldSource, LogGap, Mark, Move, MoveRefusal, Stop, StopKind, TimeTravel, TimeTravelOptions, TimeTravelSource, TimeTravelStrategy, } from './types.js';
@@ -16,5 +16,6 @@ export { filterStops, splitAxis } from './axis.js';
16
16
  export { isCommitBundle } from './bundles.js';
17
17
  export { commitStops, commitStopsStrategy } from './commitStops.js';
18
18
  export { stateAt } from './stateAt.js';
19
+ export { readStoredRecord } from './storedRecord.js';
19
20
  export { tagStops } from './tagStops.js';
20
21
  export { timeTravel } from './timeTravel.js';
@@ -0,0 +1,195 @@
1
+ /**
2
+ * storedRecord.ts — reading back what a sink stored (1.1.0).
3
+ *
4
+ * WHY THIS FILE EXISTS. A log that writes to a `RecordSink` (`foottrace/write`) leaves its record in
5
+ * storage AS IT LANDS: an `opened` entry with the fold base, one `commit` entry per bundle, `note`
6
+ * entries, and a `closed` entry with how the log ended. A run that is killed never writes its
7
+ * `closed` entry. This reader turns stored entries back into logs a person or a model can read —
8
+ * and says, before any fold, which of them never finished (`complete: false`) and what each one is
9
+ * missing.
10
+ *
11
+ * WHAT IT READS. Entries come back from storage unvalidated (a parsed file, a table scan) — untrusted
12
+ * input — so it takes `Iterable<unknown>` and checks each one where it is read: the 9.18.0 law for
13
+ * stored recordings, no cast at the seam. An entry it does not read into a log is never dropped in
14
+ * silence: it is listed in `unread` with its position and the reason.
15
+ *
16
+ * THE LAWS IT KEEPS.
17
+ * 1. Grouped by address. Every entry names its log (`logRunId` + `drillPath`); entries of many
18
+ * logs may arrive interleaved, and each log keeps its own entries in arrival order.
19
+ * 2. A commit sits at its own index. `EventLog · record` stamps `bundle.idx` before the sink sees
20
+ * the bundle, so a commit lost on the way (a sink that failed once — the writer goes on) leaves a
21
+ * GAP at its index instead of shifting every commit after it; so does a bundle that is not a
22
+ * readable bundle. Every gap keeps its index, and the fold reports it (`skipped`) with the same
23
+ * reason the stored log gives (`gaps`). One place, one entry: the first READABLE commit entry
24
+ * for an index stands (a torn first delivery yields to a readable redelivery), the first
25
+ * `opened` stands, and every other entry for a place already held — a redelivery or a conflict,
26
+ * the very same object included — is listed in `unread`. Payloads are never compared, so no
27
+ * stored value is walked.
28
+ * 3. Complete means the writer finished — and the reader read it all. A log is `complete` when a
29
+ * `closed` entry covers every stored commit (`closed.commits` is past the last index); the
30
+ * covering close with the largest `commits` gives the `outcome` and the length, so no loss an
31
+ * earlier close showed can hide behind a later one. A log that was closed and then went on (a
32
+ * later commit) is not complete until it is closed again. A closed log whose store lost commits
33
+ * is complete AND has gaps (`closed.commits` makes even a lost LAST commit a gap). A log a limit
34
+ * cut, and every log once reading stopped at the `entries` limit, is not complete: what the
35
+ * reader did not read could have gone on.
36
+ * 4. Opaque stays opaque. `meta` and a note's `kind` and `data` are handed back as stored, never
37
+ * read or merged.
38
+ * 5. Read, never touched. Nothing here copies, freezes or edits the entries it is given — not the
39
+ * base, not `meta`: the stored log hands back their own values.
40
+ * 6. Bounded work (see {@link StoredRecordLimits}). Every walk is a loop over the input, never a
41
+ * recursion, and never through an input's own iterator except the input itself; the work per
42
+ * entry is bounded by the address limits; the rows a log holds are its stored commits plus gaps
43
+ * from ONE budget for the whole record, spent on the logs that need the fewest gaps first (so a
44
+ * corrupt log asking for the most is the one cut); and reading stops after `entries` entries. Whatever a
45
+ * limit stops is listed in `unread` by name — never a hang, never a silent cut.
46
+ *
47
+ * Entries are data. An entry whose fields throw while it is checked (a getter, a revoked proxy) is
48
+ * listed in `unread`, and what the reader computes with — the kind, a commit's index and bundle, a
49
+ * close's length — is taken once, as checked. An in-memory accessor that changes or throws on a later
50
+ * read is out of contract, as everywhere in the record (docs/extraction.md): parsed JSON and structured
51
+ * clones carry none.
52
+ *
53
+ * DAG position: memory ← time-travel; this file is a reader beside `timeTravel` and imports only
54
+ * record files.
55
+ */
56
+ import type { LogAddress } from '../memory/eventPosition.js';
57
+ import { type LogOutcome, type Meta, type RecordEntry } from '../memory/sink.js';
58
+ import type { CommitBundle } from '../memory/types.js';
59
+ import type { LogGap, TimeTravelSource } from './types.js';
60
+ /** A note the writer kept beside a log (`EventLog · note`), as stored. */
61
+ export interface StoredNote {
62
+ /** The note's kind, as the writer named it. */
63
+ readonly kind: string;
64
+ /** When it was written (epoch ms). */
65
+ readonly at: number;
66
+ /** What the writer put in it — opaque. */
67
+ readonly data: Meta;
68
+ }
69
+ /** One log read back from a sink's entries. */
70
+ export interface StoredLog {
71
+ /** Which log this is: the run leg that owned it and the mounts it sits under (root: `drillPath: []`). */
72
+ readonly address: LogAddress;
73
+ /** The fold base the `opened` entry carried; ABSENT when no readable `opened` entry was stored. */
74
+ readonly initialState?: Record<string, unknown>;
75
+ /**
76
+ * The bundles, each at its own index (`bundle.idx`). An index with no readable bundle holds an empty
77
+ * stand-in (it belongs to no stage and carries nothing) and is named in {@link gaps} — check `gaps`
78
+ * before a causal query, which takes the bundles as they are; fold {@link source}, not this, so the
79
+ * fold reports them.
80
+ */
81
+ readonly commitLog: readonly CommitBundle[];
82
+ /** Every index of {@link commitLog} that holds no readable bundle, with why — empty when the log is whole. */
83
+ readonly gaps: readonly LogGap[];
84
+ /**
85
+ * The notes, in arrival order. A store that delivers an entry twice as two copies repeats its note:
86
+ * payloads are never compared (the very same object twice is listed in `unread` instead).
87
+ */
88
+ readonly notes: readonly StoredNote[];
89
+ /**
90
+ * `true` when a `closed` entry covers every stored commit and the reader read the log whole: the
91
+ * writer said it ended. `false` for a log that never closed — a crashed run, or one still going —
92
+ * for one that went on after its last close, and for one the reader did not read whole (a limit
93
+ * cut it, or reading stopped at the `entries` limit — see `StoredRecord.unread`).
94
+ */
95
+ readonly complete: boolean;
96
+ /** How the covering `closed` entry said the log ended; ABSENT while the log is not complete. */
97
+ readonly outcome?: LogOutcome;
98
+ /**
99
+ * What the writer put on the `opened` entry and on the covering `closed` entry (present only while
100
+ * the log is complete) — opaque, never merged.
101
+ */
102
+ readonly meta: {
103
+ readonly opened?: Meta;
104
+ readonly closed?: Meta;
105
+ };
106
+ /**
107
+ * The entries this log was read from, in arrival order, as stored — the evidence the rest is read
108
+ * from (each entry's `at`, every `closed` entry). An entry listed in `StoredRecord.unread` is not
109
+ * here. A commit's bundle is the stored row: {@link gaps} names one that is not a readable bundle.
110
+ */
111
+ readonly entries: readonly RecordEntry[];
112
+ /**
113
+ * The log as `timeTravel` and `stateAt` read it: `{ commitLog, initialState?, subflowResults? }`. A
114
+ * gap is a row the fold refuses with the same reason {@link gaps} gives (`skipped`), so a fold that
115
+ * crosses one says so. `subflowResults` holds every stored mount log of the run leg — this one too,
116
+ * when it is a mount — keyed by each mount's runtimeStageId (the last segment of its `drillPath`):
117
+ * one flat map per leg, as a live snapshot's, so the cursor marks those stops as mounts and
118
+ * `drill(mountRuntimeStageId)` opens the mount's own stored log. A mount log's head commit stamped
119
+ * with its own mount id is its seed (it opens `start`, as on a live cursor). The same object on
120
+ * every call.
121
+ */
122
+ source(): TimeTravelSource;
123
+ }
124
+ /** Every log a sink's entries hold, and every entry no log holds. */
125
+ export interface StoredRecord {
126
+ /** Every log, keyed by its address as a JSON array — `["<logRunId>", ...drillPath]` — in first-arrival order. */
127
+ readonly logs: ReadonlyMap<string, StoredLog>;
128
+ /** The root log of the run leg `logRunId` (`drillPath: []`), or `undefined` when none was stored. */
129
+ root(logRunId: string): StoredLog | undefined;
130
+ /** The log at `address`, or `undefined` when none was stored. */
131
+ log(address: LogAddress): StoredLog | undefined;
132
+ /**
133
+ * Entries not read into any log, by position in the input, with the reason: an entry that could not
134
+ * be read, one past a limit of the reader, or one for a place another entry holds (a second `opened`,
135
+ * a second commit for an index, the same entry again). In input order; empty when every entry was
136
+ * read. A commit that could not be read at all, or that a limit cut, leaves no row: its index is a
137
+ * gap only when a later stored commit or a close reaches past it.
138
+ */
139
+ readonly unread: readonly {
140
+ readonly index: number;
141
+ readonly reason: string;
142
+ }[];
143
+ }
144
+ /**
145
+ * The reader's limits — what bounds its work on untrusted input. Each defaults to the value below; a
146
+ * caller that reads larger records raises it. Whatever a limit stops is listed in `unread` by name.
147
+ */
148
+ export interface StoredRecordLimits {
149
+ /**
150
+ * Entries read; past it, reading stops, one `unread` item says so, and no log reads complete.
151
+ * Default 16,777,216 (2^24).
152
+ */
153
+ readonly entries?: number;
154
+ /**
155
+ * Gaps held across the WHOLE record — rows for commits the store does not have — spent on the logs
156
+ * that need the fewest first. A commit (or a close) that would need more is unread. Default
157
+ * 1,048,576 (2^20). The other rows a log holds are its stored commits.
158
+ */
159
+ readonly missingCommits?: number;
160
+ /** Segments in an address's `drillPath`. Default 1,024. */
161
+ readonly drillDepth?: number;
162
+ /** Characters in an address — its `logRunId` and `drillPath` segments together. Default 65,536. */
163
+ readonly addressLength?: number;
164
+ }
165
+ /** The defaults — internal to the package; documented on {@link StoredRecordLimits}. */
166
+ export declare const DEFAULT_LIMITS: Required<StoredRecordLimits>;
167
+ /**
168
+ * Read a sink's stored entries back into logs.
169
+ *
170
+ * Bounded on untrusted input: no recursion, no payload walk, work per entry bounded by the address
171
+ * limits, at most `limits.missingCommits` gap rows across the record, and at most `limits.entries`
172
+ * entries read — whatever a limit stops is listed in `unread` by name.
173
+ *
174
+ * @param entries What a `RecordSink` stored (`memorySink().entries`, or entries parsed back from a
175
+ * file or a table), in the order they were written — typed `unknown` because stored entries come
176
+ * back unvalidated; each is checked here.
177
+ * @param limits Raise a default to read a larger record ({@link StoredRecordLimits}).
178
+ * @returns Every log by address, the run-leg roots, and every entry not read into a log.
179
+ * @throws RangeError when a limit is not a non-negative integer or `Infinity`; TypeError when
180
+ * `entries` is not iterable; whatever the input's own iterator throws. Never for an entry that is
181
+ * data (parsed JSON, a structured clone).
182
+ *
183
+ * @example
184
+ * ```typescript
185
+ * import { readStoredRecord, stateAt, timeTravel } from 'foottrace';
186
+ *
187
+ * const stored = readStoredRecord(entries); // e.g. JSON lines read back after a crash
188
+ * const run = stored.root('run-1');
189
+ * run?.complete; // false — it never closed
190
+ * run?.notes.map((n) => n.kind); // ['attempt', 'attempt'] — kept as they happened
191
+ * timeTravel(run!.source()).last(); // the cursor over what landed
192
+ * stateAt(run!.source(), run!.commitLog.length - 1).skipped; // a lost commit, by index and reason
193
+ * ```
194
+ */
195
+ export declare function readStoredRecord(entries: Iterable<unknown>, limits?: StoredRecordLimits): StoredRecord;