@yielded/agent-platform-cloudflare 0.1.0-beta.174 → 0.1.0-beta.176

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 (30) hide show
  1. package/dist/{Alarm-4J9B8Fg8.mjs → Alarm-7uipp6LW.mjs} +155 -117
  2. package/dist/Alarm-7uipp6LW.mjs.map +1 -0
  3. package/dist/Alarm.d.mts +2 -0
  4. package/dist/Alarm.mjs +1 -1
  5. package/dist/CloudflareScheduling.mjs +2 -2
  6. package/dist/CloudflareSubscriptions.mjs +2 -2
  7. package/dist/{CloudflareThreadClient-lTm6baDP.d.mts → CloudflareThreadClient-HO_3rkM7.d.mts} +362 -362
  8. package/dist/{CloudflareThreadClient-DU548EEj.mjs → CloudflareThreadClient-bA3lGA_2.mjs} +4 -4
  9. package/dist/CloudflareThreadClient-bA3lGA_2.mjs.map +1 -0
  10. package/dist/CloudflareThreadClient.d.mts +1 -1
  11. package/dist/CloudflareThreadClient.mjs +1 -1
  12. package/dist/{ThreadObject-BLYgzcdT.d.mts → ThreadObject-CxNlhoPL.d.mts} +13 -13
  13. package/dist/{ThreadObject-C2LF3btz.mjs → ThreadObject-SNcCsrS2.mjs} +52 -50
  14. package/dist/ThreadObject-SNcCsrS2.mjs.map +1 -0
  15. package/dist/ThreadObject.d.mts +1 -1
  16. package/dist/ThreadObject.mjs +1 -1
  17. package/dist/WakeScheduler.mjs +1 -1
  18. package/dist/index.d.mts +2 -2
  19. package/dist/index.mjs +3 -3
  20. package/dist/{prepared-admission-BoUPHSNQ.mjs → prepared-admission-BvMiFR41.mjs} +2 -2
  21. package/dist/{prepared-admission-BoUPHSNQ.mjs.map → prepared-admission-BvMiFR41.mjs.map} +1 -1
  22. package/package.json +1 -1
  23. package/src/Alarm.ts +295 -201
  24. package/src/CloudflareThreadClient.ts +3 -2
  25. package/src/ThreadObject.ts +17 -22
  26. package/src/internal/layers.ts +124 -82
  27. package/src/internal/message-delivery.ts +61 -37
  28. package/dist/Alarm-4J9B8Fg8.mjs.map +0 -1
  29. package/dist/CloudflareThreadClient-DU548EEj.mjs.map +0 -1
  30. package/dist/ThreadObject-C2LF3btz.mjs.map +0 -1
