@indigoai-us/hq-cloud 6.18.35 → 6.18.38-alpha.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 (38) hide show
  1. package/dist/bin/push-failure-backoff.d.ts +22 -0
  2. package/dist/bin/push-failure-backoff.d.ts.map +1 -0
  3. package/dist/bin/push-failure-backoff.js +27 -0
  4. package/dist/bin/push-failure-backoff.js.map +1 -0
  5. package/dist/bin/sync-runner-delete-snapshot-mixed.bench.test.js +9 -2
  6. package/dist/bin/sync-runner-delete-snapshot-mixed.bench.test.js.map +1 -1
  7. package/dist/bin/sync-runner-delete-snapshot.bench.test.js +3 -1
  8. package/dist/bin/sync-runner-delete-snapshot.bench.test.js.map +1 -1
  9. package/dist/bin/sync-runner-watch-loop.d.ts.map +1 -1
  10. package/dist/bin/sync-runner-watch-loop.js +165 -34
  11. package/dist/bin/sync-runner-watch-loop.js.map +1 -1
  12. package/dist/bin/sync-runner-watch-loop.test.js +46 -0
  13. package/dist/bin/sync-runner-watch-loop.test.js.map +1 -1
  14. package/dist/bin/sync-runner.d.ts +11 -1
  15. package/dist/bin/sync-runner.d.ts.map +1 -1
  16. package/dist/bin/sync-runner.js +23 -4
  17. package/dist/bin/sync-runner.js.map +1 -1
  18. package/dist/bin/sync-runner.test.js +956 -4
  19. package/dist/bin/sync-runner.test.js.map +1 -1
  20. package/dist/lib/describe-error.d.ts +4 -0
  21. package/dist/lib/describe-error.d.ts.map +1 -1
  22. package/dist/lib/describe-error.js +37 -0
  23. package/dist/lib/describe-error.js.map +1 -1
  24. package/dist/lib/describe-error.test.js +18 -1
  25. package/dist/lib/describe-error.test.js.map +1 -1
  26. package/dist/outcome-telemetry.d.ts +16 -6
  27. package/dist/outcome-telemetry.d.ts.map +1 -1
  28. package/dist/outcome-telemetry.js +105 -11
  29. package/dist/outcome-telemetry.js.map +1 -1
  30. package/dist/outcome-telemetry.test.js +236 -0
  31. package/dist/outcome-telemetry.test.js.map +1 -1
  32. package/dist/sync/push-receiver.d.ts +5 -0
  33. package/dist/sync/push-receiver.d.ts.map +1 -1
  34. package/dist/sync/push-receiver.js +115 -52
  35. package/dist/sync/push-receiver.js.map +1 -1
  36. package/dist/sync/push-receiver.test.js +349 -2
  37. package/dist/sync/push-receiver.test.js.map +1 -1
  38. package/package.json +1 -1
@@ -9,7 +9,8 @@ import { targetedJournalSessionCensusSizes } from "../cli/sync.js";
9
9
  import { VaultClient } from "../index.js";
10
10
  import { pickCanonicalPersonEntity } from "../vault-client.js";
11
11
  import { acquireOperationLock, withOperationLock, withOperationLockSync, OperationLockedError, OPERATION_LOCKED_EXIT, } from "../operation-lock.js";
12
- import { describeError } from "../lib/describe-error.js";
12
+ import { describeError, pushFailureTelemetryLabel, sanitizePushFailureText, } from "../lib/describe-error.js";
13
+ import { RoutePushFailureBackoff, systemPushRetryScheduler, } from "./push-failure-backoff.js";
13
14
  import { TRANSIENT_NETWORK_EXIT } from "../lib/net-errors.js";
14
15
  import { PARTIAL_SYNC_EXIT, UNRESOLVED_COMPANY_EXIT, } from "../lib/exit-codes.js";
15
16
  import { getOrCreateMachineIdMetadata } from "../lib/machine-id.js";
