@victframework/runtime 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (115) hide show
  1. package/dist/agent-governance.d.ts +285 -0
  2. package/dist/agent-governance.js +438 -0
  3. package/dist/agent-governance.js.map +1 -0
  4. package/dist/agent-registry.d.ts +103 -0
  5. package/dist/agent-registry.js +758 -0
  6. package/dist/agent-registry.js.map +1 -0
  7. package/dist/agent-types.d.ts +433 -0
  8. package/dist/agent-types.js +480 -0
  9. package/dist/agent-types.js.map +1 -0
  10. package/dist/authority.d.ts +83 -0
  11. package/dist/authority.js +170 -0
  12. package/dist/authority.js.map +1 -0
  13. package/dist/boundary-conformance.d.ts +51 -0
  14. package/dist/boundary-conformance.js +364 -0
  15. package/dist/boundary-conformance.js.map +1 -0
  16. package/dist/control-conformance.d.ts +68 -0
  17. package/dist/control-conformance.js +1131 -0
  18. package/dist/control-conformance.js.map +1 -0
  19. package/dist/control-in-memory.d.ts +257 -0
  20. package/dist/control-in-memory.js +886 -0
  21. package/dist/control-in-memory.js.map +1 -0
  22. package/dist/control-types.d.ts +1017 -0
  23. package/dist/control-types.js +779 -0
  24. package/dist/control-types.js.map +1 -0
  25. package/dist/durable-run.d.ts +73 -0
  26. package/dist/durable-run.js +262 -0
  27. package/dist/durable-run.js.map +1 -0
  28. package/dist/effect-policy.d.ts +22 -0
  29. package/dist/effect-policy.js +45 -0
  30. package/dist/effect-policy.js.map +1 -0
  31. package/dist/errors.d.ts +36 -0
  32. package/dist/errors.js +71 -0
  33. package/dist/errors.js.map +1 -0
  34. package/dist/in-memory-stores.d.ts +16 -0
  35. package/dist/in-memory-stores.js +522 -0
  36. package/dist/in-memory-stores.js.map +1 -0
  37. package/dist/index.d.ts +41 -0
  38. package/dist/index.js +26 -0
  39. package/dist/index.js.map +1 -0
  40. package/dist/operator-config.d.ts +141 -0
  41. package/dist/operator-config.js +289 -0
  42. package/dist/operator-config.js.map +1 -0
  43. package/dist/orchestration-activation.d.ts +116 -0
  44. package/dist/orchestration-activation.js +223 -0
  45. package/dist/orchestration-activation.js.map +1 -0
  46. package/dist/orchestration-canary-conformance.d.ts +10 -0
  47. package/dist/orchestration-canary-conformance.js +726 -0
  48. package/dist/orchestration-canary-conformance.js.map +1 -0
  49. package/dist/orchestration-commands.d.ts +47 -0
  50. package/dist/orchestration-commands.js +538 -0
  51. package/dist/orchestration-commands.js.map +1 -0
  52. package/dist/orchestration-conformance.d.ts +59 -0
  53. package/dist/orchestration-conformance.js +594 -0
  54. package/dist/orchestration-conformance.js.map +1 -0
  55. package/dist/orchestration-driver-types.d.ts +163 -0
  56. package/dist/orchestration-driver-types.js +21 -0
  57. package/dist/orchestration-driver-types.js.map +1 -0
  58. package/dist/orchestration-driver.d.ts +48 -0
  59. package/dist/orchestration-driver.js +1030 -0
  60. package/dist/orchestration-driver.js.map +1 -0
  61. package/dist/orchestration-in-memory.d.ts +18 -0
  62. package/dist/orchestration-in-memory.js +1313 -0
  63. package/dist/orchestration-in-memory.js.map +1 -0
  64. package/dist/orchestration-join-conformance.d.ts +20 -0
  65. package/dist/orchestration-join-conformance.js +542 -0
  66. package/dist/orchestration-join-conformance.js.map +1 -0
  67. package/dist/orchestration-plan.d.ts +52 -0
  68. package/dist/orchestration-plan.js +265 -0
  69. package/dist/orchestration-plan.js.map +1 -0
  70. package/dist/orchestration-race-conformance.d.ts +35 -0
  71. package/dist/orchestration-race-conformance.js +1265 -0
  72. package/dist/orchestration-race-conformance.js.map +1 -0
  73. package/dist/orchestration-remediation-conformance.d.ts +54 -0
  74. package/dist/orchestration-remediation-conformance.js +632 -0
  75. package/dist/orchestration-remediation-conformance.js.map +1 -0
  76. package/dist/orchestration-store-types.d.ts +568 -0
  77. package/dist/orchestration-store-types.js +2 -0
  78. package/dist/orchestration-store-types.js.map +1 -0
  79. package/dist/pack-conformance.d.ts +59 -0
  80. package/dist/pack-conformance.js +222 -0
  81. package/dist/pack-conformance.js.map +1 -0
  82. package/dist/pack-install.d.ts +44 -0
  83. package/dist/pack-install.js +126 -0
  84. package/dist/pack-install.js.map +1 -0
  85. package/dist/registry.d.ts +107 -0
  86. package/dist/registry.js +481 -0
  87. package/dist/registry.js.map +1 -0
  88. package/dist/runtime.d.ts +158 -0
  89. package/dist/runtime.js +1341 -0
  90. package/dist/runtime.js.map +1 -0
  91. package/dist/serialization.d.ts +26 -0
  92. package/dist/serialization.js +162 -0
  93. package/dist/serialization.js.map +1 -0
  94. package/dist/store-conformance.d.ts +45 -0
  95. package/dist/store-conformance.js +876 -0
  96. package/dist/store-conformance.js.map +1 -0
  97. package/dist/store-errors.d.ts +49 -0
  98. package/dist/store-errors.js +48 -0
  99. package/dist/store-errors.js.map +1 -0
  100. package/dist/store-types.d.ts +331 -0
  101. package/dist/store-types.js +14 -0
  102. package/dist/store-types.js.map +1 -0
  103. package/dist/store-validation.d.ts +63 -0
  104. package/dist/store-validation.js +164 -0
  105. package/dist/store-validation.js.map +1 -0
  106. package/dist/stream-hub.d.ts +151 -0
  107. package/dist/stream-hub.js +402 -0
  108. package/dist/stream-hub.js.map +1 -0
  109. package/dist/testing.d.ts +25 -0
  110. package/dist/testing.js +19 -0
  111. package/dist/testing.js.map +1 -0
  112. package/dist/types.d.ts +221 -0
  113. package/dist/types.js +2 -0
  114. package/dist/types.js.map +1 -0
  115. package/package.json +45 -0