@@ -410,49 +410,48 @@ const makeAlarmOperation = Effect.gen(function* () {
410
410
  const queue = make(ctx.storage);
411
411
  const transaction = (body) => alarms.transaction((mutations) => Effect.withFiber((owner) => {
412
412
  let active = true;
413
- const requireActive = Effect.withFiber((fiber) => active && fiber === owner ? Effect.void : Effect.fail(DurableAlarmError.make({
414
- operation: "enroll maintenance source",
415
- message: "Source intent requires its active transaction callback's fiber"
416
- })));
417
- return body.pipe(Effect.provideService(CurrentCheckpoint, {
413
+ return Effect.onExitPrimitive(body(owner).pipe(Effect.provideService(CurrentCheckpoint, {
418
414
  mutations,
419
- requireActive
420
- }), Effect.ensuring(Effect.sync(() => {
415
+ isActive: (fiber) => active && fiber === owner
416
+ })), () => {
421
417
  active = false;
422
- })));
418
+ });
423
419
  }), { sqlClient: sql }).pipe(Effect.mapError((cause) => cause instanceof DurableObjectStorage.StorageOperationError ? SqlError.make({ reason: UnknownError.make({
424
420
  operation: "commit maintenance checkpoint",
425
421
  message: "Source facts and maintenance checkpoint could not commit",
426
422
  cause
427
423
  }) }) : cause));
428
- const withTransaction = (body) => Effect.flatMap(Effect.serviceOption(sql.transactionService), (ambient) => {
424
+ const withTransaction = (body) => Effect.withFiber((fiber) => {
425
+ const ambient = Context.getOption(fiber.context, sql.transactionService);
429
426
  if (ambient._tag === "Some" && !ownsTransaction(ctx.storage, ambient.value[1])) return Effect.fail(SqlError.make({ reason: UnknownError.make({
430
427
  operation: "begin maintenance source transaction",
431
428
  message: "Use ThreadMutationGate.withTransaction at the outermost source transaction",
432
429
  cause: "Unwrapped SQL transaction cannot own buffered maintenance intent"
433
430
  }) }));
434
- return Effect.uninterruptibleMask((restore) => Effect.suspend(() => {
435
- let frame;
436
- return transaction(Effect.flatMap(Effect.serviceOption(sql.transactionService), (transaction) => Effect.gen(function* () {
437
- if (transaction._tag === "None") return yield* SqlError.make({ reason: UnknownError.make({
438
- operation: "begin maintenance source transaction",
439
- message: "SQL transaction unavailable",
440
- cause: "SQL transaction unavailable"
441
- }) });
442
- frame = queue.begin(transaction.value[1]);
443
- })).pipe(Effect.andThen(restore(body)), Effect.tap(() => Effect.try({
431
+ let frame;
432
+ return Effect.uninterruptibleMask((restore) => Effect.onExitPrimitive(transaction((owner) => {
433
+ const current = Context.getOption(owner.context, sql.transactionService);
434
+ if (current._tag === "None") return SqlError.make({ reason: UnknownError.make({
435
+ operation: "begin maintenance source transaction",
436
+ message: "SQL transaction unavailable",
437
+ cause: "SQL transaction unavailable"
438
+ }) });
439
+ const started = queue.begin(current.value[1]);
440
+ frame = started;
441
+ return Effect.flatMap(restore(body), (value) => Effect.try({
444
442
  try: () => {
445
- if (frame !== void 0) queue.flush(frame);
443
+ queue.flush(started);
444
+ return value;
446
445
  },
447
446
  catch: (cause) => SqlError.make({ reason: UnknownError.make({
448
447
  cause,
449
448
  operation: "flush maintenance due queue",
450
449
  message: "Maintenance intent could not commit"
451
450
  }) })
452
- })))).pipe(Effect.onExit((exit) => Effect.sync(() => {
453
- if (frame !== void 0) queue.finish(frame, Exit.isSuccess(exit));
454
- })), Effect.provideService(Scheduler.PreventSchedulerYield, true));
455
- }));
451
+ }));
452
+ }), (exit) => {
453
+ if (frame !== void 0) queue.finish(frame, Exit.isSuccess(exit));
454
+ }).pipe(Effect.provideService(Scheduler.PreventSchedulerYield, true)));
456
455
  }).pipe(Effect.provideService(SqlClient, sql));
457
456
  const run = (operation, execute) => {
458
457
  const commit = Effect.withFiber((fiber) => {
@@ -461,28 +460,30 @@ const makeAlarmOperation = Effect.gen(function* () {
461
460
  operation: "checkpoint",
462
461
  message: "Maintenance checkpoint transaction unavailable"
463
462
  }));
464
- return tx.requireActive.pipe(Effect.andThen(Effect.suspend(() => {
465
- let checkpoint = Effect.void;
466
- const intent = {
467
- at: (at) => {
468
- checkpoint = tx.mutations.scheduleAlarm(maintenanceAlarmInput(at));
469
- },
470
- earlier: (at) => {
471
- checkpoint = tx.mutations.scheduleAlarmEarlier(maintenanceAlarmInput(at));
472
- },
473
- cancel: () => {
474
- checkpoint = tx.mutations.cancelAlarm(maintenanceAlarmRef);
475
- }
476
- };
477
- return Effect.tryPromise({
478
- try: () => execute(ctx.storage, intent),
479
- catch: alarmFailure(operation)
480
- }).pipe(Effect.tap(() => checkpoint));
481
- })));
463
+ if (!tx.isActive(fiber)) return Effect.fail(DurableAlarmError.make({
464
+ operation: "enroll maintenance source",
465
+ message: "Source intent requires its active transaction callback's fiber"
466
+ }));
467
+ let checkpoint = Effect.void;
468
+ const intent = {
469
+ at: (at) => {
470
+ checkpoint = tx.mutations.scheduleAlarm(maintenanceAlarmInput(at));
471
+ },
472
+ earlier: (at) => {
473
+ checkpoint = tx.mutations.scheduleAlarmEarlier(maintenanceAlarmInput(at));
474
+ },
475
+ cancel: () => {
476
+ checkpoint = tx.mutations.cancelAlarm(maintenanceAlarmRef);
477
+ }
478
+ };
479
+ return Effect.tryPromise({
480
+ try: () => execute(ctx.storage, intent),
481
+ catch: alarmFailure(operation)
482
+ }).pipe(Effect.tap(() => checkpoint));
482
483
  });
483
484
  return Effect.withFiber((fiber) => {
484
485
  if (fiber.getRef(CurrentCheckpoint) !== void 0) return commit;
485
- return Context.getOption(fiber.context, sql.transactionService)._tag === "Some" && !buffering(ctx.storage) ? transaction(commit).pipe(Effect.ensuring(Effect.sync(() => invalidate(ctx.storage)))) : withTransaction(commit);
486
+ return Context.getOption(fiber.context, sql.transactionService)._tag === "Some" && !buffering(ctx.storage) ? transaction(() => commit).pipe(Effect.ensuring(Effect.sync(() => invalidate(ctx.storage)))) : withTransaction(commit);
486
487
  }).pipe(Effect.uninterruptible, Effect.mapError(alarmFailure(operation)));
487
488
  };
488
489
  return {
@@ -624,6 +625,14 @@ const recoveryEvent = (fault, submissionId, transition, occurredAt) => ({
624
625
  firstFailedAt: fault.firstFailedAt,
625
626
  failure: fault.failure
626
627
  });
628
+ /** Observe already-owned waves without creating one waiter fiber for each wave. */
629
+ const awaitAnyCompletion = Effect.fnUntraced(function* (fibers, revision) {
630
+ const mutations = yield* ThreadMutationGate;
631
+ return yield* Effect.acquireUseRelease(Effect.sync(() => []), (observers) => Effect.callback((resume) => {
632
+ observers.push(mutations.observeChange(revision, () => resume(Effect.succeed("scan"))));
633
+ for (const fiber of fibers) observers.push(fiber.addObserver(() => resume(Effect.succeed("completed"))));
634
+ }), (observers) => Effect.sync(() => observers.forEach((remove) => remove())));
635
+ });
627
636
  /** Post-native delivery shares a finite connection budget across independent host lanes. */
628
637
  const afterNativeDispatchConcurrency = 2;
629
638
  const NativeWorkCursor = Schema.Struct({
@@ -709,12 +718,22 @@ var ThreadMutationGate = class ThreadMutationGate extends Context.Service()("@ef
709
718
  const activeLanes = /* @__PURE__ */ new Map();
710
719
  const generationGate = yield* Semaphore.make(1);
711
720
  let revision = 0;
712
- let changed = yield* Deferred.make();
713
- const notify = Effect.gen(function* () {
714
- const previous = changed;
715
- changed = yield* Deferred.make();
721
+ const listeners = /* @__PURE__ */ new Set();
722
+ const observeChange = (observed, listener) => {
723
+ if (observed !== revision) {
724
+ listener();
725
+ return () => {};
726
+ }
727
+ listeners.add(listener);
728
+ return () => {
729
+ listeners.delete(listener);
730
+ };
731
+ };
732
+ const notify = Effect.sync(() => {
716
733
  revision++;
717
- yield* Deferred.succeed(previous, void 0);
734
+ const pending = [...listeners];
735
+ listeners.clear();
736
+ for (const listener of pending) listener();
718
737
  });
719
738
  const minimumAlarmDelay = Math.max(1, Math.ceil(config.alarmBackoffBase / 2));
720
739
  const runTransaction = yield* makeStorageOperation;
@@ -740,8 +759,8 @@ var ThreadMutationGate = class ThreadMutationGate extends Context.Service()("@ef
740
759
  dirty: state.dirty + 1n
741
760
  })));
742
761
  }
743
- const next$4 = next(dueQueue.read());
744
- if (Number.isFinite(next$4)) checkpoint.earlier(Math.max(next$4, now + minimumAlarmDelay));
762
+ const next$2 = next(dueQueue.read());
763
+ if (Number.isFinite(next$2)) checkpoint.earlier(Math.max(next$2, now + minimumAlarmDelay));
745
764
  };
746
765
  yield* runAlarmTransaction("schedule maintenance lane", enroll);
747
766
  yield* notify;
@@ -818,7 +837,11 @@ var ThreadMutationGate = class ThreadMutationGate extends Context.Service()("@ef
818
837
  recordProgress,
819
838
  activeLanes: Effect.sync(() => new Set(activeLanes.keys())),
820
839
  revision: Effect.sync(() => revision),
821
- awaitChange: (observed) => Effect.suspend(() => observed === revision ? Deferred.await(changed) : Effect.void),
840
+ observeChange,
841
+ awaitChange: (observed) => Effect.callback((resume) => {
842
+ const remove = observeChange(observed, () => resume(Effect.void));
843
+ return Effect.sync(remove);
844
+ }),
822
845
  withSnapshot: (body) => generationGate.withPermit(Effect.flatMap(Ref.get(activeMutations), body))
823
846
  });
824
847
  }));
