@enderfga/claw-orchestrator 5.0.0 → 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.
Files changed (158) hide show
  1. package/README.md +28 -28
  2. package/dist/bin/cli.js +107 -1
  3. package/dist/bin/cli.js.map +1 -1
  4. package/dist/src/acp-server.d.ts +5 -5
  5. package/dist/src/acp-server.js +3 -3
  6. package/dist/src/acp-server.js.map +1 -1
  7. package/dist/src/autoloop/dispatcher.d.ts +22 -0
  8. package/dist/src/autoloop/dispatcher.js +71 -13
  9. package/dist/src/autoloop/dispatcher.js.map +1 -1
  10. package/dist/src/autoloop/messages.d.ts +10 -0
  11. package/dist/src/autoloop/messages.js.map +1 -1
  12. package/dist/src/autoloop/runner.js +6 -0
  13. package/dist/src/autoloop/runner.js.map +1 -1
  14. package/dist/src/constants.d.ts +0 -6
  15. package/dist/src/constants.js +0 -6
  16. package/dist/src/constants.js.map +1 -1
  17. package/dist/src/council.d.ts +15 -0
  18. package/dist/src/council.js +48 -35
  19. package/dist/src/council.js.map +1 -1
  20. package/dist/src/dashboard/index.html +191 -6
  21. package/dist/src/embedded-server.js +132 -9
  22. package/dist/src/embedded-server.js.map +1 -1
  23. package/dist/src/fanout.d.ts +30 -1
  24. package/dist/src/fanout.js +32 -3
  25. package/dist/src/fanout.js.map +1 -1
  26. package/dist/src/index.d.ts +1 -0
  27. package/dist/src/index.js +360 -4
  28. package/dist/src/index.js.map +1 -1
  29. package/dist/src/kernel/agent-step.d.ts +59 -0
  30. package/dist/src/kernel/agent-step.js +100 -0
  31. package/dist/src/kernel/agent-step.js.map +1 -0
  32. package/dist/src/kernel/conditions.d.ts +11 -0
  33. package/dist/src/kernel/conditions.js +24 -0
  34. package/dist/src/kernel/conditions.js.map +1 -0
  35. package/dist/src/kernel/engine.d.ts +319 -0
  36. package/dist/src/kernel/engine.js +1047 -0
  37. package/dist/src/kernel/engine.js.map +1 -0
  38. package/dist/src/kernel/exec.d.ts +43 -0
  39. package/dist/src/kernel/exec.js +112 -0
  40. package/dist/src/kernel/exec.js.map +1 -0
  41. package/dist/src/kernel/file-lock.d.ts +50 -0
  42. package/dist/src/kernel/file-lock.js +135 -0
  43. package/dist/src/kernel/file-lock.js.map +1 -0
  44. package/dist/src/kernel/nodes/agent.d.ts +4 -0
  45. package/dist/src/kernel/nodes/agent.js +35 -0
  46. package/dist/src/kernel/nodes/agent.js.map +1 -0
  47. package/dist/src/kernel/nodes/autoloop.d.ts +78 -0
  48. package/dist/src/kernel/nodes/autoloop.js +75 -0
  49. package/dist/src/kernel/nodes/autoloop.js.map +1 -0
  50. package/dist/src/kernel/nodes/council.d.ts +12 -0
  51. package/dist/src/kernel/nodes/council.js +88 -0
  52. package/dist/src/kernel/nodes/council.js.map +1 -0
  53. package/dist/src/kernel/nodes/fanout.d.ts +11 -0
  54. package/dist/src/kernel/nodes/fanout.js +63 -0
  55. package/dist/src/kernel/nodes/fanout.js.map +1 -0
  56. package/dist/src/kernel/nodes/human-gate.d.ts +4 -0
  57. package/dist/src/kernel/nodes/human-gate.js +7 -0
  58. package/dist/src/kernel/nodes/human-gate.js.map +1 -0
  59. package/dist/src/kernel/nodes/index.d.ts +12 -0
  60. package/dist/src/kernel/nodes/index.js +21 -0
  61. package/dist/src/kernel/nodes/index.js.map +1 -0
  62. package/dist/src/kernel/nodes/router.d.ts +4 -0
  63. package/dist/src/kernel/nodes/router.js +12 -0
  64. package/dist/src/kernel/nodes/router.js.map +1 -0
  65. package/dist/src/kernel/nodes/subflow.d.ts +13 -0
  66. package/dist/src/kernel/nodes/subflow.js +38 -0
  67. package/dist/src/kernel/nodes/subflow.js.map +1 -0
  68. package/dist/src/kernel/nodes/ultraapp.d.ts +60 -0
  69. package/dist/src/kernel/nodes/ultraapp.js +62 -0
  70. package/dist/src/kernel/nodes/ultraapp.js.map +1 -0
  71. package/dist/src/kernel/nodes/verifier.d.ts +14 -0
  72. package/dist/src/kernel/nodes/verifier.js +84 -0
  73. package/dist/src/kernel/nodes/verifier.js.map +1 -0
  74. package/dist/src/kernel/projections.d.ts +42 -0
  75. package/dist/src/kernel/projections.js +133 -0
  76. package/dist/src/kernel/projections.js.map +1 -0
  77. package/dist/src/kernel/repo.d.ts +13 -0
  78. package/dist/src/kernel/repo.js +64 -0
  79. package/dist/src/kernel/repo.js.map +1 -0
  80. package/dist/src/kernel/secrets.d.ts +25 -0
  81. package/dist/src/kernel/secrets.js +48 -0
  82. package/dist/src/kernel/secrets.js.map +1 -0
  83. package/dist/src/kernel/store.d.ts +225 -0
  84. package/dist/src/kernel/store.js +838 -0
  85. package/dist/src/kernel/store.js.map +1 -0
  86. package/dist/src/kernel/templates/index.d.ts +140 -0
  87. package/dist/src/kernel/templates/index.js +266 -0
  88. package/dist/src/kernel/templates/index.js.map +1 -0
  89. package/dist/src/kernel/types.d.ts +326 -0
  90. package/dist/src/kernel/types.js +19 -0
  91. package/dist/src/kernel/types.js.map +1 -0
  92. package/dist/src/models.d.ts +1 -1
  93. package/dist/src/models.js +31 -3
  94. package/dist/src/models.js.map +1 -1
  95. package/dist/src/persistent-cursor-session.js +6 -1
  96. package/dist/src/persistent-cursor-session.js.map +1 -1
  97. package/dist/src/persistent-grok-session.d.ts +40 -0
  98. package/dist/src/persistent-grok-session.js +197 -0
  99. package/dist/src/persistent-grok-session.js.map +1 -0
  100. package/dist/src/run-ledger.d.ts +57 -3
  101. package/dist/src/run-ledger.js +45 -2
  102. package/dist/src/run-ledger.js.map +1 -1
  103. package/dist/src/session-manager.d.ts +176 -129
  104. package/dist/src/session-manager.js +657 -603
  105. package/dist/src/session-manager.js.map +1 -1
  106. package/dist/src/types.d.ts +37 -4
  107. package/dist/src/types.js +15 -1
  108. package/dist/src/types.js.map +1 -1
  109. package/dist/src/ultraapp/build.d.ts +117 -3
  110. package/dist/src/ultraapp/build.js +319 -3
  111. package/dist/src/ultraapp/build.js.map +1 -1
  112. package/dist/src/ultraapp/contract.d.ts +52 -0
  113. package/dist/src/ultraapp/contract.js +83 -0
  114. package/dist/src/ultraapp/contract.js.map +1 -0
  115. package/dist/src/ultraapp/conventions.js +9 -2
  116. package/dist/src/ultraapp/conventions.js.map +1 -1
  117. package/dist/src/ultraapp/fix-on-failure.d.ts +21 -2
  118. package/dist/src/ultraapp/fix-on-failure.js +46 -62
  119. package/dist/src/ultraapp/fix-on-failure.js.map +1 -1
  120. package/dist/src/ultraapp/manager.d.ts +107 -2
  121. package/dist/src/ultraapp/manager.js +305 -86
  122. package/dist/src/ultraapp/manager.js.map +1 -1
  123. package/dist/src/verify/baseline.d.ts +73 -0
  124. package/dist/src/verify/baseline.js +186 -0
  125. package/dist/src/verify/baseline.js.map +1 -0
  126. package/dist/src/verify/contract.d.ts +116 -0
  127. package/dist/src/verify/contract.js +142 -0
  128. package/dist/src/verify/contract.js.map +1 -0
  129. package/dist/src/verify/evidence.d.ts +61 -0
  130. package/dist/src/verify/evidence.js +133 -0
  131. package/dist/src/verify/evidence.js.map +1 -0
  132. package/dist/src/verify/runner.d.ts +63 -0
  133. package/dist/src/verify/runner.js +317 -0
  134. package/dist/src/verify/runner.js.map +1 -0
  135. package/openclaw.plugin.json +8 -0
  136. package/package.json +2 -2
  137. package/skills/SKILL.md +121 -80
  138. package/skills/references/acp.md +18 -18
  139. package/skills/references/autoloop.md +148 -72
  140. package/skills/references/claude-cli-tracking.md +4 -4
  141. package/skills/references/cli.md +103 -60
  142. package/skills/references/council.md +109 -37
  143. package/skills/references/dashboard.md +34 -6
  144. package/skills/references/getting-started.md +14 -14
  145. package/skills/references/inbox.md +4 -4
  146. package/skills/references/mcp.md +39 -34
  147. package/skills/references/multi-engine.md +109 -51
  148. package/skills/references/observability.md +88 -27
  149. package/skills/references/openai-compat.md +40 -40
  150. package/skills/references/sessions.md +44 -26
  151. package/skills/references/tools.md +402 -309
  152. package/skills/references/ultra.md +45 -45
  153. package/skills/references/ultraapp.md +126 -50
  154. package/skills/references/verification.md +187 -0
  155. package/skills/references/workflow.md +362 -0
  156. package/dist/src/ultraapp/fix-on-failure-session.d.ts +0 -23
  157. package/dist/src/ultraapp/fix-on-failure-session.js +0 -51
  158. package/dist/src/ultraapp/fix-on-failure-session.js.map +0 -1
