@bastani/atomic 0.9.60 → 0.9.61-alpha.1

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.
@@ -5135,6 +5135,18 @@ function failedOnInfrastructureFault(result) {
5135
5135
  return true;
5136
5136
  return Array.isArray(result.stages) && result.stages.some((stage) => stage?.status === "failed" && (names(stage.failureCode) || names(stage.error)));
5137
5137
  }
5138
+ function infrastructureFaultMessage(result) {
5139
+ const names = (value2) => typeof value2 === "string" && (WORKFLOW_INFRASTRUCTURE_FAULTS.has(value2) || workflowInfrastructureFault(value2) !== undefined);
5140
+ if (names(result.error))
5141
+ return result.error;
5142
+ const stage = Array.isArray(result.stages) ? result.stages.find((candidate) => candidate?.status === "failed" && (names(candidate.failureCode) || names(candidate.error))) : undefined;
5143
+ if (typeof stage?.error === "string")
5144
+ return stage.error;
5145
+ if (typeof stage?.failureCode === "string")
5146
+ return stage.failureCode;
5147
+ const code = result.failure?.code;
5148
+ return typeof code === "string" ? code : typeof result.error === "string" ? result.error : "pool fault";
5149
+ }
5138
5150
  var WORKFLOW_INFRASTRUCTURE_ERROR_NAME = "WorkflowInfrastructureError", WORKFLOW_INFRASTRUCTURE_FAULTS, MAX_CAUSE_DEPTH = 8, FAULT_PATTERNS, WorkflowInfrastructureError;