@@ -849,6 +872,7 @@ var ThreadMaintenance = class ThreadMaintenance extends Context.Service()("@effe
849
872
  const runtime = yield* DurableAgentRuntime;
850
873
  const recoveryConfig = yield* DurableRuntimeConfig;
851
874
  const ledger = yield* SubmissionLedger;
875
+ const listNonterminal = ledger.listNonterminal ?? Stream.runCollect(ledger.scanNonterminal);
852
876
  const wakes = yield* WakeScheduler;
853
877
  const hints = yield* DurableAlarmService;
854
878
  const alarms = yield* ThreadMaintenanceAlarm;
@@ -865,6 +889,8 @@ var ThreadMaintenance = class ThreadMaintenance extends Context.Service()("@effe
865
889
  const messages = yield* ThreadMessageDelivery;
866
890
  const host = yield* ThreadHostMaintenance;
867
891
  const framework = yield* ThreadNativeMaintenance;
892
+ const placement = yield* Effect.serviceOption(ThreadObjectPlacement);
893
+ const nativeThread = Option.isSome(placement) ? placement.value.threadId : void 0;
868
894
  const nativeDispatchConcurrency = () => framework.singleThreadRuntime === runtime ? 1 : 2;
869
895
  for (const lane of host.lanes) yield* Schema.decodeEffect(HostLaneId)(lane.id).pipe(Effect.mapError(alarmFailure("maintenance lane ID")));
870
896
  const recoveryEvents = Option.getOrElse(yield* Effect.serviceOption(ThreadRecoveryEvents), () => ThreadRecoveryEvents.of({ publish: () => Effect.void }));
@@ -914,8 +940,8 @@ var ThreadMaintenance = class ThreadMaintenance extends Context.Service()("@effe
914
940
  const now = yield* Clock.currentTimeMillis;
915
941
  const claimed = yield* runAlarmTransaction("charge maintenance attempt", async (_transaction, checkpoint) => {
916
942
  const claimed = dueQueue.claim(selected, now);
917
- const next$1 = next(dueQueue.read());
918
- if (Number.isFinite(next$1)) checkpoint.at(Math.max(now + minimumAlarmDelay, next$1));
943
+ const next$3 = next(dueQueue.read());
944
+ if (Number.isFinite(next$3)) checkpoint.at(Math.max(now + minimumAlarmDelay, next$3));
919
945
  else checkpoint.cancel();
920
946
  return claimed;
921
947
  });
@@ -1008,7 +1034,7 @@ var ThreadMaintenance = class ThreadMaintenance extends Context.Service()("@effe
1008
1034
  for (const blocked of result.blocked) threads.set(blocked.threadId, blocked.failure);
1009
1035
  if (threads.size === 0 || [...threads].every(([threadId, failure]) => failure === void 0 && recovery.loaded.has(threadId) && !recovery.faults.has(threadId))) return /* @__PURE__ */ new Map();
1010
1036
  const now = yield* Clock.currentTimeMillis;
1011
- const submissions = current ?? (result.blocked.length === 0 ? [] : yield* Stream.runCollect(ledger.scanNonterminal));
1037
+ const submissions = current ?? (result.blocked.length === 0 ? [] : yield* listNonterminal);
1012
1038
  yield* failpoint.hit("maintenance:recovery-status:before");
1013
1039
  const retained = yield* runTransaction("record Thread recovery faults", () => dueQueue.transaction(async (transaction) => {
1014
1040
  const events = [];
@@ -1078,6 +1104,7 @@ var ThreadMaintenance = class ThreadMaintenance extends Context.Service()("@effe
1078
1104
  do {
1079
1105
  const result = yield* runtime.runRecovery({
1080
1106
  threadId,
1107
+ deferUnclaimedAdmissions: true,
1081
1108
  ...cursor === void 0 ? {} : { cursor }
1082
1109
  }).pipe(Effect.provideService(CurrentNativeSource, true));
1083
1110
  const faults = yield* recordRecoveryFaults(result, recovery, void 0, threadId);
@@ -1124,8 +1151,8 @@ var ThreadMaintenance = class ThreadMaintenance extends Context.Service()("@effe
1124
1151
  if (!initialized) await transaction.put(MAINTENANCE_STATE_KEY, encodeMaintenanceState(state));
1125
1152
  const native = state.dirty > state.processed ? state.retry?.generation === state.dirty ? state.retry.notBefore : rows.find((row) => row.id === "effect-agent:native")?.dueAt ?? now + minimumAlarmDelay : null;
1126
1153
  dueQueue.checkpointNative(native);
1127
- const next$3 = next(dueQueue.read());
1128
- if (Number.isFinite(next$3)) checkpoint.earlier(Math.max(now + minimumAlarmDelay, next$3));
1154
+ const next$1 = next(dueQueue.read());
1155
+ if (Number.isFinite(next$1)) checkpoint.earlier(Math.max(now + minimumAlarmDelay, next$1));
1129
1156
  });
1130
1157
  yield* failpoint.hit("maintenance:ensure:after");
1131
1158
  });
@@ -1199,8 +1226,8 @@ var ThreadMaintenance = class ThreadMaintenance extends Context.Service()("@effe
1199
1226
  const charged = observed.charged.get(row.id) === row.revision ? row : dueQueue.claim(row, now);
1200
1227
  if (charged !== void 0) dueQueue.complete(charged, now + backoffDelay(charged.stalls, jitter));
1201
1228
  }
1202
- const next$5 = next(dueQueue.read());
1203
- if (Number.isFinite(next$5)) checkpoint.at(Math.max(now + minimumAlarmDelay, next$5));
1229
+ const next$4 = next(dueQueue.read());
1230
+ if (Number.isFinite(next$4)) checkpoint.at(Math.max(now + minimumAlarmDelay, next$4));
1204
1231
  else checkpoint.cancel();
1205
1232
  });
1206
1233
  }));
@@ -1291,11 +1318,14 @@ var ThreadMaintenance = class ThreadMaintenance extends Context.Service()("@effe
1291
1318
  const { state } = await readMaintenanceState(ctx.storage);
1292
1319
  return {
1293
1320
  scannedGeneration: state.dirty,
1294
- afterWorkThread: state.lastRecoveredThreadId
1321
+ afterWorkThread: framework.singleThreadRuntime === runtime && state.lastRecoveredThreadId === nativeThread ? void 0 : state.lastRecoveredThreadId
1295
1322
  };
1296
1323
  });
