@objectstack/plugin-webhooks 17.1.0 → 17.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -194,7 +194,7 @@ var AutoEnqueuer = class {
194
194
  this.droppedForSecret = /* @__PURE__ */ new Set();
195
195
  this.subscriptionsObject = _nullishCoalesce(opts.subscriptionsObject, () => ( "sys_webhook"));
196
196
  this.refreshIntervalMs = _nullishCoalesce(opts.refreshIntervalMs, () => ( 6e4));
197
- this.logger = _nullishCoalesce(opts.logger, () => ( {}));
197
+ this.logger = opts.logger;
198
198
  }
199
199
  /**
200
200
  * Load the subscription cache and start listening for events.
@@ -219,7 +219,7 @@ var AutoEnqueuer = class {
219
219
  if (this.refreshIntervalMs > 0) {
220
220
  this.refreshTimer = setInterval(() => {
221
221
  this.refresh().catch(
222
- (err) => _optionalChain([this, 'access', _7 => _7.logger, 'access', _8 => _8.warn, 'optionalCall', _9 => _9("[webhook-auto-enqueuer] periodic refresh failed", err)])
222
+ (err) => _optionalChain([this, 'access', _7 => _7.logger, 'optionalAccess', _8 => _8.warn, 'optionalCall', _9 => _9("[webhook-auto-enqueuer] periodic refresh failed", err)])
223
223
  );
224
224
  }, this.refreshIntervalMs);
225
225
  _optionalChain([this, 'access', _10 => _10.refreshTimer, 'access', _11 => _11.unref, 'optionalCall', _12 => _12()]);
@@ -252,7 +252,7 @@ var AutoEnqueuer = class {
252
252
  rearmAfterCryptoRegistered() {
253
253
  const inFlight = _nullishCoalesce(this.refreshing, () => ( Promise.resolve()));
254
254
  void inFlight.catch(() => void 0).then(() => this.running ? this.refresh() : void 0).catch(
255
- (err) => _optionalChain([this, 'access', _15 => _15.logger, 'access', _16 => _16.warn, 'optionalCall', _17 => _17(
255
+ (err) => _optionalChain([this, 'access', _15 => _15.logger, 'optionalAccess', _16 => _16.warn, 'optionalCall', _17 => _17(
256
256
  "[webhook-auto-enqueuer] re-arm after CryptoProvider registration failed",
257
257
  err
258
258
  )])
@@ -276,7 +276,7 @@ var AutoEnqueuer = class {
276
276
  where: { active: true }
277
277
  });
278
278
  } catch (err) {
279
- _optionalChain([this, 'access', _18 => _18.logger, 'access', _19 => _19.warn, 'optionalCall', _20 => _20(
279
+ _optionalChain([this, 'access', _18 => _18.logger, 'optionalAccess', _19 => _19.warn, 'optionalCall', _20 => _20(
280
280
  `[webhook-auto-enqueuer] failed to load ${this.subscriptionsObject}`,
281
281
  err
282
282
  )]);
@@ -300,7 +300,7 @@ var AutoEnqueuer = class {
300
300
  if (!live.has(id)) this.droppedForSecret.delete(id);
301
301
  }
302
302
  }
303
- _optionalChain([this, 'access', _22 => _22.logger, 'access', _23 => _23.debug, 'optionalCall', _24 => _24("[webhook-auto-enqueuer] cache refreshed", {
303
+ _optionalChain([this, 'access', _22 => _22.logger, 'optionalAccess', _23 => _23.debug, 'optionalCall', _24 => _24("[webhook-auto-enqueuer] cache refreshed", {
304
304
  objects: this.subscriptions.size,
305
305
  rows: rows.length
306
306
  })]);
@@ -371,20 +371,20 @@ var AutoEnqueuer = class {
371
371
  reportWriteFailure(sub, eventId, err, verb) {
372
372
  const meta = { webhook: sub.name, eventId, err: _nullishCoalesce(_optionalChain([err, 'optionalAccess', _25 => _25.message]), () => ( err)) };
373
373
  if (!sub.parkedReason) {
374
- _optionalChain([this, 'access', _26 => _26.logger, 'access', _27 => _27.warn, 'optionalCall', _28 => _28(`[webhook-auto-enqueuer] ${verb} failed`, meta)]);
374
+ _optionalChain([this, 'access', _26 => _26.logger, 'optionalAccess', _27 => _27.warn, 'optionalCall', _28 => _28(`[webhook-auto-enqueuer] ${verb} failed`, meta)]);
375
375
  return;
376
376
  }
377
377
  const message = `[webhook-auto-enqueuer] could not record the undeliverable event for webhook '${sub.name}' \u2014 the subscription is parked for an unresolvable credential, and this event is now DISCARDED WITH NO TRACE in sys_http_delivery, which is the durability gap #8069 closes. Most likely cause: the enqueue callback was wired directly to IHttpOutbox.enqueue instead of MessagingService.enqueueHttp \u2014 only the messaging seam routes a parked event to recordUndeliverable(), and the delivery door refuses it rather than minting a pending row that would be sent UNSIGNED.`;
378
- if (typeof this.logger.error === "function") {
379
- this.logger.error(message, err, meta);
378
+ if (typeof _optionalChain([this, 'access', _29 => _29.logger, 'optionalAccess', _30 => _30.error]) === "function") {
379
+ _optionalChain([this, 'access', _31 => _31.logger, 'optionalAccess', _32 => _32.error, 'call', _33 => _33(message, err, meta)]);
380
380
  } else {
381
- _optionalChain([this, 'access', _29 => _29.logger, 'access', _30 => _30.warn, 'optionalCall', _31 => _31(message, meta)]);
381
+ _optionalChain([this, 'access', _34 => _34.logger, 'optionalAccess', _35 => _35.warn, 'optionalCall', _36 => _36(message, meta)]);
382
382
  }
383
383
  }
384
384
  park(sub, err, credential) {
385
385
  sub.secret = void 0;
386
386
  sub.headers = void 0;
387
- sub.parkedReason = `[${WEBHOOK_SECRET_REFUSAL_CODE}/${WEBHOOK_SECRET_REFUSAL_STATUS}] webhook '${sub.name}' holds ${credential.article} that could not be decrypted, so this event was NOT delivered \u2014 recording it here rather than ${credential.ratherThan} (${credential.issue}, #8069). This row was never sent and cannot be redelivered: it carries no HMAC signature, because the ${credential.noun} that would have produced one is exactly what is missing. Fix: register a CryptoProvider (engine.setCryptoProvider \u2014 LocalCryptoProvider in dev, KMS/Vault in production) with the same key the ${credential.noun} was written under, and make sure the sys_secret row is reachable; the subscription re-arms on registration (#8022) and at the next periodic refresh, and later events are delivered normally. Cause: ${_nullishCoalesce(_optionalChain([err, 'optionalAccess', _32 => _32.message]), () => ( String(err)))}`;
387
+ sub.parkedReason = `[${WEBHOOK_SECRET_REFUSAL_CODE}/${WEBHOOK_SECRET_REFUSAL_STATUS}] webhook '${sub.name}' holds ${credential.article} that could not be decrypted, so this event was NOT delivered \u2014 recording it here rather than ${credential.ratherThan} (${credential.issue}, #8069). This row was never sent and cannot be redelivered: it carries no HMAC signature, because the ${credential.noun} that would have produced one is exactly what is missing. Fix: register a CryptoProvider (engine.setCryptoProvider \u2014 LocalCryptoProvider in dev, KMS/Vault in production) with the same key the ${credential.noun} was written under, and make sure the sys_secret row is reachable; the subscription re-arms on registration (#8022) and at the next periodic refresh, and later events are delivered normally. Cause: ${_nullishCoalesce(_optionalChain([err, 'optionalAccess', _37 => _37.message]), () => ( String(err)))}`;
388
388
  }
389
389
  /**
390
390
  * [#7799] Resolve `sub.secret`. Returns `false` when the subscription must
@@ -428,9 +428,9 @@ var AutoEnqueuer = class {
428
428
  this.park(sub, err, SIGNING_SECRET_CREDENTIAL);
429
429
  return false;
430
430
  }
431
- const legacy = readLegacySecret(_optionalChain([row, 'optionalAccess', _33 => _33.definition_json]));
431
+ const legacy = readLegacySecret(_optionalChain([row, 'optionalAccess', _38 => _38.definition_json]));
432
432
  if (legacy) {
433
- _optionalChain([this, 'access', _34 => _34.logger, 'access', _35 => _35.warn, 'optionalCall', _36 => _36(
433
+ _optionalChain([this, 'access', _39 => _39.logger, 'optionalAccess', _40 => _40.warn, 'optionalCall', _41 => _41(
434
434
  `[webhook-auto-enqueuer] webhook '${sub.name}' still carries its signing secret as CLEARTEXT in definition_json, readable over the data API (#7799). Signing continues from it; run the boot sweep (migrateLegacyWebhookSecrets) with a CryptoProvider wired to move it into sys_secret.`,
435
435
  { id: sub.id }
436
436
  )]);
@@ -478,9 +478,9 @@ var AutoEnqueuer = class {
478
478
  this.park(sub, err, CUSTOM_HEADERS_CREDENTIAL);
479
479
  return false;
480
480
  }
481
- const legacy = readLegacyHeaders(_optionalChain([row, 'optionalAccess', _37 => _37.definition_json]));
481
+ const legacy = readLegacyHeaders(_optionalChain([row, 'optionalAccess', _42 => _42.definition_json]));
482
482
  if (legacy) {
483
- _optionalChain([this, 'access', _38 => _38.logger, 'access', _39 => _39.warn, 'optionalCall', _40 => _40(
483
+ _optionalChain([this, 'access', _43 => _43.logger, 'optionalAccess', _44 => _44.warn, 'optionalCall', _45 => _45(
484
484
  `[webhook-auto-enqueuer] webhook '${sub.name}' still carries its custom headers as CLEARTEXT in definition_json, readable over the data API (#7986) \u2014 that map is the ordinary place an Authorization header goes. Delivery continues from it; run the boot sweep (migrateLegacyWebhookSecrets) with a CryptoProvider wired to move them into sys_secret.`,
485
485
  { id: sub.id }
486
486
  )]);
@@ -520,10 +520,10 @@ var AutoEnqueuer = class {
520
520
  field: credential.field,
521
521
  code: WEBHOOK_SECRET_REFUSAL_CODE,
522
522
  status: WEBHOOK_SECRET_REFUSAL_STATUS,
523
- err: _nullishCoalesce(_optionalChain([err, 'optionalAccess', _41 => _41.message]), () => ( err))
523
+ err: _nullishCoalesce(_optionalChain([err, 'optionalAccess', _46 => _46.message]), () => ( err))
524
524
  };
525
525
  if (this.droppedForSecret.has(sub.id)) {
526
- _optionalChain([this, 'access', _42 => _42.logger, 'access', _43 => _43.debug, 'optionalCall', _44 => _44(
526
+ _optionalChain([this, 'access', _47 => _47.logger, 'optionalAccess', _48 => _48.debug, 'optionalCall', _49 => _49(
527
527
  `[webhook-auto-enqueuer] webhook '${sub.name}' is still dropped for an unresolvable ${credential.noun} (${credential.issues})`,
528
528
  meta
529
529
  )]);
@@ -531,14 +531,14 @@ var AutoEnqueuer = class {
531
531
  }
532
532
  this.droppedForSecret.add(sub.id);
533
533
  const message = `[webhook-auto-enqueuer] webhook '${sub.name}' holds ${credential.article} that could not be decrypted \u2014 the subscription is PARKED rather than ${credential.ratherThan} (${credential.issue}), so every matching record change is discarded with NO delivery, while the row keeps reading active:true in Setup. Each discarded event IS recorded in sys_http_delivery as a dead row with 0 attempts carrying this cause (#8069) \u2014 look there for the backlog; those rows can never be sent or redelivered, because a parked row has no HMAC signature. Fix: register a CryptoProvider (engine.setCryptoProvider \u2014 LocalCryptoProvider in dev, KMS/Vault in production) with the same key the ${credential.noun} was written under, and make sure the sys_secret row is reachable; the subscription re-arms on registration (#8022) and at the next periodic refresh.`;
534
- if (typeof this.logger.error === "function") {
535
- this.logger.error(message, err, meta);
534
+ if (typeof _optionalChain([this, 'access', _50 => _50.logger, 'optionalAccess', _51 => _51.error]) === "function") {
535
+ _optionalChain([this, 'access', _52 => _52.logger, 'optionalAccess', _53 => _53.error, 'call', _54 => _54(message, err, meta)]);
536
536
  } else {
537
- _optionalChain([this, 'access', _45 => _45.logger, 'access', _46 => _46.warn, 'optionalCall', _47 => _47(message, meta)]);
537
+ _optionalChain([this, 'access', _55 => _55.logger, 'optionalAccess', _56 => _56.warn, 'optionalCall', _57 => _57(message, meta)]);
538
538
  }
539
539
  }
540
540
  parseRow(row) {
541
- if (!_optionalChain([row, 'optionalAccess', _48 => _48.id]) || !_optionalChain([row, 'optionalAccess', _49 => _49.url])) return null;
541
+ if (!_optionalChain([row, 'optionalAccess', _58 => _58.id]) || !_optionalChain([row, 'optionalAccess', _59 => _59.url])) return null;
542
542
  const rawTriggers = row.triggers;
543
543
  let triggerList;
544
544
  if (Array.isArray(rawTriggers)) {
@@ -559,7 +559,7 @@ var AutoEnqueuer = class {
559
559
  const normalized = triggerList.map((t) => t.trim().toLowerCase()).filter(Boolean);
560
560
  const unknown = normalized.filter((t) => !DISPATCHABLE_WEBHOOK_TRIGGERS.has(t));
561
561
  if (unknown.length > 0) {
562
- _optionalChain([this, 'access', _50 => _50.logger, 'access', _51 => _51.warn, 'optionalCall', _52 => _52(
562
+ _optionalChain([this, 'access', _60 => _60.logger, 'optionalAccess', _61 => _61.warn, 'optionalCall', _62 => _62(
563
563
  `[webhook-auto-enqueuer] webhook '${_nullishCoalesce(row.name, () => ( row.id))}' declares trigger(s) the engine never emits: ${unknown.join(", ")} \u2014 ignored. Dispatchable triggers: ${[...DISPATCHABLE_WEBHOOK_TRIGGERS].join(", ")}.`,
564
564
  { id: row.id, unknown }
565
565
  )]);
@@ -568,7 +568,7 @@ var AutoEnqueuer = class {
568
568
  normalized.filter((t) => DISPATCHABLE_WEBHOOK_TRIGGERS.has(t))
569
569
  );
570
570
  if (triggers.size === 0) {
571
- _optionalChain([this, 'access', _53 => _53.logger, 'access', _54 => _54.warn, 'optionalCall', _55 => _55(
571
+ _optionalChain([this, 'access', _63 => _63.logger, 'optionalAccess', _64 => _64.warn, 'optionalCall', _65 => _65(
572
572
  `[webhook-auto-enqueuer] webhook '${_nullishCoalesce(row.name, () => ( row.id))}' has no dispatchable triggers \u2014 it will NEVER fire (rule webhook/without-triggers): there is no manual fire path (#3196), so this row is dead while looking armed in Setup. Declare one of: ${[...DISPATCHABLE_WEBHOOK_TRIGGERS].join(", ")}, or set it inactive if it should be off.`,
573
573
  { id: row.id }
574
574
  )]);
@@ -609,11 +609,11 @@ var AutoEnqueuer = class {
609
609
  handleEvent(event) {
610
610
  if (!event.object) return;
611
611
  if (event.object === this.subscriptionsObject) return;
612
- if (_optionalChain([event, 'access', _56 => _56.type, 'optionalAccess', _57 => _57.startsWith, 'call', _58 => _58("data.records.")])) {
612
+ if (_optionalChain([event, 'access', _66 => _66.type, 'optionalAccess', _67 => _67.startsWith, 'call', _68 => _68("data.records.")])) {
613
613
  this.handleBulkEvent(event);
614
614
  return;
615
615
  }
616
- if (!_optionalChain([event, 'access', _59 => _59.type, 'optionalAccess', _60 => _60.startsWith, 'call', _61 => _61("data.record.")])) return;
616
+ if (!_optionalChain([event, 'access', _69 => _69.type, 'optionalAccess', _70 => _70.startsWith, 'call', _71 => _71("data.record.")])) return;
617
617
  const action = event.type.slice("data.record.".length);
618
618
  const trigger = mapActionToTrigger(action);
619
619
  if (!trigger) return;
@@ -625,7 +625,7 @@ var AutoEnqueuer = class {
625
625
  const payload = _nullishCoalesce(event.payload, () => ( {}));
626
626
  const recordId = payload.recordId;
627
627
  if (typeof recordId !== "string" || recordId === "") {
628
- _optionalChain([this, 'access', _62 => _62.logger, 'access', _63 => _63.warn, 'optionalCall', _64 => _64(
628
+ _optionalChain([this, 'access', _72 => _72.logger, 'optionalAccess', _73 => _73.warn, 'optionalCall', _74 => _74(
629
629
  "[webhook-auto-enqueuer] dropping off-contract data event: payload is not a DataEvent (no top-level string `recordId`) \u2014 fix the producer",
630
630
  { type: event.type, object: event.object }
631
631
  )]);
@@ -694,7 +694,7 @@ var AutoEnqueuer = class {
694
694
  const payload = _nullishCoalesce(event.payload, () => ( {}));
695
695
  const matched = payload.matched;
696
696
  if (typeof matched !== "number" || !Number.isInteger(matched) || matched < 0) {
697
- _optionalChain([this, 'access', _65 => _65.logger, 'access', _66 => _66.warn, 'optionalCall', _67 => _67(
697
+ _optionalChain([this, 'access', _75 => _75.logger, 'optionalAccess', _76 => _76.warn, 'optionalCall', _77 => _77(
698
698
  "[webhook-auto-enqueuer] dropping off-contract bulk data event: payload is not a BulkDataEvent (no top-level non-negative integer `matched`) \u2014 fix the producer",
699
699
  { type: event.type, object: event.object }
700
700
  )]);
@@ -702,7 +702,7 @@ var AutoEnqueuer = class {
702
702
  }
703
703
  const eventUuid = payload.id;
704
704
  if (typeof eventUuid !== "string" || eventUuid === "") {
705
- _optionalChain([this, 'access', _68 => _68.logger, 'access', _69 => _69.warn, 'optionalCall', _70 => _70(
705
+ _optionalChain([this, 'access', _78 => _78.logger, 'optionalAccess', _79 => _79.warn, 'optionalCall', _80 => _80(
706
706
  "[webhook-auto-enqueuer] dropping off-contract bulk data event: payload has no top-level string `id` to dedup on \u2014 fix the producer",
707
707
  { type: event.type, object: event.object }
708
708
  )]);
@@ -737,9 +737,9 @@ var AutoEnqueuer = class {
737
737
  }
738
738
  handleSelfHealEvent(event) {
739
739
  if (event.object !== this.subscriptionsObject) return;
740
- if (!_optionalChain([event, 'access', _71 => _71.type, 'optionalAccess', _72 => _72.startsWith, 'call', _73 => _73("data.record.")]) && !_optionalChain([event, 'access', _74 => _74.type, 'optionalAccess', _75 => _75.startsWith, 'call', _76 => _76("data.records.")])) return;
740
+ if (!_optionalChain([event, 'access', _81 => _81.type, 'optionalAccess', _82 => _82.startsWith, 'call', _83 => _83("data.record.")]) && !_optionalChain([event, 'access', _84 => _84.type, 'optionalAccess', _85 => _85.startsWith, 'call', _86 => _86("data.records.")])) return;
741
741
  this.refresh().catch(
742
- (err) => _optionalChain([this, 'access', _77 => _77.logger, 'access', _78 => _78.warn, 'optionalCall', _79 => _79("[webhook-auto-enqueuer] self-heal refresh failed", err)])
742
+ (err) => _optionalChain([this, 'access', _87 => _87.logger, 'optionalAccess', _88 => _88.warn, 'optionalCall', _89 => _89("[webhook-auto-enqueuer] self-heal refresh failed", err)])
743
743
  );
744
744
  }
745
745
  /** Test / admin accessor. */