@@ -889,6 +890,7 @@ export function stripTransientWatchScopeArgs(argv) {
889
890
  const valueFlags = new Set([
890
891
  "--poll-remote-ms",
891
892
  "--owner",
893
+ "--watch-parent-pid",
892
894
  "--scope-path",
893
895
  "--scope-upsert-path",
894
896
  "--delete-scope-path",
@@ -1690,7 +1692,16 @@ export async function runWatchLoop(argv, parsed, deps, runtime) {
1690
1692
  refresh.unref?.();
1691
1693
  }
1692
1694
  const passScheduler = new CooperativePassScheduler(monotonicNow, CHORE_STARVATION_CAP_MS, { caps: fullPassCaps });
1693
- const runPassWithLock = async (passArgvForRun, prepare, lane = "slow", workClass = "chore", checkpoint, telemetry) => {
1695
+ const runPassWithLock = async (passArgvForRun, prepare, lane = "slow", workClass = "chore", checkpoint, telemetry, pullSignal, receiverSignal) => {
1696
+ const passCheckpoint = pullSignal || receiverSignal
1697
+ ? Object.assign(async () => checkpoint?.(), {
1698
+ shouldYieldToRealtime: checkpoint?.shouldYieldToRealtime,
1699
+ shouldAbort: () => pullSignal?.aborted === true ||
1700
+ receiverSignal?.aborted === true ||
1701
+ checkpoint?.shouldAbort?.() === true,
1702
+ runChoreNetworkTransfer: checkpoint?.runChoreNetworkTransfer,
1703
+ })
1704
+ : checkpoint;
1694
1705
  const reportedAppliedUpserts = new Set();
1695
1706
  const reportedAppliedDeletes = new Set();
1696
1707
  const recordDurableAppliedUpserts = (upserts) => {
@@ -1722,9 +1733,9 @@ export async function runWatchLoop(argv, parsed, deps, runtime) {
1722
1733
  // existing scheduler's cooperative hand-off checkpoint.
1723
1734
  const onJournalsReady = () => activateEventPushSurfaces();
1724
1735
  if (!runPassSupportsQmdLifecycle) {
1725
- return runPass(passArgvForRun, checkpoint, onJournalsReady, recordDurableAppliedUpserts, workClass);
1736
+ return runPass(passArgvForRun, passCheckpoint, onJournalsReady, recordDurableAppliedUpserts, workClass);
1726
1737
  }
1727
- return runPass(passArgvForRun, checkpoint, onJournalsReady, recordDurableAppliedUpserts, workClass, qmdShutdownController.signal, writeMaintenanceProgress, idleHeartbeatIntervalMs, recordDurableAppliedDeletes);
1738
+ return runPass(passArgvForRun, passCheckpoint, onJournalsReady, recordDurableAppliedUpserts, workClass, qmdShutdownController.signal, writeMaintenanceProgress, idleHeartbeatIntervalMs, recordDurableAppliedDeletes);
1728
1739
  };
1729
1740
  // Observe the heap at pass boundaries — a gated checkpoint immediately before
1730
1741
  // admitting a pass and immediately after it returns. A pass that runs a long
@@ -1987,13 +1998,16 @@ export async function runWatchLoop(argv, parsed, deps, runtime) {
1987
1998
  // chore ownership. Realtime normally waits for a chore hand-off; the
1988
1999
  // watcher fast path explicitly opts into its established scoped exception.
1989
2000
  passScheduler.submit(task, workClass, lane, allowFastRealtimeOvertake, signal, resourceCap, allowConcurrentHandoff, transferDirection, transferSizeClass);
1990
- const runGuarded = (passArgvForRun, prepare, lane = "slow", workClass = "chore", allowFastRealtimeOvertake = false, signal, resourceCap = false, telemetry, allowConcurrentHandoff = false) => {
2001
+ const runGuarded = (passArgvForRun, prepare, lane = "slow", workClass = "chore", allowFastRealtimeOvertake = false, signal, resourceCap = false, telemetry, allowConcurrentHandoff = false, receiverSignal) => {
1991
2002
  telemetry?.mark("operationGate");
2003
+ const admissionSignal = signal && receiverSignal
2004
+ ? AbortSignal.any([signal, receiverSignal])
2005
+ : signal ?? receiverSignal;
1992
2006
  return runGuardedTask((context) => {
1993
2007
  telemetry?.mark("appendLock");
1994
2008
  telemetry?.setOccupancy(passScheduler.activeRealtimeWorkerCount);
1995
- return runPassWithLock(passArgvForRun, prepare, lane, workClass, workClass === "chore" ? context.checkpoint : undefined, telemetry);
1996
- }, lane, workClass, allowFastRealtimeOvertake, signal, resourceCap, allowConcurrentHandoff, telemetry?.direction, telemetry?.sizeClass);
2009
+ return runPassWithLock(passArgvForRun, prepare, lane, workClass, workClass === "chore" ? context.checkpoint : undefined, telemetry, context.signal, receiverSignal);
2010
+ }, lane, workClass, allowFastRealtimeOvertake, admissionSignal, resourceCap, allowConcurrentHandoff, telemetry?.direction, telemetry?.sizeClass);
1997
2011
  };
1998
2012
  /*
1999
2013
  * Area storage and the parallel coordinator default to AUTO. AUTO turns
@@ -2465,15 +2479,43 @@ export async function runWatchLoop(argv, parsed, deps, runtime) {
2465
2479
  pendingWatcherBatchGeneration = null;
2466
2480
  return { batch, bare, warmupCatchup, warmupCatchupFailed, generation };
2467
2481
  };
2468
- const restorePendingWatcherPaths = (paths, generation) => {
2482
+ const restorePendingWatcherPaths = (paths, generation, changes) => {
2469
2483
  pendingWatcherBatchGeneration ??=
2470
2484
  generation ?? ++nextWatcherBatchGeneration;
2471
2485
  pendingWatcherOriginalBatch = null;
2472
2486
  for (const [absolutePath, relativePath] of paths.entries()) {
2487
+ // A newer watcher event may already be queued for this path. Keep its
2488
+ // change record instead of restoring stale delete metadata from the
2489
+ // batch that just finished.
2490
+ if (pendingWatcherChangeSequences.has(absolutePath))
2491
+ continue;
2473
2492
  pendingWatcherPaths.set(absolutePath, relativePath);
2474
2493
  pendingWatcherChangeSequences.set(absolutePath, ++nextPendingWatcherChangeSequence);
2494
+ const change = changes?.get(absolutePath);
2495
+ if (change)
2496
+ pendingWatcherChanges.set(absolutePath, change);
2475
2497
  }
2476
2498
  };
2499
+ const routePushFailureBackoff = new RoutePushFailureBackoff();
2500
+ const pushRetryScheduler = deps.pushRetryScheduler ?? systemPushRetryScheduler;
2501
+ const routePushRetryTimerHandles = new Map();
2502
+ const clearRoutePushRetry = (route) => {
2503
+ const handle = routePushRetryTimerHandles.get(route);
2504
+ if (handle === undefined)
2505
+ return;
2506
+ pushRetryScheduler.clearTimeout(handle);
2507
+ routePushRetryTimerHandles.delete(route);
2508
+ };
2509
+ const scheduleRoutePushRetry = (route, delayMs) => {
2510
+ if (routePushRetryTimerHandles.has(route))
2511
+ return;
2512
+ const handle = pushRetryScheduler.setTimeout(() => {
2513
+ routePushRetryTimerHandles.delete(route);
2514
+ if (!stopped)
2515
+ interruptWait();
2516
+ }, delayMs);
2517
+ routePushRetryTimerHandles.set(route, handle);
2518
+ };
2477
2519
  /**
2478
2520
  * Journal roots the bulk-intent quarantine has refused for the current
2479
2521
  * delete episode, keyed `slug\0journalKey`.
@@ -3332,7 +3374,8 @@ export async function runWatchLoop(argv, parsed, deps, runtime) {
3332
3374
  }
3333
3375
  return false;
3334
3376
  };
3335
- const runScopedDrain = async (batch, watcherBatchGeneration, wakeOnPushFailure = false, allowFastRealtimeOvertake = false) => {
3377
+ let emitScopedPushFailureTelemetry = null;
3378
+ const runScopedDrain = async (batch, watcherBatchGeneration, _wakeOnPushFailure = false, allowFastRealtimeOvertake = false) => {
3336
3379
  if (batch)
3337
3380
  await resolveDeleteSnapshotCaptures(batch);
3338
3381
  const grouped = batch && batch.paths.size > 0 ? groupBatchByRoute(batch) : null;
@@ -3343,6 +3386,52 @@ export async function runWatchLoop(argv, parsed, deps, runtime) {
3343
3386
  const relativePath = group.paths.get(absolutePath);
3344
3387
  return relativePath !== undefined && admittedPaths.some((scope) => relativePath === scope || relativePath.startsWith(`${scope}/`));
3345
3388
  }));
3389
+ const pushFailureText = (result, route, caughtError) => {
3390
+ if (caughtError !== undefined)
3391
+ return describeError(caughtError);
3392
+ if (typeof result === "number")
3393
+ return `sync pass exited ${result}`;
3394
+ const failures = [
3395
+ ...(result.result?.companyFailures ?? []),
3396
+ ...(result.result?.errors ?? []),
3397
+ ...(result.result?.transient ?? []),
3398
+ ];
3399
+ const routeFailure = route.kind === "company"
3400
+ ? failures.find((failure) => failure.company.toLowerCase() === route.slug.toLowerCase())
3401
+ : undefined;
3402
+ return routeFailure?.message ?? failures[0]?.message ??
3403
+ `sync pass exited ${result.exitCode}`;
3404
+ };
3405
+ const recordPushFailure = (route, exitCode, message) => {
3406
+ const routeId = routeKey(route);
3407
+ const companyRoot = route.kind === "company"
3408
+ ? path.join(hqRoot, "companies", route.slug)
3409
+ : hqRoot;
3410
+ const errorText = sanitizePushFailureText(message, companyRoot);
3411
+ const state = routePushFailureBackoff.failed(routeId, monotonicNow());
3412
+ scheduleRoutePushRetry(routeId, state.delayMs);
3413
+ if (!state.shouldReport)
3414
+ return;
3415
+ const event = {
3416
+ route: routeId,
3417
+ exitCode,
3418
+ consecutiveFailures: state.consecutiveFailures,
3419
+ nextRetryDelayMs: state.delayMs,
3420
+ errorSummary: pushFailureTelemetryLabel(errorText),
3421
+ };
3422
+ const diagnostic = {
3423
+ type: "scoped-push-failure",
3424
+ ...event,
3425
+ error: errorText,
3426
+ };
3427
+ (deps.stderr ?? process.stderr).write(`${JSON.stringify(diagnostic)}\n`);
3428
+ if (deps.emitScopedPushFailureTelemetry) {
3429
+ deps.emitScopedPushFailureTelemetry(event);
3430
+ }
3431
+ else {
3432
+ emitScopedPushFailureTelemetry?.(event);
3433
+ }
3434
+ };
3346
3435
  if (watcherDrainRunsPush && grouped) {
3347
3436
  for (const group of grouped.values()) {
3348
3437
  // Paused workspaces: drop the scoped push without uploading or
@@ -3350,6 +3439,14 @@ export async function runWatchLoop(argv, parsed, deps, runtime) {
3350
3439
  if (isPausedRoute(group.route)) {
3351
3440
  continue;
3352
3441
  }
3442
+ const routeId = routeKey(group.route);
3443
+ const retryState = routePushFailureBackoff.state(routeId);
3444
+ if (retryState && retryState.retryAt > monotonicNow()) {
3445
+ scheduleRoutePushRetry(routeId, retryState.retryAt - monotonicNow());
3446
+ restorePendingWatcherPaths(group.paths, watcherBatchGeneration, group.changes);
3447
+ continue;
3448
+ }
3449
+ clearRoutePushRetry(routeId);
3353
3450
  try {
3354
3451
  const sessionLogBackup = group.relPaths.every(isSessionLogsPushOnlyPath);
3355
3452
  const lane = sessionLogBackup
@@ -3384,6 +3481,8 @@ export async function runWatchLoop(argv, parsed, deps, runtime) {
3384
3481
  rememberUploadedPaths(result);
3385
3482
  const pushExit = passExitCode(result);
3386
3483
  if (execution.accepted && pushExit === 0) {
3484
+ routePushFailureBackoff.succeeded(routeId);
3485
+ clearRoutePushRetry(routeId);
3387
3486
  const pushPathResults = typeof result === "number" ? [] : (result.result?.pushPathResults ?? []);
3388
3487
  const refusedPaths = new Set(pushPathResults
3389
3488
  .filter((pathResult) => pathResult.status === "refused")
@@ -3431,21 +3530,25 @@ export async function runWatchLoop(argv, parsed, deps, runtime) {
3431
3530
  }
3432
3531
  }
3433
3532
  else if (pushExit === UNRESOLVED_COMPANY_EXIT) {
3533
+ routePushFailureBackoff.succeeded(routeId);
3534
+ clearRoutePushRetry(routeId);
3434
3535
  if (group.route.kind === "company") {
3435
3536
  logDroppedUnresolvedWatcherRoute(group.route.slug);
3436
3537
  }
3437
3538
  }
3438
3539
  else {
3439
- restorePendingWatcherPaths(group.paths, watcherBatchGeneration);
3440
- if (wakeOnPushFailure)
3540
+ restorePendingWatcherPaths(group.paths, watcherBatchGeneration, group.changes);
3541
+ if (pushExit !== 0) {
3542
+ recordPushFailure(group.route, pushExit, pushFailureText(result, group.route));
3543
+ }
3544
+ else if (_wakeOnPushFailure) {
3441
3545
  interruptWait();
3546
+ }
3442
3547
  }
3443
3548
  }
3444
3549
  catch (err) {
3445
- restorePendingWatcherPaths(group.paths, watcherBatchGeneration);
3446
- process.stderr.write(`watch scoped push failed, will retry next tick: ${describeError(err)}\n`);
3447
- if (wakeOnPushFailure)
3448
- interruptWait();
3550
+ recordPushFailure(group.route, 1, pushFailureText(1, group.route, err));
3551
+ restorePendingWatcherPaths(group.paths, watcherBatchGeneration, group.changes);
3449
3552
  }
3450
3553
  }
3451
3554
  }
@@ -3534,6 +3637,11 @@ export async function runWatchLoop(argv, parsed, deps, runtime) {
3534
3637
  if (watcherDrainRunsPush) {
3535
3638
  const push = await runGuarded(buildScopedPushArgv(route, [], passArgv));
3536
3639
  const pushExit = scopedRouteDrainExit({ result: push, accepted: true });
3640
+ if (pushExit === 0) {
3641
+ const routeId = routeKey(route);
3642
+ routePushFailureBackoff.succeeded(routeId);
3643
+ clearRoutePushRetry(routeId);
3644
+ }
3537
3645
  if (pushExit !== 0)
3538
3646
  return push;
3539
3647
  }
@@ -3562,10 +3670,16 @@ export async function runWatchLoop(argv, parsed, deps, runtime) {
3562
3670
  if (watcherDrainRunsPush) {
3563
3671
  // Whole-route push (no --scope-path): the dropped paths are unknown,
3564
3672
  // so the route's full walk is the narrowest sound scope.
3673
+ const push = await runGuarded(buildScopedPushArgv(route, [], passArgv));
3565
3674
  const exit = scopedRouteDrainExit({
3566
- result: await runGuarded(buildScopedPushArgv(route, [], passArgv)),
3675
+ result: push,
3567
3676
  accepted: true,
3568
3677
  });
3678
+ if (exit === 0) {
3679
+ const routeId = routeKey(route);
3680
+ routePushFailureBackoff.succeeded(routeId);
3681
+ clearRoutePushRetry(routeId);
3682
+ }
3569
3683
  if (exit !== 0 && worstExit === 0)
3570
3684
  worstExit = exit;
3571
3685
  }
@@ -3631,6 +3745,16 @@ export async function runWatchLoop(argv, parsed, deps, runtime) {
3631
3745
  authToken: getAccessToken,
3632
3746
  region: runtime.region,
3633
3747
  });
3748
+ emitScopedPushFailureTelemetry = (event) => {
3749
+ void emitCloudTelemetry(watcherTelemetryClient, {
3750
+ eventName: "sync_runner_scoped_push_failed",
3751
+ source: "sync_runner_watch_loop",
3752
+ properties: event,
3753
+ }, {
3754
+ claims,
3755
+ log: (message) => (deps.stderr ?? process.stderr).write(`hq-sync-runner: ${message}\n`),
3756
+ });
3757
+ };
3634
3758
  const createWatcher = deps.createWatcher ??
3635
3759
  ((opts) => new TreeWatcher({
3636
3760
  hqRoot: opts.hqRoot,
@@ -3862,7 +3986,7 @@ export async function runWatchLoop(argv, parsed, deps, runtime) {
3862
3986
  // a known-small event can be slow solely because an earlier event exhausted
3863
3987
  // the delivery-wide fast budget.
3864
3988
  const receiverLaneByBatch = new WeakMap();
3865
- const drainParallelReceiverLane = async (lane, laneEvents, receiveCountsByPath) => {
3989
+ const drainParallelReceiverLane = async (lane, laneEvents, receiveCountsByPath, settleEvents, receiverSignal) => {
3866
3990
  const eventsByRoute = new Map();
3867
3991
  for (const event of laneEvents) {
3868
3992
  const route = routeChangeToTarget(event.relativePath);
@@ -3877,15 +4001,12 @@ export async function runWatchLoop(argv, parsed, deps, runtime) {
3877
4001
  // particular, a later route's fast receipt must never wait for an
3878
4002
  // earlier route's slow pull to finish.
3879
4003
  const routeGroups = [...eventsByRoute.values()];
3880
- const completed = await Promise.all(routeGroups.map(({ route, events }) => {
3881
- return runRealtimePass("receiver", "pull", events.map((event) => event.relativePath), lane, (slice, telemetry, coordinatorFenced) => {
4004
+ const settlements = await Promise.allSettled(routeGroups.map(async ({ route, events }) => {
4005
+ const execution = await runRealtimePass("receiver", "pull", events.map((event) => event.relativePath), lane, (slice, telemetry, coordinatorFenced) => {
3882
4006
  const slicePaths = slice.paths.map((path) => path.canonicalPath);
3883
4007
  const exactFileUpserts = exactUpsertPathsForCoordinatorSlice(events, slicePaths);
3884
- return withScopeContentHashesFile(route, events, slicePaths, (contentHashesFile) => runGuarded(buildTargetedPullArgv(route, passArgv, slicePaths, exactFileUpserts, contentHashesFile), undefined, lane, "realtime", lane === "fast", slice.signal, false, telemetry, coordinatorFenced));
4008
+ return withScopeContentHashesFile(route, events, slicePaths, (contentHashesFile) => runGuarded(buildTargetedPullArgv(route, passArgv, slicePaths, exactFileUpserts, contentHashesFile), undefined, lane, "realtime", lane === "fast", slice.signal, false, telemetry, coordinatorFenced, receiverSignal));
3885
4009
  });
3886
- }));
3887
- for (const [index, execution] of completed.entries()) {
3888
- const { route, events } = routeGroups[index];
3889
4010
  const pathCount = events.length;
3890
4011
  const exitCode = passExitCode(execution.result);
3891
4012
  if (shouldAcknowledgeParallelUnresolvedCompany(execution.accepted, exitCode)) {
@@ -3893,14 +4014,19 @@ export async function runWatchLoop(argv, parsed, deps, runtime) {
3893
4014
  throw targetedReceiverDispatchError(execution.result, execution.accepted, runtime.authRequiredPassExit);
3894
4015
  }
3895
4016
  warnUnresolvedReceiverCompany(route, pathCount);
3896
- continue;
3897
4017
  }
3898
- if (!execution.accepted || exitCode !== 0) {
4018
+ else if (!execution.accepted || exitCode !== 0) {
3899
4019
  throw targetedReceiverDispatchError(execution.result, execution.accepted, runtime.authRequiredPassExit);
3900
4020
  }
3901
- }
4021
+ // The receiver can acknowledge this tenant's receipts now, while
4022
+ // independent route groups continue to settle.
4023
+ await settleEvents?.(events);
4024
+ }));
4025
+ const failedSettlement = settlements.find((settlement) => settlement.status === "rejected");
4026
+ if (failedSettlement)
4027
+ throw failedSettlement.reason;
3902
4028
  };
3903
- const receiverSyncBatchFn = async ({ events, approximateReceiveCountsByPath, }) => {
4029
+ const receiverSyncBatchFn = async ({ events, signal, approximateReceiveCountsByPath, settleEvents, }) => {
3904
4030
  if (stopped)
3905
4031
  return;
3906
4032
  // The injectable receiver seam and the production SQS receiver both
@@ -3918,19 +4044,19 @@ export async function runWatchLoop(argv, parsed, deps, runtime) {
3918
4044
  const lane = receiverLaneByBatch.get(events);
3919
4045
  if (lane) {
3920
4046
  receiverLaneByBatch.delete(events);
3921
- await drainParallelReceiverLane(lane, events, approximateReceiveCountsByPath);
4047
+ await drainParallelReceiverLane(lane, events, approximateReceiveCountsByPath, settleEvents, signal);
3922
4048
  return;
3923
4049
  }
3924
4050
  // Direct embedders may invoke the engine without first going through
3925
4051
  // SqsPushReceiver. Preserve that seam while applying the same
3926
4052
  // delivery-wide partition the receiver would expose.
3927
4053
  const lanes = partitionByKnownSizes(events, receiverEventSize);
3928
- await drainParallelReceiverLane("fast", lanes.fast, approximateReceiveCountsByPath);
3929
- await drainParallelReceiverLane("slow", lanes.slow, approximateReceiveCountsByPath);
4054
+ await drainParallelReceiverLane("fast", lanes.fast, approximateReceiveCountsByPath, settleEvents, signal);
4055
+ await drainParallelReceiverLane("slow", lanes.slow, approximateReceiveCountsByPath, settleEvents, signal);
3930
4056
  return;
3931
4057
  }
3932
4058
  const lanes = partitionByKnownSizes(events, receiverEventSize);
3933
- const drainLane = async (lane, laneEvents, receiveCountsByPath) => {
4059
+ const drainLane = async (lane, laneEvents, receiveCountsByPath, receiverSignal) => {
3934
4060
  const eventsByRoute = new Map();
3935
4061
  for (const event of laneEvents) {
3936
4062
  const route = routeChangeToTarget(event.relativePath);
@@ -3949,26 +4075,28 @@ export async function runWatchLoop(argv, parsed, deps, runtime) {
3949
4075
  }
3950
4076
  for (const { route, events: routeEvents, upsertPaths } of eventsByRoute.values()) {
3951
4077
  const paths = routeEvents.map((event) => event.relativePath);
3952
- const result = await withScopeContentHashesFile(route, routeEvents, paths, (contentHashesFile) => runGuarded(buildTargetedPullArgv(route, passArgv, paths, upsertPaths, contentHashesFile), undefined, lane, "realtime"));
4078
+ const result = await withScopeContentHashesFile(route, routeEvents, paths, (contentHashesFile) => runGuarded(buildTargetedPullArgv(route, passArgv, paths, upsertPaths, contentHashesFile), undefined, lane, "realtime", false, receiverSignal));
3953
4079
  const exitCode = passExitCode(result);
3954
4080
  if (exitCode === UNRESOLVED_COMPANY_EXIT) {
3955
4081
  if (!unresolvedReceiverMessagesWereRedelivered(routeEvents, receiveCountsByPath)) {
3956
4082
  throw targetedReceiverDispatchError(result, true, runtime.authRequiredPassExit);
3957
4083
  }
3958
4084
  warnUnresolvedReceiverCompany(route, routeEvents.length);
4085
+ await settleEvents?.(routeEvents);
3959
4086
  continue;
3960
4087
  }
3961
4088
  if (exitCode !== 0) {
3962
4089
  throw targetedReceiverDispatchError(result, true, runtime.authRequiredPassExit);
3963
4090
  }
4091
+ await settleEvents?.(routeEvents);
3964
4092
  }
3965
4093
  };
3966
4094
  // Dispatch realtime work before awaiting its bulk residue. This lets an
3967
4095
  // established slow pass continue while the small scoped pull uses its
3968
4096
  // ref-counted lock carve-out; the residue remains serialized and cannot
3969
4097
  // starve the slow lane.
3970
- await drainLane("fast", lanes.fast, approximateReceiveCountsByPath);
3971
- await drainLane("slow", lanes.slow, approximateReceiveCountsByPath);
4098
+ await drainLane("fast", lanes.fast, approximateReceiveCountsByPath, signal);
4099
+ await drainLane("slow", lanes.slow, approximateReceiveCountsByPath, signal);
3972
4100
  };
3973
4101
  receiverSyncBatchFn.partition = (events, maxPaths) => {
3974
4102
  const lanes = partitionByKnownSizes(events, receiverEventSize);
@@ -4110,6 +4238,9 @@ export async function runWatchLoop(argv, parsed, deps, runtime) {
4110
4238
  if (stopped)
4111
4239
  return shutdownPromise ?? Promise.resolve();
4112
4240
  stopped = true;
4241
+ for (const route of routePushRetryTimerHandles.keys()) {
4242
+ clearRoutePushRetry(route);
4243
+ }
4113
4244
  qmdShutdownController.abort();
4114
4245
  eventSyncShutdownDeadlineMs =
4115
4246
  Date.now() + EVENT_SYNC_STARTUP_SHUTDOWN_WAIT_MS;