1297
- const current = yield* Stream.runCollect(ledger.scanNonterminal);
1298
- let workOwners = yield* runtime.discoverWorkThreads({
1324
+ const current = yield* listNonterminal;
1325
+ let workOwners = framework.singleThreadRuntime === runtime && nativeThread !== void 0 && current.some((submission) => submission.threadId === nativeThread) ? {
1326
+ threadIds: [nativeThread],
1327
+ afterThreadId: void 0
1328
+ } : yield* runtime.discoverWorkThreads({
1299
1329
  limit: 32,
1300
1330
  ...afterWorkThread === void 0 ? {} : { afterThreadId: afterWorkThread }
1301
1331
  }).pipe(Effect.provideService(CurrentNativeSource, true));
@@ -1419,7 +1449,7 @@ var ThreadMaintenance = class ThreadMaintenance extends Context.Service()("@effe
1419
1449
  nextAttemptAt: void 0,
1420
1450
  dispatched: selection.selected.length > 0
1421
1451
  };
1422
- const remaining = yield* Stream.runCollect(ledger.scanNonterminal);
1452
+ const remaining = yield* listNonterminal;
1423
1453
  const waitingHeads = /* @__PURE__ */ new Map();
1424
1454
  const autonomous = recovery.pending.size > 0 || recovery.discoveryPending || recoveryFaults.size > 0 || recovery.retryAtMillis !== void 0 || remaining.some((snapshot) => {
1425
1455
  if (snapshot.state === "unknown" && waiting(snapshot)) return false;
@@ -1494,7 +1524,8 @@ var ThreadMaintenance = class ThreadMaintenance extends Context.Service()("@effe
1494
1524
  });
1495
1525
  });
1496
1526
  const dispatch = Effect.fnUntraced(function* (yieldAfter, dispatchUntil, observed) {
1497
- const notified = (yield* Stream.toPull(wakes.wakes)).pipe(Effect.asVoid, Effect.catch(() => Effect.never));
1527
+ const leanSelection = framework.singleThreadRuntime === runtime && publication === emptyPublication && host.lanes.length === 0 && framework.lanes.length === 0;
1528
+ const notified = leanSelection ? Effect.never : (yield* Stream.toPull(wakes.wakes)).pipe(Effect.asVoid, Effect.catch(() => Effect.never));
1498
1529
  const started = yield* beginNative(observed);
1499
1530
  yield* mutations.withSnapshot(() => Effect.gen(function* () {
1500
1531
  const queued = yield* queueSnapshot;
@@ -1531,8 +1562,35 @@ var ThreadMaintenance = class ThreadMaintenance extends Context.Service()("@effe
1531
1562
  repaired: false,
1532
1563
  discoveryPending: false
1533
1564
  };
1534
- const recoveryFiber = yield* fork(Effect.gen(function* () {
1535
- for (const threadId of yield* Deferred.await(recovery.queue)) {
1565
+ let recoveryFiber;
1566
+ let backfill;
1567
+ const dispatch = {
1568
+ scope: auxiliaryScope,
1569
+ active: /* @__PURE__ */ new Map(),
1570
+ deferred: /* @__PURE__ */ new Set(),
1571
+ dispatched: 0,
1572
+ settled: 0,
1573
+ progressed: false,
1574
+ needsCheckpoint: false
1575
+ };
1576
+ let native;
1577
+ let result;
1578
+ let backlog;
1579
+ if (leanSelection) {
1580
+ result = yield* advance(started, yieldAfter, observed, recovery, dispatch);
1581
+ recovery.started = true;
1582
+ yield* Deferred.succeed(recovery.queue, []);
1583
+ backlog = yield* Deferred.await(recovery.queue);
1584
+ } else {
1585
+ native = yield* fork(advance(started, yieldAfter, observed, recovery, dispatch));
1586
+ result = {
1587
+ phase: "caught-up",
1588
+ nonterminal: started.nonterminal,
1589
+ nextAttemptAt: void 0
1590
+ };
1591
+ }
1592
+ if (backlog === void 0 || backlog.length > 0) recoveryFiber = yield* fork(Effect.gen(function* () {
1593
+ for (const threadId of backlog ?? (yield* Deferred.await(recovery.queue))) {
1536
1594
  yield* failpoint.hit("maintenance:select:before");
1537
1595
  yield* runTransaction("select old recovery lane", () => dueQueue.transaction(async (transaction) => {
1538
1596
  const { state } = await readMaintenanceState(transaction);
@@ -1547,31 +1605,11 @@ var ThreadMaintenance = class ThreadMaintenance extends Context.Service()("@effe
1547
1605
  if (!recovery.faults.has(threadId)) yield* wakes.notify(threadId);
1548
1606
  }
1549
1607
  }));
1550
- const backfill = yield* fork(Effect.gen(function* () {
1551
- if ((yield* mutations.activeLanes).has("effect-agent:projection")) return;
1552
- const row = (yield* queueSnapshot).find((row) => row.id === Projection);
1553
- if (row?.dueAt === null || row === void 0 || row.dueAt > (yield* Clock.currentTimeMillis)) return;
1554
- return yield* runQueued(row, projection.drain.pipe(Effect.andThen(projection.pendingDeadline)), observed).pipe(Effect.timeoutOption(config.projectionDispatchTimeoutMillis));
1555
- }));
1556
- let backfillObserved = false;
1557
- let recoveryObserved = false;
1558
- const dispatch = {
1559
- scope: auxiliaryScope,
1560
- active: /* @__PURE__ */ new Map(),
1561
- deferred: /* @__PURE__ */ new Set(),
1562
- dispatched: 0,
1563
- settled: 0,
1564
- progressed: false,
1565
- needsCheckpoint: false
1566
- };
1567
- let native = yield* fork(advance(started, yieldAfter, observed, recovery, dispatch));
1568
- let result = {
1569
- phase: "caught-up",
1570
- nonterminal: started.nonterminal,
1571
- nextAttemptAt: void 0
1572
- };
1608
+ const projectionRow = (yield* queueSnapshot).find((row) => row.id === Projection);
1609
+ if (projectionRow !== void 0 && projectionRow.dueAt !== null && projectionRow.dueAt <= (yield* Clock.currentTimeMillis) && !(yield* mutations.activeLanes).has("effect-agent:projection")) backfill = yield* fork(runQueued(projectionRow, projection.drain.pipe(Effect.andThen(projection.pendingDeadline)), observed).pipe(Effect.timeoutOption(config.projectionDispatchTimeoutMillis), Effect.asVoid));
1573
1610
  let phase = result.phase;
1574
- let nativeCheck = false;
1611
+ let nativeCheck = result.dispatched === true && dispatch.active.size < nativeDispatchConcurrency();
1612
+ observed.nativeOnly = dispatch.active.size > 0;
1575
1613
  const until = DateTime.toEpochMillis(yieldAfter);
1576
1614
  const dispatchEnd = DateTime.toEpochMillis(dispatchUntil);
1577
1615
  while (true) {
@@ -1599,18 +1637,16 @@ var ThreadMaintenance = class ThreadMaintenance extends Context.Service()("@effe
1599
1637
  if (exit.value === 0) dispatch.deferred.add(threadId);
1600
1638
  nativeCheck = true;
1601
1639
  }
1602
- if (!recoveryObserved && recoveryFiber.pollUnsafe() !== void 0) {
1640
+ if (recoveryFiber?.pollUnsafe() !== void 0) {
1603
1641
  yield* Fiber.join(recoveryFiber);
1604
- recoveryObserved = true;
1642
+ recoveryFiber = void 0;
1605
1643
  dispatch.scanGeneration = void 0;
1606
1644
  nativeCheck ||= recovery.needsCheckpoint;
1607
1645
  }
1608
- if (!backfillObserved) {
1609
- const exit = backfill.pollUnsafe();
1610
- if (exit !== void 0) {
1611
- backfillObserved = true;
1612
- if (Exit.isFailure(exit)) failure ??= exit.cause;
1613
- }
1646
+ const backfillExit = backfill?.pollUnsafe();
1647
+ if (backfillExit !== void 0) {
1648
+ backfill = void 0;
1649
+ if (Exit.isFailure(backfillExit)) failure ??= backfillExit.cause;
1614
1650
  }
1615
1651
  for (const lane of lanes) {
1616
1652
  const exit = lane.fiber?.pollUnsafe();
@@ -1668,11 +1704,11 @@ var ThreadMaintenance = class ThreadMaintenance extends Context.Service()("@effe
1668
1704
  return yield* wave.run;
1669
1705
  }), observed).pipe(Effect.asVoid));
1670
1706
  }
1671
- if (native === void 0 && dispatch.active.size === 0 && recoveryObserved && !recovery.needsCheckpoint && backfillObserved && delivery === void 0 && lanes.every((lane) => lane.fiber === void 0)) break;
1707
+ if (native === void 0 && dispatch.active.size === 0 && recoveryFiber === void 0 && !recovery.needsCheckpoint && backfill === void 0 && delivery === void 0 && lanes.every((lane) => lane.fiber === void 0)) break;
1672
1708
  if (native === void 0 && nativeCheck && dispatch.active.size < nativeDispatchConcurrency()) {
1673
1709
  nativeCheck = false;
1674
1710
  const nativeCheckpoint = dispatch.needsCheckpoint && dispatch.active.size === 0;
1675
- const checkpoint = recoveryObserved && recovery.needsCheckpoint || nativeCheckpoint;
1711
+ const checkpoint = recoveryFiber === void 0 && recovery.needsCheckpoint || nativeCheckpoint;
1676
1712
  if (checkpoint) recovery.needsCheckpoint = false;
1677
1713
  const reserved = new Set(dispatch.active.keys());
1678
1714
  native = yield* fork(Effect.gen(function* () {
@@ -1682,18 +1718,19 @@ var ThreadMaintenance = class ThreadMaintenance extends Context.Service()("@effe
1682
1718
  }
1683
1719
  const next = Math.min(native === void 0 ? result.nextAttemptAt ?? Infinity : Infinity, ...queued.flatMap((row) => row.id !== "effect-agent:native" && row.dueAt !== null && row.dueAt > now ? [row.dueAt] : []), until);
1684
1720
  const completing = [
1685
- ...native === void 0 ? [] : [native],
1721
+ native,
1686
1722
  ...dispatch.active.values(),
1687
- ...recoveryObserved ? [] : [recoveryFiber],
1688
- ...backfillObserved ? [] : [backfill],
1689
- ...delivery === void 0 ? [] : [delivery],
1690
- ...lanes.flatMap((lane) => lane.fiber === void 0 ? [] : [lane.fiber])
1691
- ];
1692
- if ((yield* Effect.raceAllFirst([...completing.map((fiber) => Fiber.await(fiber).pipe(Effect.as("completed"))), Effect.raceAllFirst([
1693
- notified,
1694
- mutations.awaitChange(queueRevision),
1695
- Effect.sleep(Math.max(0, next - now))
1696
- ]).pipe(Effect.as("scan"))])) === "scan") nativeCheck = true;
1723
+ recoveryFiber,
1724
+ backfill,
1725
+ delivery,
1726
+ ...lanes.map((lane) => lane.fiber)
1727
+ ].filter((fiber) => fiber !== void 0);
1728
+ const changed = awaitAnyCompletion(completing, queueRevision);
1729
+ if ((yield* leanSelection && native === void 0 && dispatch.active.size === 1 && completing.length === 1 && !queued.some((row) => row.id !== "effect-agent:native" && row.dueAt !== null && row.dueAt > now && row.dueAt < until) ? changed : Effect.raceAllFirst([
1730
+ changed,
1731
+ ...leanSelection ? [] : [notified.pipe(Effect.as("scan"))],
1732
+ Effect.sleep(Math.max(0, next - now)).pipe(Effect.as("scan"))
1733
+ ])) === "scan") nativeCheck = true;
1697
1734
  }
1698
1735
  if (native !== void 0) {
1699
1736
  result = yield* Fiber.join(native);
@@ -1712,9 +1749,10 @@ var ThreadMaintenance = class ThreadMaintenance extends Context.Service()("@effe
1712
1749
  for (const fiber of [
1713
1750
  recoveryFiber,
1714
1751
  backfill,
1715
- ...delivery === void 0 ? [] : [delivery],
1716
- ...lanes.flatMap((lane) => lane.fiber === void 0 ? [] : [lane.fiber])
1752
+ delivery,
1753
+ ...lanes.map((lane) => lane.fiber)
1717
1754
  ]) {
1755
+ if (fiber === void 0) continue;
1718
1756
  const exit = yield* Fiber.await(fiber);
1719
1757
  if (Exit.isFailure(exit)) failure ??= exit.cause;
1720
1758
  }
@@ -1732,7 +1770,7 @@ var ThreadMaintenance = class ThreadMaintenance extends Context.Service()("@effe
1732
1770
  };
1733
1771
  });
1734
1772
  const pass = Effect.fnUntraced(function* (yieldAfter, dispatchUntil, observed) {
1735
- const concurrent = yield* Effect.exit(Effect.scoped(dispatch(yieldAfter, dispatchUntil, observed)));
1773
+ const concurrent = yield* Effect.exit(Effect.scoped(dispatch(yieldAfter, dispatchUntil, observed)).pipe(Effect.provideService(ThreadMutationGate, mutations)));
1736
1774
  const afterNative = yield* Effect.gen(function* () {
1737
1775
  const lanes = [
1738
1776
  recoveryEventLane,
@@ -1803,9 +1841,9 @@ var ThreadMaintenance = class ThreadMaintenance extends Context.Service()("@effe
1803
1841
  const { state } = await readMaintenanceState(transaction);
1804
1842
  const native = active > 0 || state.dirty > state.processed ? state.retry?.generation === state.dirty && active === 0 ? Math.max(now + minimumAlarmDelay, state.retry.notBefore) : state.dirty === observed.generation && active === 0 ? result.nextAttemptAt ?? now + minimumAlarmDelay : now + minimumAlarmDelay : Infinity;
1805
1843
  dueQueue.checkpointNative(Number.isFinite(native) ? native : null);
1806
- const next$2 = next(dueQueue.read());
1807
- if (Number.isFinite(next$2)) {
1808
- checkpoint.at(Math.max(now + minimumAlarmDelay, next$2));
1844
+ const next$5 = next(dueQueue.read());
1845
+ if (Number.isFinite(next$5)) {
1846
+ checkpoint.at(Math.max(now + minimumAlarmDelay, next$5));
1809
1847
  return "rearmed";
1810
1848
  }
1811
1849
  checkpoint.cancel();
@@ -1859,4 +1897,4 @@ var ThreadMaintenance = class ThreadMaintenance extends Context.Service()("@effe
1859
1897
  //#endregion
1860
1898
  export { Lifecycle as _, ThreadHostMaintenance as a, Projection as b, ThreadMaintenanceFailpoint as c, ThreadNativeMaintenance as d, ThreadPublication as f, HostLaneId as g, publishCommitted as h, MaintenancePassReport as i, ThreadMessageDelivery as l, ThreadRecoveryFaultEvent as m, DurableAlarmError as n, ThreadMaintenance as o, ThreadRecoveryEvents as p, DurableAlarmService as r, ThreadMaintenanceAlarm as s, Alarm_exports as t, ThreadMutationGate as u, Messages as v, Publication as x, Native as y };
1861
1899
 
1862
- //# sourceMappingURL=Alarm-4J9B8Fg8.mjs.map
1900
+ //# sourceMappingURL=Alarm-7uipp6LW.mjs.map