5139
5151
  var init_workflow_infrastructure_error = __esm(() => {
5140
5152
  WORKFLOW_INFRASTRUCTURE_FAULTS = new Set([
@@ -16878,6 +16890,25 @@ function admissionProcessStopped(pid) {
16878
16890
  return error.code === "ESRCH";
16879
16891
  }
16880
16892
  }
16893
+ function workerProcessExited(pid, final = false) {
16894
+ if (admissionProcessStopped(pid))
16895
+ return true;
16896
+ if (process.platform === "linux") {
16897
+ try {
16898
+ const stat = readFileSync2(`/proc/${pid}/stat`, "utf8");
16899
+ return stat.slice(stat.lastIndexOf(")") + 2).startsWith("Z");
16900
+ } catch {
16901
+ return admissionProcessStopped(pid);
16902
+ }
16903
+ }
16904
+ if (!final)
16905
+ return false;
16906
+ try {
16907
+ return execFileSync("ps", ["-o", "stat=", "-p", String(pid)], { encoding: "utf8", timeout: 5000 }).trim().startsWith("Z");
16908
+ } catch {
16909
+ return admissionProcessStopped(pid);
16910
+ }
16911
+ }
16881
16912
  function scanProcessGroup(group) {
16882
16913
  const members = new Map;
16883
16914
  if (process.platform === "linux") {
@@ -59371,17 +59402,26 @@ function isSameCheckpointValue(stored, next) {
59371
59402
  const hash = (value2) => durableHash(value2);
59372
59403
  return hash(stored) === hash(next);
59373
59404
  }
59405
+ function withTimesOf(later, recorded) {
59406
+ const both = (key) => typeof recorded[key] === "number" && typeof later[key] === "number";
59407
+ return {
59408
+ ...later,
59409
+ ...both("startedAt") ? { startedAt: recorded.startedAt } : {},
59410
+ ...both("endedAt") ? { endedAt: recorded.endedAt } : {},
59411
+ ...both("completedAt") ? { completedAt: recorded.completedAt } : {}
59412
+ };
59413
+ }
59374
59414
  function isRecordedCheckpointValue(stored, next) {
59375
59415
  const recorded = stored;
59376
59416
  if (isSameCheckpointValue(recorded, next))
59377
59417
  return true;
59378
- const { completedAt, startedAt } = recorded;
59379
- if (typeof completedAt !== "number")
59418
+ if (typeof recorded.completedAt !== "number")
59380
59419
  return false;
59381
59420
  const later = next;
59382
- if (startedAt === undefined !== (later.startedAt === undefined))
59421
+ if (recorded.startedAt === undefined !== (later.startedAt === undefined))
59383
59422
  return false;
59384
- return isSameCheckpointValue({ ...later, completedAt, ...startedAt === undefined ? {} : { startedAt } }, recorded);
59423
+ const retimed = withTimesOf(later, recorded);
59424
+ return isSameCheckpointValue(recorded.topology !== undefined && later.topology !== undefined ? { ...retimed, topology: withTimesOf(later.topology, recorded.topology) } : retimed, recorded);
59385
59425
  }
59386
59426
  var MIRROR_FRESH_MS = 2000;
59387
59427
  var init_pool_remote_journal = __esm(() => {
@@ -60417,15 +60457,7 @@ class SqlPoolQueue {
60417
60457
  }
60418
60458
  }
60419
60459
  function isRecordedCheckpoint(stored, payload) {
60420
- if (stored === payload)
60421
- return true;
60422
- const { completedAt, startedAt } = JSON.parse(stored);
60423
- if (typeof completedAt !== "number")
60424
- return false;
60425
- const next = JSON.parse(payload);
60426
- if (startedAt === undefined !== (next.startedAt === undefined))
60427
- return false;
60428
- return serializeJson({ ...next, completedAt, ...startedAt === undefined ? {} : { startedAt } }) === stored;
60460
+ return stored === payload || isRecordedCheckpointValue(JSON.parse(stored), JSON.parse(payload));
60429
60461
  }
60430
60462
  function openSqlPoolQueue(journal, remote) {
60431
60463
  const storage = sqlPoolStorage(journal);
@@ -65176,7 +65208,7 @@ var init_startup_timeout = () => {};
65176
65208
  import { fork } from "node:child_process";
65177
65209
  import { randomUUID as randomUUID16 } from "node:crypto";
65178
65210
  import { createWriteStream } from "node:fs";
65179
- import { mkdir as mkdir6, rm as rm3, rmdir } from "node:fs/promises";
65211
+ import { appendFile, mkdir as mkdir6, rm as rm3, rmdir } from "node:fs/promises";
65180
65212
  import { dirname as dirname13, join as join29, resolve as resolve12 } from "node:path";
65181
65213
  import { setTimeout as delay2 } from "node:timers/promises";
65182
65214
  function isRootLeaseLost(error) {
@@ -65368,6 +65400,9 @@ class PoolCoordinator {
65368
65400
  incarnations = new Map;
65369
65401
  readyWorkers = new Set;
65370
65402
  logs = new Map;
65403
+ keptLogs = new Set;
65404
+ unconfirmedExits = new WeakSet;
65405
+ failed = false;
65371
65406
  cleanups = new Set;
65372
65407
  controlAcks = new Map;
65373
65408
  lastHeard = new Map;
@@ -65415,7 +65450,6 @@ class PoolCoordinator {
65415
65450
  nextRecoveryAt = 0;
65416
65451
  cleanupFailure;
65417
65452
  held = new Set;
65418
- quarantined = new Set;
65419
65453
  unreaped = [];
65420
65454
  retained = new Map;
65421
65455
  retainedTransitions = Promise.resolve();
@@ -65608,18 +65642,15 @@ class PoolCoordinator {
65608
65642
  try {
65609
65643
  const { survivors, unprobeable } = await reapProcessGroups(this.journal, this.root, { attempt: work });
65610
65644
  if (survivors.length > 0) {
65611
- this.quarantined.add(work.id);
65612
65645
  this.unreaped.push({ attempt: work });
65613
- this.port.send({ kind: "work-failure", workId: work.id, error: unconfirmedCleanup(survivors).message });
65614
- return;
65646
+ this.reportHealth(undefined, `${unconfirmedCleanup(survivors).message}; child ${work.childRunId} of an earlier incarnation replays`);
65615
65647
  }
65616
65648
  if (unprobeable.length === 0)
65617
65649
  await this.releaseRetained(work);
65618
65650
  } catch (error) {
65619
65651
  this.cleanupFailure ??= error instanceof Error ? error : new Error(String(error));
65620
65652
  } finally {
65621
- if (!this.quarantined.has(work.id))
65622
- this.held.delete(work.id);
65653
+ this.held.delete(work.id);
65623
65654
  }
65624
65655
  }
65625
65656
  releaseRetained(work) {
@@ -65750,6 +65781,7 @@ class PoolCoordinator {
65750
65781
  });
65751
65782
  }
65752
65783
  fail(error) {
65784
+ this.failed = true;
65753
65785
  this.port.send({ kind: "failure", error: error.message });
65754
65786
  }
65755
65787
  failWork(work, error) {
@@ -66158,7 +66190,11 @@ class PoolCoordinator {
66158
66190
  terminate();
66159
66191
  });
66160
66192
  const closed = new Promise((resolve) => child.once("close", () => resolve()));
66161
- child.once("exit", () => {
66193
+ let retired = false;
66194
+ const retire = () => {
66195
+ if (retired)
66196
+ return;
66197
+ retired = true;
66162
66198
  Promise.race([closed, delay2(STDERR_DRAIN_GRACE_MS)]).then(() => reject(diagnosed(timedOut ? startupTimeoutMessage(startupTimeoutMs) : "Local worker exited before readiness")));
66163
66199
  this.children.delete(child);
66164
66200
  this.logs.delete(child);
@@ -66182,7 +66218,7 @@ class PoolCoordinator {
66182
66218
  const held = slot.work;
66183
66219
  if (held)
66184
66220
  this.held.add(held.id);
66185
- let childFailed = false;
66221
+ let unconfirmed;
66186
66222
  const cleanup = disconnected.then(() => serial).then(async () => {
66187
66223
  try {
66188
66224
  const admission = this.admission;
@@ -66195,18 +66231,13 @@ class PoolCoordinator {
66195
66231
  throw unconfirmedCleanup(alive);
66196
66232
  if (alive.length === 0)
66197
66233
  return;
66198
- const error = unconfirmedCleanup(alive);
66199
- if (held && await this.queue.result(this.root, held.id) === undefined) {
66200
- this.quarantined.add(held.id);
66201
- slot.work = undefined;
66202
- childFailed = true;
66203
- this.port.send({ kind: "work-failure", workId: held.id, error: error.message });
66204
- }
66234
+ unconfirmed = unconfirmedCleanup(alive);
66235
+ this.reportHealth(logPath, `${unconfirmed.message}; the worker was retired and replaced`);
66205
66236
  } catch (error) {
66206
66237
  this.cleanupFailure = error instanceof Error ? error : new Error(String(error));
66207
66238
  throw this.cleanupFailure;
66208
66239
  } finally {
66209
- if (held && !this.quarantined.has(held.id))
66240
+ if (held)
66210
66241
  this.held.delete(held.id);
66211
66242
  }
66212
66243
  }).then(async () => {
@@ -66227,8 +66258,8 @@ class PoolCoordinator {
66227
66258
  const settled = attempt !== undefined && await this.whileRootLate(queue, () => exhausted ? queue.failExited(lease, attempt) : queue.releaseExited(lease, attempt));
66228
66259
  const superseded = work !== undefined && !settled && await this.superseded(work);
66229
66260
  if (work !== undefined && exhausted && !superseded)
66230
- this.failWork(work, diagnosed(childRetryLimit(work, "lost its worker")));
66231
- if (work === undefined && !childFailed && ++this.replacements > 3)
66261
+ this.failWork(work, diagnosed(`${childRetryLimit(work, "lost its worker")}${unconfirmed === undefined ? "" : `; ${unconfirmed.message}`}`));
66262
+ if (work === undefined && ++this.replacements > 3)
66232
66263
  this.fail(diagnosed("Local worker retry limit"));
66233
66264
  else
66234
66265
  await this.spawn();
@@ -66237,7 +66268,9 @@ class PoolCoordinator {
66237
66268
  });
66238
66269
  this.cleanups.add(cleanup);
66239
66270
  cleanup.finally(() => this.cleanups.delete(cleanup));
66240
- });
66271
+ };
66272
+ slot.retire = retire;
66273
+ child.once("exit", retire);
66241
66274
  child.on("message", (message) => {
66242
66275
  this.observeResponsiveness();
66243
66276
  this.lastHeard.set(worker, { at: performance.now(), stalled: this.stalledMs });
@@ -66311,10 +66344,11 @@ class PoolCoordinator {
66311
66344
  const fenced = slot.work;
66312
66345
  slot.work = undefined;
66313
66346
  if (fenced && !await this.superseded(fenced)) {
66347
+ this.fence(fenced);
66314
66348
  if (this.attempts(fenced) < MAX_CHILD_ATTEMPTS)
66315
66349
  await leased(() => queue.releaseExited(lease, fenced));
66316
66350
  else if (await leased(() => queue.failExited(lease, fenced)))
66317
- this.failWork(fenced, new Error(childRetryLimit(fenced, "was refused by the root as stale on every attempt")));
66351
+ this.failWork(fenced, new Error(childRetryLimit(fenced, message.fault === undefined ? "was refused by the root as stale on every attempt" : `failed on a fault of the pool on every attempt (last: ${message.fault})`)));
66318
66352
  }
66319
66353
  return;
66320
66354
  }
@@ -66475,8 +66509,6 @@ class PoolCoordinator {
66475
66509
  return;
66476
66510
  }
66477
66511
  for (const work of expired) {
66478
- if (this.quarantined.has(work.id))
66479
- continue;
66480
66512
  if (this.held.has(work.id)) {
66481
66513
  await queue.extend(lease, work, leaseMs).catch((error) => {
66482
66514
  if (error.message !== STALE_WORK_LEASE)
@@ -66492,9 +66524,9 @@ class PoolCoordinator {
66492
66524
  });
66493
66525
  continue;
66494
66526
  }
66495
- if (child && !await this.stop(child).then(() => true, () => false))
66496
- continue;
66497
- if (this.held.has(work.id) || this.quarantined.has(work.id))
66527
+ if (child && !await this.stop(child))
66528
+ this.retireUnconfirmed(child);
66529
+ if (this.held.has(work.id))
66498
66530
  continue;
66499
66531
  if ((await unconfirmedProcessGroups(this.journal, root, { attempt: work })).length > 0)
66500
66532
  continue;
@@ -66521,17 +66553,49 @@ class PoolCoordinator {
66521
66553
  const heard = this.lastHeard.get(worker);
66522
66554
  return child.exitCode === null && child.signalCode === null && child.connected && heard !== undefined && performance.now() - heard.at - (this.stalledMs - heard.stalled) < this.options.leaseMs;
66523
66555
  }
66524
- stop(child) {
66525
- if (child.pid === undefined || child.exitCode !== null || child.signalCode !== null)
66526
- return Promise.resolve();
66527
- return new Promise((resolve, reject) => {
66528
- const timeout = setTimeout(() => reject(new Error("Local worker cleanup was not confirmed")), 5000);
66529
- child.once("exit", () => {
66530
- clearTimeout(timeout);
66531
- resolve();
66532
- });
66533
- child.kill("SIGKILL");
66534
- });
66556
+ async stop(child) {
66557
+ const pid = child.pid;
66558
+ const exited = () => child.exitCode !== null || child.signalCode !== null;
66559
+ if (pid === undefined || exited())
66560
+ return true;
66561
+ const exit = new Promise((resolve) => child.once("exit", () => resolve()));
66562
+ const confirmed = async () => {
66563
+ await Promise.race([exit, delay2(WORKER_EXIT_CONFIRMATION_MS)]);
66564
+ return true;
66565
+ };
66566
+ this.observeResponsiveness();
66567
+ child.kill("SIGKILL");
66568
+ const since = performance.now();
66569
+ const stalledAt = this.stalledMs;
66570
+ for (;; ) {
66571
+ await Promise.race([exit, delay2(WORKER_EXIT_POLL_MS)]);
66572
+ if (exited())
66573
+ return true;
66574
+ if (workerProcessExited(pid))
66575
+ return confirmed();
66576
+ this.observeResponsiveness();
66577
+ if (performance.now() - since - (this.stalledMs - stalledAt) >= WORKER_EXIT_CONFIRMATION_MS)
66578
+ return exited() || workerProcessExited(pid, true) && confirmed();
66579
+ }
66580
+ }
66581
+ retireUnconfirmed(child) {
66582
+ if (this.unconfirmedExits.has(child))
66583
+ return;
66584
+ this.unconfirmedExits.add(child);
66585
+ this.reportHealth(this.logs.get(child), `Local worker cleanup was not confirmed: worker process ${child.pid} was still running ${WORKER_EXIT_CONFIRMATION_MS} ms after SIGKILL; it was retired and replaced`);
66586
+ if (child.connected)
66587
+ child.disconnect();
66588
+ for (const slot of this.slots)
66589
+ if (slot.child === child)
66590
+ slot.retire?.();
66591
+ }
66592
+ reportHealth(log, message) {
66593
+ if (log !== undefined) {
66594
+ this.keptLogs.add(log);
66595
+ appendFile(log, `[atomic ${new Date().toISOString()}] ${message}
66596
+ `).catch(() => {});
66597
+ }
66598
+ this.port.send({ kind: "health", message: log === undefined ? message : `${message} (worker log: ${log})` });
66535
66599
  }
66536
66600
  async close() {
66537
66601
  this.closed = true;
@@ -66541,10 +66605,15 @@ class PoolCoordinator {
66541
66605
  this.rateLimitOutbox.close();
66542
66606
  let failure;
66543
66607
  let stopping = [];
66608
+ let confirmed = [];
66544
66609
  try {
66545
66610
  await this.recovering?.catch(() => {});
66546
66611
  stopping = [...this.children].map((child) => ({ child, log: this.logs.get(child) }));
66547
- await Promise.all(stopping.map(({ child }) => this.stop(child)));
66612
+ confirmed = await Promise.all(stopping.map(({ child }) => this.stop(child)));
66613
+ stopping.forEach(({ child }, index) => {
66614
+ if (!confirmed[index])
66615
+ this.retireUnconfirmed(child);
66616
+ });
66548
66617
  await Promise.all([...this.cleanups]);
66549
66618
  if (this.cleanupFailure)
66550
66619
  throw this.cleanupFailure;
@@ -66556,15 +66625,15 @@ class PoolCoordinator {
66556
66625
  if (alive.length > 0)
66557
66626
  throw unconfirmedCleanup(alive);
66558
66627
  if (this.desiredControl !== "quit" && this.queue !== undefined && this.lease !== undefined)
66559
- await this.queue.releaseClosed(this.lease, [...this.quarantined]).catch((error) => {
66628
+ await this.queue.releaseClosed(this.lease).catch((error) => {
66560
66629
  if (!(error instanceof StaleLeaseError))
66561
66630
  throw error;
66562
66631
  });
66563
66632
  } catch (error) {
66564
66633
  failure = error;
66565
66634
  }
66566
- if (failure === undefined) {
66567
- const stopped = stopping.flatMap(({ child, log }) => log !== undefined && (child.exitCode !== null || child.signalCode !== null) ? [log] : []);
66635
+ if (failure === undefined && !this.failed) {
66636
+ const stopped = stopping.flatMap(({ log }, index) => log !== undefined && !this.keptLogs.has(log) && confirmed[index] === true ? [log] : []);
66568
66637
  await Promise.all(stopped.map((log) => rm3(log, { force: true }).catch(() => {})));
66569
66638
  if (this.logDirectory !== undefined)
66570
66639
  await rmdir(this.logDirectory).catch(() => {});
@@ -66616,7 +66685,7 @@ function runPoolCoordinator() {
66616
66685
  port.onClose(() => process.exit(0));
66617
66686
  return coordinator;
66618
66687
  }
66619
- var CONTROL_ACKNOWLEDGEMENT_STALL_MS = 5000, RECOVERY_INTERVAL_MS = 500, TICK_INTERVAL_MS = 100, STALL_THRESHOLD_MS = 500, COORDINATOR_LEASE_MS, LATE_ROOT_POLL_MS, MIRROR_INTERVAL_MS = 1e4, STDERR_TAIL_CHARACTERS = 8192, STDERR_DRAIN_GRACE_MS = 1000, MAX_CHILD_ATTEMPTS = 3, ROUTE_REGISTRATION_WAIT_MS = 120000, STAGE_WATCH_BACKLOG_EVENTS = 2000, STAGE_WATCH_BACKLOG_BYTES, PENDING_STAGE_MESSAGE_EVENT2 = "atomic:workflow-pending-stage-message", STICKY_LIVE_DELIVERED_EVENT2 = "atomic:workflow-sticky-live-delivered", PENDING_STAGE_UNDELIVERABLE_EVENT2 = "atomic:workflow-pending-stage-undeliverable", ENDED_WATCHES_REMEMBERED = 1024;
66688
+ var CONTROL_ACKNOWLEDGEMENT_STALL_MS = 5000, RECOVERY_INTERVAL_MS = 500, TICK_INTERVAL_MS = 100, STALL_THRESHOLD_MS = 500, COORDINATOR_LEASE_MS, LATE_ROOT_POLL_MS, MIRROR_INTERVAL_MS = 1e4, STDERR_TAIL_CHARACTERS = 8192, STDERR_DRAIN_GRACE_MS = 1000, WORKER_EXIT_CONFIRMATION_MS = 5000, WORKER_EXIT_POLL_MS = 50, MAX_CHILD_ATTEMPTS = 3, ROUTE_REGISTRATION_WAIT_MS = 120000, STAGE_WATCH_BACKLOG_EVENTS = 2000, STAGE_WATCH_BACKLOG_BYTES, PENDING_STAGE_MESSAGE_EVENT2 = "atomic:workflow-pending-stage-message", STICKY_LIVE_DELIVERED_EVENT2 = "atomic:workflow-sticky-live-delivered", PENDING_STAGE_UNDELIVERABLE_EVENT2 = "atomic:workflow-pending-stage-undeliverable", ENDED_WATCHES_REMEMBERED = 1024;
66620
66689
  var init_pool_coordinator = __esm(() => {
66621
66690
  init_internal();
66622
66691
  init_pool_remote_journal();
@@ -66631,6 +66700,7 @@ var init_pool_coordinator = __esm(() => {
66631
66700
  init_pending_stage_route_readiness();
66632
66701
  init_store();
66633
66702
  init_workflow_infrastructure_error();
66703
+ init_admission_host();
66634
66704
  init_pool_journal();
66635
66705
  init_pooled_watch_trace();
66636
66706
  init_resource_admission();
@@ -68581,6 +68651,7 @@ class LocalWorkerPool {
68581
68651
  works = new Map;
68582
68652
  attempts = new Map;
68583
68653
  results = new Map;
68654
+ healthEvents = [];
68584
68655
  workFailures = new Map;
68585
68656
  changed = Promise.withResolvers();
68586
68657
  waiting = 0;
@@ -68638,6 +68709,9 @@ class LocalWorkerPool {
68638
68709
  if (options.admission && options.rootId === undefined)
68639
68710
  workflowResourceAdmission.set(backend, options.admission);
68640
68711
  }
68712
+ get health() {
68713
+ return this.healthEvents;
68714
+ }
68641
68715
  get workerPids() {
68642
68716
  return [...this.pids];
68643
68717
  }
@@ -69231,14 +69305,8 @@ class LocalWorkerPool {
69231
69305
  try {
69232
69306
  return await this.dispatch(definition, inputs, options);
69233
69307
  } catch (error) {
69234
- if (error instanceof WorkflowInfrastructureError && [...this.workFailures.values()].includes(error))
69235
- throw error;
69236
- try {
69237
- if (this.desiredControl !== "quit" || !findWorkflowGracefulQuit(error))
69238
- await this.shutdown();
69239
- } catch (closeError) {
69240
- throw asWorkflowInfrastructureError(closeError) ?? closeError;
69241
- }
69308
+ if (this.failure !== undefined || this.coordinatorLost !== undefined)
69309
+ await this.shutdown().catch(() => {});
69242
69310
  throw asWorkflowInfrastructureError(error) ?? error;
69243
69311
  }
69244
69312
  }
@@ -69306,8 +69374,13 @@ class LocalWorkerPool {
69306
69374
  try {
69307
69375
  for (;; ) {
69308
69376
  options.signal?.throwIfAborted();
69309
- if (this.closed)
69310
- throw this.closedError(root);
69377
+ if (!this.results.has(work.id) && (this.closed || this.failure !== undefined)) {
69378
+ const recorded = await this.queue?.result(root, work.id).catch(() => {
69379
+ return;
69380
+ });
69381
+ if (recorded !== undefined)
69382
+ this.results.set(work.id, recorded);
69383
+ }
69311
69384
  const result = this.results.get(work.id);
69312
69385
  if (result) {
69313
69386
  this.completing++;
@@ -69331,6 +69404,8 @@ class LocalWorkerPool {
69331
69404
  this.endFailedChild(work, failed);
69332
69405
  throw failed;
69333
69406
  }
69407
+ if (this.closed)
69408
+ throw this.closedError(root);
69334
69409
  if (this.failure)
69335
69410
  throw this.failure;
69336
69411
  if (this.budgetBlocked)
@@ -69365,6 +69440,19 @@ class LocalWorkerPool {
69365
69440
  if (this.reconnectingViews.size > 0 || this.outboxes.size > 0)
69366
69441
  this.reconnectStageViews();
69367
69442
  }
69443
+ recordHealth(message) {
69444
+ this.healthEvents.push(message);
69445
+ const root = this.root;
69446
+ if (root === undefined || this.store === undefined)
69447
+ return;
69448
+ this.store.recordNotice({
69449
+ id: `local-worker-health:${root}:${this.healthEvents.length}`,
69450
+ runId: root,
69451
+ level: "warning",
69452
+ message: `Local worker pool: ${message}`,
69453
+ createdAt: Date.now()
69454
+ });
69455
+ }
69368
69456
  evaluateBudget() {
69369
69457
  const decision = this.rootBudget?.checkpoint();
69370
69458
  const blocked = decision?.kind === "exhausted" || decision?.kind === "wrap_up";
@@ -69483,6 +69571,9 @@ class LocalWorkerPool {
69483
69571
  this.failure ??= new Error(event.error);
69484
69572
  this.signalChange();
69485
69573
  return;
69574
+ case "health":
69575
+ this.recordHealth(event.message);
69576
+ return;
69486
69577
  case "stage-events":
69487
69578
  this.stageWatches.get(event.watch)?.({ kind: "events", events: event.events });
69488
69579
  this.notice({ kind: "stage-events-applied", watch: event.watch });
@@ -81314,6 +81405,7 @@ async function runWorker(start) {
81314
81405
  await reader.catchUpWorkflow(start.root);
81315
81406
  const work = await request({ kind: "claim" });
81316
81407
  attemptFenced = false;
81408
+ let poolFault;
81317
81409
  if (!work) {
81318
81410
  await delay4(25 + Math.random() * 25);
81319
81411
  continue;
@@ -81486,6 +81578,11 @@ async function runWorker(start) {
81486
81578
  }
81487
81579
  if (attemptFenced)
81488
81580
  throw new WorkAttemptFencedError("Work attempt fenced before completion");
81581
+ if (failedOnInfrastructureFault(result)) {
81582
+ poolFault = infrastructureFaultMessage(result);
81583
+ attemptFenced = true;
81584
+ throw new WorkAttemptFencedError(poolFault);
81585
+ }
81489
81586
  await request({ kind: "complete", result });
81490
81587
  } catch (error) {
81491
81588
  if (!attemptFenced)
@@ -81503,7 +81600,7 @@ async function runWorker(start) {
81503
81600
  await renewal;
81504
81601
  await report;
81505
81602
  await controlPulse;
81506
- await request({ kind: "fenced" });
81603
+ await request({ kind: "fenced", ...poolFault === undefined ? {} : { fault: poolFault } });
81507
81604
  }
81508
81605
  if (rootLeaseLost)
81509
81606
  break;