mcp-event-intelligence 0.1.1 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,3 +1,4 @@
1
+ import { randomUUID } from 'node:crypto';
1
2
  import {
2
3
  WakeRecordSchema,
3
4
  sha256Hex,
@@ -11,6 +12,13 @@ async function stableId(prefix, value) {
11
12
  return `${prefix}_${hash.slice(0, 24)}`;
12
13
  }
13
14
 
15
+ function retryDelayMs(attemptCount, baseDelayMs, maxDelayMs) {
16
+ return Math.min(
17
+ maxDelayMs,
18
+ baseDelayMs * (2 ** Math.max(0, attemptCount - 1)),
19
+ );
20
+ }
21
+
14
22
  export class CompositeWakeCoordinator {
15
23
  constructor({
16
24
  store,
@@ -18,12 +26,91 @@ export class CompositeWakeCoordinator {
18
26
  deliverer = null,
19
27
  packetBuilder = buildGenericRuntimeWakePacket,
20
28
  now = () => new Date(),
29
+ workerId = `wake-worker:${randomUUID()}`,
30
+ leaseMs = 30000,
31
+ maxAttempts = 5,
32
+ retryBaseDelayMs = 1000,
33
+ retryMaxDelayMs = 60000,
21
34
  }) {
22
35
  this.store = store;
23
36
  this.triggerEngine = triggerEngine;
24
37
  this.deliverer = deliverer;
25
38
  this.packetBuilder = packetBuilder;
26
39
  this.now = now;
40
+ this.workerId = workerId;
41
+ this.leaseMs = Math.max(1000, Number(leaseMs) || 30000);
42
+ this.maxAttempts = Math.max(1, Number(maxAttempts) || 5);
43
+ this.retryBaseDelayMs = Math.max(1, Number(retryBaseDelayMs) || 1000);
44
+ this.retryMaxDelayMs = Math.max(
45
+ this.retryBaseDelayMs,
46
+ Number(retryMaxDelayMs) || 60000,
47
+ );
48
+
49
+ for (const method of [
50
+ 'ensureWakeDelivery',
51
+ 'getWakeDelivery',
52
+ 'claimWakeDelivery',
53
+ 'completeWakeDelivery',
54
+ 'failWakeDelivery',
55
+ ]) {
56
+ if (typeof store?.[method] !== 'function') {
57
+ const error = new Error(
58
+ `Wake coordinator store requires ${method}() for durable delivery`,
59
+ );
60
+ error.code = 'WAKE_DELIVERY_STORE_UNSUPPORTED';
61
+ throw error;
62
+ }
63
+ }
64
+ }
65
+
66
+ async deliveredResult({ match, wakeId, definition, delivery }) {
67
+ const existing = this.store.latestWake(wakeId);
68
+ let delivered = existing;
69
+ if (
70
+ !delivered ||
71
+ (delivered.status !== 'delivered' && delivered.status !== 'handled')
72
+ ) {
73
+ delivered = WakeRecordSchema.parse({
74
+ wakeId,
75
+ traceId: match.sourceEvents[0]?.traceId ?? match.matchId,
76
+ decisionId: null,
77
+ subscriptionId: `trigger:${match.triggerId}`,
78
+ sourceEventId: match.matchId,
79
+ createdAt: this.now().toISOString(),
80
+ target: definition.target,
81
+ status: 'delivered',
82
+ runtimeReceiptId: delivery.runtimeReceiptId,
83
+ });
84
+ await this.store.appendWake(delivered);
85
+ await this.store.appendAudit({
86
+ auditId: await stableId(
87
+ 'audit',
88
+ `${wakeId}:delivered-recovered:${this.store.auditLength()}`,
89
+ ),
90
+ traceId: delivered.traceId,
91
+ timestamp: this.now().toISOString(),
92
+ kind: 'wake.delivered',
93
+ entityType: 'wake',
94
+ entityId: wakeId,
95
+ fromState: 'wake_queued',
96
+ toState: 'wake_delivered',
97
+ details: {
98
+ triggerMatchId: match.matchId,
99
+ runtimeReceiptId: delivered.runtimeReceiptId ?? null,
100
+ recoveredFromDeliveryState: true,
101
+ composite: true,
102
+ },
103
+ });
104
+ }
105
+
106
+ if (match.status !== 'fired') {
107
+ await this.triggerEngine.markFired(match.matchId, wakeId);
108
+ }
109
+
110
+ return {
111
+ status: delivered.status === 'handled' ? 'handled' : 'wake_delivered',
112
+ wake: delivered,
113
+ };
27
114
  }
28
115
 
29
116
  async deliverMatched(match) {
@@ -61,16 +148,19 @@ export class CompositeWakeCoordinator {
61
148
  `composite:${match.matchId}:${definition.target.runtime}:${definition.target.id}`,
62
149
  );
63
150
 
64
- const existing = this.store.latestWake(wakeId);
65
- if (existing?.status === 'handled' || existing?.status === 'delivered') {
151
+ const existingWake = this.store.latestWake(wakeId);
152
+ if (existingWake?.status === 'handled' || existingWake?.status === 'delivered') {
66
153
  if (match.status !== 'fired') {
67
154
  await this.triggerEngine.markFired(match.matchId, wakeId);
68
155
  }
69
156
  return {
70
- status: existing.status === 'handled' ? 'handled' : 'wake_delivered',
71
- wake: existing,
157
+ status: existingWake.status === 'handled' ? 'handled' : 'wake_delivered',
158
+ wake: existingWake,
72
159
  };
73
160
  }
161
+ if (existingWake?.status === 'dead_letter') {
162
+ return { status: 'dead_letter', wake: existingWake };
163
+ }
74
164
 
75
165
  const queued = WakeRecordSchema.parse({
76
166
  wakeId,
@@ -83,8 +173,88 @@ export class CompositeWakeCoordinator {
83
173
  status: 'queued',
84
174
  });
85
175
 
86
- if (!existing || existing.status === 'dead_letter') {
176
+ if (!this.deliverer) {
177
+ if (!existingWake) {
178
+ await this.store.appendWake(queued);
179
+ await this.store.appendAudit({
180
+ auditId: await stableId(
181
+ 'audit',
182
+ `${wakeId}:queued:${this.store.auditLength()}`,
183
+ ),
184
+ traceId: queued.traceId,
185
+ timestamp: this.now().toISOString(),
186
+ kind: 'wake.queued',
187
+ entityType: 'wake',
188
+ entityId: wakeId,
189
+ toState: 'wake_queued',
190
+ details: {
191
+ triggerMatchId: match.matchId,
192
+ triggerId: match.triggerId,
193
+ composite: true,
194
+ },
195
+ });
196
+ }
197
+ return {
198
+ status: 'wake_queued',
199
+ wake: this.store.latestWake(wakeId) ?? queued,
200
+ };
201
+ }
202
+
203
+ const nowIso = this.now().toISOString();
204
+ await this.store.ensureWakeDelivery({
205
+ wakeId,
206
+ matchId: match.matchId,
207
+ triggerId: match.triggerId,
208
+ triggerVersion: match.triggerVersion,
209
+ runtime: definition.target.runtime,
210
+ now: nowIso,
211
+ });
212
+
213
+ const currentDelivery = this.store.getWakeDelivery(wakeId);
214
+ if (currentDelivery?.status === 'delivered') {
215
+ return this.deliveredResult({
216
+ match,
217
+ wakeId,
218
+ definition,
219
+ delivery: currentDelivery,
220
+ });
221
+ }
222
+ if (currentDelivery?.status === 'dead_letter') {
223
+ const deadLetter = existingWake?.status === 'dead_letter'
224
+ ? existingWake
225
+ : WakeRecordSchema.parse({
226
+ ...queued,
227
+ createdAt: this.now().toISOString(),
228
+ status: 'dead_letter',
229
+ });
230
+ if (existingWake?.status !== 'dead_letter') {
231
+ await this.store.appendWake(deadLetter);
232
+ }
233
+ return { status: 'dead_letter', wake: deadLetter };
234
+ }
235
+
236
+ const claim = await this.store.claimWakeDelivery(wakeId, {
237
+ workerId: this.workerId,
238
+ now: nowIso,
239
+ leaseMs: this.leaseMs,
240
+ });
241
+
242
+ if (!claim) {
243
+ const delivery = this.store.getWakeDelivery(wakeId);
244
+ return {
245
+ status:
246
+ delivery?.status === 'claimed'
247
+ ? 'delivery_in_progress'
248
+ : 'retry_scheduled',
249
+ wake: this.store.latestWake(wakeId) ?? queued,
250
+ delivery,
251
+ };
252
+ }
253
+
254
+ let persistedQueued = this.store.latestWake(wakeId);
255
+ if (!persistedQueued) {
87
256
  await this.store.appendWake(queued);
257
+ persistedQueued = queued;
88
258
  await this.store.appendAudit({
89
259
  auditId: await stableId(
90
260
  'audit',
@@ -104,10 +274,6 @@ export class CompositeWakeCoordinator {
104
274
  });
105
275
  }
106
276
 
107
- if (!this.deliverer) {
108
- return { status: 'wake_queued', wake: queued };
109
- }
110
-
111
277
  try {
112
278
  const packet = this.packetBuilder({
113
279
  wakeId,
@@ -115,6 +281,19 @@ export class CompositeWakeCoordinator {
115
281
  definition,
116
282
  });
117
283
  const receipt = await this.deliverer(packet);
284
+ if (
285
+ !receipt ||
286
+ typeof receipt.runtimeReceiptId !== 'string' ||
287
+ !receipt.runtimeReceiptId
288
+ ) {
289
+ throw new Error('Wake deliverer must return runtimeReceiptId');
290
+ }
291
+
292
+ const delivery = await this.store.completeWakeDelivery(wakeId, {
293
+ workerId: this.workerId,
294
+ runtimeReceiptId: receipt.runtimeReceiptId,
295
+ now: this.now().toISOString(),
296
+ });
118
297
 
119
298
  const delivered = WakeRecordSchema.parse({
120
299
  ...queued,
@@ -139,6 +318,7 @@ export class CompositeWakeCoordinator {
139
318
  triggerMatchId: match.matchId,
140
319
  runtimeReceiptId: delivered.runtimeReceiptId,
141
320
  duplicate: receipt.duplicate === true,
321
+ attemptCount: delivery?.attemptCount ?? claim.attemptCount,
142
322
  composite: true,
143
323
  },
144
324
  });
@@ -149,8 +329,50 @@ export class CompositeWakeCoordinator {
149
329
  status: 'wake_delivered',
150
330
  wake: delivered,
151
331
  packet,
332
+ delivery,
152
333
  };
153
334
  } catch (error) {
335
+ const delay = retryDelayMs(
336
+ claim.attemptCount,
337
+ this.retryBaseDelayMs,
338
+ this.retryMaxDelayMs,
339
+ );
340
+ const failureAt = this.now();
341
+ const delivery = await this.store.failWakeDelivery(wakeId, {
342
+ workerId: this.workerId,
343
+ error,
344
+ now: failureAt.toISOString(),
345
+ maxAttempts: this.maxAttempts,
346
+ nextAttemptAt: new Date(failureAt.getTime() + delay).toISOString(),
347
+ });
348
+
349
+ if (delivery?.status !== 'dead_letter') {
350
+ await this.store.appendAudit({
351
+ auditId: await stableId(
352
+ 'audit',
353
+ `${wakeId}:retry:${delivery?.attemptCount ?? claim.attemptCount}:${this.store.auditLength()}`,
354
+ ),
355
+ traceId: queued.traceId,
356
+ timestamp: this.now().toISOString(),
357
+ kind: 'wake.retry_scheduled',
358
+ entityType: 'wake',
359
+ entityId: wakeId,
360
+ details: {
361
+ triggerMatchId: match.matchId,
362
+ attemptCount: delivery?.attemptCount ?? claim.attemptCount,
363
+ nextAttemptAt: delivery?.nextAttemptAt ?? null,
364
+ message: error instanceof Error ? error.message : 'wake delivery failed',
365
+ composite: true,
366
+ },
367
+ });
368
+ return {
369
+ status: 'retry_scheduled',
370
+ wake: persistedQueued,
371
+ delivery,
372
+ error,
373
+ };
374
+ }
375
+
154
376
  const deadLetter = WakeRecordSchema.parse({
155
377
  ...queued,
156
378
  createdAt: this.now().toISOString(),
@@ -171,6 +393,7 @@ export class CompositeWakeCoordinator {
171
393
  toState: 'dead_letter',
172
394
  details: {
173
395
  triggerMatchId: match.matchId,
396
+ attemptCount: delivery?.attemptCount ?? claim.attemptCount,
174
397
  message: error instanceof Error ? error.message : 'wake delivery failed',
175
398
  composite: true,
176
399
  },
@@ -178,6 +401,7 @@ export class CompositeWakeCoordinator {
178
401
  return {
179
402
  status: 'dead_letter',
180
403
  wake: deadLetter,
404
+ delivery,
181
405
  error,
182
406
  };
183
407
  }
@@ -1,3 +1,4 @@
1
+ import { randomUUID } from 'node:crypto';
1
2
  import {
2
3
  assertDecisionLineage,
3
4
  assertWakeLineage,
@@ -20,17 +21,37 @@ async function stableId(prefix, value) {
20
21
  return `${prefix}_${hash.slice(0, 24)}`;
21
22
  }
22
23
 
24
+ function retryDelayMs(attemptCount, baseDelayMs, maxDelayMs) {
25
+ return Math.min(
26
+ maxDelayMs,
27
+ baseDelayMs * (2 ** Math.max(0, attemptCount - 1)),
28
+ );
29
+ }
30
+
23
31
  export class EventProcessor {
24
32
  constructor({
25
33
  store,
26
34
  evaluator = null,
27
35
  wakeDeliverer = null,
28
36
  now = () => new Date(),
37
+ workerId = `event-wake-worker:${randomUUID()}`,
38
+ leaseMs = 30000,
39
+ maxAttempts = 5,
40
+ retryBaseDelayMs = 1000,
41
+ retryMaxDelayMs = 60000,
29
42
  }) {
30
43
  this.store = store;
31
44
  this.evaluator = evaluator;
32
45
  this.wakeDeliverer = wakeDeliverer;
33
46
  this.now = now;
47
+ this.workerId = workerId;
48
+ this.leaseMs = Math.max(1000, Number(leaseMs) || 30000);
49
+ this.maxAttempts = Math.max(1, Number(maxAttempts) || 5);
50
+ this.retryBaseDelayMs = Math.max(1, Number(retryBaseDelayMs) || 1000);
51
+ this.retryMaxDelayMs = Math.max(
52
+ this.retryBaseDelayMs,
53
+ Number(retryMaxDelayMs) || 60000,
54
+ );
34
55
  }
35
56
 
36
57
  async ingest(rawRequest) {
@@ -307,73 +328,267 @@ export class EventProcessor {
307
328
  };
308
329
  }
309
330
 
331
+ const deliveryResult = await this.deliverQueuedWake({
332
+ wake: queuedWake,
333
+ lineage,
334
+ event: request.event,
335
+ decision,
336
+ });
337
+
338
+ return {
339
+ traceId,
340
+ sourceEventId: request.event.eventId,
341
+ status: deliveryResult.status,
342
+ decision,
343
+ wake: deliveryResult.wake,
344
+ };
345
+ }
346
+
347
+ async deliveredWakeFromState(wake, delivery) {
348
+ const latest = this.store.latestWake(wake.wakeId);
349
+ if (latest?.status === 'handled' || latest?.status === 'delivered') {
350
+ return latest;
351
+ }
352
+
353
+ const delivered = WakeRecordSchema.parse({
354
+ ...wake,
355
+ createdAt: nowIso(this.now),
356
+ status: 'delivered',
357
+ runtimeReceiptId: delivery.runtimeReceiptId ?? undefined,
358
+ });
359
+ await this.store.appendWake(delivered);
360
+ await this.store.appendAudit({
361
+ auditId: await stableId(
362
+ 'audit',
363
+ `${wake.traceId}:wake-delivered-recovered:${this.store.auditLength()}`,
364
+ ),
365
+ traceId: wake.traceId,
366
+ timestamp: nowIso(this.now),
367
+ kind: 'wake.delivered',
368
+ entityType: 'wake',
369
+ entityId: wake.wakeId,
370
+ fromState: 'wake_queued',
371
+ toState: 'wake_delivered',
372
+ details: {
373
+ runtimeReceiptId: delivered.runtimeReceiptId ?? null,
374
+ recoveredFromDeliveryState: true,
375
+ },
376
+ });
377
+ return delivered;
378
+ }
379
+
380
+ async deliverQueuedWake({ wake, lineage, event, decision }) {
381
+ if (!this.wakeDeliverer) {
382
+ return { status: 'wake_queued', wake };
383
+ }
384
+
385
+ const now = nowIso(this.now);
386
+ await this.store.ensureWakeDelivery({
387
+ wakeId: wake.wakeId,
388
+ sourceType: 'event',
389
+ sourceId: wake.traceId,
390
+ runtime: wake.target.runtime,
391
+ now,
392
+ });
393
+
394
+ const current = this.store.getWakeDelivery(wake.wakeId);
395
+ if (current?.status === 'delivered') {
396
+ return {
397
+ status: 'wake_delivered',
398
+ wake: await this.deliveredWakeFromState(wake, current),
399
+ };
400
+ }
401
+ if (current?.status === 'dead_letter') {
402
+ return {
403
+ status: 'dead_letter',
404
+ wake: this.store.latestWake(wake.wakeId) ?? wake,
405
+ };
406
+ }
407
+
408
+ const claim = await this.store.claimWakeDelivery(wake.wakeId, {
409
+ workerId: this.workerId,
410
+ now,
411
+ leaseMs: this.leaseMs,
412
+ });
413
+ if (!claim) {
414
+ const delivery = this.store.getWakeDelivery(wake.wakeId);
415
+ return {
416
+ status:
417
+ delivery?.status === 'claimed'
418
+ ? 'delivery_in_progress'
419
+ : 'retry_scheduled',
420
+ wake: this.store.latestWake(wake.wakeId) ?? wake,
421
+ };
422
+ }
423
+
310
424
  try {
311
425
  const receipt = await this.wakeDeliverer({
312
- wake: queuedWake,
426
+ wake,
313
427
  lineage,
314
- event: request.event,
428
+ event,
315
429
  decision,
316
430
  });
431
+ if (
432
+ !receipt ||
433
+ typeof receipt.runtimeReceiptId !== 'string' ||
434
+ !receipt.runtimeReceiptId
435
+ ) {
436
+ throw new Error('Wake deliverer must return runtimeReceiptId');
437
+ }
317
438
 
439
+ const delivery = await this.store.completeWakeDelivery(wake.wakeId, {
440
+ workerId: this.workerId,
441
+ runtimeReceiptId: receipt.runtimeReceiptId,
442
+ now: nowIso(this.now),
443
+ });
318
444
  const deliveredWake = WakeRecordSchema.parse({
319
- ...queuedWake,
445
+ ...wake,
320
446
  createdAt: nowIso(this.now),
321
447
  status: 'delivered',
322
- runtimeReceiptId: receipt?.runtimeReceiptId ?? undefined,
448
+ runtimeReceiptId: receipt.runtimeReceiptId,
323
449
  });
324
450
  await this.store.appendWake(deliveredWake);
325
451
  await this.store.appendAudit({
326
- auditId: await stableId('audit', `${traceId}:wake-delivered`),
327
- traceId,
452
+ auditId: await stableId(
453
+ 'audit',
454
+ `${wake.traceId}:wake-delivered:${this.store.auditLength()}`,
455
+ ),
456
+ traceId: wake.traceId,
328
457
  timestamp: nowIso(this.now),
329
458
  kind: 'wake.delivered',
330
459
  entityType: 'wake',
331
- entityId: wakeId,
460
+ entityId: wake.wakeId,
332
461
  fromState: 'wake_queued',
333
462
  toState: 'wake_delivered',
334
463
  details: {
335
464
  runtimeReceiptId: deliveredWake.runtimeReceiptId ?? null,
465
+ attemptCount: delivery?.attemptCount ?? claim.attemptCount,
336
466
  },
337
467
  });
338
-
339
468
  return {
340
- traceId,
341
- sourceEventId: request.event.eventId,
342
469
  status: 'wake_delivered',
343
- decision,
344
470
  wake: deliveredWake,
345
471
  };
346
472
  } catch (error) {
473
+ const failedAt = this.now();
474
+ const delay = retryDelayMs(
475
+ claim.attemptCount,
476
+ this.retryBaseDelayMs,
477
+ this.retryMaxDelayMs,
478
+ );
479
+ const delivery = await this.store.failWakeDelivery(wake.wakeId, {
480
+ workerId: this.workerId,
481
+ error,
482
+ now: failedAt.toISOString(),
483
+ maxAttempts: this.maxAttempts,
484
+ nextAttemptAt: new Date(failedAt.getTime() + delay).toISOString(),
485
+ });
486
+
487
+ if (delivery?.status !== 'dead_letter') {
488
+ await this.store.appendAudit({
489
+ auditId: await stableId(
490
+ 'audit',
491
+ `${wake.traceId}:wake-retry:${delivery?.attemptCount ?? claim.attemptCount}:${this.store.auditLength()}`,
492
+ ),
493
+ traceId: wake.traceId,
494
+ timestamp: nowIso(this.now),
495
+ kind: 'wake.retry_scheduled',
496
+ entityType: 'wake',
497
+ entityId: wake.wakeId,
498
+ details: {
499
+ attemptCount: delivery?.attemptCount ?? claim.attemptCount,
500
+ nextAttemptAt: delivery?.nextAttemptAt ?? null,
501
+ message:
502
+ error instanceof Error
503
+ ? error.message
504
+ : 'wake delivery failed',
505
+ },
506
+ });
507
+ return {
508
+ status: 'retry_scheduled',
509
+ wake: this.store.latestWake(wake.wakeId) ?? wake,
510
+ };
511
+ }
512
+
347
513
  const deadLetter = WakeRecordSchema.parse({
348
- ...queuedWake,
514
+ ...wake,
349
515
  createdAt: nowIso(this.now),
350
516
  status: 'dead_letter',
351
517
  });
352
518
  await this.store.appendWake(deadLetter);
353
519
  await this.store.appendAudit({
354
- auditId: await stableId('audit', `${traceId}:wake-dead-letter`),
355
- traceId,
520
+ auditId: await stableId(
521
+ 'audit',
522
+ `${wake.traceId}:wake-dead-letter:${this.store.auditLength()}`,
523
+ ),
524
+ traceId: wake.traceId,
356
525
  timestamp: nowIso(this.now),
357
526
  kind: 'wake.dead_letter',
358
527
  entityType: 'wake',
359
- entityId: wakeId,
528
+ entityId: wake.wakeId,
360
529
  fromState: 'wake_queued',
361
530
  toState: 'dead_letter',
362
531
  details: {
363
- message: error instanceof Error ? error.message : 'wake delivery failed',
532
+ attemptCount: delivery?.attemptCount ?? claim.attemptCount,
533
+ message:
534
+ error instanceof Error
535
+ ? error.message
536
+ : 'wake delivery failed',
364
537
  },
365
538
  });
366
-
367
539
  return {
368
- traceId,
369
- sourceEventId: request.event.eventId,
370
540
  status: 'dead_letter',
371
- decision,
372
541
  wake: deadLetter,
373
542
  };
374
543
  }
375
544
  }
376
545
 
546
+ async retryWake(wakeId) {
547
+ const delivery = this.store.getWakeDelivery(wakeId);
548
+ if (!delivery || delivery.sourceType !== 'event') {
549
+ return { status: 'not_event_wake', wake: null };
550
+ }
551
+
552
+ const latest = this.store.latestWake(wakeId);
553
+ if (!latest) {
554
+ throw new Error(`Unknown wake: ${wakeId}`);
555
+ }
556
+ if (latest.status === 'handled') {
557
+ return { status: 'handled', wake: latest };
558
+ }
559
+ if (latest.status === 'dead_letter') {
560
+ return { status: 'dead_letter', wake: latest };
561
+ }
562
+
563
+ const trace = this.store.trace(delivery.sourceId);
564
+ const eventRecord = trace.events[0];
565
+ if (!eventRecord?.lineage || !eventRecord?.event) {
566
+ const error = new Error(
567
+ `Cannot reconstruct event wake ${wakeId}; trace ${delivery.sourceId} is missing`,
568
+ );
569
+ error.code = 'WAKE_RETRY_SOURCE_MISSING';
570
+ throw error;
571
+ }
572
+
573
+ const decision = latest.decisionId
574
+ ? trace.decisions.find(
575
+ (candidate) => candidate.decisionId === latest.decisionId,
576
+ ) ?? null
577
+ : null;
578
+
579
+ const queued = WakeRecordSchema.parse({
580
+ ...latest,
581
+ status: 'queued',
582
+ runtimeReceiptId: undefined,
583
+ });
584
+ return this.deliverQueuedWake({
585
+ wake: queued,
586
+ lineage: eventRecord.lineage,
587
+ event: eventRecord.event,
588
+ decision,
589
+ });
590
+ }
591
+
377
592
  async acknowledgeWake(wakeId, runtimeReceiptId) {
378
593
  const latest = this.store.latestWake(wakeId);
379
594
  if (!latest) throw new Error('Unknown wake');