@@ -0,0 +1,1030 @@
1
+ import { sanitizeContractIssues } from '@victframework/contracts';
2
+ import { summarizeOutput, } from '@victframework/kernel';
3
+ import { VictRuntimeError, classifyInvocationFailure, runtimeError } from './errors.js';
4
+ import { VictStoreError } from './store-errors.js';
5
+ import { decideEffectAuthorization } from './effect-policy.js';
6
+ import { deriveAttemptId, deriveIdempotencyKey, deriveInvocationId, resolveBindings, resolveGraphForActivation, rootTokenId, } from './orchestration-activation.js';
7
+ import { planContinuation } from './orchestration-plan.js';
8
+ import { ORCHESTRATION_LIMITS } from './orchestration-driver-types.js';
9
+ class TimeoutError extends Error {
10
+ constructor() {
11
+ super('The attempt exceeded its persisted deadline.');
12
+ }
13
+ }
14
+ function randomId() {
15
+ const bytes = new Uint8Array(8);
16
+ globalThis.crypto.getRandomValues(bytes);
17
+ return Array.from(bytes, (byte) => byte.toString(16).padStart(2, '0')).join('');
18
+ }
19
+ /** Extract the decision value from a DecisionResult-shaped output. */
20
+ function extractDecisionValue(output) {
21
+ if (output === null || typeof output !== 'object') {
22
+ return undefined;
23
+ }
24
+ const candidate = output;
25
+ if (typeof candidate.route !== 'string') {
26
+ return undefined;
27
+ }
28
+ return { route: candidate.route, value: candidate.value };
29
+ }
30
+ function isTerminalStatus(status) {
31
+ return status === 'completed' || status === 'failed' || status === 'cancelled';
32
+ }
33
+ /**
34
+ * Reduce author-controlled contract issues to structurally safe evidence.
35
+ * A raw `parse()` implementation may place arbitrary strings (custom
36
+ * messages, payload echoes, nested secrets, alphanumeric codes/paths)
37
+ * anywhere in an issue object, so ONLY the framework-controlled shared
38
+ * sanitizer output survives into events: an allowlisted issue code, an
39
+ * ordinal path, and a framework-generated message. See
40
+ * `sanitizeContractIssues` in `@victframework/contracts` for the exact policy.
41
+ */
42
+ function safeContractIssues(issues) {
43
+ return sanitizeContractIssues(issues);
44
+ }
45
+ /**
46
+ * Invoke an author-supplied parser safely. A parser throw is captured and
47
+ * returned as an untrusted marker; it NEVER propagates into the driver (a
48
+ * throw escaping #executeAttempt would wedge the run in a silent
49
+ * reclaim/re-throw cycle — the Stage 03 LOW-1 defect). The caller converts
50
+ * a captured throw into a sanitized terminal failure.
51
+ */
52
+ function parseSafely(contract, input) {
53
+ try {
54
+ return { result: contract.parse(input) };
55
+ }
56
+ catch (cause) {
57
+ return { threw: cause };
58
+ }
59
+ }
60
+ export class OrchestrationDriver {
61
+ #deps;
62
+ #resolved = new Map();
63
+ /** In-process view of each run's completed root output (retention-independent). */
64
+ #completedOutputs = new Map();
65
+ /** In-flight cooperative-abort contexts per run (cancellation + shutdown). */
66
+ #inflightControllers = new Map();
67
+ constructor(deps) {
68
+ this.#deps = deps;
69
+ }
70
+ get orchestration() {
71
+ return this.#deps.orchestration;
72
+ }
73
+ get deps() {
74
+ return this.#deps;
75
+ }
76
+ /** Track one in-flight cooperative-abort context for its run. */
77
+ #trackInflight(runId, controller) {
78
+ let set = this.#inflightControllers.get(runId);
79
+ if (set === undefined) {
80
+ set = new Set();
81
+ this.#inflightControllers.set(runId, set);
82
+ }
83
+ set.add(controller);
84
+ }
85
+ #untrackInflight(runId, controller) {
86
+ this.#inflightControllers.get(runId)?.delete(controller);
87
+ }
88
+ /**
89
+ * Cooperatively abort every in-flight capability context of one run
90
+ * (used when a durable cancellation request lands). The capability's own
91
+ * abort handling decides how to unwind; the durable transition boundary
92
+ * classifies the attempt outcome.
93
+ */
94
+ abortInflight(runId) {
95
+ for (const controller of this.#inflightControllers.get(runId) ?? []) {
96
+ controller.abort();
97
+ }
98
+ }
99
+ time() {
100
+ return (this.#deps.time ?? {
101
+ now: () => this.#deps.clock.now(),
102
+ delay: (ms) => new Promise((resolve) => setTimeout(resolve, Math.max(0, ms))),
103
+ });
104
+ }
105
+ /** Resolve (and cache) the exact executable meaning of one activation. */
106
+ #resolveExecution(activationVersion) {
107
+ const cached = this.#resolved.get(activationVersion);
108
+ if (cached) {
109
+ return cached;
110
+ }
111
+ const deps = {
112
+ catalog: { get: this.#deps.catalog.get },
113
+ registry: this.#deps.registry,
114
+ };
115
+ const resolution = (async () => {
116
+ const graph = await resolveGraphForActivation(deps, activationVersion);
117
+ if (!graph.ok || graph.graph === undefined) {
118
+ throw new VictRuntimeError('VICT_RUNTIME_ACTIVATION_UNAVAILABLE', `${graph.message ?? 'Activation unavailable.'} [${(graph.issues ?? []).map((issue) => issue.code).join(',') || 'none'}]`);
119
+ }
120
+ const bindings = await resolveBindings(deps, activationVersion);
121
+ if (!bindings.ok || bindings.bindings === undefined || bindings.contracts === undefined) {
122
+ throw new VictRuntimeError('VICT_RUNTIME_ACTIVATION_UNAVAILABLE', bindings.message ?? 'Bindings unavailable.');
123
+ }
124
+ return { graph: graph.graph, bindings: bindings.bindings, contracts: bindings.contracts };
125
+ })();
126
+ // Cache only successful resolutions; failures are retried later.
127
+ this.#resolved.set(activationVersion, resolution);
128
+ resolution.catch(() => this.#resolved.delete(activationVersion));
129
+ return resolution;
130
+ }
131
+ /** Root token id helper for the facade. */
132
+ static rootTokenId(runId) {
133
+ return rootTokenId(runId);
134
+ }
135
+ /** @internal Facade accessor: resolve the exact compiled graph for an activation. */
136
+ async resolveGraphForDriver(activationVersion) {
137
+ const resolved = await this.#resolveExecution(activationVersion);
138
+ return resolved.graph;
139
+ }
140
+ /** @internal Facade accessor: resolve the exact pinned bindings. */
141
+ async resolveBindingsForDriver(activationVersion) {
142
+ await this.#resolveExecution(activationVersion);
143
+ const bindings = await resolveBindings({ catalog: { get: this.#deps.catalog.get }, registry: this.#deps.registry }, activationVersion);
144
+ if (!bindings.ok || bindings.bindings === undefined || bindings.contracts === undefined) {
145
+ throw new VictRuntimeError('VICT_RUNTIME_ACTIVATION_UNAVAILABLE', bindings.message ?? 'Bindings unavailable.');
146
+ }
147
+ return { bindings: bindings.bindings, contracts: bindings.contracts };
148
+ }
149
+ /** Public re-exports for facade wiring. */
150
+ static resolveGraph = resolveGraphForActivation;
151
+ static resolveBindingsFn = resolveBindings;
152
+ /**
153
+ * Start one orchestration run and drive it until terminal or quiescent.
154
+ */
155
+ async startRun(graph, input, mode, runId, options = {}) {
156
+ const now = this.#deps.clock.now();
157
+ const events = [
158
+ {
159
+ type: 'run.started',
160
+ runId,
161
+ graphId: graph.id,
162
+ graphVersion: graph.graphVersion,
163
+ capabilitySetVersion: graph.capabilitySetVersion,
164
+ activationVersion: graph.activationVersion,
165
+ timestamp: now,
166
+ },
167
+ ];
168
+ await this.#deps.orchestration.createOrchestrationRun({
169
+ runId,
170
+ graphId: graph.id,
171
+ graphVersion: graph.graphVersion,
172
+ capabilitySetVersion: graph.capabilitySetVersion,
173
+ activationVersion: graph.activationVersion,
174
+ mode,
175
+ retention: this.#deps.retention,
176
+ rootTokenId: rootTokenId(runId),
177
+ entryNodeId: graph.entryNodeId,
178
+ checkpoint: input,
179
+ events,
180
+ now,
181
+ });
182
+ return this.#drive(runId, mode, options);
183
+ }
184
+ /**
185
+ * Drive an existing run to terminal/quiescent against its EXACT pinned
186
+ * activation. A newer selected activation never affects this run.
187
+ */
188
+ async resumeRun(runId, options = {}) {
189
+ const stored = await this.#deps.orchestration.getOrchestrationRun(runId);
190
+ if (!stored) {
191
+ throw new VictRuntimeError('VICT_RUN_NOT_FOUND', `No orchestration run '${runId}' exists.`);
192
+ }
193
+ if (isTerminalStatus(stored.status)) {
194
+ return resultFromRun(stored, this.#deps.orchestration);
195
+ }
196
+ return this.#drive(runId, stored.mode, options);
197
+ }
198
+ async #result(runId) {
199
+ const run = await this.#deps.orchestration.getOrchestrationRun(runId);
200
+ if (!run) {
201
+ throw new VictRuntimeError('VICT_RUN_NOT_FOUND', `No orchestration run '${runId}' exists.`);
202
+ }
203
+ return resultFromRun(run, this.#deps.orchestration, this.#completedOutputs);
204
+ }
205
+ /**
206
+ * The bounded worker loop: claim ready work, execute it concurrently, and
207
+ * feed every result back as a guarded atomic transition until the run is
208
+ * terminal or quiescent.
209
+ */
210
+ async #drive(runId, mode, options = {}) {
211
+ const run = await this.#deps.orchestration.getOrchestrationRun(runId);
212
+ if (!run) {
213
+ throw new VictRuntimeError('VICT_RUN_NOT_FOUND', `No orchestration run '${runId}' exists.`);
214
+ }
215
+ const resolved = await this.#resolveExecution(run.activationVersion);
216
+ const concurrency = Math.min(Math.max(1, options.concurrency ?? ORCHESTRATION_LIMITS.defaultConcurrency), ORCHESTRATION_LIMITS.maxConcurrency);
217
+ const inFlight = new Set();
218
+ for (;;) {
219
+ while (inFlight.size < concurrency) {
220
+ const claim = await this.#deps.orchestration.claimReadyToken({
221
+ runId,
222
+ ownerId: this.#deps.ownerId,
223
+ leaseExpiresAt: this.#deps.clock.now() + (this.#deps.leaseMs ?? DEFAULT_LEASE_MS),
224
+ now: this.#deps.clock.now(),
225
+ planner: {
226
+ invocationIdFor: (token) => deriveInvocationId({
227
+ runId,
228
+ activationVersion: run.activationVersion,
229
+ lineage: token.lineage,
230
+ nodeId: token.nodeId,
231
+ }),
232
+ attemptIdFor: (token, attemptNumber) => deriveAttemptId(deriveInvocationId({
233
+ runId,
234
+ activationVersion: run.activationVersion,
235
+ lineage: token.lineage,
236
+ nodeId: token.nodeId,
237
+ }), attemptNumber),
238
+ planFor: (token) => {
239
+ const node = resolved.graph.getNode(token.nodeId);
240
+ const binding = node ? resolved.bindings.get(node.capability) : undefined;
241
+ const invocationId = deriveInvocationId({
242
+ runId,
243
+ activationVersion: run.activationVersion,
244
+ lineage: token.lineage,
245
+ nodeId: token.nodeId,
246
+ });
247
+ return {
248
+ capabilityId: node?.capability ?? '',
249
+ effectClass: binding?.effect ?? 'pure',
250
+ deadlineAt: node?.timeoutMs !== undefined ? this.#deps.clock.now() + node.timeoutMs : null,
251
+ idempotencyKey: node?.retry !== undefined
252
+ ? deriveIdempotencyKey({
253
+ runId,
254
+ activationVersion: run.activationVersion,
255
+ lineage: token.lineage,
256
+ nodeId: token.nodeId,
257
+ invocationId,
258
+ })
259
+ : null,
260
+ };
261
+ },
262
+ },
263
+ });
264
+ if (!claim.claimed) {
265
+ break;
266
+ }
267
+ const attempt = this.#executeAttempt(resolved, claim.claim, mode, options);
268
+ const wrapped = attempt
269
+ .catch(() => undefined)
270
+ .finally(() => {
271
+ inFlight.delete(wrapped);
272
+ });
273
+ inFlight.add(wrapped);
274
+ }
275
+ if (inFlight.size === 0) {
276
+ break;
277
+ }
278
+ await Promise.race([...inFlight]);
279
+ }
280
+ // Cancellation was requested and no work remains: finalize once.
281
+ const current = await this.#deps.orchestration.getOrchestrationRun(runId);
282
+ if (current && !isTerminalStatus(current.status) && current.cancellation !== null) {
283
+ await this.#deps.orchestration.applyCancellation({
284
+ runId,
285
+ now: this.#deps.clock.now(),
286
+ requestId: current.cancellation.requestId,
287
+ reasonCode: current.cancellation.reasonCode,
288
+ steps: current.steps,
289
+ removeCheckpoints: [],
290
+ events: [
291
+ {
292
+ type: 'run.cancelled',
293
+ requestId: current.cancellation.requestId,
294
+ reasonCode: current.cancellation.reasonCode,
295
+ steps: current.steps,
296
+ runId,
297
+ graphId: resolved.graph.id,
298
+ graphVersion: resolved.graph.graphVersion,
299
+ capabilitySetVersion: resolved.graph.capabilitySetVersion,
300
+ activationVersion: resolved.graph.activationVersion,
301
+ timestamp: this.#deps.clock.now(),
302
+ },
303
+ ],
304
+ });
305
+ }
306
+ return this.#result(runId);
307
+ }
308
+ /**
309
+ * Execute one claimed attempt: validate the pinned checkpoint input,
310
+ * enforce effect policy, race the invocation against the persisted
311
+ * deadline with cooperative abort, and commit the planned continuation.
312
+ * No capability may run unless durable intent committed (guaranteed by
313
+ * the claim transaction) and the claim is current (owner + fence).
314
+ */
315
+ async #executeAttempt(resolved, claim, mode, options = {}) {
316
+ const { graph, bindings, contracts } = resolved;
317
+ const run = (await this.#deps.orchestration.getOrchestrationRun(claim.token.runId));
318
+ const node = graph.getNode(claim.token.nodeId);
319
+ if (!node) {
320
+ return; // defensive: compiled graphs always resolve their nodes
321
+ }
322
+ const binding = bindings.get(node.capability);
323
+ const now = this.#deps.clock.now();
324
+ const envelope = {
325
+ runId: run.runId,
326
+ graphId: graph.id,
327
+ graphVersion: graph.graphVersion,
328
+ capabilitySetVersion: graph.capabilitySetVersion,
329
+ activationVersion: graph.activationVersion,
330
+ timestamp: now,
331
+ };
332
+ const onEvent = (event) => {
333
+ options.onEvent?.(event);
334
+ };
335
+ // ---- Validate the checkpoint input against the node input contract ----
336
+ let inputPayload = claim.checkpoint;
337
+ if (node.inputContractId !== undefined) {
338
+ const contract = contracts.get(node.inputContractId);
339
+ if (!contract) {
340
+ await this.#completeWithOutcome(resolved, claim, envelope, mode, {
341
+ kind: 'failed',
342
+ error: {
343
+ code: 'VICT_KERNEL_UNKNOWN_CONTRACT',
344
+ message: `Node '${node.id}' references unknown input contract '${node.inputContractId}'.`,
345
+ retryable: false,
346
+ },
347
+ }, undefined, onEvent);
348
+ return;
349
+ }
350
+ const parsed = parseSafely(contract, inputPayload);
351
+ if ('threw' in parsed) {
352
+ // Stage 04 (Stage 03 LOW-1 closure): a throwing author parser is a
353
+ // TERMINAL failure, committed durably as one sanitized transition —
354
+ // never a silent reclaim loop, never an error-edge route, never a
355
+ // downstream invocation. The thrown message is not retained.
356
+ await this.#completeWithOutcome(resolved, claim, envelope, mode, {
357
+ kind: 'failed',
358
+ error: this.#parserThrewError('input', node.inputContractId, node.id, parsed.threw),
359
+ }, undefined, onEvent, undefined, { forceTerminalFailure: true });
360
+ return;
361
+ }
362
+ if (!parsed.result.ok) {
363
+ const error = {
364
+ code: 'VICT_KERNEL_CONTRACT_REJECTED',
365
+ message: `Input contract '${node.inputContractId}' rejected the value at node '${node.id}'.`,
366
+ retryable: false,
367
+ };
368
+ onEvent({
369
+ type: 'contract.rejected',
370
+ stage: 'input',
371
+ nodeId: node.id,
372
+ capabilityId: node.capability,
373
+ contractId: node.inputContractId,
374
+ issues: safeContractIssues(parsed.result.issues),
375
+ ...envelope,
376
+ timestamp: this.#deps.clock.now(),
377
+ });
378
+ await this.#completeWithOutcome(resolved, claim, envelope, mode, { kind: 'failed', error }, undefined, onEvent);
379
+ return;
380
+ }
381
+ inputPayload = parsed.result.value;
382
+ }
383
+ // ---- Control nodes complete directly (no capability invocation) -----
384
+ if (node.kind === 'wait' || node.kind === 'fork' || node.kind === 'join') {
385
+ if (node.kind === 'wait') {
386
+ // First arrival parks (creates the durable wait); a post-wake
387
+ // execution (the wait is already resolved) advances along the
388
+ // wait's success edge with the resolved payload. The wake/park
389
+ // decision is bound to the CURRENT wait instance (token + node
390
+ // identity): a resolved earlier wait elsewhere on the same token
391
+ // lineage must never satisfy this node's own wait.
392
+ const snapshot = await this.#deps.orchestration.getOrchestrationSnapshot(run.runId);
393
+ const hasOpenWait = (snapshot?.waits ?? []).some((wait) => wait.tokenId === claim.token.tokenId &&
394
+ wait.nodeId === claim.token.nodeId &&
395
+ wait.status === 'open');
396
+ if (hasOpenWait ||
397
+ !(snapshot?.waits ?? []).some((wait) => wait.tokenId === claim.token.tokenId &&
398
+ wait.nodeId === claim.token.nodeId &&
399
+ wait.status === 'resolved')) {
400
+ await this.#completeWithOutcome(resolved, claim, envelope, mode, { kind: 'completed', raw: inputPayload }, inputPayload, onEvent);
401
+ return;
402
+ }
403
+ // Wake path: the wait is already resolved. Do NOT park again and do
404
+ // NOT force a plain advance — the completion is re-planned so that a
405
+ // success target which is the fork's join routes through the durable
406
+ // branch-arrival boundary (canonical checkpoint, single join-ready
407
+ // token, validated join), exactly like any other branch completion.
408
+ await this.#completeWithOutcome(resolved, claim, envelope, mode, { kind: 'completed', raw: inputPayload }, inputPayload, onEvent, undefined, { resolvedWait: true });
409
+ return;
410
+ }
411
+ if (node.kind === 'join') {
412
+ // Durable join boundary: the claimed token carries the private
413
+ // canonical branch-result checkpoint created atomically by the
414
+ // final branch arrival. #completeWithOutcome validates the join's
415
+ // own declared output contract (outside any store transaction)
416
+ // and one atomic transition either advances downstream, completes
417
+ // a terminal join with the validated output, or fails the run
418
+ // with a sanitized contract error. No capability is ever invoked
419
+ // here and no author parser runs inside the persistence adapter.
420
+ await this.#completeWithOutcome(resolved, claim, envelope, mode, { kind: 'completed', raw: inputPayload }, inputPayload, onEvent);
421
+ return;
422
+ }
423
+ await this.#completeWithOutcome(resolved, claim, envelope, mode, { kind: 'completed', raw: inputPayload }, inputPayload, onEvent);
424
+ return;
425
+ }
426
+ // ---- Effect policy -------------------------------------------------
427
+ const decision = decideEffectAuthorization({ capabilityId: node.capability, effect: binding?.effect ?? 'pure', mode }, options.policy ?? this.#deps.defaultOverrides);
428
+ if (!decision.allowed) {
429
+ const reason = decision.reason ??
430
+ `Effect class '${binding?.effect ?? 'pure'}' is not allowed in '${mode}' mode.`;
431
+ const remediation = decision.remediation ??
432
+ 'Adjust the execution policy or provide an approved implementation.';
433
+ onEvent({
434
+ type: 'effect.blocked',
435
+ nodeId: node.id,
436
+ capabilityId: node.capability,
437
+ effect: binding?.effect ?? 'pure',
438
+ mode,
439
+ reason,
440
+ remediation,
441
+ ...envelope,
442
+ timestamp: this.#deps.clock.now(),
443
+ });
444
+ await this.#completeWithOutcome(resolved, claim, envelope, mode, {
445
+ kind: 'outcome_unknown',
446
+ error: {
447
+ code: 'VICT_ORCH_EFFECT_BLOCKED',
448
+ message: reason,
449
+ retryable: false,
450
+ },
451
+ }, undefined, onEvent);
452
+ return;
453
+ }
454
+ if (decision.useDouble) {
455
+ // Doubles are a Stage 02 sequential-engine facility; the durable
456
+ // orchestration engine always runs the pinned real binding.
457
+ // (Test doubles for orchestration runs are provided as capabilities.)
458
+ }
459
+ // ---- Cooperative deadline + invocation ------------------------------
460
+ const controller = new AbortController();
461
+ this.#trackInflight(run.runId, controller);
462
+ const deadlineAt = claim.deadlineAt;
463
+ const timeoutExceeded = deadlineAt !== null && deadlineAt < now;
464
+ let timedOut = false;
465
+ const deadlinePromise = deadlineAt !== null
466
+ ? this.time()
467
+ .delay(Math.max(0, deadlineAt - this.time().now()))
468
+ .then(() => {
469
+ timedOut = true;
470
+ controller.abort();
471
+ })
472
+ : null;
473
+ if (timeoutExceeded) {
474
+ timedOut = true;
475
+ }
476
+ let outcome;
477
+ try {
478
+ if (timedOut) {
479
+ throw new TimeoutError();
480
+ }
481
+ if (!binding) {
482
+ throw new VictRuntimeError('VICT_RUNTIME_CAPABILITY_MISSING', `No implementation available for capability '${node.capability}' in the pinned activation snapshot.`);
483
+ }
484
+ const context = {
485
+ runId: run.runId,
486
+ graphId: graph.id,
487
+ graphVersion: graph.graphVersion,
488
+ capabilitySetVersion: graph.capabilitySetVersion,
489
+ activationVersion: graph.activationVersion,
490
+ nodeId: node.id,
491
+ capabilityId: node.capability,
492
+ mode,
493
+ step: run.steps,
494
+ invokedVia: 'real',
495
+ invocationId: claim.invocationId,
496
+ attemptId: claim.attempt.attemptId,
497
+ attemptNumber: claim.attempt.attemptNumber,
498
+ ...(claim.idempotencyKey !== null ? { idempotencyKey: claim.idempotencyKey } : {}),
499
+ ...(deadlineAt !== null ? { deadlineAt } : {}),
500
+ abortSignal: controller.signal,
501
+ ...(claim.token.forkId !== null
502
+ ? {
503
+ branch: {
504
+ forkId: claim.token.forkId,
505
+ joinId: graph.joinOfFork(claim.token.forkId) ?? '',
506
+ branchKey: claim.token.branchKey ?? '',
507
+ lineage: claim.token.lineage,
508
+ },
509
+ }
510
+ : {}),
511
+ };
512
+ // Promise.resolve().then ensures a SYNCHRONOUS throw from the handler
513
+ // is still converted into a capability failure, not a driver failure.
514
+ const invokePromise = Promise.resolve()
515
+ .then(async () => binding.invoke(inputPayload, context))
516
+ .then((value) => ({ ok: true, value }), (cause) => {
517
+ // Stage 04: structured authority failures keep their stable
518
+ // code; anything else is reduced to CAPABILITY_THREW without
519
+ // retaining the thrown message.
520
+ const classified = classifyInvocationFailure(cause, node.capability, {
521
+ nodeId: node.id,
522
+ invokedVia: 'real',
523
+ });
524
+ return {
525
+ ok: false,
526
+ error: runtimeError(classified.code, classified.message, classified.details),
527
+ };
528
+ });
529
+ const invocation = deadlinePromise === null
530
+ ? await invokePromise
531
+ : await Promise.race([
532
+ invokePromise,
533
+ deadlinePromise.then(() => {
534
+ throw new TimeoutError();
535
+ }),
536
+ ]);
537
+ if (invocation.ok) {
538
+ outcome = { kind: 'completed', raw: invocation.value };
539
+ }
540
+ else {
541
+ outcome = { kind: 'failed', error: invocation.error };
542
+ }
543
+ }
544
+ catch (cause) {
545
+ if (cause instanceof TimeoutError || timedOut) {
546
+ outcome = { kind: 'timed_out' };
547
+ }
548
+ else {
549
+ outcome = {
550
+ kind: 'failed',
551
+ error: runtimeError('VICT_RUNTIME_STORE_FAILURE', 'The durable orchestration driver failed outside the capability invocation; the thrown message is not retained.', {
552
+ errorName: cause instanceof Error ? cause.name : typeof cause,
553
+ ...(cause instanceof VictStoreError ? { causeCode: cause.code } : {}),
554
+ phase: 'completeWithOutcome',
555
+ }),
556
+ };
557
+ }
558
+ }
559
+ void deadlinePromise;
560
+ this.#untrackInflight(run.runId, controller);
561
+ // A cooperative abort that was NOT the deadline is a cancellation or
562
+ // shutdown request: classify the attempt honestly as cancelled so no
563
+ // downstream continuation and no retry can start from it.
564
+ if (controller.signal.aborted && !timedOut && outcome.kind === 'failed') {
565
+ outcome = { kind: 'cancelled' };
566
+ }
567
+ await this.#completeWithOutcome(resolved, claim, envelope, mode, outcome, inputPayload, onEvent);
568
+ }
569
+ /**
570
+ * Plan and commit one completed attempt as one atomic guarded transition:
571
+ * outcome classification, contract validation, retry/timeout/ambiguity
572
+ * policy, events, checkpoints, waits, forks/joins, and run status.
573
+ * Conflicts re-derive from fresh durable state (bounded).
574
+ */
575
+ /** Sanitized structured error for a throwing author contract parser. */
576
+ #parserThrewError(stage, contractId, nodeId, cause) {
577
+ return runtimeError('VICT_RUNTIME_CONTRACT_PARSER_THREW', `Contract '${contractId}' threw while parsing the ${stage} value at node '${nodeId}'; the thrown message is not retained.`, {
578
+ stage,
579
+ contractId,
580
+ nodeId,
581
+ errorName: cause instanceof Error ? cause.name : typeof cause,
582
+ errorId: this.#deps.ids.errorId?.() ?? `err_${randomId()}`,
583
+ });
584
+ }
585
+ async #completeWithOutcome(resolved, claim, envelope, mode, rawOutcome, inputPayload, onEvent, forcedContinuation, planning) {
586
+ const { graph, bindings, contracts } = resolved;
587
+ const node = graph.getNode(claim.token.nodeId);
588
+ if (!node) {
589
+ return;
590
+ }
591
+ const binding = bindings.get(node.capability);
592
+ // A throwing author parser forces the completion to a TERMINAL failed
593
+ // transition (no retry, no error edge, no downstream invocation).
594
+ let forceTerminalFailure = planning?.forceTerminalFailure === true;
595
+ // ---- Normalize the outcome and validate output contracts ----------
596
+ let outcome = rawOutcome.kind === 'completed'
597
+ ? { kind: 'completed', outputSummary: { shape: 'undefined' } }
598
+ : rawOutcome;
599
+ let validatedOutput = undefined;
600
+ let decisionResult;
601
+ if (rawOutcome.kind === 'completed') {
602
+ validatedOutput = rawOutcome.raw;
603
+ if (node.outputContractId !== undefined) {
604
+ const contract = contracts.get(node.outputContractId);
605
+ if (!contract) {
606
+ outcome = {
607
+ kind: 'failed',
608
+ error: {
609
+ code: 'VICT_KERNEL_UNKNOWN_CONTRACT',
610
+ message: `Node '${node.id}' references unknown output contract '${node.outputContractId}'.`,
611
+ retryable: false,
612
+ },
613
+ };
614
+ validatedOutput = undefined;
615
+ }
616
+ else {
617
+ // Decision nodes validate the decision VALUE against the contract.
618
+ const candidate = node.kind === 'decision' ? extractDecisionValue(validatedOutput) : validatedOutput;
619
+ if (candidate === undefined) {
620
+ outcome = {
621
+ kind: 'failed',
622
+ error: {
623
+ code: 'VICT_KERNEL_CONTRACT_REJECTED',
624
+ message: `A decision node must return a validated DecisionResult with a route and a value.`,
625
+ retryable: false,
626
+ },
627
+ };
628
+ }
629
+ else {
630
+ const parsed = parseSafely(contract, candidate);
631
+ if ('threw' in parsed) {
632
+ onEvent?.({
633
+ type: 'contract.rejected',
634
+ stage: 'output',
635
+ nodeId: node.id,
636
+ capabilityId: node.capability,
637
+ contractId: node.outputContractId,
638
+ issues: [
639
+ {
640
+ code: 'parser_threw',
641
+ path: 'issues[0]',
642
+ message: `Contract '${node.outputContractId}' threw while parsing at node '${node.id}'; the thrown message is not retained.`,
643
+ },
644
+ ],
645
+ ...envelope,
646
+ timestamp: this.#deps.clock.now(),
647
+ });
648
+ outcome = {
649
+ kind: 'failed',
650
+ error: this.#parserThrewError('output', node.outputContractId, node.id, parsed.threw),
651
+ };
652
+ validatedOutput = undefined;
653
+ // Force the terminal failure at the commit below; a parser
654
+ // throw never retries and never routes along an error edge.
655
+ forceTerminalFailure = true;
656
+ }
657
+ else if (!parsed.result.ok) {
658
+ onEvent?.({
659
+ type: 'contract.rejected',
660
+ stage: 'output',
661
+ nodeId: node.id,
662
+ capabilityId: node.capability,
663
+ contractId: node.outputContractId,
664
+ issues: safeContractIssues(parsed.result.issues),
665
+ ...envelope,
666
+ timestamp: this.#deps.clock.now(),
667
+ });
668
+ outcome = {
669
+ kind: 'failed',
670
+ error: {
671
+ code: 'VICT_KERNEL_CONTRACT_REJECTED',
672
+ message: `Output contract '${node.outputContractId}' rejected the value at node '${node.id}'.`,
673
+ retryable: false,
674
+ },
675
+ };
676
+ validatedOutput = undefined;
677
+ }
678
+ else {
679
+ validatedOutput = node.kind === 'decision' ? rawOutcome.raw : parsed.result.value;
680
+ }
681
+ }
682
+ }
683
+ }
684
+ if (outcome.kind === 'completed') {
685
+ if (node.kind === 'decision') {
686
+ const candidate = validatedOutput;
687
+ if (!candidate || typeof candidate !== 'object' || typeof candidate.route !== 'string') {
688
+ outcome = {
689
+ kind: 'failed',
690
+ error: {
691
+ code: 'VICT_ORCH_INVALID_TRANSITION',
692
+ message: `Decision node '${node.id}' must return a validated DecisionResult.`,
693
+ retryable: false,
694
+ },
695
+ };
696
+ }
697
+ else {
698
+ decisionResult = candidate;
699
+ }
700
+ }
701
+ }
702
+ }
703
+ if (outcome.kind === 'completed') {
704
+ outcome = {
705
+ kind: 'completed',
706
+ outputSummary: summarizeOutput(validatedOutput),
707
+ };
708
+ }
709
+ // ---- Plan + commit (re-derive on store conflicts) -------------------
710
+ const attemptOutcome = outcome;
711
+ let conflictRetries = 0;
712
+ for (;;) {
713
+ const plan = planContinuation({
714
+ graph,
715
+ claim,
716
+ now: this.#deps.clock.now(),
717
+ outcome: attemptOutcome,
718
+ error: attemptOutcome.kind === 'failed' ? attemptOutcome.error : undefined,
719
+ validatedOutput: attemptOutcome.kind === 'completed'
720
+ ? node.kind === 'decision'
721
+ ? decisionResult
722
+ : validatedOutput
723
+ : undefined,
724
+ descriptor: binding,
725
+ ...(planning?.resolvedWait === true ? { resolvedWait: true } : {}),
726
+ });
727
+ const effectivePlan = forceTerminalFailure === true
728
+ ? { kind: 'transition', continuation: { kind: 'none' }, runStatus: 'failed' }
729
+ : forcedContinuation !== undefined
730
+ ? { kind: 'transition', continuation: forcedContinuation, runStatus: 'running' }
731
+ : plan;
732
+ if (effectivePlan.kind === 'invalid') {
733
+ // Invalid routing/shape: fail honestly (route along error edge or fail).
734
+ const planError = effectivePlan.error;
735
+ const failurePlan = planContinuation({
736
+ graph,
737
+ claim,
738
+ now: this.#deps.clock.now(),
739
+ outcome: { kind: 'failed', error: planError },
740
+ error: planError,
741
+ descriptor: binding,
742
+ });
743
+ if (failurePlan.kind === 'invalid') {
744
+ throw new VictRuntimeError('VICT_ORCH_INVALID_TRANSITION', failurePlan.error.message);
745
+ }
746
+ const command = this.#buildCommand(graph, claim, envelope, failurePlan, attemptOutcome, validatedOutput, bindings);
747
+ await this.#commitCompletion(claim, command, onEvent);
748
+ return;
749
+ }
750
+ if (effectivePlan.kind === 'transition' &&
751
+ effectivePlan.runStatus === 'completed' &&
752
+ attemptOutcome.kind === 'completed') {
753
+ this.#completedOutputs.set(claim.token.runId, validatedOutput);
754
+ }
755
+ const command = this.#buildCommand(graph, claim, envelope, effectivePlan, attemptOutcome, validatedOutput, bindings);
756
+ try {
757
+ await this.#commitCompletion(claim, command, onEvent);
758
+ return;
759
+ }
760
+ catch (cause) {
761
+ if (cause instanceof VictStoreError &&
762
+ [
763
+ 'VICT_STORE_RUN_CONFLICT',
764
+ 'VICT_STORE_EVENT_SEQUENCE_CONFLICT',
765
+ 'VICT_STORE_TOKEN_CONFLICT',
766
+ ].includes(cause.code) &&
767
+ conflictRetries < 5) {
768
+ conflictRetries += 1;
769
+ continue; // re-derive from fresh durable state
770
+ }
771
+ throw cause;
772
+ }
773
+ }
774
+ }
775
+ /** Build the full atomic command for one planned completion. */
776
+ #buildCommand(graph, claim, envelope, plan, attemptOutcome, validatedOutput, bindings) {
777
+ const node = graph.getNode(claim.token.nodeId);
778
+ const events = [];
779
+ const now = this.#deps.clock.now();
780
+ const push = (event) => {
781
+ events.push({ ...event, ...envelope, timestamp: now });
782
+ };
783
+ const continuation = plan.continuation;
784
+ let checkpoint;
785
+ let childCheckpoints;
786
+ let removeCheckpoints;
787
+ let declaredBranchKeys;
788
+ let branchOutput;
789
+ // Node-fact events.
790
+ if (attemptOutcome.kind === 'completed') {
791
+ push({
792
+ type: 'node.completed',
793
+ nodeId: node.id,
794
+ capabilityId: node.capability,
795
+ durationMs: Math.max(0, now - envelope.timestamp),
796
+ invokedVia: 'real',
797
+ output: attemptOutcome.outputSummary,
798
+ });
799
+ }
800
+ else if (attemptOutcome.kind === 'failed') {
801
+ push({
802
+ type: 'node.failed',
803
+ nodeId: node.id,
804
+ capabilityId: node.capability,
805
+ durationMs: Math.max(0, now - envelope.timestamp),
806
+ error: attemptOutcome.error,
807
+ });
808
+ }
809
+ else if (attemptOutcome.kind === 'timed_out') {
810
+ push({
811
+ type: 'node.timed_out',
812
+ nodeId: node.id,
813
+ capabilityId: node.capability,
814
+ attempt: claim.attempt.attemptNumber,
815
+ deadlineAt: claim.deadlineAt ?? now,
816
+ });
817
+ }
818
+ else if (attemptOutcome.kind === 'cancelled') {
819
+ push({
820
+ type: 'node.cancelled',
821
+ nodeId: node.id,
822
+ capabilityId: node.capability,
823
+ attempt: claim.attempt.attemptNumber,
824
+ });
825
+ }
826
+ if (continuation.kind === 'advance') {
827
+ checkpoint = { tokenId: claim.token.tokenId, payload: continuation.payload };
828
+ }
829
+ else if (continuation.kind === 'wait') {
830
+ push({
831
+ type: 'run.waiting',
832
+ nodeId: node.id,
833
+ waitId: continuation.wait.waitId,
834
+ waitKind: continuation.wait.kind,
835
+ ...(continuation.wait.signalName !== null
836
+ ? { signalName: continuation.wait.signalName }
837
+ : {}),
838
+ ...(continuation.wait.dueAt !== null ? { dueAt: continuation.wait.dueAt } : {}),
839
+ });
840
+ if (continuation.wait.dueAt !== null) {
841
+ push({
842
+ type: 'timer.scheduled',
843
+ timerId: `timer_${continuation.wait.waitId}`,
844
+ nodeId: node.id,
845
+ dueAt: continuation.wait.dueAt,
846
+ kind: 'wait',
847
+ });
848
+ }
849
+ if (continuation.wait.timeoutAt !== null) {
850
+ push({
851
+ type: 'timer.scheduled',
852
+ timerId: `timer_timeout_${continuation.wait.waitId}`,
853
+ nodeId: node.id,
854
+ dueAt: continuation.wait.timeoutAt,
855
+ kind: 'wait-timeout',
856
+ });
857
+ }
858
+ }
859
+ else if (continuation.kind === 'fork') {
860
+ push({
861
+ type: 'fork.created',
862
+ forkId: node.id,
863
+ joinId: continuation.joinId,
864
+ branchKeys: continuation.children.map((child) => child.branchKey),
865
+ });
866
+ childCheckpoints = continuation.children.map((child) => ({
867
+ tokenId: child.tokenId,
868
+ payload: validatedOutput,
869
+ }));
870
+ }
871
+ else if (continuation.kind === 'branchArrival') {
872
+ push({
873
+ type: 'branch.completed',
874
+ forkId: continuation.forkId,
875
+ joinId: continuation.joinId,
876
+ branchKey: continuation.branchKey,
877
+ });
878
+ removeCheckpoints = [claim.token.tokenId];
879
+ declaredBranchKeys = [...graph.branchKeysOf(continuation.forkId)];
880
+ branchOutput = validatedOutput;
881
+ }
882
+ else if (continuation.kind === 'retry') {
883
+ push({
884
+ type: 'node.retry_scheduled',
885
+ nodeId: node.id,
886
+ capabilityId: node.capability,
887
+ attempt: claim.attempt.attemptNumber,
888
+ maxAttempts: continuation.maxAttempts,
889
+ dueAt: continuation.dueAt,
890
+ retryOnCode: continuation.retryOnCode,
891
+ });
892
+ push({
893
+ type: 'timer.scheduled',
894
+ timerId: `timer_retry_${claim.attempt.attemptId}`,
895
+ nodeId: node.id,
896
+ dueAt: continuation.dueAt,
897
+ kind: 'retry',
898
+ });
899
+ }
900
+ else if (continuation.kind === 'block') {
901
+ push({
902
+ type: 'run.blocked',
903
+ steps: claim.attempt.attemptNumber,
904
+ code: continuation.code,
905
+ reason: continuation.reason,
906
+ capabilityId: node.capability,
907
+ effect: bindings.get(node.capability)?.effect,
908
+ remediation: 'Resolve the blocked run through the operator API (runtime.resolveBlocked).',
909
+ });
910
+ }
911
+ // The join.completed fact is committed exactly once, in the same
912
+ // atomic transition that records the VALIDATED join completion and
913
+ // its downstream continuation (or terminal completion). A rejecting
914
+ // join contract never produces it.
915
+ if (node.kind === 'join' && attemptOutcome.kind === 'completed') {
916
+ const forkId = graph.forkOfJoin(node.id) ?? '';
917
+ push({
918
+ type: 'join.completed',
919
+ forkId,
920
+ joinId: node.id,
921
+ branchKeys: [...graph.branchKeysOf(forkId)],
922
+ });
923
+ }
924
+ // Terminal run events.
925
+ const run = { status: plan.runStatus };
926
+ if (plan.runStatus === 'completed') {
927
+ push({
928
+ type: 'run.completed',
929
+ steps: claim.attempt.attemptNumber,
930
+ output: attemptOutcome.kind === 'completed'
931
+ ? attemptOutcome.outputSummary
932
+ : { shape: 'undefined' },
933
+ });
934
+ if (this.#deps.retention === 'full' && validatedOutput !== undefined) {
935
+ run.output = validatedOutput;
936
+ run.outputSummary =
937
+ attemptOutcome.kind === 'completed' ? attemptOutcome.outputSummary : undefined;
938
+ }
939
+ else if (this.#deps.retention !== 'none' && attemptOutcome.kind === 'completed') {
940
+ run.outputSummary = attemptOutcome.outputSummary;
941
+ }
942
+ }
943
+ else if (plan.runStatus === 'failed') {
944
+ push({
945
+ type: 'run.failed',
946
+ steps: claim.attempt.attemptNumber,
947
+ error: attemptOutcome.kind === 'failed'
948
+ ? attemptOutcome.error
949
+ : {
950
+ code: 'VICT_ORCH_RUN_FAILED',
951
+ message: 'The run failed.',
952
+ retryable: false,
953
+ },
954
+ });
955
+ if (attemptOutcome.kind === 'failed') {
956
+ run.error = attemptOutcome.error;
957
+ }
958
+ }
959
+ return {
960
+ runId: claim.token.runId,
961
+ attemptId: claim.attempt.attemptId,
962
+ ownerId: claim.attempt.ownerId ?? '',
963
+ expectedAttemptFence: claim.attempt.fence,
964
+ now,
965
+ outcome: attemptOutcome,
966
+ continuation,
967
+ events,
968
+ run,
969
+ ...(checkpoint !== undefined ? { checkpoint } : {}),
970
+ ...(childCheckpoints !== undefined ? { childCheckpoints } : {}),
971
+ ...(removeCheckpoints !== undefined ? { removeCheckpoints } : {}),
972
+ ...(declaredBranchKeys !== undefined ? { declaredBranchKeys } : {}),
973
+ ...(continuation.kind === 'branchArrival' ? { branchOutput } : {}),
974
+ };
975
+ }
976
+ /** Commit one completion; conflicts re-derive (bounded retries upstream). */
977
+ async #commitCompletion(claim, command, onEvent) {
978
+ await this.#deps.orchestration.completeAttempt(command);
979
+ for (const event of command.events) {
980
+ onEvent?.(event);
981
+ }
982
+ void claim;
983
+ }
984
+ }
985
+ const DEFAULT_LEASE_MS = 30_000;
986
+ /** Assemble a safe run result (waits expose safe descriptors, never payloads). */
987
+ export async function resultFromRun(run, orchestration, completedOutputs) {
988
+ let waits;
989
+ if (run.status === 'waiting') {
990
+ const openWaits = (await orchestration.listWaits(run.runId)).filter((wait) => wait.status === 'open');
991
+ waits = openWaits.map((wait) => ({
992
+ waitId: wait.waitId,
993
+ kind: wait.kind,
994
+ ...(wait.signalName !== null ? { signalName: wait.signalName } : {}),
995
+ ...(wait.dueAt !== null ? { dueAt: wait.dueAt } : {}),
996
+ }));
997
+ }
998
+ const trace = (await orchestration
999
+ .listOrchestrationEvents(run.runId)
1000
+ .catch(() => []));
1001
+ const result = {
1002
+ runId: run.runId,
1003
+ graphId: run.graphId,
1004
+ graphVersion: run.graphVersion,
1005
+ capabilitySetVersion: run.capabilitySetVersion,
1006
+ activationVersion: run.activationVersion,
1007
+ status: run.status,
1008
+ steps: run.steps,
1009
+ trace,
1010
+ ...(waits !== undefined ? { waits } : {}),
1011
+ };
1012
+ if (run.error !== undefined) {
1013
+ return { ...result, error: run.error };
1014
+ }
1015
+ if (run.status === 'completed') {
1016
+ const output = completedOutputs?.get(run.runId) ?? run.output;
1017
+ if (output !== undefined) {
1018
+ return {
1019
+ ...result,
1020
+ output,
1021
+ };
1022
+ }
1023
+ }
1024
+ return result;
1025
+ }
1026
+ /** Inject the in-process completed-output view (used by the driver). */
1027
+ export function withCompletedOutputs(run, orchestration, completedOutputs) {
1028
+ return resultFromRun(run, orchestration, completedOutputs);
1029
+ }
1030
+ //# sourceMappingURL=orchestration-driver.js.map