@@ -0,0 +1,319 @@
1
+ /**
2
+ * The run kernel.
3
+ *
4
+ * A durable executor for workflow runs. It owns what the existing state machines
5
+ * each owned separately and differently: what is running, what to do when a step
6
+ * fails, when to give up, how to stop, and — the part none of them had — how to
7
+ * come back after the process dies.
8
+ *
9
+ * Every mode goes through it. `council_start`, `fanout_start`, `ultraplan_start`,
10
+ * `ultrareview_start` and `autoloop_start` each create a run here; the engines
11
+ * they wrap (`Council`, `Fanout`, the autoloop dispatcher) still do the work,
12
+ * but they no longer own a lifecycle. What that replaced: five result maps, four
13
+ * 30-minute eviction timers, a 5-second poller, two `Set`s fencing a start
14
+ * against a delete, and two incompatible ways of listing past runs across
15
+ * processes.
16
+ *
17
+ * Durability contract, stated precisely: every state transition is checkpointed
18
+ * (atomic `run.json`) and appended to `events.jsonl` before the next step
19
+ * begins, and `resume` restarts at the last node boundary. That makes node
20
+ * execution **at-least-once**, not exactly-once — there is no idempotency key,
21
+ * no attempt lease, and no side-effect commit marker, so a node that died after
22
+ * writing files but before its checkpoint runs again from the top. Workflows
23
+ * whose nodes are not safe to repeat need to make them safe.
24
+ *
25
+ * Ownership contract, equally precisely: this class never writes to a run
26
+ * directly. It holds a `RunGuard` from the store and every change — checkpoint,
27
+ * event, node artifact, terminal verdict — goes through `RunTxn`, which applies
28
+ * the change to a *copy*, commits it under the guard, and adopts the copy only
29
+ * if the disk accepted it. There is no code path from here to `run.json` that
30
+ * skips that, because the raw writers are no longer exported. An owner that has
31
+ * been superseded therefore cannot change the run in memory either: its record
32
+ * stops advancing at the last write the disk agreed to.
33
+ *
34
+ * Control flow is linear with an explicit `router` node for branches and loops.
35
+ * Parallelism is the `fanout` node rather than a general parallel/join construct:
36
+ * fan-out is the shape every existing mode actually needed, and a join barrier
37
+ * would add failure modes (partial joins, orphaned branches) that nothing here
38
+ * would exercise.
39
+ *
40
+ * What a node timeout can and cannot do: it stops the kernel waiting and marks
41
+ * the node failed, but an in-flight agent turn is owned by the session layer and
42
+ * finishes on its own schedule. The kernel does not pretend otherwise.
43
+ */
44
+ import { EventEmitter } from 'node:events';
45
+ import { type Logger } from '../logger.js';
46
+ import { type AcceptanceContract } from '../verify/contract.js';
47
+ import type { SessionManagerLike } from './agent-step.js';
48
+ import { runDir, summarize, type ListRunsQuery, type RunSummary } from './store.js';
49
+ import { type ConsensusVote, type KernelEvent, type NodeKind, type NodeSpec, type RunRecord, type WorkflowSpec } from './types.js';
50
+ export declare const DEFAULT_NODE_TIMEOUT_MS: number;
51
+ export declare const DEFAULT_MAX_NODE_VISITS = 50;
52
+ /**
53
+ * How long a run about to claim `verified` waits for abandoned attempts to stop.
54
+ *
55
+ * Short on purpose. The wait exists to catch an attempt that is about to finish,
56
+ * not to hold a run hostage to one that never will — a node stuck forever must
57
+ * not stop the run from ending, only from being called verified.
58
+ */
59
+ export declare const QUIESCE_GRACE_MS = 2000;
60
+ /** Terminal verifier appended when the workflow declares a run-level contract. */
61
+ export declare const IMPLICIT_VERIFIER_ID = "__verify";
62
+ /** How much node text the checkpoint carries inline. The rest goes to disk. */
63
+ export declare const OUTPUT_PREVIEW_CHARS = 4000;
64
+ export interface NodeResult {
65
+ ok: boolean;
66
+ output?: string;
67
+ error?: string;
68
+ evidenceId?: string;
69
+ /** Verifier nodes only: whether the contract passed. */
70
+ passed?: boolean;
71
+ /** Verifier nodes only: the tree digest at the moment the checks finished. */
72
+ treeFingerprint?: string;
73
+ /** human_gate nodes: park the run until a human answers. */
74
+ awaitHuman?: boolean;
75
+ costUsd?: number;
76
+ artifacts?: string[];
77
+ /** Advisory. Recorded on the run; never consulted for completion. */
78
+ consensusVotes?: ConsensusVote[];
79
+ /** Router nodes: the node id control should move to. */
80
+ goto?: string;
81
+ /** Subflow nodes: the child run this node started. */
82
+ childRunId?: string;
83
+ /** Mode-specific payload to checkpoint on the node record. */
84
+ data?: unknown;
85
+ }
86
+ export interface NodeContext {
87
+ runId: string;
88
+ record: RunRecord;
89
+ cwd: string;
90
+ /**
91
+ * 1-based attempt number for this visit. Nodes that name external resources
92
+ * must include it: a timed-out attempt is abandoned, not killed, so it is
93
+ * still running — and still holds a `finally` that tears down whatever it
94
+ * named. Sharing a name with the retry means the dying attempt stops the live
95
+ * one's session.
96
+ */
97
+ attempt: number;
98
+ manager?: SessionManagerLike;
99
+ logger: Logger;
100
+ signal: {
101
+ aborted: boolean;
102
+ };
103
+ /** Text injected by `steer()` since this node last ran. Consumed by the node. */
104
+ takeSteer(): string[];
105
+ emit(event: KernelEvent): void;
106
+ /** The workflow-level contract, for a `contract: 'run'` verifier node. */
107
+ runContract?: AcceptanceContract;
108
+ /** In-memory values the spec deliberately does not carry. Never persisted. */
109
+ secrets: Record<string, unknown>;
110
+ /** `StartOptions.tag` for the start that launched this node, when one was given. */
111
+ tag?: string;
112
+ /**
113
+ * Publish the live engine object for this node (a `Council`, a `Fanout`, an
114
+ * autoloop dispatcher) so in-flight control — inject, abort, chat — can reach
115
+ * it. In-memory by necessity: you cannot inject into a turn in a process that
116
+ * is gone, and pretending otherwise would be the kind of claim this release
117
+ * exists to remove. After a restart a run resumes; it does not reconnect.
118
+ */
119
+ setHandle(handle: unknown): void;
120
+ /** Checkpoint mode payload as the node runs, not only when it finishes. */
121
+ publish(data: unknown): void;
122
+ /**
123
+ * Record a child run started by this node, immediately.
124
+ *
125
+ * It has to land before the child finishes, not with the node's result: the
126
+ * whole point is that cancelling the parent can find the child, and a parent
127
+ * cancelled mid-subflow is exactly when the id is not yet in the record.
128
+ */
129
+ setChild(childRunId: string): void;
130
+ }
131
+ export type NodeExecutor = (node: NodeSpec, ctx: NodeContext) => Promise<NodeResult>;
132
+ export interface KernelOptions {
133
+ manager?: SessionManagerLike;
134
+ logger?: Logger;
135
+ executors?: Partial<Record<NodeKind, NodeExecutor>>;
136
+ /** Default per-node wall clock. */
137
+ nodeTimeoutMs?: number;
138
+ }
139
+ export interface StartOptions {
140
+ runId?: string;
141
+ cwd?: string;
142
+ /** Overrides `spec.contract`. Callers only — never sourced from agent output. */
143
+ contract?: unknown;
144
+ /**
145
+ * Opaque label for THIS start, handed to nodes as `ctx.tag`.
146
+ *
147
+ * A run id can be reused — a start that failed frees it for a retry — so
148
+ * anything the caller keys on the run id can be clobbered by the next start.
149
+ * The tag identifies one attempt at running it.
150
+ */
151
+ tag?: string;
152
+ /**
153
+ * Values a node needs but the spec must not carry — custom-engine configs,
154
+ * whose `env` holds credentials.
155
+ *
156
+ * These live only in this process's memory. They are never written to
157
+ * `spec.json` or `run.json`, and a resume in another process has to supply
158
+ * them again rather than reading them back, which is the point.
159
+ */
160
+ secrets?: Record<string, unknown>;
161
+ }
162
+ /**
163
+ * Append the implicit terminal verifier when a run-level contract exists and the
164
+ * author did not already place one. This is what makes `completed` mean "checked".
165
+ */
166
+ /**
167
+ * Reject a spec that cannot execute correctly, before anything runs.
168
+ *
169
+ * These are all mistakes that used to surface much later and much worse: a
170
+ * duplicate node id silently overwrote a record so one of the two nodes had no
171
+ * state; a `next` or router target naming a node that does not exist failed the
172
+ * run halfway through with `unknown node`; a negative retry or visit bound was
173
+ * accepted and then behaved arbitrarily.
174
+ */
175
+ export declare function validateSpec(spec: WorkflowSpec): void;
176
+ export declare function prepareSpec(spec: WorkflowSpec): WorkflowSpec;
177
+ export declare class RunKernel extends EventEmitter {
178
+ private readonly manager?;
179
+ private readonly logger;
180
+ private readonly executors;
181
+ private readonly nodeTimeoutMs;
182
+ private readonly live;
183
+ /**
184
+ * This kernel's identity as a run owner.
185
+ *
186
+ * Per instance, not per process: two `RunKernel`s in one process — two
187
+ * SessionManagers is not exotic — would otherwise share a pid and each treat
188
+ * the other's lease as its own, so both would execute the same run.
189
+ */
190
+ readonly ownerId: string;
191
+ /**
192
+ * Per-run secrets, in memory only. Keyed by run id so a resume in this
193
+ * process can re-use what the start supplied; a resume elsewhere must be
194
+ * given them again.
195
+ */
196
+ private readonly _secrets;
197
+ /** Caller label for the most recent start of each run. */
198
+ private readonly _tags;
199
+ constructor(opts?: KernelOptions);
200
+ /** Stop and await any live run holding this id, so a reuse cannot overlap it. */
201
+ private _retire;
202
+ /** Register (or replace) the executor for one node kind. */
203
+ setExecutor(kind: NodeKind, executor: NodeExecutor): void;
204
+ /**
205
+ * Open a transaction over a run this kernel has just claimed.
206
+ *
207
+ * The abort signal is created here rather than in `_launch` because the
208
+ * transaction has to be able to stop the run the moment a write is refused,
209
+ * and that can happen before the handle exists.
210
+ */
211
+ private _open;
212
+ /**
213
+ * Hand a claim back, retrying while the lock is merely busy.
214
+ *
215
+ * Best-effort with a bound: contention here is measured in microseconds, so a
216
+ * few backed-off attempts cover everything short of a wedged filesystem — and
217
+ * if it is wedged, saying so beats retrying forever.
218
+ */
219
+ private _scheduleRelease;
220
+ start(rawSpec: WorkflowSpec, opts?: StartOptions): Promise<RunRecord>;
221
+ /**
222
+ * Re-attach to a run whose process died. Nodes already marked succeeded are not
223
+ * re-run; the node that was in flight when the process ended is retried from
224
+ * the start, because a half-finished node left no result to trust.
225
+ */
226
+ resume(runId: string, opts?: {
227
+ restart?: boolean;
228
+ secrets?: Record<string, unknown>;
229
+ tag?: string;
230
+ }): Promise<RunRecord>;
231
+ cancel(runId: string): boolean;
232
+ /** Queue text for the current (or next) agent node. */
233
+ steer(runId: string, text: string): boolean;
234
+ /** Answer a parked `human_gate`. */
235
+ approve(runId: string, approved: boolean): boolean;
236
+ get(runId: string): RunRecord | undefined;
237
+ /**
238
+ * The live engine object a running node published, if the run is still going
239
+ * in this process. Undefined once the node finishes or the process restarts —
240
+ * which is the honest answer, not a gap.
241
+ */
242
+ handle<T>(runId: string, nodeId: string): T | undefined;
243
+ list(query?: ListRunsQuery): RunSummary[];
244
+ /**
245
+ * Remove a run.
246
+ *
247
+ * `expectTag` guards against deleting the wrong incarnation: a run id is
248
+ * reused when a failed start frees it, so a dying start's cleanup could
249
+ * otherwise delete the retry that had already taken the id. When the tag does
250
+ * not match, nothing is touched.
251
+ *
252
+ * Deleting takes the incarnation with it, which is what makes the id safe to
253
+ * reuse: any guard still held by an abandoned attempt of the deleted run names
254
+ * an incarnation that no longer exists, so it cannot write to whatever takes
255
+ * the id next.
256
+ */
257
+ delete(runId: string, opts?: {
258
+ expectTag?: string;
259
+ }): boolean;
260
+ /**
261
+ * Resolves when the run reaches a terminal state. A run that already finished
262
+ * (or belongs to another process) resolves from disk, so the caller does not
263
+ * have to race the completion.
264
+ */
265
+ wait(runId: string): Promise<RunRecord | undefined>;
266
+ shutdown(): Promise<void>;
267
+ private _launch;
268
+ private _setRunState;
269
+ private _setNodeState;
270
+ private _nodeSpec;
271
+ private _nextInOrder;
272
+ private _firstPending;
273
+ private _executeNode;
274
+ /**
275
+ * Wait for abandoned attempts to stop, so a terminal verdict describes a tree
276
+ * nobody is still writing to.
277
+ *
278
+ * A timed-out node is not killed — JS gives us no way to — so a run used to
279
+ * stamp `completed / verified`, then have the abandoned attempt write to the
280
+ * workspace afterwards. The evidence was accurate at the moment it was taken
281
+ * and wrong seconds later, with nothing recording that.
282
+ */
283
+ private _awaitQuiescence;
284
+ private _run;
285
+ private _runWithRetry;
286
+ private _park;
287
+ /** Node kinds that can change the workspace. Routers and gates cannot. */
288
+ private static readonly SIDE_EFFECT_KINDS;
289
+ /**
290
+ * Fold a node's result into the run — output, artifacts, cost, votes, verdict
291
+ * — as one committed change.
292
+ *
293
+ * Every one of these used to be assigned straight onto the live record, with
294
+ * only the events fenced. A superseded owner therefore returned a record
295
+ * carrying an output, a cost and a passing verdict that the disk had refused.
296
+ */
297
+ private _absorb;
298
+ /**
299
+ * Decide the terminal state. `completed` requires that nothing refuted the run;
300
+ * a run with no contract completes as `unverified`, which says we did not check
301
+ * rather than claiming success.
302
+ */
303
+ private _finish;
304
+ /**
305
+ * Why a passing verdict no longer stands, or undefined if it still does.
306
+ *
307
+ * `prepareSpec` cannot enforce this structurally: a router can send control
308
+ * anywhere, so which node runs last is not a property of the spec. And a
309
+ * "nothing after the verifier" rule would be the wrong rule anyway — what
310
+ * matters is not that a node ran, but that the tree moved. So this measures.
311
+ *
312
+ * The caller drops the outcome to `unverified`, not `refuted`: no check
313
+ * failed. We simply no longer know, and saying so is the whole point of having
314
+ * three outcomes.
315
+ */
316
+ private _verdictWentStale;
317
+ }
318
+ /** Run directory for a given run — re-exported so callers need not import the store. */
319
+ export { runDir, summarize };