@jeffusion/bungee-core 5.6.0 → 5.6.2

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 (2) hide show
  1. package/dist/main.js +196 -94
  2. package/package.json +2 -2
package/dist/main.js CHANGED
@@ -8126,10 +8126,10 @@ function parseNormalizeCompileAggregate(input, options) {
8126
8126
  if (!hasLogicalConfiguration) {
8127
8127
  context.add("required", "logical_configuration", "Required field is missing");
8128
8128
  }
8129
- const pluginActivations = parsePluginActivations(root.plugin_activations, "plugin_activations" in root, context, options?.availablePlugins ?? (options ? new Set(options.pluginSchemas.keys()) : undefined));
8129
+ let pluginActivations = parsePluginActivations(root.plugin_activations, "plugin_activations" in root, context, options?.availablePlugins ?? (options ? new Set(options.pluginSchemas.keys()) : undefined));
8130
8130
  if (options?.pluginDependencies) {
8131
8131
  try {
8132
- options.pluginDependencies.assertClosed(pluginActivations.map(({ plugin_name }) => plugin_name));
8132
+ pluginActivations = options.pluginDependencies.closure(pluginActivations.map(({ plugin_name }) => plugin_name)).map((plugin_name) => ({ plugin_name })).sort(comparePluginNames);
8133
8133
  } catch (error) {
8134
8134
  context.add("invalid_value", "plugin_activations", error instanceof Error ? error.message : String(error));
8135
8135
  }
@@ -12455,7 +12455,7 @@ class PluginDependencyGraph {
12455
12455
  }
12456
12456
  }
12457
12457
  function updatePluginActivations(graph, activated, name, enable) {
12458
- const active = new Set(activated);
12458
+ const active = new Set(graph.closure(activated));
12459
12459
  graph.dependenciesOf(name);
12460
12460
  if (enable) {
12461
12461
  for (const dependency of graph.closure([name]))
@@ -12479,7 +12479,7 @@ var package_default;
12479
12479
  var init_package = __esm(() => {
12480
12480
  package_default = {
12481
12481
  name: "@jeffusion/bungee-core",
12482
- version: "5.6.0",
12482
+ version: "5.6.2",
12483
12483
  description: "Bungee reverse proxy core engine",
12484
12484
  main: "./dist/main.js",
12485
12485
  types: "./dist/main.d.ts",
@@ -188233,8 +188233,6 @@ function restoreWorkerTransportRequest(request, transportSecret) {
188233
188233
  }
188234
188234
  }
188235
188235
  const headers = new Headers(request.headers);
188236
- if (identity)
188237
- headers.delete("authorization");
188238
188236
  const strippedHeaders = [];
188239
188237
  headers.forEach((_value, name) => {
188240
188238
  if (name.startsWith("x-bungee-internal-") || name === "x-bungee-next-authorization") {
@@ -211624,8 +211622,6 @@ async function proxyRequest(requestSnapshot, route, upstream, requestLog, config
211624
211622
  stripCredentialHeaders(hookHeaders, managedCredential.policy);
211625
211623
  stripHopHeaders(hookHeaders);
211626
211624
  const finalTargetUrl = new URL(targetUrlForRequest.href);
211627
- if (attemptOptions?.beforeSend)
211628
- hookHeaders.delete("authorization");
211629
211625
  const credentialExpectedPath = finalTargetUrl.pathname;
211630
211626
  let credentialRequest;
211631
211627
  if (managedCredential) {
@@ -222853,10 +222849,38 @@ var init_drain_workers = __esm(() => {
222853
222849
  function validStatus(value) {
222854
222850
  return typeof value.retired_id === "string" && RETIRED_ID.test(value.retired_id) && Number.isSafeInteger(value.pending) && value.pending >= 0 && typeof value.complete === "boolean" && Number.isSafeInteger(value.remaining_ms) && value.remaining_ms >= 0 && (!value.complete || value.pending === 0);
222855
222851
  }
222852
+ function waitBeforePoll(scheduler, delay, signal) {
222853
+ return new Promise((resolve) => {
222854
+ let settled = false;
222855
+ let timeout;
222856
+ const finish = (value) => {
222857
+ if (settled)
222858
+ return;
222859
+ settled = true;
222860
+ bestEffort(() => timeout?.cancel());
222861
+ signal?.removeEventListener("abort", abort);
222862
+ resolve(value);
222863
+ };
222864
+ const abort = () => finish("cancelled");
222865
+ signal?.addEventListener("abort", abort, { once: true });
222866
+ if (signal?.aborted) {
222867
+ abort();
222868
+ return;
222869
+ }
222870
+ try {
222871
+ timeout = scheduler.schedule(delay, () => finish("poll"));
222872
+ } catch {
222873
+ finish("unavailable");
222874
+ }
222875
+ if (settled)
222876
+ bestEffort(() => timeout?.cancel());
222877
+ });
222878
+ }
222856
222879
  async function waitForAdmissionHandoff(prepared, scheduler, hasRetiredWorkers, signal) {
222857
222880
  if (!hasRetiredWorkers && prepared.handoffStatus === undefined)
222858
222881
  return { kind: "complete" };
222859
222882
  let retiredId = null;
222883
+ let statusFailures = 0;
222860
222884
  while (true) {
222861
222885
  if (signal?.aborted)
222862
222886
  return { kind: "unknown", reason: "cancelled" };
@@ -222873,26 +222897,19 @@ async function waitForAdmissionHandoff(prepared, scheduler, hasRetiredWorkers, s
222873
222897
  return { kind: "complete" };
222874
222898
  if (status.remaining_ms === 0)
222875
222899
  return { kind: "unknown", reason: "expired" };
222876
- const resumed = await new Promise((resolve) => {
222877
- let settled = false;
222878
- const finish = (value) => {
222879
- if (settled)
222880
- return;
222881
- settled = true;
222882
- timeout.cancel();
222883
- signal?.removeEventListener("abort", abort);
222884
- resolve(value);
222885
- };
222886
- const timeout = scheduler.schedule(Math.max(1, Math.min(100, status.remaining_ms)), () => finish(true));
222887
- const abort = () => finish(false);
222888
- signal?.addEventListener("abort", abort, { once: true });
222889
- if (signal?.aborted)
222890
- abort();
222891
- });
222892
- if (!resumed)
222900
+ const resumed = await waitBeforePoll(scheduler, Math.max(1, Math.min(100, status.remaining_ms)), signal);
222901
+ if (resumed !== "poll")
222902
+ return { kind: "unknown", reason: resumed };
222903
+ } catch (error) {
222904
+ if (signal?.aborted)
222893
222905
  return { kind: "unknown", reason: "cancelled" };
222894
- } catch {
222895
- return { kind: "unknown", reason: signal?.aborted ? "cancelled" : "unavailable" };
222906
+ const code = typeof error === "object" && error !== null ? error.code : undefined;
222907
+ const retryable = code === undefined || ["network", "timeout", "unavailable", "control_recovering", "outcome_unknown"].includes(String(code));
222908
+ if (!retryable || ++statusFailures > 2)
222909
+ return { kind: "unknown", reason: "unavailable" };
222910
+ const resumed = await waitBeforePoll(scheduler, 100, signal);
222911
+ if (resumed !== "poll")
222912
+ return { kind: "unknown", reason: resumed };
222896
222913
  }
222897
222914
  }
222898
222915
  }
@@ -223026,14 +223043,15 @@ function command(active, attempt, pluginCatalogHash) {
223026
223043
  function failureDetail(failures) {
223027
223044
  return failures.map(({ slot, code }) => `${slot}:${code}`).join(", ").slice(0, 512) || "publication failed";
223028
223045
  }
223029
- function publicationPhase(options, active, phase, boundary) {
223046
+ function publicationPhase(options, active, phase, boundary, handoffReason) {
223030
223047
  try {
223031
223048
  (options.stderr ?? process.stderr).write(`${JSON.stringify({
223032
223049
  event: "publication_phase",
223033
223050
  phase,
223034
223051
  boundary,
223035
223052
  mutation_id: active.operation.mutation_id,
223036
- revision: active.snapshot.revision
223053
+ revision: active.snapshot.revision,
223054
+ ...handoffReason === undefined ? {} : { handoff_reason: handoffReason }
223037
223055
  })}
223038
223056
  `);
223039
223057
  } catch {}
@@ -223324,10 +223342,22 @@ async function runPublication(options, active, oldWorkers) {
223324
223342
  }
223325
223343
  throw error;
223326
223344
  }
223327
- const handoff = await waitForAdmissionHandoff(preparedAdmission, options.scheduler, oldWorkers.length > 0, options.signal);
223345
+ publicationPhase(options, refreshed, "admission.handoff", "enter");
223346
+ let handoff;
223347
+ try {
223348
+ handoff = await waitForAdmissionHandoff(preparedAdmission, options.scheduler, oldWorkers.length > 0, options.signal);
223349
+ } finally {
223350
+ publicationPhase(options, refreshed, "admission.handoff", "exit", handoff?.kind === "unknown" ? handoff.reason : undefined);
223351
+ }
223328
223352
  if (handoff.kind === "unknown")
223329
223353
  return retiredOutcomeUnknown(options, oldWorkers, new Error(`retired ingress handoff is unknown: ${handoff.reason}`));
223330
- const drainEvidence = await drainWorkersUntilKnown(oldWorkers, options.scheduler, publicationPolicy, () => throwIfPublicationCancelled(options.signal, admissionCommitMayHaveBeenSent));
223354
+ publicationPhase(options, refreshed, "drainWorkers", "enter");
223355
+ let drainEvidence;
223356
+ try {
223357
+ drainEvidence = await drainWorkersUntilKnown(oldWorkers, options.scheduler, publicationPolicy, () => throwIfPublicationCancelled(options.signal, admissionCommitMayHaveBeenSent));
223358
+ } finally {
223359
+ publicationPhase(options, refreshed, "drainWorkers", "exit");
223360
+ }
223331
223361
  throwIfPublicationCancelled(options.signal, admissionCommitMayHaveBeenSent);
223332
223362
  const failuresDuringDrain = drainFailures(drainEvidence);
223333
223363
  const recoveringWithoutOldOwnership = options.recoveringMaster && oldWorkers.length === 0 && await options.confirmPreviousWorkersExited?.(options.owned.serving()) !== true;
@@ -225434,7 +225464,7 @@ async function togglePluginActivation(request, pluginName, enable, options) {
225434
225464
  return json({ error: "unauthorized" }, 401);
225435
225465
  const activations = active.aggregate.plugin_activations;
225436
225466
  const activated = activations.some((activation) => activation.plugin_name === pluginName);
225437
- if (activated === enable)
225467
+ if (!options.pluginDependencies && activated === enable)
225438
225468
  return unchangedMutationResponse(options, active.revision);
225439
225469
  let nextActivations;
225440
225470
  if (options.pluginDependencies) {
@@ -225447,6 +225477,8 @@ async function togglePluginActivation(request, pluginName, enable, options) {
225447
225477
  } else {
225448
225478
  nextActivations = enable ? [...activations, { plugin_name: pluginName }].sort((left, right) => left.plugin_name.localeCompare(right.plugin_name)) : activations.filter((activation) => activation.plugin_name !== pluginName);
225449
225479
  }
225480
+ if (nextActivations.length === activations.length && nextActivations.every((value, index) => value.plugin_name === activations[index]?.plugin_name))
225481
+ return unchangedMutationResponse(options, active.revision);
225450
225482
  const next = { ...active.aggregate, plugin_activations: nextActivations };
225451
225483
  return commitConfigurationMutation(request, options, {
225452
225484
  active,
@@ -225481,12 +225513,15 @@ async function importConfig(request, options) {
225481
225513
  const active = activeResult;
225482
225514
  if (!await options.managementAuth?.recheck(request) || !matchesCurrentAuth(request, active, options))
225483
225515
  return json({ error: "unauthorized" }, 401);
225484
- const parsed = options.parseAggregate(envelope.aggregate);
225516
+ const exported = parseNormalizeCompileAggregate(envelope.aggregate);
225517
+ if (!exported.ok)
225518
+ return json({ error: "invalid_configuration", errors: exported.errors }, 422);
225519
+ if (envelope.content_hash !== hashConfigurationContent(exported.value))
225520
+ return json({ error: "invalid_snapshot" }, 400);
225521
+ const parsed = options.parseAggregate(exported.value);
225485
225522
  if (!parsed.ok)
225486
225523
  return json({ error: "invalid_configuration", errors: parsed.errors }, 422);
225487
225524
  freezeJson3(parsed.value);
225488
- if (envelope.content_hash !== hashConfigurationContent(parsed.value))
225489
- return json({ error: "invalid_snapshot" }, 400);
225490
225525
  return commitConfigurationMutation(request, options, {
225491
225526
  active,
225492
225527
  next: parsed.value,
@@ -225727,6 +225762,7 @@ var init_control_api = __esm(() => {
225727
225762
  init_config_storage();
225728
225763
  init_repository_validation();
225729
225764
  init_content_hash();
225765
+ init_aggregate();
225730
225766
  init_plugin_name();
225731
225767
  init_logger();
225732
225768
  init_error_chain();
@@ -228420,6 +228456,39 @@ var init_master_plugin_catalog_api = __esm(() => {
228420
228456
  ];
228421
228457
  });
228422
228458
 
228459
+ // src/master-runtime/reconcile-plugin-dependencies.ts
228460
+ import { randomUUID as randomUUID17 } from "crypto";
228461
+ function reconcilePluginDependencies(repository, options, workerCount, now, managementProviders) {
228462
+ const current = repository.getSnapshot();
228463
+ if (!options.pluginDependencies)
228464
+ return current;
228465
+ const required = options.pluginDependencies.closure(current.aggregate.plugin_activations.map((value) => value.plugin_name));
228466
+ if (required.length === current.aggregate.plugin_activations.length)
228467
+ return current;
228468
+ const parsed = parseNormalizeCompileAggregate(current.aggregate, options);
228469
+ if (!parsed.ok)
228470
+ throw new ConfigRepositoryError("invalid_configuration", "configuration dependency reconciliation failed", parsed.errors);
228471
+ const active = new Set(current.aggregate.plugin_activations.map((value) => value.plugin_name));
228472
+ if (required.some((name) => managementProviders.has(name) && !active.has(name))) {
228473
+ throw new ConfigRepositoryError("invalid_configuration", "dependency activation changes management authentication; enable the provider through management setup before upgrading");
228474
+ }
228475
+ const result = repository.commit({
228476
+ mutation_id: randomUUID17(),
228477
+ expected_revision: current.revision,
228478
+ aggregate: parsed.value,
228479
+ kind: "config",
228480
+ created_at: now,
228481
+ target_worker_slots: Array.from({ length: workerCount }, (_, slot) => slot)
228482
+ });
228483
+ if (result.kind !== "committed") {
228484
+ throw new ConfigRepositoryError("invalid_operation", `configuration dependency reconciliation blocked: ${result.kind}`);
228485
+ }
228486
+ return result.snapshot;
228487
+ }
228488
+ var init_reconcile_plugin_dependencies = __esm(() => {
228489
+ init_config_storage();
228490
+ });
228491
+
228423
228492
  // src/ui/assets.ts
228424
228493
  function decodeBase64Asset(content) {
228425
228494
  return new Blob([Uint8Array.from(atob(content), (character) => character.charCodeAt(0))]);
@@ -228917,7 +228986,7 @@ var init_daemon_control2 = __esm(() => {
228917
228986
 
228918
228987
  // src/master-runtime/composition.ts
228919
228988
  import { AsyncLocalStorage } from "async_hooks";
228920
- import { randomUUID as randomUUID17, createHash as createHash6 } from "crypto";
228989
+ import { randomUUID as randomUUID18, createHash as createHash6 } from "crypto";
228921
228990
  import { resolve as resolve14 } from "path";
228922
228991
  function cleanupWorkersAfterStartupFailure(factory, disposition) {
228923
228992
  if (disposition === undefined || disposition.kind === "shutdown_safe_empty") {
@@ -229098,7 +229167,7 @@ class DurableRouteProtections {
229098
229167
  const previous = this.state?.get(name), value = { plugin: name, routeIds: [...routeIds] };
229099
229168
  if (this.state && JSON.stringify(previous?.value) !== JSON.stringify(value))
229100
229169
  this.state.execute({
229101
- commandId: randomUUID17(),
229170
+ commandId: randomUUID18(),
229102
229171
  mutations: [{ key: name, expectedVersion: previous?.version ?? 0, value }]
229103
229172
  });
229104
229173
  if (routeIds.length)
@@ -229153,7 +229222,7 @@ async function startMasterComposition(dependencies, daemonBootstrap = null) {
229153
229222
  if (createClaim === undefined || bootstrap.claimControllerWithCapability === undefined) {
229154
229223
  throw new MasterRuntimeError("startup_incomplete", "supervision claim is unavailable");
229155
229224
  }
229156
- supervisionState = bootstrap.claimControllerWithCapability(createClaim(configLock, accessLock), randomUUID17(), dependencies.clock.now());
229225
+ supervisionState = bootstrap.claimControllerWithCapability(createClaim(configLock, accessLock), randomUUID18(), dependencies.clock.now());
229157
229226
  await bootstrap.close();
229158
229227
  resources.repository = null;
229159
229228
  }
@@ -229182,6 +229251,10 @@ async function startMasterComposition(dependencies, daemonBootstrap = null) {
229182
229251
  if (dependencies.createIngressController !== undefined && supervisionState === null) {
229183
229252
  throw new MasterRuntimeError("startup_incomplete", "supervision state was not claimed");
229184
229253
  }
229254
+ if (compileOptions.pluginDependencies) {
229255
+ const managementProviders = new Set((catalog.records?.() ?? []).filter((record) => record.manifest.management).map((record) => record.name));
229256
+ reconcilePluginDependencies(resources.repository, compileOptions, options.workerCount, dependencies.clock.now(), managementProviders);
229257
+ }
229185
229258
  const configDatabase = resources.repository.getDatabase?.();
229186
229259
  const accessDatabase = resources.stats?.getDatabase?.();
229187
229260
  const secretStores = configDatabase === undefined ? {
@@ -229198,10 +229271,10 @@ async function startMasterComposition(dependencies, daemonBootstrap = null) {
229198
229271
  const rpcIdentityState = durableState?.forNamespace("core-master-state-rpc");
229199
229272
  let rpcIdentity = rpcIdentityState?.get("identity");
229200
229273
  if (rpcIdentityState && !rpcIdentity) {
229201
- rpcIdentity = rpcIdentityState.execute({ commandId: randomUUID17(), mutations: [{ key: "identity", expectedVersion: 0, value: { process_instance_id: supervisionState?.instance_id ?? randomUUID17(), boot_nonce: randomUUID17() } }] })[0];
229274
+ rpcIdentity = rpcIdentityState.execute({ commandId: randomUUID18(), mutations: [{ key: "identity", expectedVersion: 0, value: { process_instance_id: supervisionState?.instance_id ?? randomUUID18(), boot_nonce: randomUUID18() } }] })[0];
229202
229275
  }
229203
229276
  const stableIdentity = rpcIdentity?.value;
229204
- const masterStateIdentity = { role: "ingress", process_instance_id: stableIdentity?.process_instance_id ?? randomUUID17(), boot_nonce: stableIdentity?.boot_nonce ?? randomUUID17() };
229277
+ const masterStateIdentity = { role: "ingress", process_instance_id: stableIdentity?.process_instance_id ?? randomUUID18(), boot_nonce: stableIdentity?.boot_nonce ?? randomUUID18() };
229205
229278
  let publishPolicy = async () => false;
229206
229279
  const managementOrigin = parseManagementOrigin(process.env.BUNGEE_PUBLIC_ORIGIN);
229207
229280
  const trustedProxyAddresses = parseTrustedManagementProxies(process.env.BUNGEE_TRUSTED_MANAGEMENT_PROXIES);
@@ -229296,7 +229369,7 @@ async function startMasterComposition(dependencies, daemonBootstrap = null) {
229296
229369
  stateLeases.set(record.key, record.value);
229297
229370
  const saveStateLease = (id, lease) => {
229298
229371
  const previous = leaseState?.get(id);
229299
- leaseState?.execute({ commandId: randomUUID17(), mutations: [{ key: id, expectedVersion: previous?.version ?? 0, value: lease ? { ...lease } : null }] });
229372
+ leaseState?.execute({ commandId: randomUUID18(), mutations: [{ key: id, expectedVersion: previous?.version ?? 0, value: lease ? { ...lease } : null }] });
229300
229373
  if (lease)
229301
229374
  stateLeases.set(id, lease);
229302
229375
  else
@@ -229371,7 +229444,7 @@ async function startMasterComposition(dependencies, daemonBootstrap = null) {
229371
229444
  if (!lease)
229372
229445
  saveStateLease(id, { worker: workerId, principal, plugin: call.plugin });
229373
229446
  if (!requestLease)
229374
- requestLeaseState?.execute({ commandId: randomUUID17(), mutations: [{ key: requestLeaseId, expectedVersion: 0, value: { worker: workerId, principal, plugin: call.plugin } }] });
229447
+ requestLeaseState?.execute({ commandId: randomUUID18(), mutations: [{ key: requestLeaseId, expectedVersion: 0, value: { worker: workerId, principal, plugin: call.plugin } }] });
229375
229448
  }
229376
229449
  if (call.method === "settle")
229377
229450
  await resources.ingressController.freezePluginKey(call.plugin, call.target.principal.keyId);
@@ -230747,6 +230820,7 @@ var init_composition = __esm(() => {
230747
230820
  init_plugin_control();
230748
230821
  init_master_http_bridge();
230749
230822
  init_master_plugin_catalog_api();
230823
+ init_reconcile_plugin_dependencies();
230750
230824
  init_server();
230751
230825
  init_daemon_control2();
230752
230826
  NO_INGRESS_STARTUP_FAILURE_DISPOSITION = Object.freeze({
@@ -230799,7 +230873,6 @@ function privateRequestHeaders(request, transportSecret, trustedPeer, identity)
230799
230873
  }
230800
230874
  if (identity) {
230801
230875
  const wire = JSON.stringify(identity);
230802
- headers.delete("authorization");
230803
230876
  headers.set(INTERNAL_DATA_IDENTITY_HEADER, wire);
230804
230877
  headers.set(INTERNAL_DATA_IDENTITY_MAC_HEADER, signDataIdentity(wire, request.method, request.url, transportSecret));
230805
230878
  }
@@ -230837,7 +230910,7 @@ var init_headers = __esm(() => {
230837
230910
  });
230838
230911
 
230839
230912
  // src/public-listener/forwarding.ts
230840
- import { randomUUID as randomUUID18 } from "crypto";
230913
+ import { randomUUID as randomUUID19 } from "crypto";
230841
230914
  function errorResponse4(status) {
230842
230915
  const error = status === 503 ? "service_unavailable" : status === 502 ? "bad_gateway" : "client_closed_request";
230843
230916
  const headers = new Headers(JSON_HEADERS6);
@@ -230859,7 +230932,7 @@ async function forwardToSelectedWorker(request, options, trustedPeer) {
230859
230932
  });
230860
230933
  }
230861
230934
  const principal = options.authenticate?.(request) ?? ANONYMOUS_PRINCIPAL;
230862
- const identity = { requestId: randomUUID18(), principal };
230935
+ const identity = { requestId: randomUUID19(), principal };
230863
230936
  const lease = options.admission.acquire();
230864
230937
  const worker = lease.worker;
230865
230938
  if (worker === null) {
@@ -230893,7 +230966,7 @@ async function forwardToSelectedWorker(request, options, trustedPeer) {
230893
230966
  try {
230894
230967
  logger.warn({ ingressForwarding: {
230895
230968
  phase: "private_response_headers",
230896
- correlation_id: randomUUID18(),
230969
+ correlation_id: randomUUID19(),
230897
230970
  error_code: errorCode,
230898
230971
  elapsed_ms: Math.max(0, Math.round(performance.now() - startedAt))
230899
230972
  } }, "Ingress private request forwarding failed");
@@ -231157,7 +231230,7 @@ var init_admission_registry2 = __esm(() => {
231157
231230
  });
231158
231231
 
231159
231232
  // src/ingress/supervision-http.ts
231160
- import { randomUUID as randomUUID19 } from "crypto";
231233
+ import { randomUUID as randomUUID20 } from "crypto";
231161
231234
  function optionalAdmission(value) {
231162
231235
  return value === null ? null : parseAdmissionSet(value);
231163
231236
  }
@@ -231716,7 +231789,7 @@ class IngressControllerClient {
231716
231789
  }
231717
231790
  return value;
231718
231791
  }
231719
- async challenge(authority, requestId = randomUUID19(), sequence = 1, signal) {
231792
+ async challenge(authority, requestId = randomUUID20(), sequence = 1, signal) {
231720
231793
  if (signal?.aborted)
231721
231794
  throw signal.reason;
231722
231795
  this.statusRequestSequence = Math.max(this.statusRequestSequence, sequence + 1);
@@ -231731,7 +231804,7 @@ class IngressControllerClient {
231731
231804
  async status(authority, sequence = this.statusRequestSequence, signal) {
231732
231805
  if (signal?.aborted)
231733
231806
  throw signal.reason;
231734
- const requestId = randomUUID19();
231807
+ const requestId = randomUUID20();
231735
231808
  const message = signSupervisionMessage({
231736
231809
  protocol: "bungee-supervision-v1",
231737
231810
  kind: "status",
@@ -231749,13 +231822,13 @@ class IngressControllerClient {
231749
231822
  throw signal.reason;
231750
231823
  return this.verifyStatus(response, authority, requestId);
231751
231824
  }
231752
- async fence(authority, sequence, requestId = randomUUID19(), signal) {
231825
+ async fence(authority, sequence, requestId = randomUUID20(), signal) {
231753
231826
  const response = await this.command(authority, sequence, "/admission/fence", null, requestId, signal);
231754
231827
  if (signal?.aborted)
231755
231828
  throw signal.reason;
231756
231829
  return this.verifyStatus(response, authority, requestId);
231757
231830
  }
231758
- async attach(challenge, authority, sequence = 1, requestId = randomUUID19(), signal) {
231831
+ async attach(challenge, authority, sequence = 1, requestId = randomUUID20(), signal) {
231759
231832
  if (signal?.aborted)
231760
231833
  throw signal.reason;
231761
231834
  this.statusRequestSequence = Math.max(this.statusRequestSequence, sequence + 1);
@@ -231774,7 +231847,7 @@ class IngressControllerClient {
231774
231847
  throw signal.reason;
231775
231848
  return this.verifyStatus(response, authority, requestId);
231776
231849
  }
231777
- async lease(authority, leaseExpiresAt, sequence, requestId = randomUUID19(), signal) {
231850
+ async lease(authority, leaseExpiresAt, sequence, requestId = randomUUID20(), signal) {
231778
231851
  if (signal?.aborted)
231779
231852
  throw signal.reason;
231780
231853
  this.statusRequestSequence = Math.max(this.statusRequestSequence, sequence + 1);
@@ -231793,7 +231866,7 @@ class IngressControllerClient {
231793
231866
  throw signal.reason;
231794
231867
  return this.verifyStatus(response, authority, requestId);
231795
231868
  }
231796
- async command(authority, sequence, path, body, requestId = randomUUID19(), signal) {
231869
+ async command(authority, sequence, path, body, requestId = randomUUID20(), signal) {
231797
231870
  if (signal?.aborted)
231798
231871
  throw signal.reason;
231799
231872
  this.statusRequestSequence = Math.max(this.statusRequestSequence, sequence + 1);
@@ -232251,7 +232324,7 @@ var init_ingress2 = __esm(async () => {
232251
232324
  });
232252
232325
 
232253
232326
  // src/master-runtime/supervised-worker-client.ts
232254
- import { randomUUID as randomUUID20 } from "crypto";
232327
+ import { randomUUID as randomUUID21 } from "crypto";
232255
232328
  function plain8(value) {
232256
232329
  if (typeof value !== "object" || value === null || Array.isArray(value) || Object.getPrototypeOf(value) !== Object.prototype && Object.getPrototypeOf(value) !== null) {
232257
232330
  throw new WorkerControllerClientError("protocol", "supervision response must be a plain object");
@@ -232730,7 +232803,7 @@ class WorkerControllerClient {
232730
232803
  try {
232731
232804
  if (this.stopped || this.terminating || controller.signal.aborted || Date.now() >= deadline)
232732
232805
  throw new WorkerControllerClientError("timeout", "worker runtime snapshot timed out");
232733
- const requestId = randomUUID20();
232806
+ const requestId = randomUUID21();
232734
232807
  const sequence = this.nextSequence();
232735
232808
  const message = signSupervisionMessage({
232736
232809
  protocol: "bungee-supervision-v1",
@@ -232756,7 +232829,7 @@ class WorkerControllerClient {
232756
232829
  }, controller.signal, deadline).finally(finish);
232757
232830
  }
232758
232831
  async challenge(signal = this.ordinaryAbort.signal) {
232759
- const requestId = randomUUID20();
232832
+ const requestId = randomUUID21();
232760
232833
  const sequence = this.nextSequence();
232761
232834
  const response = plain8(await this.fetchJson("/__supervision/challenge", {
232762
232835
  controller_epoch: this.options.authority.controller_epoch,
@@ -232772,7 +232845,7 @@ class WorkerControllerClient {
232772
232845
  }
232773
232846
  async attachOnce(signal = this.ordinaryAbort.signal) {
232774
232847
  const challengeNonce = await this.challenge(signal);
232775
- const attachId = randomUUID20();
232848
+ const attachId = randomUUID21();
232776
232849
  const attachSequence = this.nextSequence();
232777
232850
  const attach = signSupervisionMessage({
232778
232851
  protocol: "bungee-supervision-v1",
@@ -232799,7 +232872,7 @@ class WorkerControllerClient {
232799
232872
  throw error;
232800
232873
  }
232801
232874
  }
232802
- const leaseId = randomUUID20();
232875
+ const leaseId = randomUUID21();
232803
232876
  const leaseSequence = this.nextSequence();
232804
232877
  const leaseStartedAt = performance.now();
232805
232878
  const lease = signSupervisionMessage({
@@ -232844,7 +232917,7 @@ class WorkerControllerClient {
232844
232917
  }, delayMs);
232845
232918
  }
232846
232919
  async renewOnce(signal = this.ordinaryAbort.signal) {
232847
- const requestId = randomUUID20();
232920
+ const requestId = randomUUID21();
232848
232921
  const sequence = this.nextSequence();
232849
232922
  const leaseStartedAt = performance.now();
232850
232923
  const lease = signSupervisionMessage({
@@ -232889,12 +232962,11 @@ class WorkerControllerClient {
232889
232962
  return this.enqueueRenewal((signal) => this.renewOnce(signal));
232890
232963
  }
232891
232964
  status(timeoutMs = this.timeoutMs) {
232892
- const budget = this.leaseDeadlineAt === null ? timeoutMs : Math.max(1, Math.min(timeoutMs, this.leaseDeadlineAt - performance.now()));
232893
- const deadline = performance.now() + budget;
232965
+ const deadline = performance.now() + timeoutMs;
232894
232966
  return this.enqueue(() => deadline <= performance.now() ? Promise.reject(new WorkerControllerClientError("timeout", "worker status deadline expired")) : this.statusOnce(this.ordinaryAbort.signal, deadline));
232895
232967
  }
232896
232968
  async statusOnce(signal = this.ordinaryAbort.signal, deadline) {
232897
- const requestId = randomUUID20();
232969
+ const requestId = randomUUID21();
232898
232970
  const sequence = this.nextSequence();
232899
232971
  const message = signSupervisionMessage({
232900
232972
  protocol: "bungee-supervision-v1",
@@ -232927,7 +232999,7 @@ class WorkerControllerClient {
232927
232999
  });
232928
233000
  }
232929
233001
  async commandOnce(path, body, signal, allowTerminating = false, deadline) {
232930
- const requestId = randomUUID20();
233002
+ const requestId = randomUUID21();
232931
233003
  const sequence = this.nextSequence();
232932
233004
  const message = signSupervisionMessage({
232933
233005
  protocol: "bungee-supervision-v1",
@@ -232976,7 +233048,7 @@ class WorkerControllerClient {
232976
233048
  for (const controller of this.runtimeControllers)
232977
233049
  controller.abort("disconnected");
232978
233050
  if (expireLease && !this.terminating && this.state === "attached") {
232979
- const requestId = randomUUID20();
233051
+ const requestId = randomUUID21();
232980
233052
  const sequence = this.nextSequence();
232981
233053
  const lease = signSupervisionMessage({
232982
233054
  protocol: "bungee-supervision-v1",
@@ -233214,6 +233286,7 @@ class SupervisedConfigWorkerProcessAdapter {
233214
233286
  stopped = false;
233215
233287
  drainPoll = null;
233216
233288
  drainCommand = null;
233289
+ drainStatusFailureReported = false;
233217
233290
  exitPublication = null;
233218
233291
  kernelBootId;
233219
233292
  constructor(options) {
@@ -233416,7 +233489,23 @@ class SupervisedConfigWorkerProcessAdapter {
233416
233489
  return status;
233417
233490
  }
233418
233491
  async drainStatus(timeoutMs) {
233419
- const status = await this.status(timeoutMs);
233492
+ const startedAt = performance.now();
233493
+ let status;
233494
+ try {
233495
+ status = await this.status(timeoutMs);
233496
+ this.drainStatusFailureReported = false;
233497
+ } catch (error) {
233498
+ if (!this.drainStatusFailureReported) {
233499
+ this.drainStatusFailureReported = true;
233500
+ recordShutdownFailure("worker_drain_status", {
233501
+ pid: this.pid,
233502
+ origin: this.origin,
233503
+ elapsedMs: shutdownElapsedMs(startedAt),
233504
+ ...timeoutMs === undefined ? {} : { timeoutMs }
233505
+ }, error);
233506
+ }
233507
+ throw error;
233508
+ }
233420
233509
  if (status.evidence.kind === "draining" && status.evidence.message?.status === "worker-draining") {
233421
233510
  return status.evidence.message;
233422
233511
  }
@@ -233468,27 +233557,42 @@ class SupervisedConfigWorkerProcessAdapter {
233468
233557
  throw new Error("supervised worker control message is unsupported");
233469
233558
  if (message.command === "drain-worker")
233470
233559
  this.drainCommand = message;
233560
+ const startedAt = performance.now();
233471
233561
  let deadline;
233472
- if (message.command === "drain-worker") {
233473
- if (message.start_boot_id !== this.kernelBootId)
233474
- throw new Error("worker start deadline boot identity is unknown or mismatched");
233475
- const remaining = BigInt(message.start_deadline_ns) - process.hrtime.bigint();
233476
- if (remaining <= 0n)
233477
- throw new Error("worker drain start deadline expired before control dispatch");
233478
- deadline = performance.now() + Number((remaining + 999999n) / 1000000n);
233479
- }
233480
- const client = await this.readyClient();
233481
- if (deadline !== undefined && performance.now() >= deadline) {
233482
- this.followDrain(client, message);
233483
- throw new Error("worker drain start deadline expired before control dispatch");
233484
- }
233485
233562
  try {
233486
- const status = message.command === "drain-worker" ? await client.drain(message, deadline) : await client.start(message);
233487
- this.lastStatus = status;
233488
- this.publish(status.evidence.message);
233489
- } finally {
233490
- if (message.command === "drain-worker")
233563
+ if (message.command === "drain-worker") {
233564
+ if (message.start_boot_id !== this.kernelBootId)
233565
+ throw new Error("worker start deadline boot identity is unknown or mismatched");
233566
+ const remaining = BigInt(message.start_deadline_ns) - process.hrtime.bigint();
233567
+ deadline = performance.now() + Number((remaining + 999999n) / 1000000n);
233568
+ if (remaining <= 0n)
233569
+ throw new Error("worker drain start deadline expired before control dispatch");
233570
+ }
233571
+ const client = await this.readyClient();
233572
+ if (deadline !== undefined && performance.now() >= deadline) {
233491
233573
  this.followDrain(client, message);
233574
+ throw new Error("worker drain start deadline expired before control dispatch");
233575
+ }
233576
+ try {
233577
+ const status = message.command === "drain-worker" ? await client.drain(message, deadline) : await client.start(message);
233578
+ this.lastStatus = status;
233579
+ this.publish(status.evidence.message);
233580
+ } finally {
233581
+ if (message.command === "drain-worker")
233582
+ this.followDrain(client, message);
233583
+ }
233584
+ } catch (error) {
233585
+ if (message.command === "drain-worker") {
233586
+ recordShutdownFailure("worker_drain_command", {
233587
+ pid: this.pid,
233588
+ origin: this.origin,
233589
+ elapsedMs: shutdownElapsedMs(startedAt),
233590
+ timeoutMs: message.policy.drain_start_timeout_ms,
233591
+ deadlineExceeded: deadline !== undefined && performance.now() >= deadline,
233592
+ commandOutcome: "failed"
233593
+ }, error);
233594
+ }
233595
+ throw error;
233492
233596
  }
233493
233597
  }
233494
233598
  followDrain(client, command) {
@@ -235409,7 +235513,7 @@ var init_management_listener = __esm(() => {
235409
235513
  });
235410
235514
 
235411
235515
  // src/ingress/master-controller.ts
235412
- import { randomUUID as randomUUID21 } from "crypto";
235516
+ import { randomUUID as randomUUID22 } from "crypto";
235413
235517
  import { spawn as spawnChild2 } from "child_process";
235414
235518
  function immutableRegistry(registry) {
235415
235519
  const immutableAdmission = (set) => set === null ? null : Object.freeze({
@@ -235493,7 +235597,7 @@ class MasterIngressController {
235493
235597
  requirePositive(this.startupTimeoutMs, "startupTimeoutMs");
235494
235598
  requirePositive(this.leaseDurationMs, "leaseDurationMs");
235495
235599
  this.authority = { controller_epoch: options.controllerEpoch, controller_id: options.controllerId };
235496
- this.childCredential = deriveSupervisionProcessKey(options.rootKey, options.instanceId, "ingress", randomUUID21(), randomUUID21());
235600
+ this.childCredential = deriveSupervisionProcessKey(options.rootKey, options.instanceId, "ingress", randomUUID22(), randomUUID22());
235497
235601
  this.fetch = options.fetch ?? fetch;
235498
235602
  this.identityControl = options.processIdentity ?? DEFAULT_INGRESS_PROCESS_IDENTITY;
235499
235603
  this.probeSleep = options.probeSleep ?? ((ms) => new Promise((resolve) => setTimeout(resolve, ms)));
@@ -235988,8 +236092,6 @@ class MasterIngressController {
235988
236092
  evidence.lastProbe = probe;
235989
236093
  if (probe === "dead" || probe === "mismatch")
235990
236094
  return probe;
235991
- if (probe === "unknown")
235992
- return "unverified";
235993
236095
  const remaining = deadline - this.now();
235994
236096
  if (remaining <= 0)
235995
236097
  break;
@@ -236050,7 +236152,7 @@ class MasterIngressController {
236050
236152
  const lifecycle = this.lifecycleGeneration;
236051
236153
  if (!this.isRecoveryEligible(lifecycle))
236052
236154
  return null;
236053
- this.childCredential = deriveSupervisionProcessKey(this.options.rootKey, this.options.instanceId, "ingress", randomUUID21(), randomUUID21());
236155
+ this.childCredential = deriveSupervisionProcessKey(this.options.rootKey, this.options.instanceId, "ingress", randomUUID22(), randomUUID22());
236054
236156
  const childEnvironment = { ...this.options.environment ?? process.env };
236055
236157
  for (const name of [
236056
236158
  "BUNGEE_PLUGIN_SECRETS_KEY",
@@ -236080,9 +236182,9 @@ class MasterIngressController {
236080
236182
  throw this.mapDiscoveryFailure(cause);
236081
236183
  }
236082
236184
  this.assertRecoveryEligible(lifecycle);
236083
- const challenge = await client.challenge(this.authority, randomUUID21(), 1, signal);
236185
+ const challenge = await client.challenge(this.authority, randomUUID22(), 1, signal);
236084
236186
  this.assertRecoveryEligible(lifecycle);
236085
- await client.attach(challenge, this.authority, this.nextSequence(signal), randomUUID21(), signal);
236187
+ await client.attach(challenge, this.authority, this.nextSequence(signal), randomUUID22(), signal);
236086
236188
  this.assertRecoveryEligible(lifecycle);
236087
236189
  const attachedStatus = await client.status(this.authority, this.nextSequence(signal), signal);
236088
236190
  this.assertRecoveryEligible(lifecycle);
@@ -236393,10 +236495,10 @@ class MasterIngressController {
236393
236495
  await client.identity(signal);
236394
236496
  if (!this.isRecoveryEligible(lifecycle))
236395
236497
  return null;
236396
- const challenge = await client.challenge(this.authority, randomUUID21(), 1, signal);
236498
+ const challenge = await client.challenge(this.authority, randomUUID22(), 1, signal);
236397
236499
  if (!this.isRecoveryEligible(lifecycle))
236398
236500
  return null;
236399
- await client.attach(challenge, this.authority, this.nextSequence(signal), randomUUID21(), signal);
236501
+ await client.attach(challenge, this.authority, this.nextSequence(signal), randomUUID22(), signal);
236400
236502
  if (!this.isRecoveryEligible(lifecycle))
236401
236503
  return null;
236402
236504
  const attachedStatus = await client.status(this.authority, this.nextSequence(signal), signal);
@@ -237103,7 +237205,7 @@ var init_master_controller = __esm(() => {
237103
237205
  });
237104
237206
 
237105
237207
  // src/master.ts
237106
- import { randomUUID as randomUUID22 } from "crypto";
237208
+ import { randomUUID as randomUUID23 } from "crypto";
237107
237209
  import { resolve as resolve17 } from "path";
237108
237210
  function environment() {
237109
237211
  return Object.fromEntries(Object.entries(process.env).filter((entry) => entry[1] !== undefined));
@@ -237187,7 +237289,7 @@ var init_master = __esm(async () => {
237187
237289
  deriveTransportSecret: deriveWorkerTransportSecret,
237188
237290
  resolveWorkerLaunch,
237189
237291
  createWorkerFactory: (options) => new SupervisedConfigWorkerFactory(options),
237190
- createMasterGeneration: randomUUID22,
237292
+ createMasterGeneration: randomUUID23,
237191
237293
  createCoordinator: (options) => new MasterConfigPublicationCoordinator(options),
237192
237294
  createManagementListener,
237193
237295
  createControllerClaim: mintControllerClaimCapability,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jeffusion/bungee-core",
3
- "version": "5.6.0",
3
+ "version": "5.6.2",
4
4
  "description": "Bungee reverse proxy core engine",
5
5
  "main": "./dist/main.js",
6
6
  "types": "./dist/main.d.ts",
@@ -12,7 +12,7 @@
12
12
  "test": "bun test"
13
13
  },
14
14
  "dependencies": {
15
- "@jeffusion/bungee-types": "^5.6.0",
15
+ "@jeffusion/bungee-types": "^5.6.2",
16
16
  "@types/lodash-es": "^4.17.12",
17
17
  "acorn": "^8.15.0",
18
18
  "canonicalize": "3.0.0",