@@ -782,21 +782,21 @@ var _automation = require('@objectstack/spec/automation');
782
782
  var SYSTEM_CTX = { isSystem: true, positions: [], permissions: [] };
783
783
  function uid(prefix) {
784
784
  const g = globalThis;
785
- if (_optionalChain([g, 'access', _80 => _80.crypto, 'optionalAccess', _81 => _81.randomUUID])) return `${prefix}_${g.crypto.randomUUID()}`;
785
+ if (_optionalChain([g, 'access', _90 => _90.crypto, 'optionalAccess', _91 => _91.randomUUID])) return `${prefix}_${g.crypto.randomUUID()}`;
786
786
  return `${prefix}_${Math.random().toString(36).slice(2, 10)}`;
787
787
  }
788
788
  function readDeclared(engine, metadataService, type) {
789
789
  try {
790
- const reg = _optionalChain([engine, 'optionalAccess', _82 => _82._registry]);
791
- if (_optionalChain([reg, 'optionalAccess', _83 => _83.listItems])) {
790
+ const reg = _optionalChain([engine, 'optionalAccess', _92 => _92._registry]);
791
+ if (_optionalChain([reg, 'optionalAccess', _93 => _93.listItems])) {
792
792
  const items = (_nullishCoalesce(reg.listItems(type), () => ( []))).filter(Boolean);
793
793
  if (items.length > 0) return items;
794
794
  }
795
795
  } catch (e7) {
796
796
  }
797
797
  try {
798
- const listed = _optionalChain([metadataService, 'optionalAccess', _84 => _84.list, 'optionalCall', _85 => _85(type)]);
799
- const arr = typeof _optionalChain([listed, 'optionalAccess', _86 => _86.then]) === "function" ? [] : _nullishCoalesce(listed, () => ( []));
798
+ const listed = _optionalChain([metadataService, 'optionalAccess', _94 => _94.list, 'optionalCall', _95 => _95(type)]);
799
+ const arr = typeof _optionalChain([listed, 'optionalAccess', _96 => _96.then]) === "function" ? [] : _nullishCoalesce(listed, () => ( []));
800
800
  return Array.isArray(arr) ? arr.filter(Boolean) : [];
801
801
  } catch (e8) {
802
802
  return [];
@@ -813,9 +813,9 @@ async function bootstrapDeclaredWebhooks(engine, metadataService, logger, subscr
813
813
  try {
814
814
  wh = _automation.WebhookSchema.parse(raw);
815
815
  } catch (err) {
816
- _optionalChain([logger, 'optionalAccess', _87 => _87.warn, 'optionalCall', _88 => _88("[webhook] declared webhook failed validation \u2014 skipped", {
817
- name: _optionalChain([raw, 'optionalAccess', _89 => _89.name]),
818
- error: _nullishCoalesce(_optionalChain([err, 'optionalAccess', _90 => _90.message]), () => ( String(err)))
816
+ _optionalChain([logger, 'optionalAccess', _97 => _97.warn, 'optionalCall', _98 => _98("[webhook] declared webhook failed validation \u2014 skipped", {
817
+ name: _optionalChain([raw, 'optionalAccess', _99 => _99.name]),
818
+ error: _nullishCoalesce(_optionalChain([err, 'optionalAccess', _100 => _100.message]), () => ( String(err)))
819
819
  })]);
820
820
  skipped += 1;
821
821
  continue;
@@ -829,7 +829,7 @@ async function bootstrapDeclaredWebhooks(engine, metadataService, logger, subscr
829
829
  const row = Array.isArray(existing) ? existing[0] : void 0;
830
830
  if (row) {
831
831
  if (row.managed_by === "admin") {
832
- _optionalChain([logger, 'optionalAccess', _91 => _91.warn, 'optionalCall', _92 => _92("[webhook] declared name collides with an admin-authored row \u2014 seed skipped", {
832
+ _optionalChain([logger, 'optionalAccess', _101 => _101.warn, 'optionalCall', _102 => _102("[webhook] declared name collides with an admin-authored row \u2014 seed skipped", {
833
833
  name: wh.name
834
834
  })]);
835
835
  skipped += 1;
@@ -881,18 +881,18 @@ async function bootstrapDeclaredWebhooks(engine, metadataService, logger, subscr
881
881
  seeded += 1;
882
882
  } catch (err) {
883
883
  const protection = isSecretProtectionFailure(err);
884
- _optionalChain([logger, 'optionalAccess', _93 => _93.warn, 'optionalCall', _94 => _94(
884
+ _optionalChain([logger, 'optionalAccess', _103 => _103.warn, 'optionalCall', _104 => _104(
885
885
  protection ? "[webhook] declared webhook NOT seeded \u2014 its signing secret cannot be stored encrypted (#7799)" : "[webhook] declared webhook seed failed",
886
886
  {
887
887
  name: wh.name,
888
888
  ...protection ? { code: WEBHOOK_SECRET_REFUSAL_CODE, status: WEBHOOK_SECRET_REFUSAL_STATUS } : {},
889
- error: _nullishCoalesce(_optionalChain([err, 'optionalAccess', _95 => _95.message]), () => ( String(err)))
889
+ error: _nullishCoalesce(_optionalChain([err, 'optionalAccess', _105 => _105.message]), () => ( String(err)))
890
890
  }
891
891
  )]);
892
892
  skipped += 1;
893
893
  }
894
894
  }
895
- _optionalChain([logger, 'optionalAccess', _96 => _96.info, 'optionalCall', _97 => _97("[webhook] declared webhooks materialized into sys_webhook", {
895
+ _optionalChain([logger, 'optionalAccess', _106 => _106.info, 'optionalCall', _107 => _107("[webhook] declared webhooks materialized into sys_webhook", {
896
896
  seeded,
897
897
  skipped,
898
898
  total: declared.length
@@ -901,7 +901,7 @@ async function bootstrapDeclaredWebhooks(engine, metadataService, logger, subscr
901
901
  }
902
902
  async function secretPatch(engine, wh, row, subscriptionsObject) {
903
903
  const { secret } = splitWebhookSecret(wh);
904
- const hasStored = _optionalChain([row, 'optionalAccess', _98 => _98[WEBHOOK_SECRET_FIELD]]) != null && row[WEBHOOK_SECRET_FIELD] !== "";
904
+ const hasStored = _optionalChain([row, 'optionalAccess', _108 => _108[WEBHOOK_SECRET_FIELD]]) != null && row[WEBHOOK_SECRET_FIELD] !== "";
905
905
  if (!secret) return hasStored ? { [WEBHOOK_SECRET_FIELD]: null } : {};
906
906
  if (!hasStored || !canResolveSecrets(engine)) return { [WEBHOOK_SECRET_FIELD]: secret };
907
907
  try {
@@ -943,16 +943,16 @@ async function migrateLegacyWebhookSecrets(engine, logger, subscriptionsObject =
943
943
  let rows;
944
944
  try {
945
945
  const found = await engine.find(subscriptionsObject, SYSTEM_QUERY);
946
- rows = Array.isArray(found) ? found : _nullishCoalesce(_optionalChain([found, 'optionalAccess', _99 => _99.data]), () => ( []));
946
+ rows = Array.isArray(found) ? found : _nullishCoalesce(_optionalChain([found, 'optionalAccess', _109 => _109.data]), () => ( []));
947
947
  } catch (err) {
948
- _optionalChain([logger, 'optionalAccess', _100 => _100.warn, 'optionalCall', _101 => _101("[webhook] legacy secret sweep skipped \u2014 could not read subscriptions", {
948
+ _optionalChain([logger, 'optionalAccess', _110 => _110.warn, 'optionalCall', _111 => _111("[webhook] legacy secret sweep skipped \u2014 could not read subscriptions", {
949
949
  object: subscriptionsObject,
950
- error: _nullishCoalesce(_optionalChain([err, 'optionalAccess', _102 => _102.message]), () => ( String(err)))
950
+ error: _nullishCoalesce(_optionalChain([err, 'optionalAccess', _112 => _112.message]), () => ( String(err)))
951
951
  })]);
952
952
  return out;
953
953
  }
954
954
  for (const row of rows) {
955
- if (!_optionalChain([row, 'optionalAccess', _103 => _103.id])) continue;
955
+ if (!_optionalChain([row, 'optionalAccess', _113 => _113.id])) continue;
956
956
  const legacySecret = readLegacySecret(row.definition_json);
957
957
  const legacyHeaders = readLegacyHeaders(row.definition_json);
958
958
  if (!legacySecret && !legacyHeaders) continue;
@@ -973,20 +973,20 @@ async function migrateLegacyWebhookSecrets(engine, logger, subscriptionsObject =
973
973
  out.failed += 1;
974
974
  const protection = isSecretProtectionFailure(err);
975
975
  const what = legacySecret && legacyHeaders ? "signing secret and custom headers" : legacySecret ? "signing secret" : "custom headers";
976
- _optionalChain([logger, 'optionalAccess', _104 => _104.warn, 'optionalCall', _105 => _105(
976
+ _optionalChain([logger, 'optionalAccess', _114 => _114.warn, 'optionalCall', _115 => _115(
977
977
  protection ? `[webhook] ${what} STILL CLEARTEXT in definition_json \u2014 no CryptoProvider to encrypt them (#7799/#7986)` : `[webhook] ${what} migration failed \u2014 row left unchanged (#7799/#7986)`,
978
978
  {
979
979
  name: _nullishCoalesce(row.name, () => ( row.id)),
980
980
  id: row.id,
981
981
  code: WEBHOOK_SECRET_REFUSAL_CODE,
982
982
  status: WEBHOOK_SECRET_REFUSAL_STATUS,
983
- error: _nullishCoalesce(_optionalChain([err, 'optionalAccess', _106 => _106.message]), () => ( String(err)))
983
+ error: _nullishCoalesce(_optionalChain([err, 'optionalAccess', _116 => _116.message]), () => ( String(err)))
984
984
  }
985
985
  )]);
986
986
  }
987
987
  }
988
988
  if (out.found > 0) {
989
- _optionalChain([logger, 'optionalAccess', _107 => _107.info, 'optionalCall', _108 => _108("[webhook] legacy cleartext credentials swept into sys_secret", { ...out })]);
989
+ _optionalChain([logger, 'optionalAccess', _117 => _117.info, 'optionalCall', _118 => _118("[webhook] legacy cleartext credentials swept into sys_secret", { ...out })]);
990
990
  }
991
991
  return out;
992
992
  }
@@ -1024,14 +1024,14 @@ function createWebhookRedeliverGuard(engine, subscriptionsObject = WEBHOOK_OBJEC
1024
1024
  var WEBHOOK_PROVENANCE_PACKAGE = "plugin-webhooks:provenance";
1025
1025
  var SYSTEM_CTX3 = { isSystem: true, positions: [], permissions: [] };
1026
1026
  function bindWebhookProvenanceStamp(engine, logger) {
1027
- if (typeof _optionalChain([engine, 'optionalAccess', _109 => _109.registerHook]) !== "function") return;
1027
+ if (typeof _optionalChain([engine, 'optionalAccess', _119 => _119.registerHook]) !== "function") return;
1028
1028
  engine.registerHook(
1029
1029
  "beforeUpdate",
1030
1030
  async (ctx) => {
1031
- if (_optionalChain([ctx, 'optionalAccess', _110 => _110.session, 'optionalAccess', _111 => _111.isSystem])) return;
1032
- const id = _nullishCoalesce(_optionalChain([ctx, 'optionalAccess', _112 => _112.input, 'optionalAccess', _113 => _113.id]), () => ( _optionalChain([ctx, 'optionalAccess', _114 => _114.input, 'optionalAccess', _115 => _115.data, 'optionalAccess', _116 => _116.id])));
1031
+ if (_optionalChain([ctx, 'optionalAccess', _120 => _120.session, 'optionalAccess', _121 => _121.isSystem])) return;
1032
+ const id = _nullishCoalesce(_optionalChain([ctx, 'optionalAccess', _122 => _122.input, 'optionalAccess', _123 => _123.id]), () => ( _optionalChain([ctx, 'optionalAccess', _124 => _124.input, 'optionalAccess', _125 => _125.data, 'optionalAccess', _126 => _126.id])));
1033
1033
  if (!id) return;
1034
- const data = _optionalChain([ctx, 'optionalAccess', _117 => _117.input, 'optionalAccess', _118 => _118.data]);
1034
+ const data = _optionalChain([ctx, 'optionalAccess', _127 => _127.input, 'optionalAccess', _128 => _128.data]);
1035
1035
  if (!data || typeof data !== "object") return;
1036
1036
  try {
1037
1037
  const rows = await engine.find("sys_webhook", {
@@ -1046,18 +1046,18 @@ function bindWebhookProvenanceStamp(engine, logger) {
1046
1046
  data.customized = true;
1047
1047
  }
1048
1048
  } catch (err) {
1049
- _optionalChain([logger, 'optionalAccess', _119 => _119.warn, 'optionalCall', _120 => _120("[webhook] provenance stamp failed (edit proceeds unstamped)", {
1049
+ _optionalChain([logger, 'optionalAccess', _129 => _129.warn, 'optionalCall', _130 => _130("[webhook] provenance stamp failed (edit proceeds unstamped)", {
1050
1050
  id,
1051
- error: _optionalChain([err, 'optionalAccess', _121 => _121.message])
1051
+ error: _optionalChain([err, 'optionalAccess', _131 => _131.message])
1052
1052
  })]);
1053
1053
  }
1054
1054
  },
1055
1055
  { object: "sys_webhook", packageId: WEBHOOK_PROVENANCE_PACKAGE, priority: 150 }
1056
1056
  );
1057
- _optionalChain([logger, 'optionalAccess', _122 => _122.info, 'optionalCall', _123 => _123("[webhook] provenance stamp hook bound")]);
1057
+ _optionalChain([logger, 'optionalAccess', _132 => _132.info, 'optionalCall', _133 => _133("[webhook] provenance stamp hook bound")]);
1058
1058
  }
1059
1059
  function unbindWebhookProvenanceStamp(engine) {
1060
- if (typeof _optionalChain([engine, 'optionalAccess', _124 => _124.unregisterHooksByPackage]) === "function") {
1060
+ if (typeof _optionalChain([engine, 'optionalAccess', _134 => _134.unregisterHooksByPackage]) === "function") {
1061
1061
  engine.unregisterHooksByPackage(WEBHOOK_PROVENANCE_PACKAGE);
1062
1062
  }
1063
1063
  }
@@ -1135,9 +1135,9 @@ function assertWritableWebhookHeaders(data, object = WEBHOOK_OBJECT, field = WEB
1135
1135
  var WEBHOOK_HEADERS_GATE_PACKAGE = "plugin-webhooks:headers-shape-gate";
1136
1136
  var GATE_PRIORITY = 50;
1137
1137
  function bindWebhookHeadersShapeGate(engine, logger) {
1138
- if (typeof _optionalChain([engine, 'optionalAccess', _125 => _125.registerHook]) !== "function") return;
1138
+ if (typeof _optionalChain([engine, 'optionalAccess', _135 => _135.registerHook]) !== "function") return;
1139
1139
  const handler = (ctx) => {
1140
- assertWritableWebhookHeaders(_optionalChain([ctx, 'optionalAccess', _126 => _126.input, 'optionalAccess', _127 => _127.data]));
1140
+ assertWritableWebhookHeaders(_optionalChain([ctx, 'optionalAccess', _136 => _136.input, 'optionalAccess', _137 => _137.data]));
1141
1141
  };
1142
1142
  for (const event of ["beforeInsert", "beforeUpdate"]) {
1143
1143
  engine.registerHook(event, handler, {
@@ -1146,10 +1146,10 @@ function bindWebhookHeadersShapeGate(engine, logger) {
1146
1146
  priority: GATE_PRIORITY
1147
1147
  });
1148
1148
  }
1149
- _optionalChain([logger, 'optionalAccess', _128 => _128.info, 'optionalCall', _129 => _129("[webhook] headers_secret shape gate bound (refuses non-flat-string-map plaintext)")]);
1149
+ _optionalChain([logger, 'optionalAccess', _138 => _138.info, 'optionalCall', _139 => _139("[webhook] headers_secret shape gate bound (refuses non-flat-string-map plaintext)")]);
1150
1150
  }
1151
1151
  function unbindWebhookHeadersShapeGate(engine) {
1152
- if (typeof _optionalChain([engine, 'optionalAccess', _130 => _130.unregisterHooksByPackage]) === "function") {
1152
+ if (typeof _optionalChain([engine, 'optionalAccess', _140 => _140.unregisterHooksByPackage]) === "function") {
1153
1153
  engine.unregisterHooksByPackage(WEBHOOK_HEADERS_GATE_PACKAGE);
1154
1154
  }
1155
1155
  }
@@ -1198,7 +1198,7 @@ var WebhookOutboxPlugin = class {
1198
1198
  ]
1199
1199
  });
1200
1200
  } else {
1201
- _optionalChain([ctx, 'access', _131 => _131.logger, 'access', _132 => _132.warn, 'optionalCall', _133 => _133(
1201
+ _optionalChain([ctx, 'access', _141 => _141.logger, 'access', _142 => _142.warn, 'optionalCall', _143 => _143(
1202
1202
  "[webhook-outbox] manifest service unavailable \u2014 sys_webhook will NOT appear in REST or Studio nav. Register MetadataService before WebhookOutboxPlugin."
1203
1203
  )]);
1204
1204
  }
@@ -1224,12 +1224,27 @@ var WebhookOutboxPlugin = class {
1224
1224
  this.registerAdminRoutes(ctx);
1225
1225
  });
1226
1226
  }
1227
- _optionalChain([ctx, 'access', _134 => _134.logger, 'access', _135 => _135.info, 'optionalCall', _136 => _136("[webhook-outbox] initialised (delivery via shared messaging HTTP outbox)", {
1227
+ _optionalChain([ctx, 'access', _144 => _144.logger, 'access', _145 => _145.info, 'optionalCall', _146 => _146("[webhook-outbox] initialised (delivery via shared messaging HTTP outbox)", {
1228
1228
  autoEnqueue: autoEnqueueOpt !== false
1229
1229
  })]);
1230
1230
  }
1231
- async dispose() {
1232
- await _optionalChain([this, 'access', _137 => _137.autoEnqueuer, 'optionalAccess', _138 => _138.stop, 'call', _139 => _139()]);
1231
+ /**
1232
+ * Teardown the kernel's ONLY teardown hook.
1233
+ *
1234
+ * [#10772] This body used to be spelled `dispose()`. `Plugin`
1235
+ * (`@objectstack/core`'s `types.ts`) declares `init()`, `start?(ctx)` and
1236
+ * `destroy?()` and no `dispose()`, and `ObjectKernel.performShutdown()` /
1237
+ * `LiteKernel.destroy()` walk the plugins in reverse calling
1238
+ * `plugin.destroy()` — so after `await kernel.shutdown()` had RESOLVED the
1239
+ * auto-enqueuer was still running and both engine hooks were still bound.
1240
+ * Measured on the same revision: `dispose()` had ZERO callers anywhere in
1241
+ * the repo, so this teardown had never run in any process at all.
1242
+ *
1243
+ * Idempotent: `boundEngine` is cleared as it is unbound, so a second
1244
+ * teardown is a no-op rather than a second unbind.
1245
+ */
1246
+ async destroy() {
1247
+ await _optionalChain([this, 'access', _147 => _147.autoEnqueuer, 'optionalAccess', _148 => _148.stop, 'call', _149 => _149()]);
1233
1248
  if (this.boundEngine) {
1234
1249
  try {
1235
1250
  unbindWebhookProvenanceStamp(this.boundEngine);
@@ -1242,6 +1257,15 @@ var WebhookOutboxPlugin = class {
1242
1257
  this.boundEngine = void 0;
1243
1258
  }
1244
1259
  }
1260
+ /**
1261
+ * Retained alias for {@link destroy}. Kept because it is public API of an
1262
+ * exported class: an embedder may have learned to call it directly
1263
+ * precisely BECAUSE the kernel never did, and deleting it would break them.
1264
+ * Same signature, same return type — a direct caller sees no change.
1265
+ */
1266
+ async dispose() {
1267
+ await this.destroy();
1268
+ }
1245
1269
  getMessaging(ctx) {
1246
1270
  const svc = this.tryGetService(ctx, ["messaging"]);
1247
1271
  return svc && typeof svc.enqueueHttp === "function" ? svc : void 0;
@@ -1261,7 +1285,7 @@ var WebhookOutboxPlugin = class {
1261
1285
  async bootDeclaredWebhooks(ctx) {
1262
1286
  const engine = this.tryGetService(ctx, ["objectql", "data"]);
1263
1287
  if (!engine) {
1264
- _optionalChain([ctx, 'access', _140 => _140.logger, 'access', _141 => _141.warn, 'optionalCall', _142 => _142("[webhook] declared-webhook bootstrap skipped \u2014 no data engine available")]);
1288
+ _optionalChain([ctx, 'access', _150 => _150.logger, 'access', _151 => _151.warn, 'optionalCall', _152 => _152("[webhook] declared-webhook bootstrap skipped \u2014 no data engine available")]);
1265
1289
  return;
1266
1290
  }
1267
1291
  this.boundEngine = engine;
@@ -1275,15 +1299,15 @@ var WebhookOutboxPlugin = class {
1275
1299
  try {
1276
1300
  await bootstrapDeclaredWebhooks(engine, metadataService, ctx.logger);
1277
1301
  } catch (err) {
1278
- _optionalChain([ctx, 'access', _143 => _143.logger, 'access', _144 => _144.warn, 'optionalCall', _145 => _145("[webhook] declared-webhook bootstrap failed (dispatcher still serves admin rows)", {
1279
- error: _nullishCoalesce(_optionalChain([err, 'optionalAccess', _146 => _146.message]), () => ( String(err)))
1302
+ _optionalChain([ctx, 'access', _153 => _153.logger, 'access', _154 => _154.warn, 'optionalCall', _155 => _155("[webhook] declared-webhook bootstrap failed (dispatcher still serves admin rows)", {
1303
+ error: _nullishCoalesce(_optionalChain([err, 'optionalAccess', _156 => _156.message]), () => ( String(err)))
1280
1304
  })]);
1281
1305
  }
1282
1306
  try {
1283
1307
  await migrateLegacyWebhookSecrets(engine, ctx.logger);
1284
1308
  } catch (err) {
1285
- _optionalChain([ctx, 'access', _147 => _147.logger, 'access', _148 => _148.warn, 'optionalCall', _149 => _149("[webhook] legacy signing-secret sweep failed (rows left unchanged)", {
1286
- error: _nullishCoalesce(_optionalChain([err, 'optionalAccess', _150 => _150.message]), () => ( String(err)))
1309
+ _optionalChain([ctx, 'access', _157 => _157.logger, 'access', _158 => _158.warn, 'optionalCall', _159 => _159("[webhook] legacy signing-secret sweep failed (rows left unchanged)", {
1310
+ error: _nullishCoalesce(_optionalChain([err, 'optionalAccess', _160 => _160.message]), () => ( String(err)))
1287
1311
  })]);
1288
1312
  }
1289
1313
  }
@@ -1293,14 +1317,14 @@ var WebhookOutboxPlugin = class {
1293
1317
  const realtime = this.tryGetService(ctx, ["realtime"]);
1294
1318
  const messaging = this.getMessaging(ctx);
1295
1319
  if (!engine || !realtime || !messaging) {
1296
- _optionalChain([ctx, 'access', _151 => _151.logger, 'access', _152 => _152.warn, 'optionalCall', _153 => _153(
1320
+ _optionalChain([ctx, 'access', _161 => _161.logger, 'access', _162 => _162.warn, 'optionalCall', _163 => _163(
1297
1321
  "[webhook-auto-enqueuer] disabled \u2014 ObjectQL, Realtime, or Messaging service not available",
1298
1322
  { hasEngine: !!engine, hasRealtime: !!realtime, hasMessaging: !!messaging }
1299
1323
  )]);
1300
1324
  return;
1301
1325
  }
1302
1326
  if (!messaging.isHttpDeliveryReady()) {
1303
- _optionalChain([ctx, 'access', _154 => _154.logger, 'access', _155 => _155.warn, 'optionalCall', _156 => _156(
1327
+ _optionalChain([ctx, 'access', _164 => _164.logger, 'access', _165 => _165.warn, 'optionalCall', _166 => _166(
1304
1328
  "[webhook-auto-enqueuer] messaging HTTP outbox not ready (no data engine / reliableDelivery off) \u2014 webhook deliveries will not be durable"
1305
1329
  )]);
1306
1330
  }
@@ -1314,7 +1338,7 @@ var WebhookOutboxPlugin = class {
1314
1338
  );
1315
1339
  await this.autoEnqueuer.start();
1316
1340
  ctx.registerService("webhook.autoEnqueuer", this.autoEnqueuer);
1317
- _optionalChain([ctx, 'access', _157 => _157.logger, 'access', _158 => _158.info, 'optionalCall', _159 => _159("[webhook-auto-enqueuer] started (enqueues source=webhook onto sys_http_delivery)")]);
1341
+ _optionalChain([ctx, 'access', _167 => _167.logger, 'access', _168 => _168.info, 'optionalCall', _169 => _169("[webhook-auto-enqueuer] started (enqueues source=webhook onto sys_http_delivery)")]);
1318
1342
  }
1319
1343
  /**
1320
1344
  * [#8069] Register {@link createWebhookRedeliverGuard} with messaging, so
@@ -1330,7 +1354,7 @@ var WebhookOutboxPlugin = class {
1330
1354
  */
1331
1355
  installRedeliverGuard(ctx, messaging, engine, subscriptionsObject) {
1332
1356
  if (typeof messaging.registerRedeliverGuard !== "function") {
1333
- _optionalChain([ctx, 'access', _160 => _160.logger, 'access', _161 => _161.error, 'optionalCall', _162 => _162(
1357
+ _optionalChain([ctx, 'access', _170 => _170.logger, 'access', _171 => _171.error, 'optionalCall', _172 => _172(
1334
1358
  "[webhook-outbox] messaging service exposes no registerRedeliverGuard() \u2014 redelivery of a webhook whose signing configuration is gone CANNOT be refused, so an operator pressing redeliver may send a delivery that can no longer be authenticated (#7799, #8069). The POST /api/v1/webhooks/redeliver endpoint is reachable by any authenticated user. Fix: upgrade @objectstack/service-messaging to a build that implements registerRedeliverGuard."
1335
1359
  )]);
1336
1360
  return;
@@ -1339,7 +1363,7 @@ var WebhookOutboxPlugin = class {
1339
1363
  "webhook",
1340
1364
  createWebhookRedeliverGuard(engine, subscriptionsObject)
1341
1365
  );
1342
- _optionalChain([ctx, 'access', _163 => _163.logger, 'access', _164 => _164.debug, 'optionalCall', _165 => _165("[webhook-outbox] redeliver guard installed for source=webhook")]);
1366
+ _optionalChain([ctx, 'access', _173 => _173.logger, 'access', _174 => _174.debug, 'optionalCall', _175 => _175("[webhook-outbox] redeliver guard installed for source=webhook")]);
1343
1367
  }
1344
1368
  tryGetService(ctx, names) {
1345
1369
  for (const n of names) {
@@ -1353,21 +1377,35 @@ var WebhookOutboxPlugin = class {
1353
1377
  }
1354
1378
  /**
1355
1379
  * Mount POST /api/v1/webhooks/redeliver on the host Hono app, if one is
1356
- * available. Delegates to `messaging.redeliverHttp(deliveryId)`. Auth is the
1357
- * better-auth session cookie — every authenticated user counts.
1380
+ * available. Delegates to `messaging.redeliverHttp(deliveryId, …)`. Auth is
1381
+ * the better-auth session cookie — every authenticated user counts.
1382
+ *
1383
+ * [#10740] Which is precisely why the caller's ACTIVE ORGANIZATION is
1384
+ * resolved here and threaded into the call. `sys_http_delivery` is
1385
+ * tenant-scoped, and this is the one door on it a request can reach: an
1386
+ * unscoped replay from here is an authenticated user reaching another
1387
+ * organization's delivery row on a walled deployment. With the tenant
1388
+ * threaded, a row outside the caller's organization is simply not found.
1389
+ *
1390
+ * ⚠️ A session with no active organization threads `undefined`, and the
1391
+ * driver's tenant-audit line then fires for that write. That is deliberate:
1392
+ * the deployment could not tell us who is asking, and reporting the gap is
1393
+ * the correct outcome. ⛔ It is never repaired with `bypassTenantAudit`,
1394
+ * which would silence the report without closing anything.
1358
1395
  */
1359
1396
  registerAdminRoutes(ctx) {
1360
1397
  const http = this.tryGetService(ctx, ["http-server"]);
1361
1398
  if (!http || typeof http.getRawApp !== "function") {
1362
- _optionalChain([ctx, 'access', _166 => _166.logger, 'access', _167 => _167.debug, 'optionalCall', _168 => _168("[webhook-outbox] HTTP server not available; redeliver endpoint not mounted")]);
1399
+ _optionalChain([ctx, 'access', _176 => _176.logger, 'access', _177 => _177.debug, 'optionalCall', _178 => _178("[webhook-outbox] HTTP server not available; redeliver endpoint not mounted")]);
1363
1400
  return;
1364
1401
  }
1365
1402
  const rawApp = http.getRawApp();
1366
1403
  const messaging = this.getMessaging(ctx);
1367
1404
  if (!rawApp || !messaging) return;
1368
1405
  rawApp.post("/api/v1/webhooks/redeliver", async (c) => {
1369
- const userId = await this.resolveSessionUserId(ctx, c);
1370
- if (!userId) {
1406
+ const session = await this.resolveSession(ctx, c);
1407
+ const userId = _optionalChain([session, 'optionalAccess', _179 => _179.user, 'optionalAccess', _180 => _180.id]);
1408
+ if (typeof userId !== "string" || userId.length === 0) {
1371
1409
  return c.json(
1372
1410
  { success: false, error: { code: "UNAUTHENTICATED", message: "Sign in to redeliver webhook deliveries." } },
1373
1411
  401
@@ -1379,7 +1417,7 @@ var WebhookOutboxPlugin = class {
1379
1417
  } catch (e17) {
1380
1418
  return c.json({ success: false, error: { code: "INVALID_REQUEST", message: "Request body must be JSON." } }, 400);
1381
1419
  }
1382
- const deliveryId = typeof _optionalChain([body, 'optionalAccess', _169 => _169.deliveryId]) === "string" ? body.deliveryId.trim() : "";
1420
+ const deliveryId = typeof _optionalChain([body, 'optionalAccess', _181 => _181.deliveryId]) === "string" ? body.deliveryId.trim() : "";
1383
1421
  if (!deliveryId) {
1384
1422
  return c.json(
1385
1423
  { success: false, error: { code: "MISSING_REQUIRED_FIELD", message: "Body must include `deliveryId: string`." } },
@@ -1387,27 +1425,40 @@ var WebhookOutboxPlugin = class {
1387
1425
  );
1388
1426
  }
1389
1427
  try {
1390
- const row = await messaging.redeliverHttp(deliveryId);
1391
- _optionalChain([ctx, 'access', _170 => _170.logger, 'access', _171 => _171.info, 'optionalCall', _172 => _172("[webhook-outbox] redelivered", { deliveryId, requestedBy: userId })]);
1428
+ const activeOrg = _optionalChain([session, 'optionalAccess', _182 => _182.session, 'optionalAccess', _183 => _183.activeOrganizationId]);
1429
+ const tenantId = typeof activeOrg === "string" && activeOrg.length > 0 ? activeOrg : void 0;
1430
+ const row = await messaging.redeliverHttp(deliveryId, { tenantId });
1431
+ _optionalChain([ctx, 'access', _184 => _184.logger, 'access', _185 => _185.info, 'optionalCall', _186 => _186("[webhook-outbox] redelivered", { deliveryId, requestedBy: userId, tenantId })]);
1392
1432
  return c.json({ success: true, data: { id: row.id, status: row.status } });
1393
1433
  } catch (err) {
1394
- const code = _optionalChain([err, 'optionalAccess', _173 => _173.code]);
1434
+ const code = _optionalChain([err, 'optionalAccess', _187 => _187.code]);
1395
1435
  if (code === "RESOURCE_NOT_FOUND") {
1396
1436
  return c.json({ success: false, error: { code, message: err.message } }, 404);
1397
1437
  }
1398
1438
  if (code === "DELIVERY_NOT_ELIGIBLE" || code === "DELIVERY_NEVER_SENT") {
1399
1439
  return c.json({ success: false, error: { code, message: err.message } }, 409);
1400
1440
  }
1401
- _optionalChain([ctx, 'access', _174 => _174.logger, 'access', _175 => _175.error, 'optionalCall', _176 => _176("[webhook-outbox] redeliver failed", err)]);
1441
+ _optionalChain([ctx, 'access', _188 => _188.logger, 'access', _189 => _189.error, 'optionalCall', _190 => _190("[webhook-outbox] redeliver failed", err)]);
1402
1442
  return c.json(
1403
- { success: false, error: { code: "INTERNAL_ERROR", message: _nullishCoalesce(_optionalChain([err, 'optionalAccess', _177 => _177.message]), () => ( String(err))) } },
1443
+ { success: false, error: { code: "INTERNAL_ERROR", message: _nullishCoalesce(_optionalChain([err, 'optionalAccess', _191 => _191.message]), () => ( String(err))) } },
1404
1444
  500
1405
1445
  );
1406
1446
  }
1407
1447
  });
1408
- _optionalChain([ctx, 'access', _178 => _178.logger, 'access', _179 => _179.info, 'optionalCall', _180 => _180("[webhook-outbox] redeliver endpoint mounted at POST /api/v1/webhooks/redeliver")]);
1448
+ _optionalChain([ctx, 'access', _192 => _192.logger, 'access', _193 => _193.info, 'optionalCall', _194 => _194("[webhook-outbox] redeliver endpoint mounted at POST /api/v1/webhooks/redeliver")]);
1409
1449
  }
1410
- async resolveSessionUserId(ctx, c) {
1450
+ /**
1451
+ * [#10740] The better-auth session envelope (`{ user, session }`) for this
1452
+ * request, or `undefined`.
1453
+ *
1454
+ * Widened from the previous `resolveSessionUserId` because the route now
1455
+ * needs two facts from ONE lookup: who is asking (`user.id`, the
1456
+ * authentication gate) and which organization they are asking as
1457
+ * (`session.activeOrganizationId`, the tenant threaded into the write).
1458
+ * Resolving them separately would mean two `getSession` calls that can
1459
+ * disagree.
1460
+ */
1461
+ async resolveSession(ctx, c) {
1411
1462
  try {
1412
1463
  const authService = this.tryGetService(ctx, ["auth"]);
1413
1464
  if (!authService) return void 0;
@@ -1415,10 +1466,8 @@ var WebhookOutboxPlugin = class {
1415
1466
  if (!api && typeof authService.getApi === "function") {
1416
1467
  api = await authService.getApi();
1417
1468
  }
1418
- if (!_optionalChain([api, 'optionalAccess', _181 => _181.getSession])) return void 0;
1419
- const session = await api.getSession({ headers: c.req.raw.headers });
1420
- const uid2 = _optionalChain([session, 'optionalAccess', _182 => _182.user, 'optionalAccess', _183 => _183.id]);
1421
- return typeof uid2 === "string" && uid2.length > 0 ? uid2 : void 0;
1469
+ if (!_optionalChain([api, 'optionalAccess', _195 => _195.getSession])) return void 0;
1470
+ return await api.getSession({ headers: c.req.raw.headers });
1422
1471
  } catch (e18) {
1423
1472
  return void 0;
1424
1473
  }