@bastani/atomic 0.9.61 → 0.9.62

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.
@@ -2378,15 +2378,12 @@ class JournalDurableBackend {
2378
2378
  commitCheckpoint(checkpoint, options) {
2379
2379
  if (options?.signal?.aborted)
2380
2380
  return Promise.reject(new JournalOperationAbortedError(checkpoint.workflowId, options.signal.reason instanceof Error ? options.signal.reason : undefined));
2381
- return this.serialize(checkpoint.workflowId, () => this.checkpoint(checkpoint));
2382
- }
2383
- *checkpoint(checkpoint) {
2384
- validateCheckpointInvocationFingerprint(checkpoint);
2385
- if (!(yield* this.current(checkpoint.workflowId)))
2386
- return;
2387
- yield* this.append(checkpoint.workflowId, { type: RUN_EVENT_TYPES.checkpointRecorded, payload: checkpoint }, {
2388
- key: JSON.stringify([RUN_EVENT_TYPES.checkpointRecorded, checkpoint.kind, checkpoint.checkpointId])
2389
- });
2381
+ try {
2382
+ validateCheckpointInvocationFingerprint(checkpoint);
2383
+ } catch (error) {
2384
+ return Promise.reject(error);
2385
+ }
2386
+ return this.queueAppend(checkpoint.workflowId, { type: RUN_EVENT_TYPES.checkpointRecorded, payload: checkpoint }, JSON.stringify([RUN_EVENT_TYPES.checkpointRecorded, checkpoint.kind, checkpoint.checkpointId]), true);
2390
2387
  }
2391
2388
  async commitAdditiveCheckpointBestEffort(checkpoint) {
2392
2389
  try {
@@ -2523,7 +2520,7 @@ class JournalDurableBackend {
2523
2520
  payload: { messageId, delivery, ...accounting === undefined ? {} : { accounting } }
2524
2521
  });
2525
2522
  }
2526
- queueAppend(runId, event, key) {
2523
+ queueAppend(runId, event, key, requiresRegisteredRun = false) {
2527
2524
  try {
2528
2525
  this.ensureOpen();
2529
2526
  this.assertExecutionActive();
@@ -2536,6 +2533,7 @@ class JournalDurableBackend {
2536
2533
  batch.push({
2537
2534
  event,
2538
2535
  ...key === undefined ? {} : { key },
2536
+ ...requiresRegisteredRun ? { requiresRegisteredRun } : {},
2539
2537
  context: AsyncLocalStorage.snapshot(),
2540
2538
  resolve,
2541
2539
  reject
@@ -2550,9 +2548,12 @@ class JournalDurableBackend {
2550
2548
  }
2551
2549
  return queued;
2552
2550
  }
2553
- *appendBatch(runId, batch) {
2554
- if (this.appendBatches.get(runId) === batch)
2551
+ *appendBatch(runId, queued) {
2552
+ if (this.appendBatches.get(runId) === queued)
2555
2553
  this.appendBatches.delete(runId);
2554
+ const batch = yield* this.registeredAppends(runId, queued);
2555
+ if (batch.length === 0)
2556
+ return;
2556
2557
  const appendMany = runJournalAppendMany(this.journal);
2557
2558
  if (appendMany === undefined || batch.length === 1) {
2558
2559
  for (const append of batch) {
@@ -2590,6 +2591,25 @@ class JournalDurableBackend {
2590
2591
  append.reject(failure);
2591
2592
  }
2592
2593
  }
2594
+ *registeredAppends(runId, batch) {
2595
+ if (!batch.some((append) => append.requiresRegisteredRun === true))
2596
+ return [...batch];
2597
+ let registered;
2598
+ try {
2599
+ registered = (yield* this.current(runId)) !== undefined;
2600
+ } catch (error) {
2601
+ for (const append of batch)
2602
+ if (append.requiresRegisteredRun === true)
2603
+ append.reject(error);
2604
+ return batch.filter((append) => append.requiresRegisteredRun !== true);
2605
+ }
2606
+ if (registered)
2607
+ return [...batch];
2608
+ for (const append of batch)
2609
+ if (append.requiresRegisteredRun === true)
2610
+ append.resolve();
2611
+ return batch.filter((append) => append.requiresRegisteredRun !== true);
2612
+ }
2593
2613
  getToolOutput(...args) {
2594
2614
  return this.projection.getToolOutput(...args);
2595
2615
  }
@@ -17442,7 +17462,7 @@ class SqlResourceAdmission {
17442
17462
  signal?.throwIfAborted();
17443
17463
  const operation = randomUUID6();
17444
17464
  const release = (reason = "resource cleanup confirmed") => this.storage.transaction(async (tx) => {
17445
- await tx.run(`UPDATE ${this.permits} SET state = 'released', reason = ? WHERE operation = ? AND state != 'released'`, reason, operation);
17465
+ await tx.run(`UPDATE ${this.permits} SET state = 'released', reason = ? WHERE operation = ? AND (state != 'released' OR reason = ?)`, reason, operation, FENCED_WAITER_REASON);
17446
17466
  });
17447
17467
  release.bindProcess = async (identity) => {
17448
17468
  if (kind !== "process" || identity.kind !== "unix-process-group" || !Number.isSafeInteger(identity.id) || identity.id <= 0)
@@ -17454,6 +17474,15 @@ class SqlResourceAdmission {
17454
17474
  throw new Error("Resource process permit is no longer held");
17455
17475
  });
17456
17476
  };
17477
+ const keepWaiting = async (tx) => {
17478
+ const permit = await tx.get(`SELECT state, reason FROM ${this.permits} WHERE operation = ?`, operation);
17479
+ if (permit?.state === "released" && permit.reason === FENCED_WAITER_REASON)
17480
+ await tx.run(`UPDATE ${this.permits} SET state = 'waiting', reason = NULL WHERE operation = ?`, operation);
17481
+ else if (permit === undefined)
17482
+ throw new AdmissionHolderStoppedError("permit no longer recorded");
17483
+ else if (permit.state !== "waiting")
17484
+ throw new AdmissionHolderStoppedError(permit.reason === null ? undefined : String(permit.reason));
17485
+ };
17457
17486
  await this.storage.transaction(async (tx) => {
17458
17487
  await tx.run(`UPDATE ${this.owners} SET expires_at = CAST(unixepoch('subsec') * 1000 AS INTEGER) + ? WHERE id = ?`, OWNER_LEASE_MS, this.owner);
17459
17488
  await tx.run(`INSERT INTO ${this.permits}(operation, root, holder, kind, state, owner, attempt) VALUES (?, ?, ?, ?, 'waiting', ?, ?)`, operation, this.options.root, this.options.holder, kind, this.owner, this.options.attempt ? JSON.stringify({
@@ -17476,11 +17505,12 @@ class SqlResourceAdmission {
17476
17505
  const granted = await this.storage.transaction(async (tx) => {
17477
17506
  quarantined = undefined;
17478
17507
  await tx.run(`UPDATE ${this.owners} SET expires_at = CAST(unixepoch('subsec') * 1000 AS INTEGER) + ? WHERE id = ?`, OWNER_LEASE_MS, this.owner);
17508
+ await keepWaiting(tx);
17479
17509
  const { root } = this.options;
17480
17510
  if (!await this.assertCurrentAttempt(tx)) {
17481
17511
  const waiting = await tx.run(`UPDATE ${this.permits} SET reason = ? WHERE operation = ? AND state = 'waiting'`, LATE_ATTEMPT_REASON, operation);
17482
17512
  if (waiting.changes !== 1)
17483
- throw new Error("Resource admission holder was stopped");
17513
+ throw new AdmissionHolderStoppedError(undefined);
17484
17514
  return false;
17485
17515
  }
17486
17516
  let reason = hostReason;
@@ -17523,7 +17553,7 @@ class SqlResourceAdmission {
17523
17553
  const stageStart = kind === "session" && request?.delegated !== true;
17524
17554
  const updated = await tx.run(`UPDATE ${this.permits} SET state = ?, reason = ?, granted_at = CASE WHEN ? IS NULL AND ? THEN ${NOW_MS} ELSE NULL END WHERE operation = ? AND state = 'waiting'`, reason ? "waiting" : "granted", reason ?? null, reason ?? null, stageStart ? 1 : 0, operation);
17525
17555
  if (updated.changes !== 1)
17526
- throw new Error("Resource admission holder was stopped");
17556
+ throw new AdmissionHolderStoppedError(undefined);
17527
17557
  return !reason;
17528
17558
  });
17529
17559
  if (granted) {
@@ -17565,7 +17595,7 @@ class SqlResourceAdmission {
17565
17595
  WHERE p.state IN ('waiting', 'granted')`);
17566
17596
  if (live.some((permit) => this.needsFencing(permit, fence)))
17567
17597
  return true;
17568
- return !!await db.get(`SELECT 1 AS prunable FROM ${this.permits} WHERE +state = 'released' AND ticket <= ${this.prunableBefore} LIMIT 1`, RELEASED_PERMIT_RETENTION);
17598
+ return !!await db.get(`SELECT 1 AS prunable FROM ${this.permits} r WHERE ${this.prunable} LIMIT 1`, RELEASED_PERMIT_RETENTION, FENCED_WAITER_REASON, FENCED_WAITER_RETENTION_MS);
17569
17599
  });
17570
17600
  if (!pending)
17571
17601
  return;
@@ -17574,7 +17604,7 @@ class SqlResourceAdmission {
17574
17604
  const owners = await tx.all(`SELECT o.* FROM ${this.owners} o WHERE o.id IN (SELECT owner FROM ${this.permits} WHERE state IN ('waiting', 'granted'))`);
17575
17605
  for (const owner of owners) {
17576
17606
  const { rebooted, stopped } = fence(owner);
17577
- await tx.run(`UPDATE ${this.permits} SET state = 'released', reason = 'abandoned waiter fenced' WHERE owner = ? AND state = 'waiting' AND (? OR EXISTS (SELECT 1 FROM ${this.owners} WHERE id = ? AND expires_at < ${NOW_MS}))`, owner.id, stopped ? 1 : 0, owner.id);
17607
+ await tx.run(`UPDATE ${this.permits} SET state = 'released', reason = ? WHERE owner = ? AND state = 'waiting' AND (? OR EXISTS (SELECT 1 FROM ${this.owners} WHERE id = ? AND expires_at < ${NOW_MS}))`, FENCED_WAITER_REASON, owner.id, stopped ? 1 : 0, owner.id);
17578
17608
  if (rebooted) {
17579
17609
  await tx.run(`UPDATE ${this.permits} SET state = 'released', reason = 'host reboot confirmed' WHERE owner = ? AND state = 'granted'`, owner.id);
17580
17610
  } else if (stopped) {
@@ -17592,13 +17622,17 @@ class SqlResourceAdmission {
17592
17622
  }
17593
17623
  await tx.run(`UPDATE ${this.permits} SET state = 'released', reason = 'legacy waiter fenced' WHERE owner IS NULL AND state = 'waiting'`);
17594
17624
  await tx.run(`UPDATE ${this.permits} SET reason = ? WHERE owner IS NULL AND state = 'granted' AND reason IS NOT ?`, ANONYMOUS_REASON, ANONYMOUS_REASON);
17595
- await tx.run(`DELETE FROM ${this.permits} WHERE ticket IN (SELECT ticket FROM ${this.permits} WHERE +state = 'released'
17596
- AND ticket <= ${this.prunableBefore} ORDER BY ticket LIMIT ?)`, RELEASED_PERMIT_RETENTION, RELEASED_PERMIT_PRUNE_BATCH);
17625
+ await tx.run(`DELETE FROM ${this.permits} WHERE ticket IN (SELECT ticket FROM ${this.permits} r WHERE ${this.prunable}
17626
+ ORDER BY ticket LIMIT ?)`, RELEASED_PERMIT_RETENTION, FENCED_WAITER_REASON, FENCED_WAITER_RETENTION_MS, RELEASED_PERMIT_PRUNE_BATCH);
17597
17627
  });
17598
17628
  }
17599
17629
  get prunableBefore() {
17600
17630
  return `(SELECT ticket FROM ${this.permits} WHERE state = 'released' ORDER BY ticket DESC LIMIT 1 OFFSET ?)`;
17601
17631
  }
17632
+ get prunable() {
17633
+ return `+r.state = 'released' AND r.ticket <= ${this.prunableBefore} AND (r.reason IS NOT ? OR NOT EXISTS
17634
+ (SELECT 1 FROM ${this.owners} o WHERE o.id = r.owner AND o.expires_at >= ${NOW_MS} - ?))`;
17635
+ }
17602
17636
  needsFencing(permit, fence) {
17603
17637
  if (permit.owner === null)
17604
17638
  return permit.state === "waiting" || permit.reason !== ANONYMOUS_REASON;
@@ -17623,6 +17657,7 @@ class SqlResourceAdmission {
17623
17657
  await this.reconcile();
17624
17658
  return this.storage.transaction(async (tx) => {
17625
17659
  await tx.run(`UPDATE ${this.permits} SET state = 'released', reason = 'worker exit confirmed' WHERE root = ? AND holder = ? AND state IN ('waiting', 'granted') AND (kind = 'session' OR state = 'waiting')`, this.options.root, holder);
17660
+ await tx.run(`UPDATE ${this.permits} SET reason = 'worker exit confirmed' WHERE root = ? AND holder = ? AND state = 'released' AND reason = ?`, this.options.root, holder, FENCED_WAITER_REASON);
17626
17661
  await tx.run(`UPDATE ${this.permits} SET reason = ? WHERE root = ? AND holder = ? AND kind = 'process' AND state = 'granted' AND reason IS NOT ?`, UNCONFIRMED_REASON, this.options.root, holder, QUARANTINED_REASON);
17627
17662
  const held = await tx.get(`SELECT COUNT(*) AS count FROM ${this.permits} WHERE root = ? AND holder = ? AND kind = 'process' AND state = 'granted'`, this.options.root, holder);
17628
17663
  return Number(held?.count);
@@ -17695,7 +17730,7 @@ async function reapProcessGroups(journal, root, scope) {
17695
17730
  return { survivors: [], unprobeable: [] };
17696
17731
  return settleProcessGroups(storage, root, scope, true);
17697
17732
  }
17698
- var OWNER_LEASE_MS = 30000, REAP_TERM_GRACE_MS = 5000, REAP_KILL_GRACE_MS = 1e4, REAP_POLL_MS = 50, RELEASED_PERMIT_RETENTION = 256, RELEASED_PERMIT_PRUNE_BATCH = 1000, EXPIRED_REASON = "owner expired; cleanup unconfirmed", UNCONFIRMED_REASON = "cleanup unconfirmed", QUARANTINED_REASON = "cleanup unconfirmed; permit quarantined", ANONYMOUS_REASON = "owner identity unavailable; cleanup unconfirmed", QUARANTINED_CAPACITY_WAIT_MS, QuarantinedCapacityError, NOW_MS = "CAST(unixepoch('subsec') * 1000 AS INTEGER)", LATE_ATTEMPT_POLL_MS = 100, LATE_ATTEMPT_REASON = "waiting for its attempt lease", STAGE_START_SPACING_MS = 5000, STAGE_START_PRESSURE_REASON = "paced for host load";
17733
+ var OWNER_LEASE_MS = 30000, REAP_TERM_GRACE_MS = 5000, REAP_KILL_GRACE_MS = 1e4, REAP_POLL_MS = 50, RELEASED_PERMIT_RETENTION = 256, RELEASED_PERMIT_PRUNE_BATCH = 1000, EXPIRED_REASON = "owner expired; cleanup unconfirmed", UNCONFIRMED_REASON = "cleanup unconfirmed", QUARANTINED_REASON = "cleanup unconfirmed; permit quarantined", ANONYMOUS_REASON = "owner identity unavailable; cleanup unconfirmed", QUARANTINED_CAPACITY_WAIT_MS, QuarantinedCapacityError, FENCED_WAITER_REASON = "abandoned waiter fenced", FENCED_WAITER_RETENTION_MS, AdmissionHolderStoppedError, NOW_MS = "CAST(unixepoch('subsec') * 1000 AS INTEGER)", LATE_ATTEMPT_POLL_MS = 100, LATE_ATTEMPT_REASON = "waiting for its attempt lease", STAGE_START_SPACING_MS = 5000, STAGE_START_PRESSURE_REASON = "paced for host load";
17699
17734
  var init_resource_admission = __esm(() => {
17700
17735
  init_internal();
17701
17736
  init_sql_run_journal();
@@ -17709,6 +17744,12 @@ var init_resource_admission = __esm(() => {
17709
17744
  super("worker_cleanup_unconfirmed", `Local worker process cleanup unconfirmed; process permit refused after ${Math.round(waitedMs)} ms: ${limits} ${capacity.length === 1 ? "is" : "are"} held by quarantined permits of process groups ${groups} that survived SIGKILL`);
17710
17745
  }
17711
17746
  };
17747
+ FENCED_WAITER_RETENTION_MS = 10 * 60000;
17748
+ AdmissionHolderStoppedError = class AdmissionHolderStoppedError extends WorkflowInfrastructureError {
17749
+ constructor(reason) {
17750
+ super("stale_work_lease", `Stale resource admission attempt: the admission holder was stopped${reason === undefined ? "" : ` (${reason})`}`);
17751
+ }
17752
+ };
17712
17753
  });
17713
17754
 
17714
17755
  // dist/builtin/workflows/src/workers/admission-binding.ts
@@ -65111,6 +65152,37 @@ var init_workflow_route_owner = __esm(() => {
65111
65152
  });
65112
65153
 
65113
65154
  // dist/builtin/workflows/src/runs/foreground/sdk-intercom.ts
65155
+ function indexRoots(runs) {
65156
+ const runById = new Map(runs.map((run) => [run.id, run]));
65157
+ const rootOf = new Map;
65158
+ const deliverable = new Set;
65159
+ for (const run of runs) {
65160
+ const root = reciprocalWorkflowRootRunId(runById, run.id);
65161
+ rootOf.set(run.id, root);
65162
+ if (root !== undefined && run.stages.some((stage) => stage.pendingStageDeliveryAvailable === true))
65163
+ deliverable.add(root);
65164
+ }
65165
+ return { rootOf, deliverable };
65166
+ }
65167
+ function createStandaloneRouteOwnership(store, roots) {
65168
+ const activated = new Set;
65169
+ let index;
65170
+ const dispose = store.subscribeInvalidation(() => {
65171
+ index = undefined;
65172
+ });
65173
+ return {
65174
+ owns: (runId) => {
65175
+ index ??= indexRoots(store.runs());
65176
+ const root = index.rootOf.get(runId) ?? runId;
65177
+ if (!roots.has(root))
65178
+ return false;
65179
+ if (!activated.has(root) && index.deliverable.has(root))
65180
+ activated.add(root);
65181
+ return activated.has(root);
65182
+ },
65183
+ dispose
65184
+ };
65185
+ }
65114
65186
  function releaseRoot(owner, runId) {
65115
65187
  const remaining = (owner.roots.get(runId) ?? 1) - 1;
65116
65188
  if (remaining > 0) {
@@ -65126,22 +65198,14 @@ async function acquireStandaloneIntercom(store, runId, cwd) {
65126
65198
  if (hasWorkflowPendingStageRouteOwner(store))
65127
65199
  return async () => {};
65128
65200
  const roots = new Map;
65129
- const activated = new Set;
65130
65201
  owner = {
65131
65202
  roots,
65132
65203
  ready: outsideWorkflowExecutions(async () => {
65133
65204
  await Promise.resolve().then(() => init_workflow_route_owner());
65134
65205
  const routes = createWorkflowRouteOwner(cwd);
65135
- const bridge = registerPendingStageIntercomBridge({ events: routes.events }, store, (id) => {
65136
- const runs = store.runs();
65137
- const root = durableRootRunIdForRun(runs, id) ?? id;
65138
- if (!roots.has(root))
65139
- return false;
65140
- if (!activated.has(root) && runs.some((run) => durableRootRunIdForRun(runs, run.id) === root && run.stages.some((stage) => stage.pendingStageDeliveryAvailable === true)))
65141
- activated.add(root);
65142
- return activated.has(root);
65143
- });
65144
- return { routes, bridge };
65206
+ const ownership = createStandaloneRouteOwnership(store, roots);
65207
+ const bridge = registerPendingStageIntercomBridge({ events: routes.events }, store, ownership.owns);
65208
+ return { routes, bridge, unwatch: ownership.dispose };
65145
65209
  })
65146
65210
  };
65147
65211
  owners2.set(store, owner);
@@ -65165,6 +65229,7 @@ async function acquireStandaloneIntercom(store, runId, cwd) {
65165
65229
  initialized.bridge.refresh();
65166
65230
  if (retained.roots.size === 0) {
65167
65231
  initialized.bridge();
65232
+ initialized.unwatch();
65168
65233
  if (owners2.get(store) === retained)
65169
65234
  owners2.delete(store);
65170
65235
  }
@@ -65176,10 +65241,10 @@ async function acquireStandaloneIntercom(store, runId, cwd) {
65176
65241
  var owners2;
65177
65242
  var init_sdk_intercom = __esm(() => {
65178
65243
  init_execution_context_boundary();
65179
- init_run_owner_backend();
65180
65244
  init_pending_stage_intercom();
65181
65245
  init_intercom_group();
65182
65246
  init_pending_stage_route_readiness();
65247
+ init_workflow_run_ownership();
65183
65248
  owners2 = new WeakMap;
65184
65249
  });
65185
65250
 
@@ -82001,7 +82066,7 @@ async function runWorker(start) {
82001
82066
  try {
82002
82067
  return await admission.acquire(kind, signal, requestOptions);
82003
82068
  } catch (error) {
82004
- if (error instanceof QuarantinedCapacityError && !attemptFenced) {
82069
+ if ((error instanceof QuarantinedCapacityError || error instanceof AdmissionHolderStoppedError) && !attemptFenced) {
82005
82070
  poolFault = error.message;
82006
82071
  attemptFenced = true;
82007
82072
  controller.abort(new WorkAttemptFencedError(error.message));
@@ -12229,15 +12229,12 @@ class JournalDurableBackend {
12229
12229
  commitCheckpoint(checkpoint, options) {
12230
12230
  if (options?.signal?.aborted)
12231
12231
  return Promise.reject(new JournalOperationAbortedError(checkpoint.workflowId, options.signal.reason instanceof Error ? options.signal.reason : undefined));
12232
- return this.serialize(checkpoint.workflowId, () => this.checkpoint(checkpoint));
12233
- }
12234
- *checkpoint(checkpoint) {
12235
- validateCheckpointInvocationFingerprint(checkpoint);
12236
- if (!(yield* this.current(checkpoint.workflowId)))
12237
- return;
12238
- yield* this.append(checkpoint.workflowId, { type: RUN_EVENT_TYPES.checkpointRecorded, payload: checkpoint }, {
12239
- key: JSON.stringify([RUN_EVENT_TYPES.checkpointRecorded, checkpoint.kind, checkpoint.checkpointId])
12240
- });
12232
+ try {
12233
+ validateCheckpointInvocationFingerprint(checkpoint);
12234
+ } catch (error) {
12235
+ return Promise.reject(error);
12236
+ }
12237
+ return this.queueAppend(checkpoint.workflowId, { type: RUN_EVENT_TYPES.checkpointRecorded, payload: checkpoint }, JSON.stringify([RUN_EVENT_TYPES.checkpointRecorded, checkpoint.kind, checkpoint.checkpointId]), true);
12241
12238
  }
12242
12239
  async commitAdditiveCheckpointBestEffort(checkpoint) {
12243
12240
  try {
@@ -12374,7 +12371,7 @@ class JournalDurableBackend {
12374
12371
  payload: { messageId, delivery, ...accounting === undefined ? {} : { accounting } }
12375
12372
  });
12376
12373
  }
12377
- queueAppend(runId, event, key) {
12374
+ queueAppend(runId, event, key, requiresRegisteredRun = false) {
12378
12375
  try {
12379
12376
  this.ensureOpen();
12380
12377
  this.assertExecutionActive();
@@ -12387,6 +12384,7 @@ class JournalDurableBackend {
12387
12384
  batch.push({
12388
12385
  event,
12389
12386
  ...key === undefined ? {} : { key },
12387
+ ...requiresRegisteredRun ? { requiresRegisteredRun } : {},
12390
12388
  context: AsyncLocalStorage3.snapshot(),
12391
12389
  resolve,
12392
12390
  reject
@@ -12401,9 +12399,12 @@ class JournalDurableBackend {
12401
12399
  }
12402
12400
  return queued;
12403
12401
  }
12404
- *appendBatch(runId, batch) {
12405
- if (this.appendBatches.get(runId) === batch)
12402
+ *appendBatch(runId, queued) {
12403
+ if (this.appendBatches.get(runId) === queued)
12406
12404
  this.appendBatches.delete(runId);
12405
+ const batch = yield* this.registeredAppends(runId, queued);
12406
+ if (batch.length === 0)
12407
+ return;
12407
12408
  const appendMany = runJournalAppendMany(this.journal);
12408
12409
  if (appendMany === undefined || batch.length === 1) {
12409
12410
  for (const append of batch) {
@@ -12441,6 +12442,25 @@ class JournalDurableBackend {
12441
12442
  append.reject(failure);
12442
12443
  }
12443
12444
  }
12445
+ *registeredAppends(runId, batch) {
12446
+ if (!batch.some((append) => append.requiresRegisteredRun === true))
12447
+ return [...batch];
12448
+ let registered;
12449
+ try {
12450
+ registered = (yield* this.current(runId)) !== undefined;
12451
+ } catch (error) {
12452
+ for (const append of batch)
12453
+ if (append.requiresRegisteredRun === true)
12454
+ append.reject(error);
12455
+ return batch.filter((append) => append.requiresRegisteredRun !== true);
12456
+ }
12457
+ if (registered)
12458
+ return [...batch];
12459
+ for (const append of batch)
12460
+ if (append.requiresRegisteredRun === true)
12461
+ append.resolve();
12462
+ return batch.filter((append) => append.requiresRegisteredRun !== true);
12463
+ }
12444
12464
  getToolOutput(...args) {
12445
12465
  return this.projection.getToolOutput(...args);
12446
12466
  }
@@ -17553,7 +17573,7 @@ class SqlResourceAdmission {
17553
17573
  signal?.throwIfAborted();
17554
17574
  const operation = randomUUID7();
17555
17575
  const release = (reason = "resource cleanup confirmed") => this.storage.transaction(async (tx) => {
17556
- await tx.run(`UPDATE ${this.permits} SET state = 'released', reason = ? WHERE operation = ? AND state != 'released'`, reason, operation);
17576
+ await tx.run(`UPDATE ${this.permits} SET state = 'released', reason = ? WHERE operation = ? AND (state != 'released' OR reason = ?)`, reason, operation, FENCED_WAITER_REASON);
17557
17577
  });
17558
17578
  release.bindProcess = async (identity) => {
17559
17579
  if (kind !== "process" || identity.kind !== "unix-process-group" || !Number.isSafeInteger(identity.id) || identity.id <= 0)
@@ -17565,6 +17585,15 @@ class SqlResourceAdmission {
17565
17585
  throw new Error("Resource process permit is no longer held");
17566
17586
  });
17567
17587
  };
17588
+ const keepWaiting = async (tx) => {
17589
+ const permit = await tx.get(`SELECT state, reason FROM ${this.permits} WHERE operation = ?`, operation);
17590
+ if (permit?.state === "released" && permit.reason === FENCED_WAITER_REASON)
17591
+ await tx.run(`UPDATE ${this.permits} SET state = 'waiting', reason = NULL WHERE operation = ?`, operation);
17592
+ else if (permit === undefined)
17593
+ throw new AdmissionHolderStoppedError("permit no longer recorded");
17594
+ else if (permit.state !== "waiting")
17595
+ throw new AdmissionHolderStoppedError(permit.reason === null ? undefined : String(permit.reason));
17596
+ };
17568
17597
  await this.storage.transaction(async (tx) => {
17569
17598
  await tx.run(`UPDATE ${this.owners} SET expires_at = CAST(unixepoch('subsec') * 1000 AS INTEGER) + ? WHERE id = ?`, OWNER_LEASE_MS, this.owner);
17570
17599
  await tx.run(`INSERT INTO ${this.permits}(operation, root, holder, kind, state, owner, attempt) VALUES (?, ?, ?, ?, 'waiting', ?, ?)`, operation, this.options.root, this.options.holder, kind, this.owner, this.options.attempt ? JSON.stringify({
@@ -17587,11 +17616,12 @@ class SqlResourceAdmission {
17587
17616
  const granted = await this.storage.transaction(async (tx) => {
17588
17617
  quarantined = undefined;
17589
17618
  await tx.run(`UPDATE ${this.owners} SET expires_at = CAST(unixepoch('subsec') * 1000 AS INTEGER) + ? WHERE id = ?`, OWNER_LEASE_MS, this.owner);
17619
+ await keepWaiting(tx);
17590
17620
  const { root } = this.options;
17591
17621
  if (!await this.assertCurrentAttempt(tx)) {
17592
17622
  const waiting = await tx.run(`UPDATE ${this.permits} SET reason = ? WHERE operation = ? AND state = 'waiting'`, LATE_ATTEMPT_REASON, operation);
17593
17623
  if (waiting.changes !== 1)
17594
- throw new Error("Resource admission holder was stopped");
17624
+ throw new AdmissionHolderStoppedError(undefined);
17595
17625
  return false;
17596
17626
  }
17597
17627
  let reason = hostReason;
@@ -17634,7 +17664,7 @@ class SqlResourceAdmission {
17634
17664
  const stageStart = kind === "session" && request?.delegated !== true;
17635
17665
  const updated = await tx.run(`UPDATE ${this.permits} SET state = ?, reason = ?, granted_at = CASE WHEN ? IS NULL AND ? THEN ${NOW_MS} ELSE NULL END WHERE operation = ? AND state = 'waiting'`, reason ? "waiting" : "granted", reason ?? null, reason ?? null, stageStart ? 1 : 0, operation);
17636
17666
  if (updated.changes !== 1)
17637
- throw new Error("Resource admission holder was stopped");
17667
+ throw new AdmissionHolderStoppedError(undefined);
17638
17668
  return !reason;
17639
17669
  });
17640
17670
  if (granted) {
@@ -17676,7 +17706,7 @@ class SqlResourceAdmission {
17676
17706
  WHERE p.state IN ('waiting', 'granted')`);
17677
17707
  if (live.some((permit) => this.needsFencing(permit, fence)))
17678
17708
  return true;
17679
- return !!await db.get(`SELECT 1 AS prunable FROM ${this.permits} WHERE +state = 'released' AND ticket <= ${this.prunableBefore} LIMIT 1`, RELEASED_PERMIT_RETENTION);
17709
+ return !!await db.get(`SELECT 1 AS prunable FROM ${this.permits} r WHERE ${this.prunable} LIMIT 1`, RELEASED_PERMIT_RETENTION, FENCED_WAITER_REASON, FENCED_WAITER_RETENTION_MS);
17680
17710
  });
17681
17711
  if (!pending)
17682
17712
  return;
@@ -17685,7 +17715,7 @@ class SqlResourceAdmission {
17685
17715
  const owners = await tx.all(`SELECT o.* FROM ${this.owners} o WHERE o.id IN (SELECT owner FROM ${this.permits} WHERE state IN ('waiting', 'granted'))`);
17686
17716
  for (const owner of owners) {
17687
17717
  const { rebooted, stopped } = fence(owner);
17688
- await tx.run(`UPDATE ${this.permits} SET state = 'released', reason = 'abandoned waiter fenced' WHERE owner = ? AND state = 'waiting' AND (? OR EXISTS (SELECT 1 FROM ${this.owners} WHERE id = ? AND expires_at < ${NOW_MS}))`, owner.id, stopped ? 1 : 0, owner.id);
17718
+ await tx.run(`UPDATE ${this.permits} SET state = 'released', reason = ? WHERE owner = ? AND state = 'waiting' AND (? OR EXISTS (SELECT 1 FROM ${this.owners} WHERE id = ? AND expires_at < ${NOW_MS}))`, FENCED_WAITER_REASON, owner.id, stopped ? 1 : 0, owner.id);
17689
17719
  if (rebooted) {
17690
17720
  await tx.run(`UPDATE ${this.permits} SET state = 'released', reason = 'host reboot confirmed' WHERE owner = ? AND state = 'granted'`, owner.id);
17691
17721
  } else if (stopped) {
@@ -17703,13 +17733,17 @@ class SqlResourceAdmission {
17703
17733
  }
17704
17734
  await tx.run(`UPDATE ${this.permits} SET state = 'released', reason = 'legacy waiter fenced' WHERE owner IS NULL AND state = 'waiting'`);
17705
17735
  await tx.run(`UPDATE ${this.permits} SET reason = ? WHERE owner IS NULL AND state = 'granted' AND reason IS NOT ?`, ANONYMOUS_REASON, ANONYMOUS_REASON);
17706
- await tx.run(`DELETE FROM ${this.permits} WHERE ticket IN (SELECT ticket FROM ${this.permits} WHERE +state = 'released'
17707
- AND ticket <= ${this.prunableBefore} ORDER BY ticket LIMIT ?)`, RELEASED_PERMIT_RETENTION, RELEASED_PERMIT_PRUNE_BATCH);
17736
+ await tx.run(`DELETE FROM ${this.permits} WHERE ticket IN (SELECT ticket FROM ${this.permits} r WHERE ${this.prunable}
17737
+ ORDER BY ticket LIMIT ?)`, RELEASED_PERMIT_RETENTION, FENCED_WAITER_REASON, FENCED_WAITER_RETENTION_MS, RELEASED_PERMIT_PRUNE_BATCH);
17708
17738
  });
17709
17739
  }
17710
17740
  get prunableBefore() {
17711
17741
  return `(SELECT ticket FROM ${this.permits} WHERE state = 'released' ORDER BY ticket DESC LIMIT 1 OFFSET ?)`;
17712
17742
  }
17743
+ get prunable() {
17744
+ return `+r.state = 'released' AND r.ticket <= ${this.prunableBefore} AND (r.reason IS NOT ? OR NOT EXISTS
17745
+ (SELECT 1 FROM ${this.owners} o WHERE o.id = r.owner AND o.expires_at >= ${NOW_MS} - ?))`;
17746
+ }
17713
17747
  needsFencing(permit, fence) {
17714
17748
  if (permit.owner === null)
17715
17749
  return permit.state === "waiting" || permit.reason !== ANONYMOUS_REASON;
@@ -17734,6 +17768,7 @@ class SqlResourceAdmission {
17734
17768
  await this.reconcile();
17735
17769
  return this.storage.transaction(async (tx) => {
17736
17770
  await tx.run(`UPDATE ${this.permits} SET state = 'released', reason = 'worker exit confirmed' WHERE root = ? AND holder = ? AND state IN ('waiting', 'granted') AND (kind = 'session' OR state = 'waiting')`, this.options.root, holder);
17771
+ await tx.run(`UPDATE ${this.permits} SET reason = 'worker exit confirmed' WHERE root = ? AND holder = ? AND state = 'released' AND reason = ?`, this.options.root, holder, FENCED_WAITER_REASON);
17737
17772
  await tx.run(`UPDATE ${this.permits} SET reason = ? WHERE root = ? AND holder = ? AND kind = 'process' AND state = 'granted' AND reason IS NOT ?`, UNCONFIRMED_REASON, this.options.root, holder, QUARANTINED_REASON);
17738
17773
  const held = await tx.get(`SELECT COUNT(*) AS count FROM ${this.permits} WHERE root = ? AND holder = ? AND kind = 'process' AND state = 'granted'`, this.options.root, holder);
17739
17774
  return Number(held?.count);
@@ -17806,7 +17841,7 @@ async function reapProcessGroups(journal, root, scope) {
17806
17841
  return { survivors: [], unprobeable: [] };
17807
17842
  return settleProcessGroups(storage, root, scope, true);
17808
17843
  }
17809
- var OWNER_LEASE_MS = 30000, REAP_TERM_GRACE_MS = 5000, REAP_KILL_GRACE_MS = 1e4, REAP_POLL_MS = 50, RELEASED_PERMIT_RETENTION = 256, RELEASED_PERMIT_PRUNE_BATCH = 1000, EXPIRED_REASON = "owner expired; cleanup unconfirmed", UNCONFIRMED_REASON = "cleanup unconfirmed", QUARANTINED_REASON = "cleanup unconfirmed; permit quarantined", ANONYMOUS_REASON = "owner identity unavailable; cleanup unconfirmed", QUARANTINED_CAPACITY_WAIT_MS, QuarantinedCapacityError, NOW_MS = "CAST(unixepoch('subsec') * 1000 AS INTEGER)", LATE_ATTEMPT_POLL_MS = 100, LATE_ATTEMPT_REASON = "waiting for its attempt lease", STAGE_START_SPACING_MS = 5000, STAGE_START_PRESSURE_REASON = "paced for host load";
17844
+ var OWNER_LEASE_MS = 30000, REAP_TERM_GRACE_MS = 5000, REAP_KILL_GRACE_MS = 1e4, REAP_POLL_MS = 50, RELEASED_PERMIT_RETENTION = 256, RELEASED_PERMIT_PRUNE_BATCH = 1000, EXPIRED_REASON = "owner expired; cleanup unconfirmed", UNCONFIRMED_REASON = "cleanup unconfirmed", QUARANTINED_REASON = "cleanup unconfirmed; permit quarantined", ANONYMOUS_REASON = "owner identity unavailable; cleanup unconfirmed", QUARANTINED_CAPACITY_WAIT_MS, QuarantinedCapacityError, FENCED_WAITER_REASON = "abandoned waiter fenced", FENCED_WAITER_RETENTION_MS, AdmissionHolderStoppedError, NOW_MS = "CAST(unixepoch('subsec') * 1000 AS INTEGER)", LATE_ATTEMPT_POLL_MS = 100, LATE_ATTEMPT_REASON = "waiting for its attempt lease", STAGE_START_SPACING_MS = 5000, STAGE_START_PRESSURE_REASON = "paced for host load";
17810
17845
  var init_resource_admission = __esm(() => {
17811
17846
  init_internal();
17812
17847
  init_sql_run_journal();
@@ -17820,6 +17855,12 @@ var init_resource_admission = __esm(() => {
17820
17855
  super("worker_cleanup_unconfirmed", `Local worker process cleanup unconfirmed; process permit refused after ${Math.round(waitedMs)} ms: ${limits} ${capacity.length === 1 ? "is" : "are"} held by quarantined permits of process groups ${groups} that survived SIGKILL`);
17821
17856
  }
17822
17857
  };
17858
+ FENCED_WAITER_RETENTION_MS = 10 * 60000;
17859
+ AdmissionHolderStoppedError = class AdmissionHolderStoppedError extends WorkflowInfrastructureError {
17860
+ constructor(reason) {
17861
+ super("stale_work_lease", `Stale resource admission attempt: the admission holder was stopped${reason === undefined ? "" : ` (${reason})`}`);
17862
+ }
17863
+ };
17823
17864
  });
17824
17865
 
17825
17866
  // dist/builtin/workflows/src/workers/admission-binding.ts
@@ -65222,6 +65263,37 @@ var init_workflow_route_owner = __esm(() => {
65222
65263
  });
65223
65264
 
65224
65265
  // dist/builtin/workflows/src/runs/foreground/sdk-intercom.ts
65266
+ function indexRoots(runs) {
65267
+ const runById = new Map(runs.map((run) => [run.id, run]));
65268
+ const rootOf = new Map;
65269
+ const deliverable = new Set;
65270
+ for (const run of runs) {
65271
+ const root = reciprocalWorkflowRootRunId(runById, run.id);
65272
+ rootOf.set(run.id, root);
65273
+ if (root !== undefined && run.stages.some((stage) => stage.pendingStageDeliveryAvailable === true))
65274
+ deliverable.add(root);
65275
+ }
65276
+ return { rootOf, deliverable };
65277
+ }
65278
+ function createStandaloneRouteOwnership(store, roots) {
65279
+ const activated = new Set;
65280
+ let index;
65281
+ const dispose = store.subscribeInvalidation(() => {
65282
+ index = undefined;
65283
+ });
65284
+ return {
65285
+ owns: (runId) => {
65286
+ index ??= indexRoots(store.runs());
65287
+ const root = index.rootOf.get(runId) ?? runId;
65288
+ if (!roots.has(root))
65289
+ return false;
65290
+ if (!activated.has(root) && index.deliverable.has(root))
65291
+ activated.add(root);
65292
+ return activated.has(root);
65293
+ },
65294
+ dispose
65295
+ };
65296
+ }
65225
65297
  function releaseRoot(owner, runId) {
65226
65298
  const remaining = (owner.roots.get(runId) ?? 1) - 1;
65227
65299
  if (remaining > 0) {
@@ -65237,22 +65309,14 @@ async function acquireStandaloneIntercom(store, runId, cwd) {
65237
65309
  if (hasWorkflowPendingStageRouteOwner(store))
65238
65310
  return async () => {};
65239
65311
  const roots = new Map;
65240
- const activated = new Set;
65241
65312
  owner = {
65242
65313
  roots,
65243
65314
  ready: outsideWorkflowExecutions(async () => {
65244
65315
  await Promise.resolve().then(() => init_workflow_route_owner());
65245
65316
  const routes = createWorkflowRouteOwner(cwd);
65246
- const bridge = registerPendingStageIntercomBridge({ events: routes.events }, store, (id) => {
65247
- const runs = store.runs();
65248
- const root = durableRootRunIdForRun(runs, id) ?? id;
65249
- if (!roots.has(root))
65250
- return false;
65251
- if (!activated.has(root) && runs.some((run) => durableRootRunIdForRun(runs, run.id) === root && run.stages.some((stage) => stage.pendingStageDeliveryAvailable === true)))
65252
- activated.add(root);
65253
- return activated.has(root);
65254
- });
65255
- return { routes, bridge };
65317
+ const ownership = createStandaloneRouteOwnership(store, roots);
65318
+ const bridge = registerPendingStageIntercomBridge({ events: routes.events }, store, ownership.owns);
65319
+ return { routes, bridge, unwatch: ownership.dispose };
65256
65320
  })
65257
65321
  };
65258
65322
  owners2.set(store, owner);
@@ -65276,6 +65340,7 @@ async function acquireStandaloneIntercom(store, runId, cwd) {
65276
65340
  initialized.bridge.refresh();
65277
65341
  if (retained.roots.size === 0) {
65278
65342
  initialized.bridge();
65343
+ initialized.unwatch();
65279
65344
  if (owners2.get(store) === retained)
65280
65345
  owners2.delete(store);
65281
65346
  }
@@ -65287,10 +65352,10 @@ async function acquireStandaloneIntercom(store, runId, cwd) {
65287
65352
  var owners2;
65288
65353
  var init_sdk_intercom = __esm(() => {
65289
65354
  init_execution_context_boundary();
65290
- init_run_owner_backend();
65291
65355
  init_pending_stage_intercom();
65292
65356
  init_intercom_group();
65293
65357
  init_pending_stage_route_readiness();
65358
+ init_workflow_run_ownership();
65294
65359
  owners2 = new WeakMap;
65295
65360
  });
65296
65361