@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,1265 @@
1
+ import { neutralJsonContract } from '@victframework/sdk';
2
+ import { signalWaitGraph, stringContract } from './orchestration-conformance.js';
3
+ /** Deterministic store-level planner for raw claim tests. */
4
+ function rawPlanner(effectClass = 'pure') {
5
+ return {
6
+ invocationIdFor: (token) => `inv_${token.tokenId}`,
7
+ attemptIdFor: (token, attemptNumber) => `att_${token.tokenId}_${attemptNumber}`,
8
+ planFor: (token) => {
9
+ void token;
10
+ return {
11
+ capabilityId: 'raw',
12
+ effectClass,
13
+ deadlineAt: null,
14
+ idempotencyKey: null,
15
+ };
16
+ },
17
+ };
18
+ }
19
+ export function createManualOrchestrationClock(start = 1_000_000) {
20
+ let current = start;
21
+ let waiters = [];
22
+ return {
23
+ now: () => current,
24
+ delay: (ms) => new Promise((resolve) => {
25
+ const due = current + Math.max(0, ms);
26
+ if (due <= current) {
27
+ resolve();
28
+ return;
29
+ }
30
+ waiters.push({ due, resolve });
31
+ }),
32
+ advance: (ms) => {
33
+ current += Math.max(0, ms);
34
+ const due = waiters.filter((w) => w.due <= current).sort((a, b) => a.due - b.due);
35
+ waiters = waiters.filter((w) => w.due > current);
36
+ for (const waiter of due) {
37
+ waiter.resolve();
38
+ }
39
+ },
40
+ };
41
+ }
42
+ async function envelopeOf(orchestration, runId) {
43
+ const run = await orchestration.getOrchestrationRun(runId);
44
+ return {
45
+ runId,
46
+ graphId: run?.graphId ?? '',
47
+ graphVersion: run?.graphVersion ?? '',
48
+ capabilitySetVersion: run?.capabilitySetVersion ?? '',
49
+ activationVersion: run?.activationVersion ?? '',
50
+ };
51
+ }
52
+ const settle = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
53
+ export function runOrchestrationRaceSuite(runner, expect, factory) {
54
+ const t = runner.test;
55
+ t(`[${factory.name}] fencing: two competing claims cannot own one attempt`, async () => {
56
+ const fixture = await factory.create();
57
+ try {
58
+ const { runtime, orchestration } = fixture;
59
+ runtime.registerCapability({
60
+ id: 'hold',
61
+ revision: '1',
62
+ effect: 'pure',
63
+ input: neutralJsonContract,
64
+ output: neutralJsonContract,
65
+ invoke: () => 'x',
66
+ });
67
+ const activated = await runtime.activate({
68
+ id: 'race-claims',
69
+ entry: 'hold',
70
+ nodes: [{ id: 'hold', capability: 'hold' }],
71
+ edges: [],
72
+ });
73
+ expect(activated.ok).toBe(true);
74
+ const runId = `run_race_${Date.now()}`;
75
+ const now = Date.now();
76
+ await orchestration.createOrchestrationRun({
77
+ runId,
78
+ graphId: 'race-claims',
79
+ graphVersion: 'v2_test',
80
+ capabilitySetVersion: 'v1_test',
81
+ activationVersion: activated.activationVersion,
82
+ mode: 'normal',
83
+ retention: 'summary',
84
+ rootTokenId: `tok_${runId}_root`,
85
+ entryNodeId: 'hold',
86
+ checkpoint: 'seed',
87
+ events: [],
88
+ now,
89
+ });
90
+ // Two concurrent claimers: exactly one wins the only ready token.
91
+ const results = await Promise.all([
92
+ orchestration.claimReadyToken({
93
+ runId,
94
+ ownerId: 'owner-a',
95
+ leaseExpiresAt: now + 60_000,
96
+ now,
97
+ planner: rawPlanner(),
98
+ }),
99
+ orchestration.claimReadyToken({
100
+ runId,
101
+ ownerId: 'owner-b',
102
+ leaseExpiresAt: now + 60_000,
103
+ now,
104
+ planner: rawPlanner(),
105
+ }),
106
+ ]);
107
+ const claimed = results.filter((result) => result.claimed);
108
+ expect(claimed.length).toBe(1);
109
+ const attemptId = claimed[0].claim.attempt
110
+ .attemptId;
111
+ expect(attemptId).toBeTruthy();
112
+ }
113
+ finally {
114
+ await fixture.dispose();
115
+ }
116
+ });
117
+ t(`[${factory.name}] fencing: stale owner cannot complete after lease recovery; invocation identity stable`, async () => {
118
+ const fixture = await factory.create();
119
+ try {
120
+ const { runtime, orchestration } = fixture;
121
+ runtime.registerCapability({
122
+ id: 'hold',
123
+ revision: '1',
124
+ effect: 'pure',
125
+ input: neutralJsonContract,
126
+ output: neutralJsonContract,
127
+ invoke: () => 'x',
128
+ });
129
+ const activated = await runtime.activate({
130
+ id: 'race-fence',
131
+ entry: 'hold',
132
+ nodes: [{ id: 'hold', capability: 'hold' }],
133
+ edges: [],
134
+ });
135
+ expect(activated.ok).toBe(true);
136
+ const runId = `run_fence_${Date.now()}`;
137
+ const now = Date.now();
138
+ await orchestration.createOrchestrationRun({
139
+ runId,
140
+ graphId: 'race-fence',
141
+ graphVersion: 'v2_test',
142
+ capabilitySetVersion: 'v1_test',
143
+ activationVersion: activated
144
+ .activationVersion,
145
+ mode: 'normal',
146
+ retention: 'summary',
147
+ rootTokenId: `tok_${runId}_root`,
148
+ entryNodeId: 'hold',
149
+ checkpoint: 'seed',
150
+ events: [],
151
+ now,
152
+ });
153
+ // Owner A claims, then its lease lapses.
154
+ const claimed = await orchestration.claimReadyToken({
155
+ runId,
156
+ ownerId: 'owner-a',
157
+ leaseExpiresAt: now + 1,
158
+ now,
159
+ planner: rawPlanner(),
160
+ });
161
+ expect(claimed.claimed).toBe(true);
162
+ const claim = claimed.claim;
163
+ // Lease recovery reclaims the abandoned claim.
164
+ const recoverable = await orchestration.findRecoverableClaims({ now: now + 5_000 });
165
+ expect(recoverable.length).toBe(1);
166
+ await orchestration.recoverAttempt({
167
+ runId,
168
+ attemptId: claim.attempt.attemptId,
169
+ expectedAttemptFence: claim.attempt.fence,
170
+ now: now + 5_000,
171
+ action: { kind: 'reclaim' },
172
+ events: [],
173
+ });
174
+ // The recovered token is claimed again by a fresh owner; that claim
175
+ // creates attempt 2 of the SAME logical invocation.
176
+ const reclaimed = await orchestration.claimReadyToken({
177
+ runId,
178
+ ownerId: 'owner-b',
179
+ leaseExpiresAt: now + 60_000,
180
+ now: now + 5_000,
181
+ planner: rawPlanner(),
182
+ });
183
+ expect(reclaimed.claimed).toBe(true);
184
+ // The stale owner's genuinely still-running result arrives late and
185
+ // must be REJECTED — never applied.
186
+ let rejected = false;
187
+ try {
188
+ await orchestration.completeAttempt({
189
+ runId,
190
+ attemptId: claim.attempt.attemptId,
191
+ ownerId: 'owner-a',
192
+ expectedAttemptFence: claim.attempt.fence,
193
+ now: now + 5_000,
194
+ outcome: { kind: 'completed', outputSummary: { shape: 'string', length: 3 } },
195
+ continuation: { kind: 'none' },
196
+ events: [],
197
+ run: { status: 'completed' },
198
+ });
199
+ }
200
+ catch {
201
+ rejected = true;
202
+ }
203
+ expect(rejected).toBe(true);
204
+ // Attempt numbers and logical invocation identity remain stable.
205
+ const snapshot = await orchestration.getOrchestrationSnapshot(runId);
206
+ const attempts = snapshot?.attempts ?? [];
207
+ expect(attempts.length).toBe(2);
208
+ expect(attempts[0]?.invocationId === attempts[1]?.invocationId).toBe(true);
209
+ expect(attempts.map((attempt) => attempt.attemptNumber).sort()).toEqual([1, 2]);
210
+ }
211
+ finally {
212
+ await fixture.dispose();
213
+ }
214
+ });
215
+ t(`[${factory.name}] durable intent: attempt and node.started are durable before every invocation`, async () => {
216
+ const fixture = await factory.create();
217
+ try {
218
+ const runtime = fixture.runtime;
219
+ let observed;
220
+ runtime.registerCapability({
221
+ id: 'probe',
222
+ revision: '1',
223
+ effect: 'pure',
224
+ input: neutralJsonContract,
225
+ output: neutralJsonContract,
226
+ invoke: async (input, context) => {
227
+ const snapshot = await fixture.orchestration.getOrchestrationSnapshot(context.runId);
228
+ const attempt = snapshot?.attempts.find((candidate) => candidate.attemptId === context.attemptId);
229
+ const events = await fixture.orchestration.listOrchestrationEvents(context.runId);
230
+ observed = {
231
+ attemptState: attempt?.state ?? 'missing',
232
+ nodeStarted: events.some((event) => event.type === 'node.started' && event.nodeId === 'probe'),
233
+ };
234
+ return `seen:${String(input)}`;
235
+ },
236
+ });
237
+ const activated = await runtime.activate({
238
+ id: 'race-intent',
239
+ entry: 'probe',
240
+ // A declared timeout forces the durable orchestration engine.
241
+ nodes: [{ id: 'probe', capability: 'probe', timeoutMs: 30_000 }],
242
+ edges: [],
243
+ });
244
+ expect(activated.ok).toBe(true);
245
+ const result = await runtime.run('seed');
246
+ expect(result.status).toBe('completed');
247
+ expect(observed?.attemptState === 'started' || observed?.attemptState === 'claimed').toBe(true);
248
+ expect(observed?.nodeStarted).toBe(true);
249
+ }
250
+ finally {
251
+ await fixture.dispose();
252
+ }
253
+ });
254
+ t(`[${factory.name}] signal versus due-timeout race: exactly one winner, no duplicate continuation`, async () => {
255
+ const fixture = await factory.create();
256
+ try {
257
+ const runtime = fixture.runtime;
258
+ let downstreamCalls = 0;
259
+ let timedOutCalls = 0;
260
+ runtime
261
+ .registerCapability({
262
+ id: 'first',
263
+ revision: '1',
264
+ effect: 'pure',
265
+ input: neutralJsonContract,
266
+ output: neutralJsonContract,
267
+ invoke: () => 'one',
268
+ })
269
+ .registerCapability({
270
+ id: 'second',
271
+ revision: '1',
272
+ effect: 'pure',
273
+ input: neutralJsonContract,
274
+ output: neutralJsonContract,
275
+ invoke: (input) => {
276
+ downstreamCalls += 1;
277
+ return `got:${String(input)}`;
278
+ },
279
+ })
280
+ .registerCapability({
281
+ id: 'timedOut',
282
+ revision: '1',
283
+ effect: 'pure',
284
+ input: neutralJsonContract,
285
+ output: neutralJsonContract,
286
+ invoke: () => {
287
+ timedOutCalls += 1;
288
+ return 'timed-out-path';
289
+ },
290
+ });
291
+ const activated = await runtime.activate({
292
+ id: 'race-signal-timeout',
293
+ entry: 'a',
294
+ nodes: [
295
+ { id: 'a', capability: 'first' },
296
+ { id: 'w', kind: 'wait', wait: { kind: 'signal', name: 'go', timeoutMs: 25 } },
297
+ { id: 't', capability: 'timedOut' },
298
+ { id: 'b', capability: 'second' },
299
+ ],
300
+ edges: [
301
+ { from: 'a', to: 'w' },
302
+ { from: 'w', to: 'b' },
303
+ { from: 'w', to: 't', kind: 'timeout' },
304
+ ],
305
+ });
306
+ expect(activated.ok).toBe(true);
307
+ const parked = await runtime.run('seed');
308
+ expect(parked.status).toBe('waiting');
309
+ const waitId = parked.waits?.[0]?.waitId;
310
+ // Race a valid signal against the due-timeout pump at the same
311
+ // instant. Exactly one wins: a losing signal arrives as
312
+ // 'already_resolved', never a duplicate wake or receipt.
313
+ await settle(40); // past the 25ms timeout
314
+ const [signalOutcome, timerOutcome] = await Promise.all([
315
+ runtime.signal({
316
+ runId: parked.runId,
317
+ waitId,
318
+ signalId: 'race-sig-1',
319
+ signalName: 'go',
320
+ payload: 'in-time',
321
+ }),
322
+ runtime.processDueTimers({ runId: parked.runId }),
323
+ ]);
324
+ const signalWon = signalOutcome.ok && signalOutcome.status === 'accepted';
325
+ const timerWon = timerOutcome.fired === 1;
326
+ expect(signalWon !== timerWon).toBe(true);
327
+ const final = await runtime.resumeRun(parked.runId);
328
+ expect(final.status).toBe('completed');
329
+ expect(downstreamCalls + timedOutCalls).toBe(1);
330
+ const events = await fixture.orchestration.listOrchestrationEvents(parked.runId);
331
+ const received = events.filter((event) => event.type === 'signal.received').length;
332
+ const fired = events.filter((event) => event.type === 'timer.fired').length;
333
+ expect(received + fired).toBe(1);
334
+ expect(events.filter((event) => event.type === 'run.resumed').length).toBe(1);
335
+ // The losing command creates no receipt when the timer won.
336
+ const receipts = await fixture.orchestration.listSignalReceipts(parked.runId);
337
+ expect(receipts.length).toBe(signalWon ? 1 : 0);
338
+ }
339
+ finally {
340
+ await fixture.dispose();
341
+ }
342
+ });
343
+ t(`[${factory.name}] timeout wins before a late signal: the late command creates no receipt or wake`, async () => {
344
+ const fixture = await factory.create();
345
+ try {
346
+ const runtime = fixture.runtime;
347
+ let downstreamCalls = 0;
348
+ let timedOutCalls = 0;
349
+ runtime
350
+ .registerCapability({
351
+ id: 'first',
352
+ revision: '1',
353
+ effect: 'pure',
354
+ input: neutralJsonContract,
355
+ output: neutralJsonContract,
356
+ invoke: () => 'one',
357
+ })
358
+ .registerCapability({
359
+ id: 'second',
360
+ revision: '1',
361
+ effect: 'pure',
362
+ input: neutralJsonContract,
363
+ output: neutralJsonContract,
364
+ invoke: () => {
365
+ downstreamCalls += 1;
366
+ return 'success-path';
367
+ },
368
+ })
369
+ .registerCapability({
370
+ id: 'timedOut',
371
+ revision: '1',
372
+ effect: 'pure',
373
+ input: neutralJsonContract,
374
+ output: neutralJsonContract,
375
+ invoke: () => {
376
+ timedOutCalls += 1;
377
+ return 'timed-out-path';
378
+ },
379
+ });
380
+ const activated = await runtime.activate({
381
+ id: 'race-late-signal',
382
+ entry: 'a',
383
+ nodes: [
384
+ { id: 'a', capability: 'first' },
385
+ { id: 'w', kind: 'wait', wait: { kind: 'signal', name: 'go', timeoutMs: 15 } },
386
+ { id: 't', capability: 'timedOut' },
387
+ { id: 'b', capability: 'second' },
388
+ ],
389
+ edges: [
390
+ { from: 'a', to: 'w' },
391
+ { from: 'w', to: 'b' },
392
+ { from: 'w', to: 't', kind: 'timeout' },
393
+ ],
394
+ });
395
+ expect(activated.ok).toBe(true);
396
+ const parked = await runtime.run('seed');
397
+ expect(parked.status).toBe('waiting');
398
+ // The timeout fires first; the signal arrives late.
399
+ await settle(40);
400
+ const pumped = await runtime.processDueTimers({ runId: parked.runId });
401
+ expect(pumped.fired).toBe(1);
402
+ const late = await runtime.signal({
403
+ runId: parked.runId,
404
+ waitId: parked.waits?.[0]?.waitId,
405
+ signalId: 'late-1',
406
+ signalName: 'go',
407
+ payload: 'late',
408
+ });
409
+ expect(late.ok).toBe(true);
410
+ expect(late.ok ? late.status : '').toBe('already_resolved');
411
+ const receipts = await fixture.orchestration.listSignalReceipts(parked.runId);
412
+ expect(receipts.length).toBe(0);
413
+ const final = await runtime.resumeRun(parked.runId);
414
+ expect(final.status).toBe('completed');
415
+ expect(timedOutCalls).toBe(1);
416
+ expect(downstreamCalls).toBe(0);
417
+ }
418
+ finally {
419
+ await fixture.dispose();
420
+ }
421
+ });
422
+ t(`[${factory.name}] keyed-write timeout retries with the same idempotency key and completes once`, async () => {
423
+ // Deterministic by construction: a manual clock drives BOTH the
424
+ // persisted deadline and the retry-timer eligibility, and the first
425
+ // attempt is held by an explicit invocation barrier. Nothing depends
426
+ // on wall-clock sleeps (the previous real-time version could flake
427
+ // under suite load: a slow claim-to-execution start legitimately
428
+ // failed attempt two on its ALREADY-EXPIRED persisted deadline
429
+ // before invocation, scheduling a second retry the test never
430
+ // pumped — the runtime behaved correctly; the test guessed wrong).
431
+ const clock = createManualOrchestrationClock();
432
+ const fixture = await factory.create(clock);
433
+ try {
434
+ const runtime = fixture.runtime;
435
+ const keys = [];
436
+ let attempts = 0;
437
+ let releaseFirstAttempt;
438
+ const firstAttemptGate = new Promise((resolve) => {
439
+ releaseFirstAttempt = resolve;
440
+ });
441
+ let firstAttemptStartedResolve;
442
+ const firstAttemptStarted = new Promise((resolve) => {
443
+ firstAttemptStartedResolve = resolve;
444
+ });
445
+ runtime.registerCapability({
446
+ id: 'keyedSlow',
447
+ revision: '1',
448
+ effect: 'write',
449
+ input: neutralJsonContract,
450
+ output: neutralJsonContract,
451
+ idempotency: 'keyed',
452
+ invoke: async (input, context) => {
453
+ attempts += 1;
454
+ keys.push(context.idempotencyKey);
455
+ if (attempts === 1) {
456
+ // Hold attempt one open past its deadline until the test
457
+ // releases it: its late return value must be fenced.
458
+ firstAttemptStartedResolve?.();
459
+ await firstAttemptGate;
460
+ return `late:${String(input)}`;
461
+ }
462
+ return `applied:${String(input)}`;
463
+ },
464
+ });
465
+ const activated = await runtime.activate({
466
+ id: 'race-keyed-timeout',
467
+ entry: 'w',
468
+ nodes: [
469
+ {
470
+ id: 'w',
471
+ capability: 'keyedSlow',
472
+ timeoutMs: 20,
473
+ retry: {
474
+ maxAttempts: 3,
475
+ retryOn: ['timeout'],
476
+ backoff: { kind: 'fixed', delayMs: 1 },
477
+ },
478
+ },
479
+ ],
480
+ edges: [],
481
+ });
482
+ expect(activated.ok).toBe(true);
483
+ const startedRun = runtime.run('seed');
484
+ // Barrier: attempt one is genuinely in flight (invoked) before the
485
+ // clock moves — a state-based wait, never a sleep.
486
+ await firstAttemptStarted;
487
+ // Advance the manual clock past the persisted 20ms deadline: the
488
+ // deadline promise fires, the attempt is classified timed_out, and
489
+ // a DURABLE retry timer is scheduled (backoff 1ms).
490
+ clock.advance(25);
491
+ const parked = await startedRun;
492
+ // Attempt one genuinely timed out; the retry is durable.
493
+ expect(parked.status).toBe('running');
494
+ expect(attempts).toBe(1);
495
+ expect(parked.trace.some((event) => event.type === 'node.timed_out')).toBe(true);
496
+ expect(parked.trace.some((event) => event.type === 'node.retry_scheduled')).toBe(true);
497
+ expect(parked.trace.some((event) => event.type === 'timer.scheduled')).toBe(true);
498
+ // Timer eligibility is deterministic: the retry timer became due
499
+ // exactly when the clock passed (completion time + backoff).
500
+ clock.advance(5);
501
+ const pumped = await runtime.processDueTimers({ runId: parked.runId });
502
+ expect(pumped.fired).toBe(1);
503
+ expect(pumped.timers[0]?.kind).toBe('retry');
504
+ // Attempt one's late value arrives only after its attempt was
505
+ // terminally classified — the old attempt cannot overwrite the
506
+ // result (the release order relative to attempt two is irrelevant).
507
+ releaseFirstAttempt?.();
508
+ const final = await runtime.resumeRun(parked.runId);
509
+ expect(final.status).toBe('completed');
510
+ // Attempt two used the SAME idempotency key: one logical
511
+ // invocation, one external mutation, the late value discarded.
512
+ expect(attempts).toBe(2);
513
+ expect(keys[0]).toBeTruthy();
514
+ expect(keys[0] === keys[1]).toBe(true);
515
+ expect(final.output).toBe('applied:seed');
516
+ }
517
+ finally {
518
+ await fixture.dispose();
519
+ }
520
+ });
521
+ t(`[${factory.name}] irreversible timeout blocks without replay`, async () => {
522
+ // Deterministic: manual clock + invocation barrier (same mechanism and
523
+ // rationale as the keyed-write timeout test above).
524
+ const clock = createManualOrchestrationClock();
525
+ const fixture = await factory.create(clock);
526
+ try {
527
+ const runtime = fixture.runtime;
528
+ let invokeCount = 0;
529
+ let invokedResolve;
530
+ const invoked = new Promise((resolve) => {
531
+ invokedResolve = resolve;
532
+ });
533
+ let releaseIrreversible;
534
+ const irreversibleGate = new Promise((resolve) => {
535
+ releaseIrreversible = resolve;
536
+ });
537
+ runtime.registerCapability({
538
+ id: 'slowIrreversible',
539
+ revision: '1',
540
+ effect: 'irreversible',
541
+ input: neutralJsonContract,
542
+ output: neutralJsonContract,
543
+ invoke: async () => {
544
+ invokeCount += 1;
545
+ invokedResolve?.();
546
+ await irreversibleGate;
547
+ return 'done';
548
+ },
549
+ });
550
+ const activated = await runtime.activate({
551
+ id: 'race-irreversible-timeout',
552
+ entry: 'x',
553
+ nodes: [{ id: 'x', capability: 'slowIrreversible', timeoutMs: 20 }],
554
+ edges: [],
555
+ });
556
+ expect(activated.ok).toBe(true);
557
+ const startedRun = runtime.run('seed', { policy: { allowIrreversible: true } });
558
+ await invoked;
559
+ clock.advance(25);
560
+ const result = await startedRun;
561
+ releaseIrreversible?.();
562
+ expect(result.status).toBe('blocked');
563
+ expect(invokeCount).toBe(1);
564
+ const run = await fixture.orchestration.getOrchestrationRun(result.runId);
565
+ expect(run?.status).toBe('blocked');
566
+ // Operator retry is denied for irreversible work even when authorized.
567
+ const operatorRuntime = await fixture.createOperatorRuntime();
568
+ operatorRuntime.registerCapability({
569
+ id: 'slowIrreversible',
570
+ revision: '1',
571
+ effect: 'irreversible',
572
+ input: neutralJsonContract,
573
+ output: neutralJsonContract,
574
+ invoke: async () => 'done',
575
+ });
576
+ const denied = await operatorRuntime.resolveBlocked({
577
+ runId: result.runId,
578
+ resolutionId: 'retry-attempt',
579
+ action: 'retry',
580
+ reasonCode: 'operator_request',
581
+ });
582
+ expect(denied.ok).toBe(false);
583
+ expect(denied.ok ? '' : denied.code).toBe('VICT_ORCH_OPERATOR_DENIED');
584
+ expect(invokeCount).toBe(1);
585
+ }
586
+ finally {
587
+ await fixture.dispose();
588
+ }
589
+ });
590
+ t(`[${factory.name}] timer polling is deterministic and repeated polling is idempotent`, async () => {
591
+ const fixture = await factory.create();
592
+ try {
593
+ const runtime = fixture.runtime;
594
+ let downstream = 0;
595
+ runtime
596
+ .registerCapability({
597
+ id: 'first',
598
+ revision: '1',
599
+ effect: 'pure',
600
+ input: neutralJsonContract,
601
+ output: neutralJsonContract,
602
+ invoke: () => 'one',
603
+ })
604
+ .registerCapability({
605
+ id: 'second',
606
+ revision: '1',
607
+ effect: 'pure',
608
+ input: neutralJsonContract,
609
+ output: neutralJsonContract,
610
+ invoke: (input) => {
611
+ downstream += 1;
612
+ return `got:${String(input)}`;
613
+ },
614
+ });
615
+ // Two independent runs with different timer delays.
616
+ const graph = (delayMs) => ({
617
+ id: `race-timer-order-${delayMs}`,
618
+ entry: 'a',
619
+ nodes: [
620
+ { id: 'a', capability: 'first' },
621
+ { id: 't', kind: 'wait', wait: { kind: 'timer', delayMs } },
622
+ { id: 'b', capability: 'second' },
623
+ ],
624
+ edges: [
625
+ { from: 'a', to: 't' },
626
+ { from: 't', to: 'b' },
627
+ ],
628
+ });
629
+ const activated1 = await runtime.activate(graph(5));
630
+ expect(activated1.ok).toBe(true);
631
+ const activated2 = await runtime.activate(graph(40));
632
+ expect(activated2.ok).toBe(true);
633
+ const parked1 = await runtime.run('seed');
634
+ expect(parked1.status).toBe('waiting');
635
+ const parked2 = await runtime.run('seed');
636
+ expect(parked2.status).toBe('waiting');
637
+ await settle(60);
638
+ // First pump fires both due timers; repeated polling is idempotent.
639
+ const fired = await runtime.processDueTimers({});
640
+ expect(fired.fired).toBe(2);
641
+ const repeat = await runtime.processDueTimers({});
642
+ expect(repeat.fired).toBe(0);
643
+ const final1 = await runtime.resumeRun(parked1.runId);
644
+ const final2 = await runtime.resumeRun(parked2.runId);
645
+ expect(final1.status).toBe('completed');
646
+ expect(final2.status).toBe('completed');
647
+ expect(downstream).toBe(2);
648
+ }
649
+ finally {
650
+ await fixture.dispose();
651
+ }
652
+ });
653
+ t(`[${factory.name}] cancellation after durable claim: the run finalizes cancelled; no downstream work`, async () => {
654
+ const fixture = await factory.create();
655
+ try {
656
+ const { runtime, orchestration } = fixture;
657
+ let downstreamCalls = 0;
658
+ runtime
659
+ .registerCapability({
660
+ id: 'start',
661
+ revision: '1',
662
+ effect: 'pure',
663
+ input: neutralJsonContract,
664
+ output: neutralJsonContract,
665
+ invoke: () => 'seed',
666
+ })
667
+ .registerCapability({
668
+ id: 'next',
669
+ revision: '1',
670
+ effect: 'pure',
671
+ input: neutralJsonContract,
672
+ output: neutralJsonContract,
673
+ invoke: () => {
674
+ downstreamCalls += 1;
675
+ return 'next';
676
+ },
677
+ });
678
+ const activated = await runtime.activate({
679
+ id: 'race-cancel-claim',
680
+ entry: 'start',
681
+ nodes: [
682
+ { id: 'start', capability: 'start' },
683
+ { id: 'next', capability: 'next' },
684
+ ],
685
+ edges: [{ from: 'start', to: 'next' }],
686
+ });
687
+ expect(activated.ok).toBe(true);
688
+ const runId = `run_cancel_claim_${Date.now()}`;
689
+ const now = Date.now();
690
+ await orchestration.createOrchestrationRun({
691
+ runId,
692
+ graphId: 'race-cancel-claim',
693
+ graphVersion: 'v2_test',
694
+ capabilitySetVersion: 'v1_test',
695
+ activationVersion: activated
696
+ .activationVersion,
697
+ mode: 'normal',
698
+ retention: 'summary',
699
+ rootTokenId: `tok_${runId}_root`,
700
+ entryNodeId: 'start',
701
+ checkpoint: 'seed',
702
+ events: [],
703
+ now,
704
+ });
705
+ const identity = await envelopeOf(orchestration, runId);
706
+ // Durable claim happens; the (hypothetical) handler is mid-flight.
707
+ const claimed = await orchestration.claimReadyToken({
708
+ runId,
709
+ ownerId: 'owner-a',
710
+ leaseExpiresAt: now + 60_000,
711
+ now,
712
+ planner: rawPlanner(),
713
+ });
714
+ expect(claimed.claimed).toBe(true);
715
+ // Durable cancellation: request + apply (no driver loop is driving
716
+ // this hand-built run, so the store transition is applied directly).
717
+ const requested = await orchestration.requestCancellation({
718
+ runId,
719
+ requestId: 'cancel-1',
720
+ reasonCode: 'operator_request',
721
+ commandHash: 'hash-cancel-1',
722
+ now: now + 5,
723
+ events: [
724
+ {
725
+ type: 'run.cancel_requested',
726
+ requestId: 'cancel-1',
727
+ reasonCode: 'operator_request',
728
+ ...identity,
729
+ timestamp: now + 5,
730
+ },
731
+ ],
732
+ });
733
+ expect(requested.status === 'accepted' || requested.status === 'duplicate').toBe(true);
734
+ await orchestration.applyCancellation({
735
+ runId,
736
+ now: now + 6,
737
+ requestId: 'cancel-1',
738
+ reasonCode: 'operator_request',
739
+ steps: 1,
740
+ removeCheckpoints: [],
741
+ events: [
742
+ {
743
+ type: 'run.cancelled',
744
+ requestId: 'cancel-1',
745
+ reasonCode: 'operator_request',
746
+ steps: 1,
747
+ ...identity,
748
+ timestamp: now + 6,
749
+ },
750
+ ],
751
+ });
752
+ // The late completion attempt must never resurrect downstream work.
753
+ const claim = claimed.claim;
754
+ let lateRejected = false;
755
+ try {
756
+ await orchestration.completeAttempt({
757
+ runId,
758
+ attemptId: claim.attempt.attemptId,
759
+ ownerId: 'owner-a',
760
+ expectedAttemptFence: claim.attempt.fence,
761
+ now: now + 10,
762
+ outcome: { kind: 'completed', outputSummary: { shape: 'string', length: 4 } },
763
+ continuation: { kind: 'advance', toNodeId: 'next', payload: 'late' },
764
+ events: [],
765
+ run: { status: 'running' },
766
+ });
767
+ }
768
+ catch {
769
+ lateRejected = true;
770
+ }
771
+ void lateRejected;
772
+ const run = await orchestration.getOrchestrationRun(runId);
773
+ expect(run?.status).toBe('cancelled');
774
+ const snapshot = await orchestration.getOrchestrationSnapshot(runId);
775
+ expect((snapshot?.tokens ?? []).some((token) => token.status === 'ready')).toBe(false);
776
+ expect(downstreamCalls).toBe(0);
777
+ }
778
+ finally {
779
+ await fixture.dispose();
780
+ }
781
+ });
782
+ t(`[${factory.name}] an active capability observes its abort signal; downstream never starts`, async () => {
783
+ const fixture = await factory.create();
784
+ try {
785
+ const runtime = fixture.runtime;
786
+ let abortedObserved = false;
787
+ let downstreamCalls = 0;
788
+ runtime
789
+ .registerCapability({
790
+ id: 'first',
791
+ revision: '1',
792
+ effect: 'pure',
793
+ input: neutralJsonContract,
794
+ output: neutralJsonContract,
795
+ invoke: () => 'one',
796
+ })
797
+ .registerCapability({
798
+ id: 'abortable',
799
+ revision: '1',
800
+ effect: 'pure',
801
+ input: neutralJsonContract,
802
+ output: neutralJsonContract,
803
+ invoke: (input, context) => new Promise((resolve, reject) => {
804
+ const signal = context.abortSignal;
805
+ if (signal?.aborted) {
806
+ abortedObserved = true;
807
+ reject(new Error('aborted-before-start'));
808
+ return;
809
+ }
810
+ signal?.addEventListener('abort', () => {
811
+ abortedObserved = true;
812
+ reject(new Error('aborted'));
813
+ }, { once: true });
814
+ void input;
815
+ setTimeout(() => resolve('completed-anyway'), 10_000);
816
+ }),
817
+ })
818
+ .registerCapability({
819
+ id: 'after',
820
+ revision: '1',
821
+ effect: 'pure',
822
+ input: neutralJsonContract,
823
+ output: neutralJsonContract,
824
+ invoke: () => {
825
+ downstreamCalls += 1;
826
+ return 'after';
827
+ },
828
+ });
829
+ const activated = await runtime.activate({
830
+ id: 'race-cancel-active',
831
+ entry: 'a',
832
+ nodes: [
833
+ { id: 'a', capability: 'first' },
834
+ { id: 'h', capability: 'abortable', timeoutMs: 30_000 },
835
+ { id: 'z', capability: 'after' },
836
+ ],
837
+ edges: [
838
+ { from: 'a', to: 'h' },
839
+ { from: 'h', to: 'z' },
840
+ ],
841
+ });
842
+ expect(activated.ok).toBe(true);
843
+ const driving = runtime.run('seed');
844
+ const settled = await Promise.race([
845
+ driving.then(() => 'done'),
846
+ settle(200).then(() => 'waiting'),
847
+ ]);
848
+ expect(settled).toBe('waiting'); // the handler is genuinely in flight
849
+ const parked = (await Promise.race([driving, settle(1).then(() => null)]));
850
+ const runId = parked?.runId ??
851
+ (await (async () => {
852
+ const runs = await fixture.orchestration.listOrchestrationRuns({ status: 'running' });
853
+ return runs[0]?.runId;
854
+ })());
855
+ expect(runId).toBeTruthy();
856
+ // Durable cancellation cooperatively aborts the in-flight context.
857
+ const cancelled = await runtime.cancel({
858
+ runId: runId,
859
+ requestId: 'c-1',
860
+ reasonCode: 'operator_request',
861
+ });
862
+ expect(cancelled.ok).toBe(true);
863
+ const result = await driving;
864
+ expect(abortedObserved).toBe(true);
865
+ expect(result.status).toBe('cancelled');
866
+ expect(downstreamCalls).toBe(0);
867
+ }
868
+ finally {
869
+ await fixture.dispose();
870
+ }
871
+ });
872
+ t(`[${factory.name}] fan-out cancellation: unfinished siblings reach a consistent non-running state`, async () => {
873
+ const fixture = await factory.create();
874
+ try {
875
+ const runtime = fixture.runtime;
876
+ let releaseSlow;
877
+ const slowGate = new Promise((resolve) => {
878
+ releaseSlow = resolve;
879
+ });
880
+ runtime
881
+ .registerCapability({
882
+ id: 'first',
883
+ revision: '1',
884
+ effect: 'pure',
885
+ input: neutralJsonContract,
886
+ output: neutralJsonContract,
887
+ invoke: () => 'one',
888
+ })
889
+ .registerCapability({
890
+ id: 'fast',
891
+ revision: '1',
892
+ effect: 'pure',
893
+ input: neutralJsonContract,
894
+ output: neutralJsonContract,
895
+ invoke: () => 'fast',
896
+ })
897
+ .registerCapability({
898
+ id: 'slow',
899
+ revision: '1',
900
+ effect: 'pure',
901
+ input: neutralJsonContract,
902
+ output: neutralJsonContract,
903
+ invoke: (input, context) => new Promise((resolve) => {
904
+ context.abortSignal?.addEventListener('abort', () => resolve('aborted'), {
905
+ once: true,
906
+ });
907
+ void slowGate.then(() => resolve('slow'));
908
+ void input;
909
+ }),
910
+ })
911
+ .registerCapability({
912
+ id: 'after',
913
+ revision: '1',
914
+ effect: 'pure',
915
+ input: neutralJsonContract,
916
+ output: neutralJsonContract,
917
+ invoke: () => 'after',
918
+ });
919
+ const activated = await runtime.activate({
920
+ id: 'race-cancel-fanout',
921
+ entry: 'a',
922
+ nodes: [
923
+ { id: 'a', capability: 'first' },
924
+ { id: 'f', kind: 'fork', join: 'j' },
925
+ { id: 'b1', capability: 'fast' },
926
+ { id: 'b2', capability: 'slow' },
927
+ { id: 'j', kind: 'join', fork: 'f' },
928
+ { id: 'z', capability: 'after' },
929
+ ],
930
+ edges: [
931
+ { from: 'a', to: 'f' },
932
+ { from: 'f', to: 'b1', kind: 'branch', key: 'a' },
933
+ { from: 'f', to: 'b2', kind: 'branch', key: 'b' },
934
+ { from: 'b1', to: 'j' },
935
+ { from: 'b2', to: 'j' },
936
+ { from: 'j', to: 'z' },
937
+ ],
938
+ });
939
+ expect(activated.ok).toBe(true);
940
+ const driving = runtime.run('seed', { concurrency: 2 });
941
+ await settle(80); // fast branch completes; slow branch is in flight
942
+ const runs = await fixture.orchestration.listOrchestrationRuns({ status: 'running' });
943
+ const runId = runs[0]?.runId;
944
+ expect(runId).toBeTruthy();
945
+ // Durable cancellation mid-fan-out.
946
+ const cancelled = await runtime.cancel({
947
+ runId: runId,
948
+ requestId: 'cx-1',
949
+ reasonCode: 'shutdown',
950
+ });
951
+ expect(cancelled.ok).toBe(true);
952
+ releaseSlow?.();
953
+ const result = await driving;
954
+ const snapshot = await fixture.orchestration.getOrchestrationSnapshot(runId);
955
+ expect(result.status).toBe('cancelled');
956
+ // No unfinished sibling remains ready, claimed, or waiting.
957
+ const unfinished = (snapshot?.tokens ?? []).filter((token) => ['ready', 'claimed', 'waiting'].includes(token.status));
958
+ expect(unfinished.length).toBe(0);
959
+ // The completed branch stays completed (a durable fact, never reversed).
960
+ const branchCompleted = (snapshot?.tokens ?? []).filter((token) => token.nodeId === 'b1' && token.status === 'completed');
961
+ expect(branchCompleted.length).toBe(1);
962
+ }
963
+ finally {
964
+ await fixture.dispose();
965
+ }
966
+ });
967
+ t(`[${factory.name}] cancellation idempotency: duplicate IDs dedupe; competing IDs one canonical outcome`, async () => {
968
+ const fixture = await factory.create();
969
+ try {
970
+ const runtime = fixture.runtime;
971
+ let downstreamCalls = 0;
972
+ runtime
973
+ .registerCapability({
974
+ id: 'first',
975
+ revision: '1',
976
+ effect: 'pure',
977
+ input: neutralJsonContract,
978
+ output: neutralJsonContract,
979
+ invoke: () => 'one',
980
+ })
981
+ .registerCapability({
982
+ id: 'second',
983
+ revision: '1',
984
+ effect: 'pure',
985
+ input: neutralJsonContract,
986
+ output: neutralJsonContract,
987
+ invoke: () => {
988
+ downstreamCalls += 1;
989
+ return 'second';
990
+ },
991
+ })
992
+ .registerContract(stringContract('conf-string'));
993
+ const activated = await runtime.activate(signalWaitGraph());
994
+ expect(activated.ok).toBe(true);
995
+ const parked = await runtime.run('seed');
996
+ expect(parked.status).toBe('waiting');
997
+ // Duplicate ID: idempotent, no duplicate terminal event.
998
+ const first = await runtime.cancel({
999
+ runId: parked.runId,
1000
+ requestId: 'same-id',
1001
+ reasonCode: 'operator_request',
1002
+ });
1003
+ expect(first.ok).toBe(true);
1004
+ expect(first.ok ? first.status : '').toBe('accepted');
1005
+ const duplicate = await runtime.cancel({
1006
+ runId: parked.runId,
1007
+ requestId: 'same-id',
1008
+ reasonCode: 'operator_request',
1009
+ });
1010
+ expect(duplicate.ok).toBe(true);
1011
+ expect(duplicate.ok ? duplicate.status : '').toBe('duplicate');
1012
+ // Competing ID: still exactly one canonical terminal outcome.
1013
+ const competing = await runtime.cancel({
1014
+ runId: parked.runId,
1015
+ requestId: 'other-id',
1016
+ reasonCode: 'policy',
1017
+ });
1018
+ expect(competing.ok).toBe(true);
1019
+ const events = await fixture.orchestration.listOrchestrationEvents(parked.runId);
1020
+ expect(events.filter((event) => event.type === 'run.cancelled').length).toBe(1);
1021
+ const final = await runtime.resumeRun(parked.runId);
1022
+ expect(final.status).toBe('cancelled');
1023
+ expect(downstreamCalls).toBe(0);
1024
+ }
1025
+ finally {
1026
+ await fixture.dispose();
1027
+ }
1028
+ });
1029
+ t(`[${factory.name}] unhandled branch failure cancels unfinished siblings and fails the run exactly once`, async () => {
1030
+ const fixture = await factory.create();
1031
+ try {
1032
+ const runtime = fixture.runtime;
1033
+ let downstreamCalls = 0;
1034
+ let releaseSlow;
1035
+ const slowGate = new Promise((resolve) => {
1036
+ releaseSlow = resolve;
1037
+ });
1038
+ runtime
1039
+ .registerCapability({
1040
+ id: 'first',
1041
+ revision: '1',
1042
+ effect: 'pure',
1043
+ input: neutralJsonContract,
1044
+ output: neutralJsonContract,
1045
+ invoke: () => 'one',
1046
+ })
1047
+ .registerCapability({
1048
+ id: 'boom',
1049
+ revision: '1',
1050
+ effect: 'pure',
1051
+ input: neutralJsonContract,
1052
+ output: neutralJsonContract,
1053
+ invoke: () => {
1054
+ throw new Error('SECRET-CAUSE-SHOULD-NOT-LEAK');
1055
+ },
1056
+ })
1057
+ .registerCapability({
1058
+ id: 'slow',
1059
+ revision: '1',
1060
+ effect: 'pure',
1061
+ input: neutralJsonContract,
1062
+ output: neutralJsonContract,
1063
+ invoke: (input, context) => new Promise((resolve) => {
1064
+ context.abortSignal?.addEventListener('abort', () => resolve('aborted'), {
1065
+ once: true,
1066
+ });
1067
+ void slowGate.then(() => resolve('slow'));
1068
+ void input;
1069
+ }),
1070
+ })
1071
+ .registerCapability({
1072
+ id: 'after',
1073
+ revision: '1',
1074
+ effect: 'pure',
1075
+ input: neutralJsonContract,
1076
+ output: neutralJsonContract,
1077
+ invoke: () => {
1078
+ downstreamCalls += 1;
1079
+ return 'after';
1080
+ },
1081
+ });
1082
+ const activated = await runtime.activate({
1083
+ id: 'race-branch-failure',
1084
+ entry: 'a',
1085
+ nodes: [
1086
+ { id: 'a', capability: 'first' },
1087
+ { id: 'f', kind: 'fork', join: 'j' },
1088
+ { id: 'b1', capability: 'boom' },
1089
+ { id: 'b2', capability: 'slow' },
1090
+ { id: 'j', kind: 'join', fork: 'f' },
1091
+ { id: 'z', capability: 'after' },
1092
+ ],
1093
+ edges: [
1094
+ { from: 'a', to: 'f' },
1095
+ { from: 'f', to: 'b1', kind: 'branch', key: 'a' },
1096
+ { from: 'f', to: 'b2', kind: 'branch', key: 'b' },
1097
+ { from: 'b1', to: 'j' },
1098
+ { from: 'b2', to: 'j' },
1099
+ { from: 'j', to: 'z' },
1100
+ ],
1101
+ });
1102
+ expect(activated.ok).toBe(true);
1103
+ // Release the slow branch after the failure has landed; its late
1104
+ // completion is then fenced by the cancelled sibling token.
1105
+ setTimeout(() => releaseSlow?.(), 150);
1106
+ const result = await runtime.run('seed', { concurrency: 2 });
1107
+ expect(result.status).toBe('failed');
1108
+ expect(downstreamCalls).toBe(0);
1109
+ const events = await fixture.orchestration.listOrchestrationEvents(result.runId);
1110
+ expect(events.filter((event) => event.type === 'run.failed').length).toBe(1);
1111
+ // The thrown message never enters the durable ledger.
1112
+ expect(JSON.stringify(events).includes('SECRET-CAUSE-SHOULD-NOT-LEAK')).toBe(false);
1113
+ const snapshot = await fixture.orchestration.getOrchestrationSnapshot(result.runId);
1114
+ const unfinished = (snapshot?.tokens ?? []).filter((token) => ['ready', 'claimed', 'waiting'].includes(token.status));
1115
+ expect(unfinished.length).toBe(0);
1116
+ }
1117
+ finally {
1118
+ await fixture.dispose();
1119
+ }
1120
+ });
1121
+ t(`[${factory.name}] blocked resolution: public surface denied by default; authorized, validated, idempotent`, async () => {
1122
+ const fixture = await factory.create();
1123
+ try {
1124
+ const { runtime, orchestration } = fixture;
1125
+ let invokeCount = 0;
1126
+ runtime.registerCapability({
1127
+ id: 'slowWrite',
1128
+ revision: '1',
1129
+ effect: 'write',
1130
+ input: neutralJsonContract,
1131
+ output: neutralJsonContract,
1132
+ invoke: async () => {
1133
+ invokeCount += 1;
1134
+ await settle(120);
1135
+ return 'applied';
1136
+ },
1137
+ });
1138
+ runtime.registerCapability({
1139
+ id: 'after',
1140
+ revision: '1',
1141
+ effect: 'pure',
1142
+ input: neutralJsonContract,
1143
+ output: neutralJsonContract,
1144
+ invoke: (input) => `after:${JSON.stringify(input)}`,
1145
+ });
1146
+ runtime.registerContract({
1147
+ id: 'conf-blocked-output',
1148
+ revision: '1',
1149
+ expected: 'a record',
1150
+ parse: (input) => typeof input === 'object' && input !== null
1151
+ ? { ok: true, value: input, issues: [] }
1152
+ : { ok: false, issues: [{ code: 'TYPE', path: '$', message: 'record' }] },
1153
+ });
1154
+ const activated = await runtime.activate({
1155
+ id: 'race-blocked',
1156
+ entry: 'w',
1157
+ nodes: [
1158
+ { id: 'w', capability: 'slowWrite', timeoutMs: 20, output: 'conf-blocked-output' },
1159
+ { id: 'z', capability: 'after' },
1160
+ ],
1161
+ edges: [{ from: 'w', to: 'z' }],
1162
+ });
1163
+ expect(activated.ok).toBe(true);
1164
+ const result = await runtime.run('seed');
1165
+ expect(result.status).toBe('blocked');
1166
+ expect(invokeCount).toBe(1);
1167
+ // Denial by default: no resolution happens without authorization.
1168
+ const denied = await runtime.resolveBlocked({
1169
+ runId: result.runId,
1170
+ resolutionId: 'res-1',
1171
+ action: 'confirm_applied',
1172
+ output: { applied: true },
1173
+ reasonCode: 'operator_request',
1174
+ });
1175
+ expect(denied.ok).toBe(false);
1176
+ expect(denied.ok ? '' : denied.code).toBe('VICT_ORCH_OPERATOR_DENIED');
1177
+ expect(invokeCount).toBe(1);
1178
+ // Authorized surface: same stores, explicitly authorized runtime that
1179
+ // re-registers the identical pinned artifacts.
1180
+ const operatorRuntime = await fixture.createOperatorRuntime();
1181
+ operatorRuntime
1182
+ .registerCapability({
1183
+ id: 'slowWrite',
1184
+ revision: '1',
1185
+ effect: 'write',
1186
+ input: neutralJsonContract,
1187
+ output: neutralJsonContract,
1188
+ invoke: async () => {
1189
+ invokeCount += 1;
1190
+ return 'applied';
1191
+ },
1192
+ })
1193
+ .registerCapability({
1194
+ id: 'after',
1195
+ revision: '1',
1196
+ effect: 'pure',
1197
+ input: neutralJsonContract,
1198
+ output: neutralJsonContract,
1199
+ invoke: (input) => `after:${JSON.stringify(input)}`,
1200
+ })
1201
+ .registerContract({
1202
+ id: 'conf-blocked-output',
1203
+ revision: '1',
1204
+ expected: 'a record',
1205
+ parse: (input) => typeof input === 'object' && input !== null
1206
+ ? { ok: true, value: input, issues: [] }
1207
+ : { ok: false, issues: [{ code: 'TYPE', path: '$', message: 'record' }] },
1208
+ });
1209
+ const runBefore = await orchestration.getOrchestrationRun(result.runId);
1210
+ const accepted = await operatorRuntime.resolveBlocked({
1211
+ runId: result.runId,
1212
+ resolutionId: 'res-2',
1213
+ action: 'confirm_applied',
1214
+ output: { applied: true },
1215
+ reasonCode: 'operator_request',
1216
+ expectedRunRevision: runBefore?.recordRevision,
1217
+ });
1218
+ expect(accepted.ok).toBe(true);
1219
+ expect(accepted.ok ? accepted.status : '').toBe('accepted');
1220
+ // Duplicate resolution ID is idempotent.
1221
+ const duplicate = await operatorRuntime.resolveBlocked({
1222
+ runId: result.runId,
1223
+ resolutionId: 'res-2',
1224
+ action: 'confirm_applied',
1225
+ output: { applied: true },
1226
+ reasonCode: 'operator_request',
1227
+ expectedRunRevision: runBefore?.recordRevision,
1228
+ });
1229
+ expect(duplicate.ok).toBe(true);
1230
+ expect(duplicate.ok ? duplicate.status : '').toBe('duplicate');
1231
+ // Same ID with different content conflicts.
1232
+ const conflicting = await operatorRuntime.resolveBlocked({
1233
+ runId: result.runId,
1234
+ resolutionId: 'res-2',
1235
+ action: 'fail',
1236
+ failCode: 'VICT_ORCH_OPERATOR_FAILED',
1237
+ reasonCode: 'policy',
1238
+ });
1239
+ expect(conflicting.ok).toBe(false);
1240
+ // Stale expected revision conflicts.
1241
+ const stale = await operatorRuntime.resolveBlocked({
1242
+ runId: result.runId,
1243
+ resolutionId: 'res-3',
1244
+ action: 'fail',
1245
+ failCode: 'VICT_ORCH_OPERATOR_FAILED',
1246
+ reasonCode: 'policy',
1247
+ expectedRunRevision: (runBefore?.recordRevision ?? 0) - 1,
1248
+ });
1249
+ expect(stale.ok).toBe(false);
1250
+ // The authorized resolution resumes the durable work; drive it to
1251
+ // completion under the same pinned activation.
1252
+ const final = await runtime.resumeRun(result.runId);
1253
+ expect(final.status).toBe('completed');
1254
+ expect(final.output).toBe('after:{"applied":true}');
1255
+ const events = await orchestration.listOrchestrationEvents(result.runId);
1256
+ expect(events.filter((event) => event.type === 'operator.intervened').length).toBe(1);
1257
+ const run = await orchestration.getOrchestrationRun(result.runId);
1258
+ expect(run?.status).toBe('completed');
1259
+ }
1260
+ finally {
1261
+ await fixture.dispose();
1262
+ }
1263
+ });
1264
+ }
1265
+ //# sourceMappingURL=orchestration-race-conformance.js.map