@odla-ai/chapter 0.59.0 → 0.59.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.
@@ -1633,9 +1633,9 @@ async function backfillCrm(deps) {
1633
1633
  }
1634
1634
 
1635
1635
  // src/email-retirement.ts
1636
- function isRetiredEmailCopy(subject, text9) {
1636
+ function isRetiredEmailCopy(subject, text10) {
1637
1637
  const subjectCopy = subject.trimStart().replace(/^\[dev\]\s*/i, "");
1638
- const bodyCopy = text9.trimStart().replace(/^\(dev redirect; original recipient: [^\r\n]*\)\r?\n\r?\n/, "");
1638
+ const bodyCopy = text10.trimStart().replace(/^\(dev redirect; original recipient: [^\r\n]*\)\r?\n\r?\n/, "");
1639
1639
  return /^\(\s*unused\s*:/i.test(subjectCopy) || /^Unused\.\s*Switched off\b/i.test(bodyCopy);
1640
1640
  }
1641
1641
 
@@ -1713,10 +1713,10 @@ function planDelivery(input) {
1713
1713
  if (isRetiredEmailCopy(renderedSubject, renderedText)) return { deliver: false, reason: "template-retired" };
1714
1714
  if (validateEmailCopy({ subject: renderedSubject, text: renderedText }, { allowedVariables: [] }).length) return { deliver: false, reason: "template-invalid" };
1715
1715
  const subject = (redirect ? "[dev] " : "") + renderedSubject;
1716
- const text9 = redirect ? `(dev redirect; original recipient: ${input.to})
1716
+ const text10 = redirect ? `(dev redirect; original recipient: ${input.to})
1717
1717
 
1718
1718
  ` + renderedText : renderedText;
1719
- return { deliver: true, transport, to, subject, text: text9, redirected: redirect };
1719
+ return { deliver: true, transport, to, subject, text: text10, redirected: redirect };
1720
1720
  }
1721
1721
 
1722
1722
  // src/notify.ts
@@ -1864,8 +1864,8 @@ function formatInstant(at2, timeZone) {
1864
1864
  var adminUrlFor = (origin) => `${origin}/admin/`;
1865
1865
  var membersUrlFor = (origin) => `${origin}/members/`;
1866
1866
  function applicantVariables(app) {
1867
- const text9 = (v) => typeof v === "string" ? v : "";
1868
- return { firstName: text9(app.firstName), lastName: text9(app.lastName), email: text9(app.email), phone: text9(app.phone), state: text9(app.state) };
1867
+ const text10 = (v) => typeof v === "string" ? v : "";
1868
+ return { firstName: text10(app.firstName), lastName: text10(app.lastName), email: text10(app.email), phone: text10(app.phone), state: text10(app.state) };
1869
1869
  }
1870
1870
  function sampleVariables(origin, timeZone, now = Date.now()) {
1871
1871
  const startAt = formatInstant(now + 864e5, timeZone);
@@ -3599,12 +3599,87 @@ function paymentLineItems(group, tier) {
3599
3599
 
3600
3600
  // src/membership-dispute-authority.ts
3601
3601
  import { stripeRequest as stripeRequest4 } from "@odla-ai/stripe";
3602
+
3603
+ // src/membership-dispute-transition.ts
3602
3604
  var MEMBERSHIP_DISPUTED_STATUS = "payment_disputed";
3603
3605
  var DISPUTE_WON_STATUSES = ["won", "warning_closed"];
3604
3606
  var CLOSED_STATUSES = [...DISPUTE_WON_STATUSES, "lost"];
3605
- var TERMINAL = ["refunded", "refund_pending", "canceled", "declined", "restarting", "superseded"];
3607
+ function disputeTransition(row2, fact, eventId, now, terminal) {
3608
+ const status2 = String(fact.status ?? "unknown"), closed = CLOSED_STATUSES.includes(status2);
3609
+ const sameDispute = row2.disputeId === fact.objectId;
3610
+ if (sameDispute && Number(row2.disputeClosedAt ?? 0) > 0 && !closed) return null;
3611
+ const won = DISPUTE_WON_STATUSES.includes(status2);
3612
+ const suspended = row2.status === MEMBERSHIP_DISPUTED_STATUS, done = terminal.includes(String(row2.status));
3613
+ const restored = String(row2.disputePriorStatus ?? (row2.journeyApprovedAt ? "active" : "paid_pending_approval"));
3614
+ const next = done ? String(row2.status) : won ? suspended ? restored : String(row2.status) : MEMBERSHIP_DISPUTED_STATUS;
3615
+ return {
3616
+ status: next,
3617
+ disputeId: fact.objectId,
3618
+ disputeStatus: status2,
3619
+ disputeEventId: eventId,
3620
+ disputeClosedAt: closed ? now : 0,
3621
+ ...fact.disputeReason ? { disputeReason: fact.disputeReason } : {},
3622
+ ...fact.amountCents !== void 0 ? { disputeAmountCents: fact.amountCents } : {},
3623
+ ...sameDispute ? {} : { disputedAt: now },
3624
+ ...next === MEMBERSHIP_DISPUTED_STATUS && !suspended ? { disputePriorStatus: String(row2.status) } : {},
3625
+ revision: Number(row2.revision) + 1,
3626
+ updatedAt: now
3627
+ };
3628
+ }
3629
+
3630
+ // src/membership-seat-dispute.ts
3606
3631
  var text2 = (value) => typeof value === "string" && value ? value : void 0;
3607
- var refOf = (value) => typeof value === "string" ? text2(value) : text2(value?.id);
3632
+ var TERMINAL = ["payment_pending", "payment_processing", "payment_canceling", "refund_pending", "refunded", "canceled"];
3633
+ var namedSeatDisputeStanding = (seat, applied) => ({ seatId: String(seat.id), status: String(seat.status), ...text2(seat.disputeStatus) ? { disputeStatus: text2(seat.disputeStatus) } : {}, applied });
3634
+ function disputedChargeCoversSeat(charge, seat) {
3635
+ return charge.seatId === seat.id && charge.customerId === seat.providerCustomerId && charge.paymentIntentId !== void 0 && (charge.paymentIntentId === seat.providerPaymentIntentId || charge.paymentIntentId === seat.rechargePaymentIntentId);
3636
+ }
3637
+ async function reconcileNamedSeatDispute(db, input, now, charge, evidence) {
3638
+ if (!charge.seatId) membershipFail("membership_dispute_scope_mismatch");
3639
+ const membership = async (applied, seat) => {
3640
+ const id7 = await networkMembershipId(input), member = await membershipLoad(db, "networkMembership", id7);
3641
+ requireMembershipScope(member, input);
3642
+ return {
3643
+ membershipId: id7,
3644
+ status: String(member.status),
3645
+ termEndsAt: Number(member.termEndsAt),
3646
+ graceEndsAt: Number(member.graceEndsAt ?? 0),
3647
+ autoRenew: member.autoRenew === true,
3648
+ applied,
3649
+ ...text2(member.disputeStatus) ? { disputeStatus: text2(member.disputeStatus) } : {},
3650
+ ...seat ? { seat: namedSeatDisputeStanding(seat, applied) } : {}
3651
+ };
3652
+ };
3653
+ for (let attempt = 0; attempt < 8; attempt++) {
3654
+ const state = await loadMembershipAuthorityState(db, input), seat = await membershipLoad(db, "networkSeat", charge.seatId);
3655
+ if (!seat) return membership(false);
3656
+ requireMembershipScope(seat, input);
3657
+ if (charge.customerId !== seat.providerCustomerId) membershipFail("membership_dispute_scope_mismatch");
3658
+ if (!disputedChargeCoversSeat(charge, seat) || seat.disputeEventId === input.eventId) return membership(seat.disputeEventId === input.eventId, seat);
3659
+ const attrs = disputeTransition(seat, evidence.fact, input.eventId, now, TERMINAL);
3660
+ if (!attrs) return membership(false, seat);
3661
+ try {
3662
+ await db.transact([membershipUpdate("networkSeat", String(seat.id), attrs)], { mutationId: `seat-dispute:${seat.id}:${input.eventId}`, guards: [
3663
+ { ns: "networkSeat", id: String(seat.id), exists: true, equals: { revision: seat.revision } },
3664
+ { ns: "membershipAuthorityState", id: String(state.id), exists: true, equals: { mode: "authoritative", revision: state.revision } },
3665
+ { ns: "networkCommercialEvents", id: String(evidence.row.id), exists: true, equals: { digest: evidence.fact.digest } },
3666
+ evidence.guard
3667
+ ] });
3668
+ } catch (error) {
3669
+ if (error.code !== "transact_guard_failed") throw error;
3670
+ continue;
3671
+ }
3672
+ const saved = await membershipLoad(db, "networkSeat", String(seat.id));
3673
+ requireMembershipScope(saved, input);
3674
+ return membership(true, saved);
3675
+ }
3676
+ return membershipFail("membership_dispute_retry_required");
3677
+ }
3678
+
3679
+ // src/membership-dispute-authority.ts
3680
+ var TERMINAL2 = ["refunded", "refund_pending", "canceled", "declined", "restarting", "superseded"];
3681
+ var text3 = (value) => typeof value === "string" && value ? value : void 0;
3682
+ var refOf = (value) => typeof value === "string" ? text3(value) : text3(value?.id);
3608
3683
  async function readDisputedCharge(secretKey, chargeId, fetch2) {
3609
3684
  if (!/^ch_[A-Za-z0-9]+$/.test(chargeId)) membershipFail("invalid_dispute_charge");
3610
3685
  const config = { secretKey, ...fetch2 ? { fetch: fetch2 } : {} };
@@ -3618,7 +3693,7 @@ async function readDisputedCharge(secretKey, chargeId, fetch2) {
3618
3693
  if (!invoice.ok || invoice.body.id !== invoiceId) membershipFail("membership_dispute_provider_unavailable");
3619
3694
  subscriptionId = refOf(invoice.body.subscription) ?? refOf(invoice.body.parent?.subscription_details?.subscription);
3620
3695
  }
3621
- const seatId = text2(charge.body.metadata?.seatId);
3696
+ const seatId = text3(charge.body.metadata?.seatId);
3622
3697
  return {
3623
3698
  chargeId,
3624
3699
  customerId,
@@ -3644,37 +3719,23 @@ async function reconcileMembershipDispute(db, input, now, secretKey, resolved) {
3644
3719
  termEndsAt: Number(value.termEndsAt),
3645
3720
  graceEndsAt: Number(value.graceEndsAt ?? 0),
3646
3721
  autoRenew: value.autoRenew === true,
3647
- ...text2(value.disputeStatus) ? { disputeStatus: text2(value.disputeStatus) } : {},
3722
+ ...text3(value.disputeStatus) ? { disputeStatus: text3(value.disputeStatus) } : {},
3648
3723
  applied
3649
3724
  });
3650
3725
  const known = await membershipLoad(db, "networkMembership", id7);
3651
3726
  requireMembershipScope(known, input);
3652
3727
  if (known.disputeEventId === input.eventId) return receipt(known);
3728
+ const seatKnown = known.namedSeatId ? await membershipLoad(db, "networkSeat", String(known.namedSeatId)) : void 0;
3729
+ if (seatKnown?.disputeEventId === input.eventId) return { ...receipt(known, true), seat: namedSeatDisputeStanding(seatKnown, true) };
3653
3730
  const charge = resolved && resolved.chargeId === fact.chargeId ? resolved : await readDisputedCharge(secretKey, fact.chargeId);
3731
+ if (charge.customerId !== known.providerCustomerId) membershipFail("membership_dispute_scope_mismatch");
3732
+ if (charge.seatId) return reconcileNamedSeatDispute(db, input, now, charge, { row: row2, fact, guard: evidenceGuard });
3654
3733
  for (let attempt = 0; attempt < 8; attempt++) {
3655
3734
  const state = await loadMembershipAuthorityState(db, input), member = await membershipLoad(db, "networkMembership", id7);
3656
3735
  requireMembershipScope(member, input);
3657
- if (charge.customerId !== member.providerCustomerId) membershipFail("membership_dispute_scope_mismatch");
3658
3736
  if (!disputedChargeCoversMembership(charge, member) || member.disputeEventId === input.eventId) return receipt(member);
3659
- const status2 = String(fact.status ?? "unknown"), closed = CLOSED_STATUSES.includes(status2);
3660
- const sameDispute = member.disputeId === fact.objectId;
3661
- if (sameDispute && Number(member.disputeClosedAt ?? 0) > 0 && !closed) return receipt(member, false);
3662
- const won = DISPUTE_WON_STATUSES.includes(status2);
3663
- const suspended = member.status === MEMBERSHIP_DISPUTED_STATUS, terminal = TERMINAL.includes(String(member.status));
3664
- const next = terminal ? String(member.status) : won ? suspended ? String(member.disputePriorStatus ?? (member.journeyApprovedAt ? "active" : "paid_pending_approval")) : String(member.status) : MEMBERSHIP_DISPUTED_STATUS;
3665
- const attrs = {
3666
- status: next,
3667
- disputeId: fact.objectId,
3668
- disputeStatus: status2,
3669
- disputeEventId: input.eventId,
3670
- disputeClosedAt: closed ? now : 0,
3671
- ...fact.disputeReason ? { disputeReason: fact.disputeReason } : {},
3672
- ...fact.amountCents !== void 0 ? { disputeAmountCents: fact.amountCents } : {},
3673
- ...sameDispute ? {} : { disputedAt: now },
3674
- ...next === MEMBERSHIP_DISPUTED_STATUS && !suspended ? { disputePriorStatus: String(member.status) } : {},
3675
- revision: Number(member.revision) + 1,
3676
- updatedAt: now
3677
- };
3737
+ const attrs = disputeTransition(member, fact, input.eventId, now, TERMINAL2);
3738
+ if (!attrs) return receipt(member, false);
3678
3739
  try {
3679
3740
  await db.transact([membershipUpdate("networkMembership", id7, attrs)], { mutationId: `membership-dispute:${id7}:${input.eventId}`, guards: [
3680
3741
  { ns: "networkMembership", id: id7, exists: true, equals: { revision: member.revision } },
@@ -4796,6 +4857,9 @@ function namedSeatRecipientState(seat) {
4796
4857
  return seat.refundReason === "declined" ? "declined" : "refunded";
4797
4858
  case "canceled":
4798
4859
  return "canceled";
4860
+ // A card-network dispute on the seat's own payment pauses the recipient until it is won or re-charged.
4861
+ case "payment_disputed":
4862
+ return "disputed";
4799
4863
  default:
4800
4864
  return "payment_pending";
4801
4865
  }
@@ -5124,6 +5188,7 @@ var handleAdminMembershipSeats = async (req, url, env, ctx) => {
5124
5188
 
5125
5189
  // src/membership-dispute-recharge.ts
5126
5190
  import { stripeRequest as stripeRequest7 } from "@odla-ai/stripe";
5191
+ var NS = { membership: "networkMembership", seat: "networkSeat" };
5127
5192
  var fail6 = (code, extra = {}) => {
5128
5193
  throw Object.assign(new Error(code), { code, ...extra });
5129
5194
  };
@@ -5137,65 +5202,64 @@ async function defaultPaymentMethod(config, subscriptionId, customerId) {
5137
5202
  if (!customer.ok) fail6("membership_dispute_provider_unavailable");
5138
5203
  return refOf2(customer.body.invoice_settings?.default_payment_method);
5139
5204
  }
5140
- async function rechargeDisputedMembership(db, membershipId, secretKey, now) {
5205
+ async function rechargeDisputed(db, kind, id7, secretKey, now) {
5141
5206
  membershipClock(now);
5142
- let member = await membershipLoad(db, "networkMembership", membershipId);
5143
- if (!member) fail6("membership_not_found");
5144
- const scope2 = { chapterId: String(member.chapterId), environment: String(member.environment) };
5145
- const amountCents = Number(member.disputeAmountCents ?? member.amountCents), currency = String(member.currency);
5207
+ const ns = NS[kind];
5208
+ let row2 = await membershipLoad(db, ns, id7);
5209
+ if (!row2) fail6("membership_not_found");
5210
+ const scope2 = { chapterId: String(row2.chapterId), environment: String(row2.environment) };
5211
+ const membershipId = kind === "membership" ? id7 : String(row2.primaryMembershipId);
5212
+ const amountCents = Number(row2.disputeAmountCents ?? row2.amountCents), currency = String(row2.currency);
5146
5213
  const done = (value, replayed) => ({
5147
5214
  membershipId,
5215
+ ...kind === "seat" ? { seatId: id7 } : {},
5148
5216
  status: String(value.status),
5149
5217
  paymentIntentId: String(value.rechargePaymentIntentId),
5150
5218
  amountCents,
5151
5219
  currency,
5152
5220
  ...replayed ? { replayed } : {}
5153
5221
  });
5154
- if (member.status !== MEMBERSHIP_DISPUTED_STATUS) {
5155
- if (member.disputeStatus === "recharged" && member.rechargePaymentIntentId) return done(member, true);
5222
+ if (row2.status !== MEMBERSHIP_DISPUTED_STATUS) {
5223
+ if (row2.disputeStatus === "recharged" && row2.rechargePaymentIntentId) return done(row2, true);
5156
5224
  fail6("membership_not_disputed");
5157
5225
  }
5158
- if (member.disputeStatus !== "lost") fail6("membership_dispute_open");
5226
+ if (row2.disputeStatus !== "lost") fail6("membership_dispute_open");
5159
5227
  if (!Number.isSafeInteger(amountCents) || amountCents <= 0 || !/^[a-z]{3}$/.test(currency)) fail6("membership_dispute_amount_invalid");
5160
- let attempt = member.rechargeAttempt;
5228
+ let attempt = row2.rechargeAttempt;
5161
5229
  if (!attempt || attempt.finishedAt !== void 0) {
5162
- attempt = { key: `membership-recharge:${membershipId}:${String(member.disputeId)}:${now}`, startedAt: now };
5230
+ attempt = { key: `${kind}-recharge:${id7}:${String(row2.disputeId)}:${now}`, startedAt: now };
5163
5231
  await db.transact(
5164
- [membershipUpdate("networkMembership", membershipId, { rechargeAttempt: attempt, revision: Number(member.revision) + 1, updatedAt: now })],
5165
- { mutationId: `membership-recharge-attempt:${attempt.key}`, guards: [{
5166
- ns: "networkMembership",
5167
- id: membershipId,
5168
- exists: true,
5169
- equals: { revision: member.revision, status: MEMBERSHIP_DISPUTED_STATUS }
5170
- }] }
5232
+ [membershipUpdate(ns, id7, { rechargeAttempt: attempt, revision: Number(row2.revision) + 1, updatedAt: now })],
5233
+ { mutationId: `membership-recharge-attempt:${attempt.key}`, guards: [{ ns, id: id7, exists: true, equals: { revision: row2.revision, status: MEMBERSHIP_DISPUTED_STATUS } }] }
5171
5234
  );
5172
- member = await membershipLoad(db, "networkMembership", membershipId);
5173
- if (!member || member.status !== MEMBERSHIP_DISPUTED_STATUS) fail6("membership_dispute_changed");
5235
+ row2 = await membershipLoad(db, ns, id7);
5236
+ if (!row2 || row2.status !== MEMBERSHIP_DISPUTED_STATUS) fail6("membership_dispute_changed");
5174
5237
  }
5175
5238
  const config = { secretKey };
5176
- const paymentMethod = await defaultPaymentMethod(config, String(member.providerSubscriptionId), String(member.providerCustomerId));
5239
+ const paymentMethod = await defaultPaymentMethod(config, String(row2.providerSubscriptionId), String(row2.providerCustomerId));
5177
5240
  if (!paymentMethod) fail6("recharge_payment_method_unavailable");
5178
5241
  const intent = await stripeRequest7(config, "POST", "/v1/payment_intents", {
5179
5242
  amount: amountCents,
5180
5243
  currency,
5181
- customer: String(member.providerCustomerId),
5244
+ customer: String(row2.providerCustomerId),
5182
5245
  payment_method: paymentMethod,
5183
5246
  off_session: "true",
5184
5247
  confirm: "true",
5185
- description: `Membership payment reissued after dispute ${String(member.disputeId)}`,
5248
+ description: `${kind === "seat" ? "Named seat" : "Membership"} payment reissued after dispute ${String(row2.disputeId)}`,
5186
5249
  metadata: {
5187
- ...stripePartitionMetadata({ appId: String(member.appId), chapterId: scope2.chapterId, environment: scope2.environment, runtime: String(member.runtime) }),
5188
- applicationId: String(member.applicationId),
5250
+ ...stripePartitionMetadata({ appId: String(row2.appId), chapterId: scope2.chapterId, environment: scope2.environment, runtime: String(row2.runtime) }),
5251
+ applicationId: String(row2.applicationId),
5189
5252
  membershipId,
5190
- disputeId: String(member.disputeId),
5253
+ ...kind === "seat" ? { seatId: id7 } : {},
5254
+ disputeId: String(row2.disputeId),
5191
5255
  membershipRecharge: "true"
5192
5256
  }
5193
5257
  }, { idempotencyKey: attempt.key });
5194
5258
  const body = intent.body, error = body.error ?? {}, paymentStatus = String(body.status ?? "");
5195
5259
  const finish = async (outcome) => {
5196
- const current = await membershipLoad(db, "networkMembership", membershipId);
5260
+ const current = await membershipLoad(db, ns, id7);
5197
5261
  if (!current) return;
5198
- await db.transact([membershipUpdate("networkMembership", membershipId, {
5262
+ await db.transact([membershipUpdate(ns, id7, {
5199
5263
  rechargeAttempt: { ...attempt, finishedAt: now, outcome },
5200
5264
  revision: Number(current.revision) + 1,
5201
5265
  updatedAt: now
@@ -5213,15 +5277,15 @@ async function rechargeDisputedMembership(db, membershipId, secretKey, now) {
5213
5277
  fail6("recharge_declined", { declineCode: String(body.last_payment_error?.decline_code ?? paymentStatus) });
5214
5278
  }
5215
5279
  if (paymentStatus !== "succeeded") fail6("recharge_pending", { paymentStatus });
5216
- if (refOf2(body.customer) !== member.providerCustomerId || body.amount !== amountCents) fail6("recharge_mismatch");
5280
+ if (refOf2(body.customer) !== row2.providerCustomerId || body.amount !== amountCents) fail6("recharge_mismatch");
5217
5281
  for (let retry = 0; retry < 8; retry++) {
5218
- const state = await loadMembershipAuthorityState(db, scope2), current = await membershipLoad(db, "networkMembership", membershipId);
5282
+ const state = await loadMembershipAuthorityState(db, scope2), current = await membershipLoad(db, ns, id7);
5219
5283
  if (!current) fail6("membership_not_found");
5220
5284
  if (current.rechargePaymentIntentId === body.id && current.status !== MEMBERSHIP_DISPUTED_STATUS) return done(current, true);
5221
- const status2 = String(current.disputePriorStatus ?? (current.journeyApprovedAt ? "active" : "paid_pending_approval"));
5285
+ const status2 = String(current.disputePriorStatus ?? (kind === "seat" ? "active" : current.journeyApprovedAt ? "active" : "paid_pending_approval"));
5222
5286
  try {
5223
5287
  await db.transact(
5224
- [membershipUpdate("networkMembership", membershipId, {
5288
+ [membershipUpdate(ns, id7, {
5225
5289
  status: status2,
5226
5290
  disputeStatus: "recharged",
5227
5291
  rechargePaymentIntentId: String(body.id),
@@ -5231,7 +5295,7 @@ async function rechargeDisputedMembership(db, membershipId, secretKey, now) {
5231
5295
  updatedAt: now
5232
5296
  })],
5233
5297
  { mutationId: `membership-recharged:${attempt.key}`, guards: [
5234
- { ns: "networkMembership", id: membershipId, exists: true, equals: { revision: current.revision, status: MEMBERSHIP_DISPUTED_STATUS } },
5298
+ { ns, id: id7, exists: true, equals: { revision: current.revision, status: MEMBERSHIP_DISPUTED_STATUS } },
5235
5299
  { ns: "membershipAuthorityState", id: String(state.id), exists: true, equals: { mode: "authoritative", revision: state.revision } }
5236
5300
  ] }
5237
5301
  );
@@ -5239,10 +5303,12 @@ async function rechargeDisputedMembership(db, membershipId, secretKey, now) {
5239
5303
  if (cause.code !== "transact_guard_failed") throw cause;
5240
5304
  continue;
5241
5305
  }
5242
- return done(await membershipLoad(db, "networkMembership", membershipId));
5306
+ return done(await membershipLoad(db, ns, id7));
5243
5307
  }
5244
5308
  return fail6("membership_dispute_retry_required");
5245
5309
  }
5310
+ var rechargeDisputedMembership = (db, membershipId, secretKey, now) => rechargeDisputed(db, "membership", membershipId, secretKey, now);
5311
+ var rechargeDisputedSeat = (db, seatId, secretKey, now) => rechargeDisputed(db, "seat", seatId, secretKey, now);
5246
5312
 
5247
5313
  // src/worker-routes-admin-membership-disputes.ts
5248
5314
  var LIST2 = "/api/admin/membership-disputes";
@@ -5259,6 +5325,9 @@ var PUBLIC2 = [
5259
5325
  "memberNumber",
5260
5326
  "amountCents",
5261
5327
  "currency",
5328
+ "recipientName",
5329
+ "primaryMembershipId",
5330
+ "purchasedAt",
5262
5331
  "disputeId",
5263
5332
  "disputeStatus",
5264
5333
  "disputeReason",
@@ -5306,14 +5375,17 @@ var handleAdminMembershipDisputes = async (req, url, env, ctx) => {
5306
5375
  return json({ error: "the membership authority is not hosted by this deployment" }, 404);
5307
5376
  }
5308
5377
  if (!one) {
5309
- const rows5 = (await db.query({ networkMembership: { $: { where: { environment: env.ODLA_ENV }, order: { updatedAt: "desc" }, limit: 500 } } })).networkMembership ?? [];
5378
+ const page = { $: { where: { environment: env.ODLA_ENV }, order: { updatedAt: "desc" }, limit: 500 } };
5379
+ const found = await db.query({ networkMembership: page, networkSeat: page });
5380
+ const memberships = found.networkMembership ?? [], seats = found.networkSeat ?? [];
5381
+ const rows5 = [...memberships.map((row2) => ({ ...project2(row2), kind: "membership" })), ...seats.map((row2) => ({ ...project2(row2), kind: "seat" }))];
5310
5382
  const disputes = rows5.filter((row2) => typeof row2.disputeId === "string").sort((a, b) => Number(b.disputedAt ?? 0) - Number(a.disputedAt ?? 0));
5311
- return json({ disputes: disputes.map(project2), truncated: rows5.length >= 500 });
5383
+ return json({ disputes, truncated: memberships.length >= 500 || seats.length >= 500 });
5312
5384
  }
5313
5385
  const secret = await getVaultSecret(db, operator.stripeSecretName ?? "stripe_secret_key");
5314
5386
  if (!secret) return json({ error: "network payment provider unavailable" }, 503);
5315
5387
  try {
5316
- const receipt = await rechargeDisputedMembership(db, one[1], secret, Date.now());
5388
+ const receipt = await (one[1].startsWith("seat_") ? rechargeDisputedSeat : rechargeDisputedMembership)(db, one[1], secret, Date.now());
5317
5389
  return json({ ok: true, ...receipt });
5318
5390
  } catch (error) {
5319
5391
  const failure = error;
@@ -5333,13 +5405,13 @@ var handleAdminMembershipDisputes = async (req, url, env, ctx) => {
5333
5405
 
5334
5406
  // src/membership-policy-versions.ts
5335
5407
  var LIMIT = 200;
5336
- var text3 = (value) => typeof value === "string" && value.length > 0 && value.length <= 200 ? value : void 0;
5408
+ var text4 = (value) => typeof value === "string" && value.length > 0 && value.length <= 200 ? value : void 0;
5337
5409
  var instant = (value) => {
5338
5410
  const at2 = typeof value === "number" ? value : typeof value === "string" ? Date.parse(value) : NaN;
5339
5411
  return Number.isSafeInteger(at2) && at2 >= 0 ? at2 : void 0;
5340
5412
  };
5341
5413
  function provenance(row2) {
5342
- const source = text3(row2.source), actor = text3(row2.actor ?? row2.$createdBy), recordedAt = instant(row2.recordedAt ?? row2.createdAt ?? row2.$createdAt);
5414
+ const source = text4(row2.source), actor = text4(row2.actor ?? row2.$createdBy), recordedAt = instant(row2.recordedAt ?? row2.createdAt ?? row2.$createdAt);
5343
5415
  return { ...source ? { source } : {}, ...actor ? { actor } : {}, ...recordedAt !== void 0 ? { recordedAt } : {} };
5344
5416
  }
5345
5417
  var live = (w, now) => w.from <= now && (w.until === void 0 || now < w.until);
@@ -6340,8 +6412,8 @@ var APPLICATION_LABELS = {
6340
6412
  state: "State"
6341
6413
  };
6342
6414
  function humanize(id7) {
6343
- const text9 = id7.replace(/([a-z0-9])([A-Z])/g, "$1 $2").replace(/[_-]+/g, " ").trim();
6344
- return text9 ? `${text9[0].toUpperCase()}${text9.slice(1)}` : id7;
6415
+ const text10 = id7.replace(/([a-z0-9])([A-Z])/g, "$1 $2").replace(/[_-]+/g, " ").trim();
6416
+ return text10 ? `${text10[0].toUpperCase()}${text10.slice(1)}` : id7;
6345
6417
  }
6346
6418
  function applicationDefs(chapter) {
6347
6419
  try {
@@ -6408,7 +6480,7 @@ var ID3 = /^[a-z][a-z0-9-]{1,79}$/;
6408
6480
  var PRICE = /^price_[A-Za-z0-9]{12,}$/;
6409
6481
  var CURRENCY = /^[a-z]{3}$/;
6410
6482
  var record = (value) => value && typeof value === "object" && !Array.isArray(value) ? value : null;
6411
- var text4 = (value, path, errors, max) => {
6483
+ var text5 = (value, path, errors, max) => {
6412
6484
  if (typeof value !== "string" || !value.trim()) {
6413
6485
  errors.push(`${path} is required`);
6414
6486
  return "";
@@ -6436,23 +6508,23 @@ function validateGiftOfferCatalog(input, tierIds) {
6436
6508
  }
6437
6509
  const allowed2 = /* @__PURE__ */ new Set(["id", "target", "billingMode", "stripePriceId", "priceCents", "currency", "redemptionLifetimeHours", "activeFrom", "activeUntil", "title", "description", "ctaLabel"]);
6438
6510
  for (const key3 of Object.keys(item)) if (!allowed2.has(key3)) errors.push(`${path}.${key3} is not supported`);
6439
- const id7 = text4(item.id, `${path}.id`, errors, 80);
6511
+ const id7 = text5(item.id, `${path}.id`, errors, 80);
6440
6512
  if (id7 && !ID3.test(id7)) errors.push(`${path}.id must be a lowercase kebab-case id`);
6441
6513
  if (ids2.has(id7)) errors.push(`${path}.id duplicates "${id7}"`);
6442
6514
  ids2.add(id7);
6443
6515
  const targetRaw = record(item.target);
6444
6516
  let target = { type: "entitlement", entitlementKey: "invalid" };
6445
6517
  if (targetRaw?.type === "tier") {
6446
- const tierId = text4(targetRaw.tierId, `${path}.target.tierId`, errors, 80);
6518
+ const tierId = text5(targetRaw.tierId, `${path}.target.tierId`, errors, 80);
6447
6519
  if (!tierIds.has(tierId)) errors.push(`${path}.target.tierId must reference a declared tier`);
6448
6520
  target = { type: "tier", tierId };
6449
6521
  } else if (targetRaw?.type === "entitlement") {
6450
- const entitlementKey = text4(targetRaw.entitlementKey, `${path}.target.entitlementKey`, errors, 80);
6522
+ const entitlementKey = text5(targetRaw.entitlementKey, `${path}.target.entitlementKey`, errors, 80);
6451
6523
  if (entitlementKey && !ID3.test(entitlementKey)) errors.push(`${path}.target.entitlementKey must be a lowercase kebab-case id`);
6452
6524
  target = { type: "entitlement", entitlementKey };
6453
6525
  } else errors.push(`${path}.target.type must be tier or entitlement`);
6454
6526
  if (item.billingMode !== "one_time" && item.billingMode !== "subscription") errors.push(`${path}.billingMode must be one_time or subscription`);
6455
- const stripePriceId = text4(item.stripePriceId, `${path}.stripePriceId`, errors, 128);
6527
+ const stripePriceId = text5(item.stripePriceId, `${path}.stripePriceId`, errors, 128);
6456
6528
  if (stripePriceId && !PRICE.test(stripePriceId)) errors.push(`${path}.stripePriceId must be a real Stripe Price id`);
6457
6529
  if (!Number.isSafeInteger(item.priceCents) || Number(item.priceCents) < 1 || Number(item.priceCents) > 1e8) errors.push(`${path}.priceCents must be an integer from 1 to 100,000,000`);
6458
6530
  const currency = typeof item.currency === "string" ? item.currency.trim().toLowerCase() : "";
@@ -6474,9 +6546,9 @@ function validateGiftOfferCatalog(input, tierIds) {
6474
6546
  redemptionLifetimeHours: lifetime,
6475
6547
  ...activeFrom !== void 0 ? { activeFrom } : {},
6476
6548
  ...activeUntil !== void 0 ? { activeUntil } : {},
6477
- title: text4(item.title, `${path}.title`, errors, 160),
6478
- description: text4(item.description, `${path}.description`, errors, 2e3),
6479
- ctaLabel: text4(item.ctaLabel, `${path}.ctaLabel`, errors, 120)
6549
+ title: text5(item.title, `${path}.title`, errors, 160),
6550
+ description: text5(item.description, `${path}.description`, errors, 2e3),
6551
+ ctaLabel: text5(item.ctaLabel, `${path}.ctaLabel`, errors, 120)
6480
6552
  });
6481
6553
  }
6482
6554
  return errors.length ? { ok: false, errors } : { ok: true, value: { schemaVersion: 1, offers } };
@@ -6892,17 +6964,17 @@ async function readBoundedRequestText(req, maxBytes) {
6892
6964
  if (!reader) return "";
6893
6965
  const decoder = new TextDecoder();
6894
6966
  let bytes = 0;
6895
- let text9 = "";
6967
+ let text10 = "";
6896
6968
  for (; ; ) {
6897
6969
  const { done, value } = await reader.read();
6898
6970
  if (req.signal.aborted) return invalid2();
6899
6971
  if (done) {
6900
6972
  complete = true;
6901
- return text9 + decoder.decode();
6973
+ return text10 + decoder.decode();
6902
6974
  }
6903
6975
  bytes += value.byteLength;
6904
6976
  if (bytes > maxBytes) return json({ error: "request body too large" }, 413);
6905
- text9 += decoder.decode(value, { stream: true });
6977
+ text10 += decoder.decode(value, { stream: true });
6906
6978
  }
6907
6979
  } catch {
6908
6980
  return invalid2();
@@ -7006,9 +7078,9 @@ async function paymentQuoteRoute(url, env, ctx, prepare = false, request) {
7006
7078
  let selection;
7007
7079
  if (request?.body) {
7008
7080
  try {
7009
- const text9 = await readBoundedRequestText(request, 2048);
7010
- if (text9 instanceof Response) return text9;
7011
- const body = JSON.parse(text9);
7081
+ const text10 = await readBoundedRequestText(request, 2048);
7082
+ if (text10 instanceof Response) return text10;
7083
+ const body = JSON.parse(text10);
7012
7084
  if (!body || typeof body !== "object" || Array.isArray(body) || Object.keys(body).some((key3) => key3 !== "namedSeat")) throw new Error("invalid body");
7013
7085
  selection = body.namedSeat;
7014
7086
  } catch {
@@ -7890,10 +7962,10 @@ function boundedString(value, path, errors, max, required = false) {
7890
7962
  errors.push(`${path} must be a string`);
7891
7963
  return void 0;
7892
7964
  }
7893
- const text9 = value.trim();
7894
- if (required && !text9) errors.push(`${path} is required`);
7895
- if (text9.length > max) errors.push(`${path} must be at most ${max.toLocaleString("en-US")} characters`);
7896
- return text9;
7965
+ const text10 = value.trim();
7966
+ if (required && !text10) errors.push(`${path} is required`);
7967
+ if (text10.length > max) errors.push(`${path} must be at most ${max.toLocaleString("en-US")} characters`);
7968
+ return text10;
7897
7969
  }
7898
7970
  function validateMembershipPageDocument(input, options = {}) {
7899
7971
  const errors = [];
@@ -7942,8 +8014,8 @@ function validateMembershipPageDocument(input, options = {}) {
7942
8014
  else {
7943
8015
  if (item.benefits.length > MAX_BENEFITS) errors.push(`${path}.benefits supports at most ${MAX_BENEFITS} entries`);
7944
8016
  benefits = item.benefits.slice(0, MAX_BENEFITS).flatMap((benefit, benefitIndex) => {
7945
- const text9 = boundedString(benefit, `${path}.benefits[${benefitIndex}]`, errors, 2e3, true);
7946
- return text9 ? [text9] : [];
8017
+ const text10 = boundedString(benefit, `${path}.benefits[${benefitIndex}]`, errors, 2e3, true);
8018
+ return text10 ? [text10] : [];
7947
8019
  });
7948
8020
  }
7949
8021
  }
@@ -8037,7 +8109,7 @@ var recordOf = (value) => value !== null && typeof value === "object" && !Array.
8037
8109
  function unknownKeys(value, allowed2, path, errors) {
8038
8110
  for (const key3 of Object.keys(value)) if (!allowed2.has(key3)) errors.push(`${path}.${key3} is not supported`);
8039
8111
  }
8040
- function text5(value, path, errors, max = 65536) {
8112
+ function text6(value, path, errors, max = 65536) {
8041
8113
  if (typeof value !== "string" || !value.trim()) {
8042
8114
  errors.push(`${path} is required`);
8043
8115
  return "";
@@ -8060,11 +8132,11 @@ function tiersOf(value, errors) {
8060
8132
  return [];
8061
8133
  }
8062
8134
  unknownKeys(item, TIER_KEYS2, path, errors);
8063
- const id7 = text5(item.id, `${path}.id`, errors, 80);
8135
+ const id7 = text6(item.id, `${path}.id`, errors, 80);
8064
8136
  if (id7 && !TIER.test(id7)) errors.push(`${path}.id must be a lowercase kebab-case id`);
8065
8137
  if (seen.has(id7)) errors.push(`${path}.id duplicates "${id7}"`);
8066
8138
  seen.add(id7);
8067
- const name = text5(item.name, `${path}.name`, errors, 160);
8139
+ const name = text6(item.name, `${path}.name`, errors, 160);
8068
8140
  if (typeof item.available !== "boolean") errors.push(`${path}.available must be boolean`);
8069
8141
  if (!Number.isInteger(item.sortOrder) || Number(item.sortOrder) < 0) errors.push(`${path}.sortOrder must be a non-negative integer`);
8070
8142
  if (!Number.isInteger(item.priceCents) || Number(item.priceCents) < 0 || Number(item.priceCents) > 1e8) {
@@ -8082,7 +8154,7 @@ function tiersOf(value, errors) {
8082
8154
  if (item.interval !== void 0 && item.interval !== "year" && item.interval !== "month") {
8083
8155
  errors.push(`${path}.interval must be year or month`);
8084
8156
  }
8085
- const blurb = item.blurb === void 0 ? void 0 : text5(item.blurb, `${path}.blurb`, errors, 4e3);
8157
+ const blurb = item.blurb === void 0 ? void 0 : text6(item.blurb, `${path}.blurb`, errors, 4e3);
8086
8158
  return [{
8087
8159
  id: id7,
8088
8160
  name,
@@ -8140,7 +8212,7 @@ function policyOf(value, errors, tiers) {
8140
8212
  const result = {};
8141
8213
  for (const key3 of allowed2) {
8142
8214
  if (input[key3] === void 0 && key3 !== "disclaimerText" && key3 !== "refundPolicyText") continue;
8143
- result[key3] = text5(input[key3], `document.policy.${key3}`, errors);
8215
+ result[key3] = text6(input[key3], `document.policy.${key3}`, errors);
8144
8216
  }
8145
8217
  const tierPolicies = {};
8146
8218
  if (input.tierPolicies !== void 0) {
@@ -8155,8 +8227,8 @@ function policyOf(value, errors, tiers) {
8155
8227
  }
8156
8228
  unknownKeys(policy2, /* @__PURE__ */ new Set(["refundPolicyText", "trustCopy"]), path, errors);
8157
8229
  tierPolicies[id7] = {
8158
- refundPolicyText: text5(policy2.refundPolicyText, `${path}.refundPolicyText`, errors),
8159
- ...policy2.trustCopy !== void 0 ? { trustCopy: text5(policy2.trustCopy, `${path}.trustCopy`, errors) } : {}
8230
+ refundPolicyText: text6(policy2.refundPolicyText, `${path}.refundPolicyText`, errors),
8231
+ ...policy2.trustCopy !== void 0 ? { trustCopy: text6(policy2.trustCopy, `${path}.trustCopy`, errors) } : {}
8160
8232
  };
8161
8233
  }
8162
8234
  }
@@ -8196,9 +8268,9 @@ function validateSignupControlDocument(input) {
8196
8268
  if (!Number.isSafeInteger(root.generatedAt) || Number(root.generatedAt) < 1) errors.push("document.generatedAt must be epoch milliseconds");
8197
8269
  const source = recordOf(root.source);
8198
8270
  const target = recordOf(root.target);
8199
- const appId = text5(source?.appId, "document.source.appId", errors, 63);
8200
- const chapterId = text5(target?.chapterId, "document.target.chapterId", errors, 63);
8201
- const environment = text5(target?.environment, "document.target.environment", errors, 63);
8271
+ const appId = text6(source?.appId, "document.source.appId", errors, 63);
8272
+ const chapterId = text6(target?.chapterId, "document.target.chapterId", errors, 63);
8273
+ const environment = text6(target?.environment, "document.target.environment", errors, 63);
8202
8274
  if (appId && !SLUG.test(appId)) errors.push("document.source.appId must be a lowercase app slug");
8203
8275
  if (chapterId && !SLUG.test(chapterId)) errors.push("document.target.chapterId must be a lowercase app slug");
8204
8276
  if (environment && !ENVIRONMENT.test(environment)) errors.push("document.target.environment is invalid");
@@ -10469,7 +10541,7 @@ async function startSubscription(req, env, ctx) {
10469
10541
 
10470
10542
  // src/worker-payment-receipt.ts
10471
10543
  var json2 = (value, status2 = 200) => Response.json(value, { status: status2, headers: { "cache-control": "no-store", "referrer-policy": "no-referrer" } });
10472
- var escape = (text9) => text9.replace(/[&<>"']/g, (char) => ({ "&": "&amp;", "<": "&lt;", ">": "&gt;", '"': "&quot;", "'": "&#39;" })[char]);
10544
+ var escape = (text10) => text10.replace(/[&<>"']/g, (char) => ({ "&": "&amp;", "<": "&lt;", ">": "&gt;", '"': "&quot;", "'": "&#39;" })[char]);
10473
10545
  async function paymentReceiptRoute(req, url, env, ctx) {
10474
10546
  const id7 = url.searchParams.get("application");
10475
10547
  if (!id7 || id7.length > 160) return json2({ error: "application required" }, 400);
@@ -10480,7 +10552,7 @@ async function paymentReceiptRoute(req, url, env, ctx) {
10480
10552
  if (!app || app.groupId !== ctx.chapter.id || !intent || intent.applicationId !== id7 || intent.appId !== env.ODLA_APP_ID || intent.chapterId !== ctx.chapter.id || intent.environment !== env.ODLA_ENV || intent.runtime !== stripeRuntimeIdentity(env.ODLA_RUNTIME) || app.stripeRuntime !== intent.runtime || app.stripeSubscriptionId !== intent.providerSubscriptionId) return json2({ error: "receipt not found" }, 404);
10481
10553
  const receipt = intent.paymentReceipt;
10482
10554
  if (!receipt) return json2({ pending: true }, 202);
10483
- const text9 = paymentReceiptText(receipt, ctx.chapter.name);
10555
+ const text10 = paymentReceiptText(receipt, ctx.chapter.name);
10484
10556
  if (req.method === "POST") {
10485
10557
  if (!env.SEND_EMAIL || !env.EMAIL_FROM) return json2({ error: "Email is unavailable. You can print or save your receipt." }, 503);
10486
10558
  const group = (await db.query({ groups: { $: { where: { id: ctx.chapter.id }, limit: 1 } } })).groups?.[0];
@@ -10498,7 +10570,7 @@ async function paymentReceiptRoute(req, url, env, ctx) {
10498
10570
  template: "paymentReceipt",
10499
10571
  to: String(intent.purchaserEmailNormalized),
10500
10572
  applicationId: id7,
10501
- vars: { chapterName: ctx.chapter.name, receiptText: text9 },
10573
+ vars: { chapterName: ctx.chapter.name, receiptText: text10 },
10502
10574
  dedupeKey: `requested-payment-receipt:${receipt.invoiceId}`,
10503
10575
  event: { label: "Payment receipt", trigger: "Purchaser requests their receipt", audience: "Purchaser", variables: ["chapterName", "receiptText"] }
10504
10576
  });
@@ -10508,7 +10580,7 @@ async function paymentReceiptRoute(req, url, env, ctx) {
10508
10580
  const nonce = crypto.randomUUID();
10509
10581
  return new Response(`<!doctype html><html lang="en"><meta charset="utf-8"><meta name="viewport" content="width=device-width"><title>Payment receipt</title>
10510
10582
  <style>body{font:16px/1.6 system-ui;max-width:680px;margin:48px auto;padding:24px;color:#17252d}pre{font:inherit;white-space:pre-wrap}button{font:inherit;padding:10px 16px}@media print{button{display:none}body{margin:0}}</style>
10511
- <h1>Payment receipt</h1><pre>${escape(text9)}</pre><button id="print">Print / save PDF</button>
10583
+ <h1>Payment receipt</h1><pre>${escape(text10)}</pre><button id="print">Print / save PDF</button>
10512
10584
  <script nonce="${nonce}">document.getElementById('print').addEventListener('click',()=>window.print())</script></html>`, {
10513
10585
  headers: {
10514
10586
  "content-type": "text/html; charset=utf-8",
@@ -10978,16 +11050,16 @@ var handleNamedSeat = async (req, url, env, ctx) => {
10978
11050
  };
10979
11051
 
10980
11052
  // src/named-seat-accepted-notice.ts
10981
- var text6 = (value) => typeof value === "string" ? value : "";
11053
+ var text7 = (value) => typeof value === "string" ? value : "";
10982
11054
  async function notifyNamedSeatAccepted(db, env, ctx, input) {
10983
11055
  const purchaser = await membershipLoad(db, "applications", input.primaryApplicationId);
10984
- if (!purchaser || !chapterBillingApplication(purchaser, ctx.chapter.id, env.ODLA_RUNTIME) || !text6(purchaser.email)) {
11056
+ if (!purchaser || !chapterBillingApplication(purchaser, ctx.chapter.id, env.ODLA_RUNTIME) || !text7(purchaser.email)) {
10985
11057
  return { sent: false, reason: "purchaser-missing" };
10986
11058
  }
10987
11059
  const group = await membershipLoad(db, "groups", ctx.chapter.id);
10988
11060
  if (!group) return { sent: false, reason: "group-missing" };
10989
11061
  const config = emailGroupFrom(group);
10990
- const purchaserName = [purchaser.firstName, purchaser.lastName].map(text6).join(" ").replace(/\s+/g, " ").trim();
11062
+ const purchaserName = [purchaser.firstName, purchaser.lastName].map(text7).join(" ").replace(/\s+/g, " ").trim();
10991
11063
  return sendTemplated({
10992
11064
  db,
10993
11065
  envName: env.ODLA_ENV,
@@ -10999,12 +11071,12 @@ async function notifyNamedSeatAccepted(db, env, ctx, input) {
10999
11071
  }, {
11000
11072
  group: config,
11001
11073
  template: "seatAccepted",
11002
- to: text6(purchaser.email),
11074
+ to: text7(purchaser.email),
11003
11075
  dedupeKey: `named-seat-accepted:${input.seatId}`,
11004
11076
  applicationId: input.primaryApplicationId,
11005
11077
  tierId: input.skuId,
11006
11078
  vars: {
11007
- firstName: text6(purchaser.firstName),
11079
+ firstName: text7(purchaser.firstName),
11008
11080
  purchaserName,
11009
11081
  recipientName: input.recipientName,
11010
11082
  tierName: await tierName(db, group, input.skuId),
@@ -12471,12 +12543,12 @@ async function updateEmailSettings(db, ctx, env, body) {
12471
12543
  if (!value || typeof value !== "object" || Array.isArray(value)) return json({ error: `Invalid template: ${key3}` }, 400);
12472
12544
  const t = value;
12473
12545
  if (typeof t.enabled !== "boolean") return json({ error: `Choose whether ${key3} is enabled.` }, 400);
12474
- const subject = str2(t.subject).trim(), text9 = str2(t.text);
12475
- const issues = validateEmailCopy4({ subject, text: text9 }, { allowedVariables: events[key3].variables });
12546
+ const subject = str2(t.subject).trim(), text10 = str2(t.text);
12547
+ const issues = validateEmailCopy4({ subject, text: text10 }, { allowedVariables: events[key3].variables });
12476
12548
  if (t.enabled && issues.length || issues.some((issue) => ["subject-invalid", "body-invalid"].includes(issue.code))) {
12477
12549
  return json({ error: `${events[key3].label} is incomplete.`, errors: issues.map((issue) => issue.message) }, 400);
12478
12550
  }
12479
- clean[key3] = { subject, text: text9, enabled: t.enabled };
12551
+ clean[key3] = { subject, text: text10, enabled: t.enabled };
12480
12552
  }
12481
12553
  const notificationEmail = str2(body.notificationEmail).trim(), replyTo = str2(body.replyTo).trim(), debugEmail = str2(body.debugEmail).trim();
12482
12554
  const emailish = /^[^\s@,;<>]+@[^\s@,;<>]+\.[^\s@,;<>]+$/;
@@ -14689,9 +14761,9 @@ var handleAdminNetworkPersonRegistry = async (req, url, env, ctx) => {
14689
14761
  if (req.method === "POST") {
14690
14762
  let body;
14691
14763
  try {
14692
- const text9 = await req.text();
14693
- if (new TextEncoder().encode(text9).byteLength > MAX_BODY2) return json({ error: "request is too large" }, 413);
14694
- const parsed = JSON.parse(text9);
14764
+ const text10 = await req.text();
14765
+ if (new TextEncoder().encode(text10).byteLength > MAX_BODY2) return json({ error: "request is too large" }, 413);
14766
+ const parsed = JSON.parse(text10);
14695
14767
  if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) throw new Error("invalid");
14696
14768
  body = parsed;
14697
14769
  } catch {
@@ -14860,7 +14932,7 @@ import { signFederatedRequest as signFederatedRequest7, verifyFederatedRequest a
14860
14932
  // src/network-commercial-parity.ts
14861
14933
  var DIGEST = /^[0-9a-f]{64}$/;
14862
14934
  var MAX_RECORDS = 500;
14863
- var text7 = (value, max = 255) => typeof value === "string" && value.length > 0 && value.length <= max ? value : null;
14935
+ var text8 = (value, max = 255) => typeof value === "string" && value.length > 0 && value.length <= max ? value : null;
14864
14936
  function parityMessage(partition, kind, value) {
14865
14937
  return [
14866
14938
  "chapter-commercial-parity",
@@ -14892,18 +14964,18 @@ async function digestWithKey(key3, partition, kind, value) {
14892
14964
  async function buildChapterCommercialParitySnapshot(input) {
14893
14965
  const requestedMax = input.maxRecords ?? MAX_RECORDS;
14894
14966
  const maxRecords = Number.isSafeInteger(requestedMax) ? Math.max(0, Math.min(requestedMax, MAX_RECORDS)) : MAX_RECORDS;
14895
- const eligible = input.rows.filter((row2) => text7(row2.id) && (text7(row2.stripeCustomerId) || text7(row2.stripeSubscriptionId)));
14967
+ const eligible = input.rows.filter((row2) => text8(row2.id) && (text8(row2.stripeCustomerId) || text8(row2.stripeSubscriptionId)));
14896
14968
  const selected2 = eligible.slice(0, maxRecords);
14897
14969
  const key3 = await parityKey(input.secret);
14898
14970
  const records = await Promise.all(selected2.map(async (row2) => {
14899
- const applicationId = text7(row2.id);
14900
- const customerId = text7(row2.stripeCustomerId);
14901
- const subscriptionId = text7(row2.stripeSubscriptionId);
14971
+ const applicationId = text8(row2.id);
14972
+ const customerId = text8(row2.stripeCustomerId);
14973
+ const subscriptionId = text8(row2.stripeSubscriptionId);
14902
14974
  return {
14903
14975
  applicationDigest: await digestWithKey(key3, input.partition, "application", applicationId),
14904
14976
  customerDigest: customerId ? await digestWithKey(key3, input.partition, "customer", customerId) : null,
14905
14977
  subscriptionDigest: subscriptionId ? await digestWithKey(key3, input.partition, "subscription", subscriptionId) : null,
14906
- status: text7(row2.status, 64),
14978
+ status: text8(row2.status, 64),
14907
14979
  canceled: row2.canceled === true
14908
14980
  };
14909
14981
  }));
@@ -14929,7 +15001,7 @@ function validSnapshot(snapshot, source) {
14929
15001
  if (snapshot?.version !== 1 || snapshot.site?.id !== source.chapterId || !partition || !Array.isArray(records) || records.length > MAX_RECORDS || partition.appId !== source.appId || partition.chapterId !== source.chapterId || partition.environment !== source.environment || partition.dataEnvironment !== source.dataEnvironment || partition.runtime !== source.runtime || typeof snapshot.complete !== "boolean" || typeof snapshot.truncated !== "boolean" || !snapshot.counts) return false;
14930
15002
  const seen = /* @__PURE__ */ new Set();
14931
15003
  for (const record4 of records) {
14932
- if (!DIGEST.test(record4.applicationDigest) || record4.customerDigest !== null && !DIGEST.test(record4.customerDigest) || record4.subscriptionDigest !== null && !DIGEST.test(record4.subscriptionDigest) || record4.status !== null && text7(record4.status, 64) === null || typeof record4.canceled !== "boolean") return false;
15004
+ if (!DIGEST.test(record4.applicationDigest) || record4.customerDigest !== null && !DIGEST.test(record4.customerDigest) || record4.subscriptionDigest !== null && !DIGEST.test(record4.subscriptionDigest) || record4.status !== null && text8(record4.status, 64) === null || typeof record4.canceled !== "boolean") return false;
14933
15005
  const key3 = recordKey(record4);
14934
15006
  if (seen.has(key3)) return false;
14935
15007
  seen.add(key3);
@@ -14940,16 +15012,16 @@ async function reconcileCommercialParitySnapshot(input) {
14940
15012
  if (!validSnapshot(input.snapshot, input.source)) throw new Error("invalid chapter commercial parity snapshot");
14941
15013
  const sourceKey = networkCommercialSourceKey(input.source);
14942
15014
  const key3 = await parityKey(input.secret);
14943
- const shadow = await Promise.all(input.projection.subscriptions.filter((row2) => row2.sourceKey === sourceKey && text7(row2.applicationId) && text7(row2.objectId)).map(async (row2) => {
14944
- const customerId = text7(row2.customerId);
14945
- const subscriptionId = text7(row2.subscriptionId) ?? text7(row2.objectId);
15015
+ const shadow = await Promise.all(input.projection.subscriptions.filter((row2) => row2.sourceKey === sourceKey && text8(row2.applicationId) && text8(row2.objectId)).map(async (row2) => {
15016
+ const customerId = text8(row2.customerId);
15017
+ const subscriptionId = text8(row2.subscriptionId) ?? text8(row2.objectId);
14946
15018
  return {
14947
15019
  applicationDigest: await digestWithKey(key3, input.snapshot.partition, "application", row2.applicationId),
14948
15020
  customerDigest: customerId ? await digestWithKey(key3, input.snapshot.partition, "customer", customerId) : null,
14949
15021
  subscriptionDigest: subscriptionId ? await digestWithKey(key3, input.snapshot.partition, "subscription", subscriptionId) : null,
14950
15022
  status: null,
14951
15023
  canceled: false,
14952
- stripeStatus: text7(row2.status, 64),
15024
+ stripeStatus: text8(row2.status, 64),
14953
15025
  stripeCanceled: row2.status === "canceled"
14954
15026
  };
14955
15027
  }));
@@ -15159,18 +15231,24 @@ async function applyHostedMembershipDispute(env, ctx, fact, now) {
15159
15231
  const secret = await getVaultSecret(db, operator.stripeSecretName ?? "stripe_secret_key");
15160
15232
  if (!secret) throw Object.assign(new Error("membership_dispute_provider_unavailable"), { code: "membership_dispute_provider_unavailable" });
15161
15233
  const charge = await readDisputedCharge(secret, fact.chargeId);
15234
+ const scopeOf = (row2) => ({
15235
+ appId: String(row2.appId),
15236
+ chapterId: String(row2.chapterId),
15237
+ environment: String(row2.environment),
15238
+ runtime: String(row2.runtime),
15239
+ applicationId: String(row2.applicationId),
15240
+ skuId: String(row2.skuId)
15241
+ });
15242
+ if (charge.seatId) {
15243
+ const seat = await membershipLoad(db, "networkSeat", charge.seatId);
15244
+ if (!seat || seat.environment !== env.ODLA_ENV || !disputedChargeCoversSeat(charge, seat)) return { disposition: "not_applicable" };
15245
+ const receipt2 = await reconcileMembershipDispute(db, { ...scopeOf(seat), eventId: fact.eventId }, now, secret, charge);
15246
+ return { disposition: "applied", membershipId: receipt2.membershipId, seatId: charge.seatId };
15247
+ }
15162
15248
  const rows5 = (await db.query({ networkMembership: { $: { where: { providerCustomerId: charge.customerId, environment: env.ODLA_ENV }, limit: 5 } } })).networkMembership ?? [];
15163
15249
  const member = rows5.find((row2) => disputedChargeCoversMembership(charge, row2));
15164
15250
  if (!member) return { disposition: "not_applicable" };
15165
- const target = {
15166
- appId: String(member.appId),
15167
- chapterId: String(member.chapterId),
15168
- environment: String(member.environment),
15169
- runtime: String(member.runtime),
15170
- applicationId: String(member.applicationId),
15171
- skuId: String(member.skuId)
15172
- };
15173
- const receipt = await reconcileMembershipDispute(db, { ...target, eventId: fact.eventId }, now, secret, charge);
15251
+ const receipt = await reconcileMembershipDispute(db, { ...scopeOf(member), eventId: fact.eventId }, now, secret, charge);
15174
15252
  return { disposition: "applied", membershipId: receipt.membershipId };
15175
15253
  }
15176
15254
 
@@ -15238,11 +15316,11 @@ function safeProjection(projection) {
15238
15316
  amountReceivedCents: seat.amountReceivedCents,
15239
15317
  refundedCents: seat.refundedCents ?? 0
15240
15318
  })),
15241
- disputes: (projection.disputes ?? []).map((dispute) => ({
15242
- ...row2(dispute),
15243
- chargeId: dispute.chargeId ?? null,
15244
- paymentIntentId: dispute.paymentIntentId ?? null,
15245
- reason: dispute.disputeReason ?? null
15319
+ disputes: (projection.disputes ?? []).map((dispute2) => ({
15320
+ ...row2(dispute2),
15321
+ chargeId: dispute2.chargeId ?? null,
15322
+ paymentIntentId: dispute2.paymentIntentId ?? null,
15323
+ reason: dispute2.disputeReason ?? null
15246
15324
  })),
15247
15325
  exceptions: projection.exceptions
15248
15326
  };
@@ -15276,10 +15354,10 @@ var handleNetworkCommercialStripeWebhook = async (req, url, env, ctx) => {
15276
15354
  if (result.status === "collision") {
15277
15355
  console.error("network commercial event collision", JSON.stringify({ eventId: fact.eventId, eventType: fact.eventType }));
15278
15356
  }
15279
- let dispute;
15357
+ let dispute2;
15280
15358
  if (fact.objectKind === "dispute" && result.status !== "collision") {
15281
15359
  try {
15282
- dispute = await applyHostedMembershipDispute(env, ctx, fact, Date.now());
15360
+ dispute2 = await applyHostedMembershipDispute(env, ctx, fact, Date.now());
15283
15361
  } catch (error) {
15284
15362
  console.warn("membership dispute pending", JSON.stringify({ eventId: fact.eventId, code: error.code ?? "unknown" }));
15285
15363
  return json({ error: "membership dispute reconciliation pending", eventId: fact.eventId }, 503);
@@ -15289,7 +15367,7 @@ var handleNetworkCommercialStripeWebhook = async (req, url, env, ctx) => {
15289
15367
  ok: true,
15290
15368
  status: result.status,
15291
15369
  eventId: fact.eventId,
15292
- ...dispute ? { dispute: dispute.disposition } : {},
15370
+ ...dispute2 ? { dispute: dispute2.disposition } : {},
15293
15371
  ...result.status === "replayed" && result.resolvedCollision ? { resolvedCollision: true } : {}
15294
15372
  });
15295
15373
  } catch (error) {
@@ -15487,7 +15565,7 @@ var record3 = (value) => value !== null && typeof value === "object" && !Array.i
15487
15565
  function unknownKeys3(value, allowed2, path, errors) {
15488
15566
  for (const key3 of Object.keys(value)) if (!allowed2.has(key3)) errors.push(`${path}.${key3} is not supported`);
15489
15567
  }
15490
- function text8(value, path, errors, max = MAX_TEXT2, required = true) {
15568
+ function text9(value, path, errors, max = MAX_TEXT2, required = true) {
15491
15569
  if (value === void 0 && !required) return void 0;
15492
15570
  if (typeof value !== "string" || required && !value.trim()) {
15493
15571
  errors.push(`${path} must be a non-empty string`);
@@ -15513,7 +15591,7 @@ function scopeValue(value, path, errors) {
15513
15591
  return { kind: raw2.kind };
15514
15592
  }
15515
15593
  if (raw2.kind === "chapter") {
15516
- const chapterId = text8(raw2.chapterId, `${path}.chapterId`, errors, 63) ?? "";
15594
+ const chapterId = text9(raw2.chapterId, `${path}.chapterId`, errors, 63) ?? "";
15517
15595
  if (chapterId && !APP_ID.test(chapterId)) errors.push(`${path}.chapterId must be a lowercase app slug`);
15518
15596
  return { kind: "chapter", chapterId };
15519
15597
  }
@@ -15527,22 +15605,22 @@ function resourceValue(value, path, errors) {
15527
15605
  return null;
15528
15606
  }
15529
15607
  unknownKeys3(raw2, RESOURCE_KEYS, path, errors);
15530
- const id7 = text8(raw2.id, `${path}.id`, errors, 80) ?? "";
15608
+ const id7 = text9(raw2.id, `${path}.id`, errors, 80) ?? "";
15531
15609
  if (id7 && !ID5.test(id7)) errors.push(`${path}.id must be lowercase kebab-case`);
15532
- const kind = text8(raw2.kind, `${path}.kind`, errors) ?? "";
15610
+ const kind = text9(raw2.kind, `${path}.kind`, errors) ?? "";
15533
15611
  if (!KINDS.has(kind)) errors.push(`${path}.kind is unsupported`);
15534
- const title = text8(raw2.title, `${path}.title`, errors, 240) ?? "";
15535
- const summary = text8(raw2.summary, `${path}.summary`, errors, 1e3, false);
15536
- const status2 = text8(raw2.status, `${path}.status`, errors) ?? "";
15612
+ const title = text9(raw2.title, `${path}.title`, errors, 240) ?? "";
15613
+ const summary = text9(raw2.summary, `${path}.summary`, errors, 1e3, false);
15614
+ const status2 = text9(raw2.status, `${path}.status`, errors) ?? "";
15537
15615
  if (!STATUSES.has(status2)) errors.push(`${path}.status must be draft, published, or archived`);
15538
15616
  const revision = integer4(raw2.revision, `${path}.revision`, errors);
15539
15617
  const scope2 = scopeValue(raw2.scope, `${path}.scope`, errors) ?? { kind: "global" };
15540
- const tags = Array.isArray(raw2.tags) ? raw2.tags.map((tag, index2) => text8(tag, `${path}.tags[${index2}]`, errors, 80) ?? "") : [];
15618
+ const tags = Array.isArray(raw2.tags) ? raw2.tags.map((tag, index2) => text9(tag, `${path}.tags[${index2}]`, errors, 80) ?? "") : [];
15541
15619
  if (!Array.isArray(raw2.tags)) errors.push(`${path}.tags must be an array`);
15542
15620
  if (tags.length > MAX_TAGS) errors.push(`${path}.tags may contain at most ${MAX_TAGS} items`);
15543
15621
  if (new Set(tags).size !== tags.length) errors.push(`${path}.tags must be unique`);
15544
- const bodyMarkdown = text8(raw2.bodyMarkdown, `${path}.bodyMarkdown`, errors, MAX_MARKDOWN3, false);
15545
- const url = text8(raw2.url, `${path}.url`, errors, 2048, false);
15622
+ const bodyMarkdown = text9(raw2.bodyMarkdown, `${path}.bodyMarkdown`, errors, MAX_MARKDOWN3, false);
15623
+ const url = text9(raw2.url, `${path}.url`, errors, 2048, false);
15546
15624
  if (url) {
15547
15625
  try {
15548
15626
  if (new URL(url).protocol !== "https:") errors.push(`${path}.url must use https`);
@@ -15554,9 +15632,9 @@ function resourceValue(value, path, errors) {
15554
15632
  if (raw2.brandAsset !== void 0 && !asset) errors.push(`${path}.brandAsset must be an object`);
15555
15633
  if (asset) unknownKeys3(asset, ASSET_KEYS, `${path}.brandAsset`, errors);
15556
15634
  const brandAsset = asset ? {
15557
- bookId: text8(asset.bookId, `${path}.brandAsset.bookId`, errors, 160) ?? "",
15558
- assetId: text8(asset.assetId, `${path}.brandAsset.assetId`, errors, 160) ?? "",
15559
- ...asset.filename === void 0 ? {} : { filename: text8(asset.filename, `${path}.brandAsset.filename`, errors, 240) ?? "" }
15635
+ bookId: text9(asset.bookId, `${path}.brandAsset.bookId`, errors, 160) ?? "",
15636
+ assetId: text9(asset.assetId, `${path}.brandAsset.assetId`, errors, 160) ?? "",
15637
+ ...asset.filename === void 0 ? {} : { filename: text9(asset.filename, `${path}.brandAsset.filename`, errors, 240) ?? "" }
15560
15638
  } : void 0;
15561
15639
  if ((kind === "document" || kind === "note") && !bodyMarkdown) errors.push(`${path}.bodyMarkdown is required for ${kind}`);
15562
15640
  if ((kind === "link" || kind === "inspiration") && !url) errors.push(`${path}.url is required for ${kind}`);
@@ -15571,17 +15649,17 @@ function resourceValue(value, path, errors) {
15571
15649
  if (provenanceRaw?.promotedFrom !== void 0 && !promotedRaw) errors.push(`${path}.provenance.promotedFrom must be an object`);
15572
15650
  if (promotedRaw) unknownKeys3(promotedRaw, PROMOTION_KEYS, `${path}.provenance.promotedFrom`, errors);
15573
15651
  const promotedFrom = promotedRaw ? {
15574
- chapterId: text8(promotedRaw.chapterId, `${path}.provenance.promotedFrom.chapterId`, errors, 63) ?? "",
15575
- resourceId: text8(promotedRaw.resourceId, `${path}.provenance.promotedFrom.resourceId`, errors, 80) ?? "",
15652
+ chapterId: text9(promotedRaw.chapterId, `${path}.provenance.promotedFrom.chapterId`, errors, 63) ?? "",
15653
+ resourceId: text9(promotedRaw.resourceId, `${path}.provenance.promotedFrom.resourceId`, errors, 80) ?? "",
15576
15654
  resourceRevision: integer4(promotedRaw.resourceRevision, `${path}.provenance.promotedFrom.resourceRevision`, errors),
15577
15655
  documentRevision: integer4(promotedRaw.documentRevision, `${path}.provenance.promotedFrom.documentRevision`, errors),
15578
- promotedBy: text8(promotedRaw.promotedBy, `${path}.provenance.promotedFrom.promotedBy`, errors, 320) ?? "",
15656
+ promotedBy: text9(promotedRaw.promotedBy, `${path}.provenance.promotedFrom.promotedBy`, errors, 320) ?? "",
15579
15657
  promotedAt: integer4(promotedRaw.promotedAt, `${path}.provenance.promotedFrom.promotedAt`, errors, 0)
15580
15658
  } : void 0;
15581
15659
  const provenance2 = {
15582
- createdBy: text8(provenanceRaw?.createdBy, `${path}.provenance.createdBy`, errors, 320) ?? "",
15660
+ createdBy: text9(provenanceRaw?.createdBy, `${path}.provenance.createdBy`, errors, 320) ?? "",
15583
15661
  createdAt: integer4(provenanceRaw?.createdAt, `${path}.provenance.createdAt`, errors, 0),
15584
- updatedBy: text8(provenanceRaw?.updatedBy, `${path}.provenance.updatedBy`, errors, 320) ?? "",
15662
+ updatedBy: text9(provenanceRaw?.updatedBy, `${path}.provenance.updatedBy`, errors, 320) ?? "",
15585
15663
  updatedAt: integer4(provenanceRaw?.updatedAt, `${path}.provenance.updatedAt`, errors, 0),
15586
15664
  ...promotedFrom ? { promotedFrom } : {}
15587
15665
  };
@@ -15600,7 +15678,7 @@ function validateBuilderLibraryDocument(value, options = {}) {
15600
15678
  if (sourceRaw) unknownKeys3(sourceRaw, SOURCE_KEYS2, "document.source", errors);
15601
15679
  const sourceType = sourceRaw?.type;
15602
15680
  if (sourceType !== "chapter" && sourceType !== "built_not_found") errors.push("document.source.type must be chapter or built_not_found");
15603
- const sourceAppId = text8(sourceRaw?.appId, "document.source.appId", errors, 63) ?? "";
15681
+ const sourceAppId = text9(sourceRaw?.appId, "document.source.appId", errors, 63) ?? "";
15604
15682
  if (sourceAppId && !APP_ID.test(sourceAppId)) errors.push("document.source.appId must be a lowercase app slug");
15605
15683
  if (options.sourceType && sourceType !== options.sourceType) errors.push(`document.source.type must be ${options.sourceType}`);
15606
15684
  if (options.sourceAppId && sourceAppId !== options.sourceAppId) errors.push(`document.source.appId must be ${options.sourceAppId}`);
@@ -15608,8 +15686,8 @@ function validateBuilderLibraryDocument(value, options = {}) {
15608
15686
  if (raw2.target !== void 0 && !targetRaw) errors.push("document.target must be an object");
15609
15687
  if (targetRaw) unknownKeys3(targetRaw, TARGET_KEYS2, "document.target", errors);
15610
15688
  const target = targetRaw ? {
15611
- appId: text8(targetRaw.appId, "document.target.appId", errors, 63) ?? "",
15612
- environment: text8(targetRaw.environment, "document.target.environment", errors, 80) ?? ""
15689
+ appId: text9(targetRaw.appId, "document.target.appId", errors, 63) ?? "",
15690
+ environment: text9(targetRaw.environment, "document.target.environment", errors, 80) ?? ""
15613
15691
  } : void 0;
15614
15692
  if (options.targetAppId && target?.appId !== options.targetAppId) errors.push(`document.target.appId must be ${options.targetAppId}`);
15615
15693
  if (!Array.isArray(raw2.resources)) errors.push("document.resources must be an array");
@@ -17134,6 +17212,19 @@ var id5 = () => attr("string", false, true, true);
17134
17212
  var key = (optional = false) => attr("string", optional, false, true);
17135
17213
  var scope = () => ({ appId: key(), chapterId: key(), environment: key(), runtime: key(), applicationId: key(), skuId: key() });
17136
17214
  var times = () => ({ revision: attr("number"), createdAt: attr("number"), updatedAt: attr("number") });
17215
+ var dispute = () => ({
17216
+ disputeId: key(true),
17217
+ disputeStatus: attr("string", true),
17218
+ disputeReason: attr("string", true),
17219
+ disputeAmountCents: attr("number", true),
17220
+ disputedAt: attr("number", true),
17221
+ disputeClosedAt: attr("number", true),
17222
+ disputePriorStatus: attr("string", true),
17223
+ disputeEventId: key(true),
17224
+ rechargePaymentIntentId: key(true),
17225
+ rechargedAt: attr("number", true),
17226
+ rechargeAttempt: attr("json", true)
17227
+ });
17137
17228
  var membershipAuthorityEntities = {
17138
17229
  networkMembership: { attrs: {
17139
17230
  id: id5(),
@@ -17174,19 +17265,7 @@ var membershipAuthorityEntities = {
17174
17265
  ...times(),
17175
17266
  refundRequestedAt: attr("number", true),
17176
17267
  restartId: key(true),
17177
- // A card-network dispute on this membership's payment: Stripe's dispute id and status, the reason the cardholder gave,
17178
- // the status the membership held before it was suspended, and the re-issued charge once an operator collected again.
17179
- disputeId: key(true),
17180
- disputeStatus: attr("string", true),
17181
- disputeReason: attr("string", true),
17182
- disputeAmountCents: attr("number", true),
17183
- disputedAt: attr("number", true),
17184
- disputeClosedAt: attr("number", true),
17185
- disputePriorStatus: attr("string", true),
17186
- disputeEventId: key(true),
17187
- rechargePaymentIntentId: key(true),
17188
- rechargedAt: attr("number", true),
17189
- rechargeAttempt: attr("json", true)
17268
+ ...dispute()
17190
17269
  } },
17191
17270
  membershipRestart: { attrs: {
17192
17271
  id: id5(),
@@ -17262,7 +17341,8 @@ var membershipAuthorityEntities = {
17262
17341
  refundedCents: attr("number", true),
17263
17342
  providerRefundIds: attr("json", true),
17264
17343
  regiftedAt: attr("number", true),
17265
- formerRecipients: attr("json", true)
17344
+ formerRecipients: attr("json", true),
17345
+ ...dispute()
17266
17346
  } },
17267
17347
  networkSeatRecipient: { attrs: { id: id5(), appId: key(), chapterId: key(), environment: key(), runtime: key(), seatId: key(), recipientEmailNormalized: key(), createdAt: attr("number") } },
17268
17348
  membershipEffect: { attrs: {
@@ -17599,7 +17679,7 @@ async function readBoundedJson(response, signal, maxBytes, label) {
17599
17679
  };
17600
17680
  signal.addEventListener("abort", cancel, { once: true });
17601
17681
  let size = 0;
17602
- let text9 = "";
17682
+ let text10 = "";
17603
17683
  const decoder = new TextDecoder();
17604
17684
  try {
17605
17685
  while (true) {
@@ -17609,10 +17689,10 @@ async function readBoundedJson(response, signal, maxBytes, label) {
17609
17689
  if (done) break;
17610
17690
  size += value.byteLength;
17611
17691
  if (size > maxBytes) throw new TypeError(`${label} exceeds limit`);
17612
- text9 += decoder.decode(value, { stream: true });
17692
+ text10 += decoder.decode(value, { stream: true });
17613
17693
  }
17614
17694
  try {
17615
- return JSON.parse(text9 + decoder.decode());
17695
+ return JSON.parse(text10 + decoder.decode());
17616
17696
  } catch {
17617
17697
  throw new TypeError(`invalid ${label}`);
17618
17698
  }
@@ -18390,6 +18470,7 @@ export {
18390
18470
  deliverMemberContent,
18391
18471
  deliverMembershipChapterEffect,
18392
18472
  deliverSignupControl,
18473
+ disputedChargeCoversSeat,
18393
18474
  drainMembershipEffects,
18394
18475
  grantIncludedNamedSeat,
18395
18476
  liftSeatPurchaseWindow,
@@ -18401,7 +18482,9 @@ export {
18401
18482
  readDisputedCharge,
18402
18483
  readNamedSeatOffer,
18403
18484
  rechargeDisputedMembership,
18485
+ rechargeDisputedSeat,
18404
18486
  reconcileMembershipDispute,
18487
+ reconcileNamedSeatDispute,
18405
18488
  refundNamedSeatByOperator,
18406
18489
  regiftNamedSeat,
18407
18490
  releaseNamedSeatRecipientApplications,