@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.
@@ -42,6 +42,7 @@ __export(worker_exports, {
42
42
  deliverMemberContent: () => deliverMemberContent,
43
43
  deliverMembershipChapterEffect: () => deliverMembershipChapterEffect,
44
44
  deliverSignupControl: () => deliverSignupControl,
45
+ disputedChargeCoversSeat: () => disputedChargeCoversSeat,
45
46
  drainMembershipEffects: () => drainMembershipEffects,
46
47
  grantIncludedNamedSeat: () => grantIncludedNamedSeat,
47
48
  liftSeatPurchaseWindow: () => liftSeatPurchaseWindow,
@@ -53,7 +54,9 @@ __export(worker_exports, {
53
54
  readDisputedCharge: () => readDisputedCharge,
54
55
  readNamedSeatOffer: () => readNamedSeatOffer,
55
56
  rechargeDisputedMembership: () => rechargeDisputedMembership,
57
+ rechargeDisputedSeat: () => rechargeDisputedSeat,
56
58
  reconcileMembershipDispute: () => reconcileMembershipDispute,
59
+ reconcileNamedSeatDispute: () => reconcileNamedSeatDispute,
57
60
  refundNamedSeatByOperator: () => refundNamedSeatByOperator,
58
61
  regiftNamedSeat: () => regiftNamedSeat,
59
62
  releaseNamedSeatRecipientApplications: () => releaseNamedSeatRecipientApplications,
@@ -1697,9 +1700,9 @@ async function backfillCrm(deps) {
1697
1700
  }
1698
1701
 
1699
1702
  // src/email-retirement.ts
1700
- function isRetiredEmailCopy(subject, text9) {
1703
+ function isRetiredEmailCopy(subject, text10) {
1701
1704
  const subjectCopy = subject.trimStart().replace(/^\[dev\]\s*/i, "");
1702
- const bodyCopy = text9.trimStart().replace(/^\(dev redirect; original recipient: [^\r\n]*\)\r?\n\r?\n/, "");
1705
+ const bodyCopy = text10.trimStart().replace(/^\(dev redirect; original recipient: [^\r\n]*\)\r?\n\r?\n/, "");
1703
1706
  return /^\(\s*unused\s*:/i.test(subjectCopy) || /^Unused\.\s*Switched off\b/i.test(bodyCopy);
1704
1707
  }
1705
1708
 
@@ -1777,10 +1780,10 @@ function planDelivery(input) {
1777
1780
  if (isRetiredEmailCopy(renderedSubject, renderedText)) return { deliver: false, reason: "template-retired" };
1778
1781
  if ((0, import_crm5.validateEmailCopy)({ subject: renderedSubject, text: renderedText }, { allowedVariables: [] }).length) return { deliver: false, reason: "template-invalid" };
1779
1782
  const subject = (redirect ? "[dev] " : "") + renderedSubject;
1780
- const text9 = redirect ? `(dev redirect; original recipient: ${input.to})
1783
+ const text10 = redirect ? `(dev redirect; original recipient: ${input.to})
1781
1784
 
1782
1785
  ` + renderedText : renderedText;
1783
- return { deliver: true, transport, to, subject, text: text9, redirected: redirect };
1786
+ return { deliver: true, transport, to, subject, text: text10, redirected: redirect };
1784
1787
  }
1785
1788
 
1786
1789
  // src/notify.ts
@@ -1928,8 +1931,8 @@ function formatInstant(at2, timeZone) {
1928
1931
  var adminUrlFor = (origin) => `${origin}/admin/`;
1929
1932
  var membersUrlFor = (origin) => `${origin}/members/`;
1930
1933
  function applicantVariables(app) {
1931
- const text9 = (v) => typeof v === "string" ? v : "";
1932
- return { firstName: text9(app.firstName), lastName: text9(app.lastName), email: text9(app.email), phone: text9(app.phone), state: text9(app.state) };
1934
+ const text10 = (v) => typeof v === "string" ? v : "";
1935
+ return { firstName: text10(app.firstName), lastName: text10(app.lastName), email: text10(app.email), phone: text10(app.phone), state: text10(app.state) };
1933
1936
  }
1934
1937
  function sampleVariables(origin, timeZone, now = Date.now()) {
1935
1938
  const startAt = formatInstant(now + 864e5, timeZone);
@@ -3663,12 +3666,87 @@ function paymentLineItems(group, tier) {
3663
3666
 
3664
3667
  // src/membership-dispute-authority.ts
3665
3668
  var import_stripe6 = require("@odla-ai/stripe");
3669
+
3670
+ // src/membership-dispute-transition.ts
3666
3671
  var MEMBERSHIP_DISPUTED_STATUS = "payment_disputed";
3667
3672
  var DISPUTE_WON_STATUSES = ["won", "warning_closed"];
3668
3673
  var CLOSED_STATUSES = [...DISPUTE_WON_STATUSES, "lost"];
3669
- var TERMINAL = ["refunded", "refund_pending", "canceled", "declined", "restarting", "superseded"];
3674
+ function disputeTransition(row2, fact, eventId, now, terminal) {
3675
+ const status2 = String(fact.status ?? "unknown"), closed = CLOSED_STATUSES.includes(status2);
3676
+ const sameDispute = row2.disputeId === fact.objectId;
3677
+ if (sameDispute && Number(row2.disputeClosedAt ?? 0) > 0 && !closed) return null;
3678
+ const won = DISPUTE_WON_STATUSES.includes(status2);
3679
+ const suspended = row2.status === MEMBERSHIP_DISPUTED_STATUS, done = terminal.includes(String(row2.status));
3680
+ const restored = String(row2.disputePriorStatus ?? (row2.journeyApprovedAt ? "active" : "paid_pending_approval"));
3681
+ const next = done ? String(row2.status) : won ? suspended ? restored : String(row2.status) : MEMBERSHIP_DISPUTED_STATUS;
3682
+ return {
3683
+ status: next,
3684
+ disputeId: fact.objectId,
3685
+ disputeStatus: status2,
3686
+ disputeEventId: eventId,
3687
+ disputeClosedAt: closed ? now : 0,
3688
+ ...fact.disputeReason ? { disputeReason: fact.disputeReason } : {},
3689
+ ...fact.amountCents !== void 0 ? { disputeAmountCents: fact.amountCents } : {},
3690
+ ...sameDispute ? {} : { disputedAt: now },
3691
+ ...next === MEMBERSHIP_DISPUTED_STATUS && !suspended ? { disputePriorStatus: String(row2.status) } : {},
3692
+ revision: Number(row2.revision) + 1,
3693
+ updatedAt: now
3694
+ };
3695
+ }
3696
+
3697
+ // src/membership-seat-dispute.ts
3670
3698
  var text2 = (value) => typeof value === "string" && value ? value : void 0;
3671
- var refOf = (value) => typeof value === "string" ? text2(value) : text2(value?.id);
3699
+ var TERMINAL = ["payment_pending", "payment_processing", "payment_canceling", "refund_pending", "refunded", "canceled"];
3700
+ var namedSeatDisputeStanding = (seat, applied) => ({ seatId: String(seat.id), status: String(seat.status), ...text2(seat.disputeStatus) ? { disputeStatus: text2(seat.disputeStatus) } : {}, applied });
3701
+ function disputedChargeCoversSeat(charge, seat) {
3702
+ return charge.seatId === seat.id && charge.customerId === seat.providerCustomerId && charge.paymentIntentId !== void 0 && (charge.paymentIntentId === seat.providerPaymentIntentId || charge.paymentIntentId === seat.rechargePaymentIntentId);
3703
+ }
3704
+ async function reconcileNamedSeatDispute(db, input, now, charge, evidence) {
3705
+ if (!charge.seatId) membershipFail("membership_dispute_scope_mismatch");
3706
+ const membership = async (applied, seat) => {
3707
+ const id7 = await networkMembershipId(input), member = await membershipLoad(db, "networkMembership", id7);
3708
+ requireMembershipScope(member, input);
3709
+ return {
3710
+ membershipId: id7,
3711
+ status: String(member.status),
3712
+ termEndsAt: Number(member.termEndsAt),
3713
+ graceEndsAt: Number(member.graceEndsAt ?? 0),
3714
+ autoRenew: member.autoRenew === true,
3715
+ applied,
3716
+ ...text2(member.disputeStatus) ? { disputeStatus: text2(member.disputeStatus) } : {},
3717
+ ...seat ? { seat: namedSeatDisputeStanding(seat, applied) } : {}
3718
+ };
3719
+ };
3720
+ for (let attempt = 0; attempt < 8; attempt++) {
3721
+ const state = await loadMembershipAuthorityState(db, input), seat = await membershipLoad(db, "networkSeat", charge.seatId);
3722
+ if (!seat) return membership(false);
3723
+ requireMembershipScope(seat, input);
3724
+ if (charge.customerId !== seat.providerCustomerId) membershipFail("membership_dispute_scope_mismatch");
3725
+ if (!disputedChargeCoversSeat(charge, seat) || seat.disputeEventId === input.eventId) return membership(seat.disputeEventId === input.eventId, seat);
3726
+ const attrs = disputeTransition(seat, evidence.fact, input.eventId, now, TERMINAL);
3727
+ if (!attrs) return membership(false, seat);
3728
+ try {
3729
+ await db.transact([membershipUpdate("networkSeat", String(seat.id), attrs)], { mutationId: `seat-dispute:${seat.id}:${input.eventId}`, guards: [
3730
+ { ns: "networkSeat", id: String(seat.id), exists: true, equals: { revision: seat.revision } },
3731
+ { ns: "membershipAuthorityState", id: String(state.id), exists: true, equals: { mode: "authoritative", revision: state.revision } },
3732
+ { ns: "networkCommercialEvents", id: String(evidence.row.id), exists: true, equals: { digest: evidence.fact.digest } },
3733
+ evidence.guard
3734
+ ] });
3735
+ } catch (error) {
3736
+ if (error.code !== "transact_guard_failed") throw error;
3737
+ continue;
3738
+ }
3739
+ const saved = await membershipLoad(db, "networkSeat", String(seat.id));
3740
+ requireMembershipScope(saved, input);
3741
+ return membership(true, saved);
3742
+ }
3743
+ return membershipFail("membership_dispute_retry_required");
3744
+ }
3745
+
3746
+ // src/membership-dispute-authority.ts
3747
+ var TERMINAL2 = ["refunded", "refund_pending", "canceled", "declined", "restarting", "superseded"];
3748
+ var text3 = (value) => typeof value === "string" && value ? value : void 0;
3749
+ var refOf = (value) => typeof value === "string" ? text3(value) : text3(value?.id);
3672
3750
  async function readDisputedCharge(secretKey, chargeId, fetch2) {
3673
3751
  if (!/^ch_[A-Za-z0-9]+$/.test(chargeId)) membershipFail("invalid_dispute_charge");
3674
3752
  const config = { secretKey, ...fetch2 ? { fetch: fetch2 } : {} };
@@ -3682,7 +3760,7 @@ async function readDisputedCharge(secretKey, chargeId, fetch2) {
3682
3760
  if (!invoice.ok || invoice.body.id !== invoiceId) membershipFail("membership_dispute_provider_unavailable");
3683
3761
  subscriptionId = refOf(invoice.body.subscription) ?? refOf(invoice.body.parent?.subscription_details?.subscription);
3684
3762
  }
3685
- const seatId = text2(charge.body.metadata?.seatId);
3763
+ const seatId = text3(charge.body.metadata?.seatId);
3686
3764
  return {
3687
3765
  chargeId,
3688
3766
  customerId,
@@ -3708,37 +3786,23 @@ async function reconcileMembershipDispute(db, input, now, secretKey, resolved) {
3708
3786
  termEndsAt: Number(value.termEndsAt),
3709
3787
  graceEndsAt: Number(value.graceEndsAt ?? 0),
3710
3788
  autoRenew: value.autoRenew === true,
3711
- ...text2(value.disputeStatus) ? { disputeStatus: text2(value.disputeStatus) } : {},
3789
+ ...text3(value.disputeStatus) ? { disputeStatus: text3(value.disputeStatus) } : {},
3712
3790
  applied
3713
3791
  });
3714
3792
  const known = await membershipLoad(db, "networkMembership", id7);
3715
3793
  requireMembershipScope(known, input);
3716
3794
  if (known.disputeEventId === input.eventId) return receipt(known);
3795
+ const seatKnown = known.namedSeatId ? await membershipLoad(db, "networkSeat", String(known.namedSeatId)) : void 0;
3796
+ if (seatKnown?.disputeEventId === input.eventId) return { ...receipt(known, true), seat: namedSeatDisputeStanding(seatKnown, true) };
3717
3797
  const charge = resolved && resolved.chargeId === fact.chargeId ? resolved : await readDisputedCharge(secretKey, fact.chargeId);
3798
+ if (charge.customerId !== known.providerCustomerId) membershipFail("membership_dispute_scope_mismatch");
3799
+ if (charge.seatId) return reconcileNamedSeatDispute(db, input, now, charge, { row: row2, fact, guard: evidenceGuard });
3718
3800
  for (let attempt = 0; attempt < 8; attempt++) {
3719
3801
  const state = await loadMembershipAuthorityState(db, input), member = await membershipLoad(db, "networkMembership", id7);
3720
3802
  requireMembershipScope(member, input);
3721
- if (charge.customerId !== member.providerCustomerId) membershipFail("membership_dispute_scope_mismatch");
3722
3803
  if (!disputedChargeCoversMembership(charge, member) || member.disputeEventId === input.eventId) return receipt(member);
3723
- const status2 = String(fact.status ?? "unknown"), closed = CLOSED_STATUSES.includes(status2);
3724
- const sameDispute = member.disputeId === fact.objectId;
3725
- if (sameDispute && Number(member.disputeClosedAt ?? 0) > 0 && !closed) return receipt(member, false);
3726
- const won = DISPUTE_WON_STATUSES.includes(status2);
3727
- const suspended = member.status === MEMBERSHIP_DISPUTED_STATUS, terminal = TERMINAL.includes(String(member.status));
3728
- const next = terminal ? String(member.status) : won ? suspended ? String(member.disputePriorStatus ?? (member.journeyApprovedAt ? "active" : "paid_pending_approval")) : String(member.status) : MEMBERSHIP_DISPUTED_STATUS;
3729
- const attrs = {
3730
- status: next,
3731
- disputeId: fact.objectId,
3732
- disputeStatus: status2,
3733
- disputeEventId: input.eventId,
3734
- disputeClosedAt: closed ? now : 0,
3735
- ...fact.disputeReason ? { disputeReason: fact.disputeReason } : {},
3736
- ...fact.amountCents !== void 0 ? { disputeAmountCents: fact.amountCents } : {},
3737
- ...sameDispute ? {} : { disputedAt: now },
3738
- ...next === MEMBERSHIP_DISPUTED_STATUS && !suspended ? { disputePriorStatus: String(member.status) } : {},
3739
- revision: Number(member.revision) + 1,
3740
- updatedAt: now
3741
- };
3804
+ const attrs = disputeTransition(member, fact, input.eventId, now, TERMINAL2);
3805
+ if (!attrs) return receipt(member, false);
3742
3806
  try {
3743
3807
  await db.transact([membershipUpdate("networkMembership", id7, attrs)], { mutationId: `membership-dispute:${id7}:${input.eventId}`, guards: [
3744
3808
  { ns: "networkMembership", id: id7, exists: true, equals: { revision: member.revision } },
@@ -4860,6 +4924,9 @@ function namedSeatRecipientState(seat) {
4860
4924
  return seat.refundReason === "declined" ? "declined" : "refunded";
4861
4925
  case "canceled":
4862
4926
  return "canceled";
4927
+ // A card-network dispute on the seat's own payment pauses the recipient until it is won or re-charged.
4928
+ case "payment_disputed":
4929
+ return "disputed";
4863
4930
  default:
4864
4931
  return "payment_pending";
4865
4932
  }
@@ -5188,6 +5255,7 @@ var handleAdminMembershipSeats = async (req, url, env, ctx) => {
5188
5255
 
5189
5256
  // src/membership-dispute-recharge.ts
5190
5257
  var import_stripe9 = require("@odla-ai/stripe");
5258
+ var NS = { membership: "networkMembership", seat: "networkSeat" };
5191
5259
  var fail6 = (code, extra = {}) => {
5192
5260
  throw Object.assign(new Error(code), { code, ...extra });
5193
5261
  };
@@ -5201,65 +5269,64 @@ async function defaultPaymentMethod(config, subscriptionId, customerId) {
5201
5269
  if (!customer.ok) fail6("membership_dispute_provider_unavailable");
5202
5270
  return refOf2(customer.body.invoice_settings?.default_payment_method);
5203
5271
  }
5204
- async function rechargeDisputedMembership(db, membershipId, secretKey, now) {
5272
+ async function rechargeDisputed(db, kind, id7, secretKey, now) {
5205
5273
  membershipClock(now);
5206
- let member = await membershipLoad(db, "networkMembership", membershipId);
5207
- if (!member) fail6("membership_not_found");
5208
- const scope2 = { chapterId: String(member.chapterId), environment: String(member.environment) };
5209
- const amountCents = Number(member.disputeAmountCents ?? member.amountCents), currency = String(member.currency);
5274
+ const ns = NS[kind];
5275
+ let row2 = await membershipLoad(db, ns, id7);
5276
+ if (!row2) fail6("membership_not_found");
5277
+ const scope2 = { chapterId: String(row2.chapterId), environment: String(row2.environment) };
5278
+ const membershipId = kind === "membership" ? id7 : String(row2.primaryMembershipId);
5279
+ const amountCents = Number(row2.disputeAmountCents ?? row2.amountCents), currency = String(row2.currency);
5210
5280
  const done = (value, replayed) => ({
5211
5281
  membershipId,
5282
+ ...kind === "seat" ? { seatId: id7 } : {},
5212
5283
  status: String(value.status),
5213
5284
  paymentIntentId: String(value.rechargePaymentIntentId),
5214
5285
  amountCents,
5215
5286
  currency,
5216
5287
  ...replayed ? { replayed } : {}
5217
5288
  });
5218
- if (member.status !== MEMBERSHIP_DISPUTED_STATUS) {
5219
- if (member.disputeStatus === "recharged" && member.rechargePaymentIntentId) return done(member, true);
5289
+ if (row2.status !== MEMBERSHIP_DISPUTED_STATUS) {
5290
+ if (row2.disputeStatus === "recharged" && row2.rechargePaymentIntentId) return done(row2, true);
5220
5291
  fail6("membership_not_disputed");
5221
5292
  }
5222
- if (member.disputeStatus !== "lost") fail6("membership_dispute_open");
5293
+ if (row2.disputeStatus !== "lost") fail6("membership_dispute_open");
5223
5294
  if (!Number.isSafeInteger(amountCents) || amountCents <= 0 || !/^[a-z]{3}$/.test(currency)) fail6("membership_dispute_amount_invalid");
5224
- let attempt = member.rechargeAttempt;
5295
+ let attempt = row2.rechargeAttempt;
5225
5296
  if (!attempt || attempt.finishedAt !== void 0) {
5226
- attempt = { key: `membership-recharge:${membershipId}:${String(member.disputeId)}:${now}`, startedAt: now };
5297
+ attempt = { key: `${kind}-recharge:${id7}:${String(row2.disputeId)}:${now}`, startedAt: now };
5227
5298
  await db.transact(
5228
- [membershipUpdate("networkMembership", membershipId, { rechargeAttempt: attempt, revision: Number(member.revision) + 1, updatedAt: now })],
5229
- { mutationId: `membership-recharge-attempt:${attempt.key}`, guards: [{
5230
- ns: "networkMembership",
5231
- id: membershipId,
5232
- exists: true,
5233
- equals: { revision: member.revision, status: MEMBERSHIP_DISPUTED_STATUS }
5234
- }] }
5299
+ [membershipUpdate(ns, id7, { rechargeAttempt: attempt, revision: Number(row2.revision) + 1, updatedAt: now })],
5300
+ { mutationId: `membership-recharge-attempt:${attempt.key}`, guards: [{ ns, id: id7, exists: true, equals: { revision: row2.revision, status: MEMBERSHIP_DISPUTED_STATUS } }] }
5235
5301
  );
5236
- member = await membershipLoad(db, "networkMembership", membershipId);
5237
- if (!member || member.status !== MEMBERSHIP_DISPUTED_STATUS) fail6("membership_dispute_changed");
5302
+ row2 = await membershipLoad(db, ns, id7);
5303
+ if (!row2 || row2.status !== MEMBERSHIP_DISPUTED_STATUS) fail6("membership_dispute_changed");
5238
5304
  }
5239
5305
  const config = { secretKey };
5240
- const paymentMethod = await defaultPaymentMethod(config, String(member.providerSubscriptionId), String(member.providerCustomerId));
5306
+ const paymentMethod = await defaultPaymentMethod(config, String(row2.providerSubscriptionId), String(row2.providerCustomerId));
5241
5307
  if (!paymentMethod) fail6("recharge_payment_method_unavailable");
5242
5308
  const intent = await (0, import_stripe9.stripeRequest)(config, "POST", "/v1/payment_intents", {
5243
5309
  amount: amountCents,
5244
5310
  currency,
5245
- customer: String(member.providerCustomerId),
5311
+ customer: String(row2.providerCustomerId),
5246
5312
  payment_method: paymentMethod,
5247
5313
  off_session: "true",
5248
5314
  confirm: "true",
5249
- description: `Membership payment reissued after dispute ${String(member.disputeId)}`,
5315
+ description: `${kind === "seat" ? "Named seat" : "Membership"} payment reissued after dispute ${String(row2.disputeId)}`,
5250
5316
  metadata: {
5251
- ...stripePartitionMetadata({ appId: String(member.appId), chapterId: scope2.chapterId, environment: scope2.environment, runtime: String(member.runtime) }),
5252
- applicationId: String(member.applicationId),
5317
+ ...stripePartitionMetadata({ appId: String(row2.appId), chapterId: scope2.chapterId, environment: scope2.environment, runtime: String(row2.runtime) }),
5318
+ applicationId: String(row2.applicationId),
5253
5319
  membershipId,
5254
- disputeId: String(member.disputeId),
5320
+ ...kind === "seat" ? { seatId: id7 } : {},
5321
+ disputeId: String(row2.disputeId),
5255
5322
  membershipRecharge: "true"
5256
5323
  }
5257
5324
  }, { idempotencyKey: attempt.key });
5258
5325
  const body = intent.body, error = body.error ?? {}, paymentStatus = String(body.status ?? "");
5259
5326
  const finish = async (outcome) => {
5260
- const current = await membershipLoad(db, "networkMembership", membershipId);
5327
+ const current = await membershipLoad(db, ns, id7);
5261
5328
  if (!current) return;
5262
- await db.transact([membershipUpdate("networkMembership", membershipId, {
5329
+ await db.transact([membershipUpdate(ns, id7, {
5263
5330
  rechargeAttempt: { ...attempt, finishedAt: now, outcome },
5264
5331
  revision: Number(current.revision) + 1,
5265
5332
  updatedAt: now
@@ -5277,15 +5344,15 @@ async function rechargeDisputedMembership(db, membershipId, secretKey, now) {
5277
5344
  fail6("recharge_declined", { declineCode: String(body.last_payment_error?.decline_code ?? paymentStatus) });
5278
5345
  }
5279
5346
  if (paymentStatus !== "succeeded") fail6("recharge_pending", { paymentStatus });
5280
- if (refOf2(body.customer) !== member.providerCustomerId || body.amount !== amountCents) fail6("recharge_mismatch");
5347
+ if (refOf2(body.customer) !== row2.providerCustomerId || body.amount !== amountCents) fail6("recharge_mismatch");
5281
5348
  for (let retry = 0; retry < 8; retry++) {
5282
- const state = await loadMembershipAuthorityState(db, scope2), current = await membershipLoad(db, "networkMembership", membershipId);
5349
+ const state = await loadMembershipAuthorityState(db, scope2), current = await membershipLoad(db, ns, id7);
5283
5350
  if (!current) fail6("membership_not_found");
5284
5351
  if (current.rechargePaymentIntentId === body.id && current.status !== MEMBERSHIP_DISPUTED_STATUS) return done(current, true);
5285
- const status2 = String(current.disputePriorStatus ?? (current.journeyApprovedAt ? "active" : "paid_pending_approval"));
5352
+ const status2 = String(current.disputePriorStatus ?? (kind === "seat" ? "active" : current.journeyApprovedAt ? "active" : "paid_pending_approval"));
5286
5353
  try {
5287
5354
  await db.transact(
5288
- [membershipUpdate("networkMembership", membershipId, {
5355
+ [membershipUpdate(ns, id7, {
5289
5356
  status: status2,
5290
5357
  disputeStatus: "recharged",
5291
5358
  rechargePaymentIntentId: String(body.id),
@@ -5295,7 +5362,7 @@ async function rechargeDisputedMembership(db, membershipId, secretKey, now) {
5295
5362
  updatedAt: now
5296
5363
  })],
5297
5364
  { mutationId: `membership-recharged:${attempt.key}`, guards: [
5298
- { ns: "networkMembership", id: membershipId, exists: true, equals: { revision: current.revision, status: MEMBERSHIP_DISPUTED_STATUS } },
5365
+ { ns, id: id7, exists: true, equals: { revision: current.revision, status: MEMBERSHIP_DISPUTED_STATUS } },
5299
5366
  { ns: "membershipAuthorityState", id: String(state.id), exists: true, equals: { mode: "authoritative", revision: state.revision } }
5300
5367
  ] }
5301
5368
  );
@@ -5303,10 +5370,12 @@ async function rechargeDisputedMembership(db, membershipId, secretKey, now) {
5303
5370
  if (cause.code !== "transact_guard_failed") throw cause;
5304
5371
  continue;
5305
5372
  }
5306
- return done(await membershipLoad(db, "networkMembership", membershipId));
5373
+ return done(await membershipLoad(db, ns, id7));
5307
5374
  }
5308
5375
  return fail6("membership_dispute_retry_required");
5309
5376
  }
5377
+ var rechargeDisputedMembership = (db, membershipId, secretKey, now) => rechargeDisputed(db, "membership", membershipId, secretKey, now);
5378
+ var rechargeDisputedSeat = (db, seatId, secretKey, now) => rechargeDisputed(db, "seat", seatId, secretKey, now);
5310
5379
 
5311
5380
  // src/worker-routes-admin-membership-disputes.ts
5312
5381
  var LIST2 = "/api/admin/membership-disputes";
@@ -5323,6 +5392,9 @@ var PUBLIC2 = [
5323
5392
  "memberNumber",
5324
5393
  "amountCents",
5325
5394
  "currency",
5395
+ "recipientName",
5396
+ "primaryMembershipId",
5397
+ "purchasedAt",
5326
5398
  "disputeId",
5327
5399
  "disputeStatus",
5328
5400
  "disputeReason",
@@ -5370,14 +5442,17 @@ var handleAdminMembershipDisputes = async (req, url, env, ctx) => {
5370
5442
  return json({ error: "the membership authority is not hosted by this deployment" }, 404);
5371
5443
  }
5372
5444
  if (!one) {
5373
- const rows5 = (await db.query({ networkMembership: { $: { where: { environment: env.ODLA_ENV }, order: { updatedAt: "desc" }, limit: 500 } } })).networkMembership ?? [];
5445
+ const page = { $: { where: { environment: env.ODLA_ENV }, order: { updatedAt: "desc" }, limit: 500 } };
5446
+ const found = await db.query({ networkMembership: page, networkSeat: page });
5447
+ const memberships = found.networkMembership ?? [], seats = found.networkSeat ?? [];
5448
+ const rows5 = [...memberships.map((row2) => ({ ...project2(row2), kind: "membership" })), ...seats.map((row2) => ({ ...project2(row2), kind: "seat" }))];
5374
5449
  const disputes = rows5.filter((row2) => typeof row2.disputeId === "string").sort((a, b) => Number(b.disputedAt ?? 0) - Number(a.disputedAt ?? 0));
5375
- return json({ disputes: disputes.map(project2), truncated: rows5.length >= 500 });
5450
+ return json({ disputes, truncated: memberships.length >= 500 || seats.length >= 500 });
5376
5451
  }
5377
5452
  const secret = await getVaultSecret(db, operator.stripeSecretName ?? "stripe_secret_key");
5378
5453
  if (!secret) return json({ error: "network payment provider unavailable" }, 503);
5379
5454
  try {
5380
- const receipt = await rechargeDisputedMembership(db, one[1], secret, Date.now());
5455
+ const receipt = await (one[1].startsWith("seat_") ? rechargeDisputedSeat : rechargeDisputedMembership)(db, one[1], secret, Date.now());
5381
5456
  return json({ ok: true, ...receipt });
5382
5457
  } catch (error) {
5383
5458
  const failure = error;
@@ -5397,13 +5472,13 @@ var handleAdminMembershipDisputes = async (req, url, env, ctx) => {
5397
5472
 
5398
5473
  // src/membership-policy-versions.ts
5399
5474
  var LIMIT = 200;
5400
- var text3 = (value) => typeof value === "string" && value.length > 0 && value.length <= 200 ? value : void 0;
5475
+ var text4 = (value) => typeof value === "string" && value.length > 0 && value.length <= 200 ? value : void 0;
5401
5476
  var instant = (value) => {
5402
5477
  const at2 = typeof value === "number" ? value : typeof value === "string" ? Date.parse(value) : NaN;
5403
5478
  return Number.isSafeInteger(at2) && at2 >= 0 ? at2 : void 0;
5404
5479
  };
5405
5480
  function provenance(row2) {
5406
- const source = text3(row2.source), actor = text3(row2.actor ?? row2.$createdBy), recordedAt = instant(row2.recordedAt ?? row2.createdAt ?? row2.$createdAt);
5481
+ const source = text4(row2.source), actor = text4(row2.actor ?? row2.$createdBy), recordedAt = instant(row2.recordedAt ?? row2.createdAt ?? row2.$createdAt);
5407
5482
  return { ...source ? { source } : {}, ...actor ? { actor } : {}, ...recordedAt !== void 0 ? { recordedAt } : {} };
5408
5483
  }
5409
5484
  var live = (w, now) => w.from <= now && (w.until === void 0 || now < w.until);
@@ -6404,8 +6479,8 @@ var APPLICATION_LABELS = {
6404
6479
  state: "State"
6405
6480
  };
6406
6481
  function humanize(id7) {
6407
- const text9 = id7.replace(/([a-z0-9])([A-Z])/g, "$1 $2").replace(/[_-]+/g, " ").trim();
6408
- return text9 ? `${text9[0].toUpperCase()}${text9.slice(1)}` : id7;
6482
+ const text10 = id7.replace(/([a-z0-9])([A-Z])/g, "$1 $2").replace(/[_-]+/g, " ").trim();
6483
+ return text10 ? `${text10[0].toUpperCase()}${text10.slice(1)}` : id7;
6409
6484
  }
6410
6485
  function applicationDefs(chapter) {
6411
6486
  try {
@@ -6472,7 +6547,7 @@ var ID3 = /^[a-z][a-z0-9-]{1,79}$/;
6472
6547
  var PRICE = /^price_[A-Za-z0-9]{12,}$/;
6473
6548
  var CURRENCY = /^[a-z]{3}$/;
6474
6549
  var record = (value) => value && typeof value === "object" && !Array.isArray(value) ? value : null;
6475
- var text4 = (value, path, errors, max) => {
6550
+ var text5 = (value, path, errors, max) => {
6476
6551
  if (typeof value !== "string" || !value.trim()) {
6477
6552
  errors.push(`${path} is required`);
6478
6553
  return "";
@@ -6500,23 +6575,23 @@ function validateGiftOfferCatalog(input, tierIds) {
6500
6575
  }
6501
6576
  const allowed2 = /* @__PURE__ */ new Set(["id", "target", "billingMode", "stripePriceId", "priceCents", "currency", "redemptionLifetimeHours", "activeFrom", "activeUntil", "title", "description", "ctaLabel"]);
6502
6577
  for (const key3 of Object.keys(item)) if (!allowed2.has(key3)) errors.push(`${path}.${key3} is not supported`);
6503
- const id7 = text4(item.id, `${path}.id`, errors, 80);
6578
+ const id7 = text5(item.id, `${path}.id`, errors, 80);
6504
6579
  if (id7 && !ID3.test(id7)) errors.push(`${path}.id must be a lowercase kebab-case id`);
6505
6580
  if (ids2.has(id7)) errors.push(`${path}.id duplicates "${id7}"`);
6506
6581
  ids2.add(id7);
6507
6582
  const targetRaw = record(item.target);
6508
6583
  let target = { type: "entitlement", entitlementKey: "invalid" };
6509
6584
  if (targetRaw?.type === "tier") {
6510
- const tierId = text4(targetRaw.tierId, `${path}.target.tierId`, errors, 80);
6585
+ const tierId = text5(targetRaw.tierId, `${path}.target.tierId`, errors, 80);
6511
6586
  if (!tierIds.has(tierId)) errors.push(`${path}.target.tierId must reference a declared tier`);
6512
6587
  target = { type: "tier", tierId };
6513
6588
  } else if (targetRaw?.type === "entitlement") {
6514
- const entitlementKey = text4(targetRaw.entitlementKey, `${path}.target.entitlementKey`, errors, 80);
6589
+ const entitlementKey = text5(targetRaw.entitlementKey, `${path}.target.entitlementKey`, errors, 80);
6515
6590
  if (entitlementKey && !ID3.test(entitlementKey)) errors.push(`${path}.target.entitlementKey must be a lowercase kebab-case id`);
6516
6591
  target = { type: "entitlement", entitlementKey };
6517
6592
  } else errors.push(`${path}.target.type must be tier or entitlement`);
6518
6593
  if (item.billingMode !== "one_time" && item.billingMode !== "subscription") errors.push(`${path}.billingMode must be one_time or subscription`);
6519
- const stripePriceId = text4(item.stripePriceId, `${path}.stripePriceId`, errors, 128);
6594
+ const stripePriceId = text5(item.stripePriceId, `${path}.stripePriceId`, errors, 128);
6520
6595
  if (stripePriceId && !PRICE.test(stripePriceId)) errors.push(`${path}.stripePriceId must be a real Stripe Price id`);
6521
6596
  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`);
6522
6597
  const currency = typeof item.currency === "string" ? item.currency.trim().toLowerCase() : "";
@@ -6538,9 +6613,9 @@ function validateGiftOfferCatalog(input, tierIds) {
6538
6613
  redemptionLifetimeHours: lifetime,
6539
6614
  ...activeFrom !== void 0 ? { activeFrom } : {},
6540
6615
  ...activeUntil !== void 0 ? { activeUntil } : {},
6541
- title: text4(item.title, `${path}.title`, errors, 160),
6542
- description: text4(item.description, `${path}.description`, errors, 2e3),
6543
- ctaLabel: text4(item.ctaLabel, `${path}.ctaLabel`, errors, 120)
6616
+ title: text5(item.title, `${path}.title`, errors, 160),
6617
+ description: text5(item.description, `${path}.description`, errors, 2e3),
6618
+ ctaLabel: text5(item.ctaLabel, `${path}.ctaLabel`, errors, 120)
6544
6619
  });
6545
6620
  }
6546
6621
  return errors.length ? { ok: false, errors } : { ok: true, value: { schemaVersion: 1, offers } };
@@ -6956,17 +7031,17 @@ async function readBoundedRequestText(req, maxBytes) {
6956
7031
  if (!reader) return "";
6957
7032
  const decoder = new TextDecoder();
6958
7033
  let bytes = 0;
6959
- let text9 = "";
7034
+ let text10 = "";
6960
7035
  for (; ; ) {
6961
7036
  const { done, value } = await reader.read();
6962
7037
  if (req.signal.aborted) return invalid2();
6963
7038
  if (done) {
6964
7039
  complete = true;
6965
- return text9 + decoder.decode();
7040
+ return text10 + decoder.decode();
6966
7041
  }
6967
7042
  bytes += value.byteLength;
6968
7043
  if (bytes > maxBytes) return json({ error: "request body too large" }, 413);
6969
- text9 += decoder.decode(value, { stream: true });
7044
+ text10 += decoder.decode(value, { stream: true });
6970
7045
  }
6971
7046
  } catch {
6972
7047
  return invalid2();
@@ -7070,9 +7145,9 @@ async function paymentQuoteRoute(url, env, ctx, prepare = false, request) {
7070
7145
  let selection;
7071
7146
  if (request?.body) {
7072
7147
  try {
7073
- const text9 = await readBoundedRequestText(request, 2048);
7074
- if (text9 instanceof Response) return text9;
7075
- const body = JSON.parse(text9);
7148
+ const text10 = await readBoundedRequestText(request, 2048);
7149
+ if (text10 instanceof Response) return text10;
7150
+ const body = JSON.parse(text10);
7076
7151
  if (!body || typeof body !== "object" || Array.isArray(body) || Object.keys(body).some((key3) => key3 !== "namedSeat")) throw new Error("invalid body");
7077
7152
  selection = body.namedSeat;
7078
7153
  } catch {
@@ -7954,10 +8029,10 @@ function boundedString(value, path, errors, max, required = false) {
7954
8029
  errors.push(`${path} must be a string`);
7955
8030
  return void 0;
7956
8031
  }
7957
- const text9 = value.trim();
7958
- if (required && !text9) errors.push(`${path} is required`);
7959
- if (text9.length > max) errors.push(`${path} must be at most ${max.toLocaleString("en-US")} characters`);
7960
- return text9;
8032
+ const text10 = value.trim();
8033
+ if (required && !text10) errors.push(`${path} is required`);
8034
+ if (text10.length > max) errors.push(`${path} must be at most ${max.toLocaleString("en-US")} characters`);
8035
+ return text10;
7961
8036
  }
7962
8037
  function validateMembershipPageDocument(input, options = {}) {
7963
8038
  const errors = [];
@@ -8006,8 +8081,8 @@ function validateMembershipPageDocument(input, options = {}) {
8006
8081
  else {
8007
8082
  if (item.benefits.length > MAX_BENEFITS) errors.push(`${path}.benefits supports at most ${MAX_BENEFITS} entries`);
8008
8083
  benefits = item.benefits.slice(0, MAX_BENEFITS).flatMap((benefit, benefitIndex) => {
8009
- const text9 = boundedString(benefit, `${path}.benefits[${benefitIndex}]`, errors, 2e3, true);
8010
- return text9 ? [text9] : [];
8084
+ const text10 = boundedString(benefit, `${path}.benefits[${benefitIndex}]`, errors, 2e3, true);
8085
+ return text10 ? [text10] : [];
8011
8086
  });
8012
8087
  }
8013
8088
  }
@@ -8101,7 +8176,7 @@ var recordOf = (value) => value !== null && typeof value === "object" && !Array.
8101
8176
  function unknownKeys(value, allowed2, path, errors) {
8102
8177
  for (const key3 of Object.keys(value)) if (!allowed2.has(key3)) errors.push(`${path}.${key3} is not supported`);
8103
8178
  }
8104
- function text5(value, path, errors, max = 65536) {
8179
+ function text6(value, path, errors, max = 65536) {
8105
8180
  if (typeof value !== "string" || !value.trim()) {
8106
8181
  errors.push(`${path} is required`);
8107
8182
  return "";
@@ -8124,11 +8199,11 @@ function tiersOf(value, errors) {
8124
8199
  return [];
8125
8200
  }
8126
8201
  unknownKeys(item, TIER_KEYS2, path, errors);
8127
- const id7 = text5(item.id, `${path}.id`, errors, 80);
8202
+ const id7 = text6(item.id, `${path}.id`, errors, 80);
8128
8203
  if (id7 && !TIER.test(id7)) errors.push(`${path}.id must be a lowercase kebab-case id`);
8129
8204
  if (seen.has(id7)) errors.push(`${path}.id duplicates "${id7}"`);
8130
8205
  seen.add(id7);
8131
- const name = text5(item.name, `${path}.name`, errors, 160);
8206
+ const name = text6(item.name, `${path}.name`, errors, 160);
8132
8207
  if (typeof item.available !== "boolean") errors.push(`${path}.available must be boolean`);
8133
8208
  if (!Number.isInteger(item.sortOrder) || Number(item.sortOrder) < 0) errors.push(`${path}.sortOrder must be a non-negative integer`);
8134
8209
  if (!Number.isInteger(item.priceCents) || Number(item.priceCents) < 0 || Number(item.priceCents) > 1e8) {
@@ -8146,7 +8221,7 @@ function tiersOf(value, errors) {
8146
8221
  if (item.interval !== void 0 && item.interval !== "year" && item.interval !== "month") {
8147
8222
  errors.push(`${path}.interval must be year or month`);
8148
8223
  }
8149
- const blurb = item.blurb === void 0 ? void 0 : text5(item.blurb, `${path}.blurb`, errors, 4e3);
8224
+ const blurb = item.blurb === void 0 ? void 0 : text6(item.blurb, `${path}.blurb`, errors, 4e3);
8150
8225
  return [{
8151
8226
  id: id7,
8152
8227
  name,
@@ -8204,7 +8279,7 @@ function policyOf(value, errors, tiers) {
8204
8279
  const result = {};
8205
8280
  for (const key3 of allowed2) {
8206
8281
  if (input[key3] === void 0 && key3 !== "disclaimerText" && key3 !== "refundPolicyText") continue;
8207
- result[key3] = text5(input[key3], `document.policy.${key3}`, errors);
8282
+ result[key3] = text6(input[key3], `document.policy.${key3}`, errors);
8208
8283
  }
8209
8284
  const tierPolicies = {};
8210
8285
  if (input.tierPolicies !== void 0) {
@@ -8219,8 +8294,8 @@ function policyOf(value, errors, tiers) {
8219
8294
  }
8220
8295
  unknownKeys(policy2, /* @__PURE__ */ new Set(["refundPolicyText", "trustCopy"]), path, errors);
8221
8296
  tierPolicies[id7] = {
8222
- refundPolicyText: text5(policy2.refundPolicyText, `${path}.refundPolicyText`, errors),
8223
- ...policy2.trustCopy !== void 0 ? { trustCopy: text5(policy2.trustCopy, `${path}.trustCopy`, errors) } : {}
8297
+ refundPolicyText: text6(policy2.refundPolicyText, `${path}.refundPolicyText`, errors),
8298
+ ...policy2.trustCopy !== void 0 ? { trustCopy: text6(policy2.trustCopy, `${path}.trustCopy`, errors) } : {}
8224
8299
  };
8225
8300
  }
8226
8301
  }
@@ -8260,9 +8335,9 @@ function validateSignupControlDocument(input) {
8260
8335
  if (!Number.isSafeInteger(root.generatedAt) || Number(root.generatedAt) < 1) errors.push("document.generatedAt must be epoch milliseconds");
8261
8336
  const source = recordOf(root.source);
8262
8337
  const target = recordOf(root.target);
8263
- const appId = text5(source?.appId, "document.source.appId", errors, 63);
8264
- const chapterId = text5(target?.chapterId, "document.target.chapterId", errors, 63);
8265
- const environment = text5(target?.environment, "document.target.environment", errors, 63);
8338
+ const appId = text6(source?.appId, "document.source.appId", errors, 63);
8339
+ const chapterId = text6(target?.chapterId, "document.target.chapterId", errors, 63);
8340
+ const environment = text6(target?.environment, "document.target.environment", errors, 63);
8266
8341
  if (appId && !SLUG.test(appId)) errors.push("document.source.appId must be a lowercase app slug");
8267
8342
  if (chapterId && !SLUG.test(chapterId)) errors.push("document.target.chapterId must be a lowercase app slug");
8268
8343
  if (environment && !ENVIRONMENT.test(environment)) errors.push("document.target.environment is invalid");
@@ -10533,7 +10608,7 @@ async function startSubscription(req, env, ctx) {
10533
10608
 
10534
10609
  // src/worker-payment-receipt.ts
10535
10610
  var json2 = (value, status2 = 200) => Response.json(value, { status: status2, headers: { "cache-control": "no-store", "referrer-policy": "no-referrer" } });
10536
- var escape = (text9) => text9.replace(/[&<>"']/g, (char) => ({ "&": "&amp;", "<": "&lt;", ">": "&gt;", '"': "&quot;", "'": "&#39;" })[char]);
10611
+ var escape = (text10) => text10.replace(/[&<>"']/g, (char) => ({ "&": "&amp;", "<": "&lt;", ">": "&gt;", '"': "&quot;", "'": "&#39;" })[char]);
10537
10612
  async function paymentReceiptRoute(req, url, env, ctx) {
10538
10613
  const id7 = url.searchParams.get("application");
10539
10614
  if (!id7 || id7.length > 160) return json2({ error: "application required" }, 400);
@@ -10544,7 +10619,7 @@ async function paymentReceiptRoute(req, url, env, ctx) {
10544
10619
  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);
10545
10620
  const receipt = intent.paymentReceipt;
10546
10621
  if (!receipt) return json2({ pending: true }, 202);
10547
- const text9 = paymentReceiptText(receipt, ctx.chapter.name);
10622
+ const text10 = paymentReceiptText(receipt, ctx.chapter.name);
10548
10623
  if (req.method === "POST") {
10549
10624
  if (!env.SEND_EMAIL || !env.EMAIL_FROM) return json2({ error: "Email is unavailable. You can print or save your receipt." }, 503);
10550
10625
  const group = (await db.query({ groups: { $: { where: { id: ctx.chapter.id }, limit: 1 } } })).groups?.[0];
@@ -10562,7 +10637,7 @@ async function paymentReceiptRoute(req, url, env, ctx) {
10562
10637
  template: "paymentReceipt",
10563
10638
  to: String(intent.purchaserEmailNormalized),
10564
10639
  applicationId: id7,
10565
- vars: { chapterName: ctx.chapter.name, receiptText: text9 },
10640
+ vars: { chapterName: ctx.chapter.name, receiptText: text10 },
10566
10641
  dedupeKey: `requested-payment-receipt:${receipt.invoiceId}`,
10567
10642
  event: { label: "Payment receipt", trigger: "Purchaser requests their receipt", audience: "Purchaser", variables: ["chapterName", "receiptText"] }
10568
10643
  });
@@ -10572,7 +10647,7 @@ async function paymentReceiptRoute(req, url, env, ctx) {
10572
10647
  const nonce = crypto.randomUUID();
10573
10648
  return new Response(`<!doctype html><html lang="en"><meta charset="utf-8"><meta name="viewport" content="width=device-width"><title>Payment receipt</title>
10574
10649
  <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>
10575
- <h1>Payment receipt</h1><pre>${escape(text9)}</pre><button id="print">Print / save PDF</button>
10650
+ <h1>Payment receipt</h1><pre>${escape(text10)}</pre><button id="print">Print / save PDF</button>
10576
10651
  <script nonce="${nonce}">document.getElementById('print').addEventListener('click',()=>window.print())</script></html>`, {
10577
10652
  headers: {
10578
10653
  "content-type": "text/html; charset=utf-8",
@@ -11042,16 +11117,16 @@ var handleNamedSeat = async (req, url, env, ctx) => {
11042
11117
  };
11043
11118
 
11044
11119
  // src/named-seat-accepted-notice.ts
11045
- var text6 = (value) => typeof value === "string" ? value : "";
11120
+ var text7 = (value) => typeof value === "string" ? value : "";
11046
11121
  async function notifyNamedSeatAccepted(db, env, ctx, input) {
11047
11122
  const purchaser = await membershipLoad(db, "applications", input.primaryApplicationId);
11048
- if (!purchaser || !chapterBillingApplication(purchaser, ctx.chapter.id, env.ODLA_RUNTIME) || !text6(purchaser.email)) {
11123
+ if (!purchaser || !chapterBillingApplication(purchaser, ctx.chapter.id, env.ODLA_RUNTIME) || !text7(purchaser.email)) {
11049
11124
  return { sent: false, reason: "purchaser-missing" };
11050
11125
  }
11051
11126
  const group = await membershipLoad(db, "groups", ctx.chapter.id);
11052
11127
  if (!group) return { sent: false, reason: "group-missing" };
11053
11128
  const config = emailGroupFrom(group);
11054
- const purchaserName = [purchaser.firstName, purchaser.lastName].map(text6).join(" ").replace(/\s+/g, " ").trim();
11129
+ const purchaserName = [purchaser.firstName, purchaser.lastName].map(text7).join(" ").replace(/\s+/g, " ").trim();
11055
11130
  return sendTemplated({
11056
11131
  db,
11057
11132
  envName: env.ODLA_ENV,
@@ -11063,12 +11138,12 @@ async function notifyNamedSeatAccepted(db, env, ctx, input) {
11063
11138
  }, {
11064
11139
  group: config,
11065
11140
  template: "seatAccepted",
11066
- to: text6(purchaser.email),
11141
+ to: text7(purchaser.email),
11067
11142
  dedupeKey: `named-seat-accepted:${input.seatId}`,
11068
11143
  applicationId: input.primaryApplicationId,
11069
11144
  tierId: input.skuId,
11070
11145
  vars: {
11071
- firstName: text6(purchaser.firstName),
11146
+ firstName: text7(purchaser.firstName),
11072
11147
  purchaserName,
11073
11148
  recipientName: input.recipientName,
11074
11149
  tierName: await tierName(db, group, input.skuId),
@@ -12535,12 +12610,12 @@ async function updateEmailSettings(db, ctx, env, body) {
12535
12610
  if (!value || typeof value !== "object" || Array.isArray(value)) return json({ error: `Invalid template: ${key3}` }, 400);
12536
12611
  const t = value;
12537
12612
  if (typeof t.enabled !== "boolean") return json({ error: `Choose whether ${key3} is enabled.` }, 400);
12538
- const subject = str2(t.subject).trim(), text9 = str2(t.text);
12539
- const issues = (0, import_crm16.validateEmailCopy)({ subject, text: text9 }, { allowedVariables: events[key3].variables });
12613
+ const subject = str2(t.subject).trim(), text10 = str2(t.text);
12614
+ const issues = (0, import_crm16.validateEmailCopy)({ subject, text: text10 }, { allowedVariables: events[key3].variables });
12540
12615
  if (t.enabled && issues.length || issues.some((issue) => ["subject-invalid", "body-invalid"].includes(issue.code))) {
12541
12616
  return json({ error: `${events[key3].label} is incomplete.`, errors: issues.map((issue) => issue.message) }, 400);
12542
12617
  }
12543
- clean[key3] = { subject, text: text9, enabled: t.enabled };
12618
+ clean[key3] = { subject, text: text10, enabled: t.enabled };
12544
12619
  }
12545
12620
  const notificationEmail = str2(body.notificationEmail).trim(), replyTo = str2(body.replyTo).trim(), debugEmail = str2(body.debugEmail).trim();
12546
12621
  const emailish = /^[^\s@,;<>]+@[^\s@,;<>]+\.[^\s@,;<>]+$/;
@@ -14744,9 +14819,9 @@ var handleAdminNetworkPersonRegistry = async (req, url, env, ctx) => {
14744
14819
  if (req.method === "POST") {
14745
14820
  let body;
14746
14821
  try {
14747
- const text9 = await req.text();
14748
- if (new TextEncoder().encode(text9).byteLength > MAX_BODY2) return json({ error: "request is too large" }, 413);
14749
- const parsed = JSON.parse(text9);
14822
+ const text10 = await req.text();
14823
+ if (new TextEncoder().encode(text10).byteLength > MAX_BODY2) return json({ error: "request is too large" }, 413);
14824
+ const parsed = JSON.parse(text10);
14750
14825
  if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) throw new Error("invalid");
14751
14826
  body = parsed;
14752
14827
  } catch {
@@ -14915,7 +14990,7 @@ var import_db13 = require("@odla-ai/db");
14915
14990
  // src/network-commercial-parity.ts
14916
14991
  var DIGEST = /^[0-9a-f]{64}$/;
14917
14992
  var MAX_RECORDS = 500;
14918
- var text7 = (value, max = 255) => typeof value === "string" && value.length > 0 && value.length <= max ? value : null;
14993
+ var text8 = (value, max = 255) => typeof value === "string" && value.length > 0 && value.length <= max ? value : null;
14919
14994
  function parityMessage(partition, kind, value) {
14920
14995
  return [
14921
14996
  "chapter-commercial-parity",
@@ -14947,18 +15022,18 @@ async function digestWithKey(key3, partition, kind, value) {
14947
15022
  async function buildChapterCommercialParitySnapshot(input) {
14948
15023
  const requestedMax = input.maxRecords ?? MAX_RECORDS;
14949
15024
  const maxRecords = Number.isSafeInteger(requestedMax) ? Math.max(0, Math.min(requestedMax, MAX_RECORDS)) : MAX_RECORDS;
14950
- const eligible = input.rows.filter((row2) => text7(row2.id) && (text7(row2.stripeCustomerId) || text7(row2.stripeSubscriptionId)));
15025
+ const eligible = input.rows.filter((row2) => text8(row2.id) && (text8(row2.stripeCustomerId) || text8(row2.stripeSubscriptionId)));
14951
15026
  const selected2 = eligible.slice(0, maxRecords);
14952
15027
  const key3 = await parityKey(input.secret);
14953
15028
  const records = await Promise.all(selected2.map(async (row2) => {
14954
- const applicationId = text7(row2.id);
14955
- const customerId = text7(row2.stripeCustomerId);
14956
- const subscriptionId = text7(row2.stripeSubscriptionId);
15029
+ const applicationId = text8(row2.id);
15030
+ const customerId = text8(row2.stripeCustomerId);
15031
+ const subscriptionId = text8(row2.stripeSubscriptionId);
14957
15032
  return {
14958
15033
  applicationDigest: await digestWithKey(key3, input.partition, "application", applicationId),
14959
15034
  customerDigest: customerId ? await digestWithKey(key3, input.partition, "customer", customerId) : null,
14960
15035
  subscriptionDigest: subscriptionId ? await digestWithKey(key3, input.partition, "subscription", subscriptionId) : null,
14961
- status: text7(row2.status, 64),
15036
+ status: text8(row2.status, 64),
14962
15037
  canceled: row2.canceled === true
14963
15038
  };
14964
15039
  }));
@@ -14984,7 +15059,7 @@ function validSnapshot(snapshot, source) {
14984
15059
  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;
14985
15060
  const seen = /* @__PURE__ */ new Set();
14986
15061
  for (const record4 of records) {
14987
- 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;
15062
+ 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;
14988
15063
  const key3 = recordKey(record4);
14989
15064
  if (seen.has(key3)) return false;
14990
15065
  seen.add(key3);
@@ -14995,16 +15070,16 @@ async function reconcileCommercialParitySnapshot(input) {
14995
15070
  if (!validSnapshot(input.snapshot, input.source)) throw new Error("invalid chapter commercial parity snapshot");
14996
15071
  const sourceKey = networkCommercialSourceKey(input.source);
14997
15072
  const key3 = await parityKey(input.secret);
14998
- const shadow = await Promise.all(input.projection.subscriptions.filter((row2) => row2.sourceKey === sourceKey && text7(row2.applicationId) && text7(row2.objectId)).map(async (row2) => {
14999
- const customerId = text7(row2.customerId);
15000
- const subscriptionId = text7(row2.subscriptionId) ?? text7(row2.objectId);
15073
+ const shadow = await Promise.all(input.projection.subscriptions.filter((row2) => row2.sourceKey === sourceKey && text8(row2.applicationId) && text8(row2.objectId)).map(async (row2) => {
15074
+ const customerId = text8(row2.customerId);
15075
+ const subscriptionId = text8(row2.subscriptionId) ?? text8(row2.objectId);
15001
15076
  return {
15002
15077
  applicationDigest: await digestWithKey(key3, input.snapshot.partition, "application", row2.applicationId),
15003
15078
  customerDigest: customerId ? await digestWithKey(key3, input.snapshot.partition, "customer", customerId) : null,
15004
15079
  subscriptionDigest: subscriptionId ? await digestWithKey(key3, input.snapshot.partition, "subscription", subscriptionId) : null,
15005
15080
  status: null,
15006
15081
  canceled: false,
15007
- stripeStatus: text7(row2.status, 64),
15082
+ stripeStatus: text8(row2.status, 64),
15008
15083
  stripeCanceled: row2.status === "canceled"
15009
15084
  };
15010
15085
  }));
@@ -15214,18 +15289,24 @@ async function applyHostedMembershipDispute(env, ctx, fact, now) {
15214
15289
  const secret = await getVaultSecret(db, operator.stripeSecretName ?? "stripe_secret_key");
15215
15290
  if (!secret) throw Object.assign(new Error("membership_dispute_provider_unavailable"), { code: "membership_dispute_provider_unavailable" });
15216
15291
  const charge = await readDisputedCharge(secret, fact.chargeId);
15292
+ const scopeOf = (row2) => ({
15293
+ appId: String(row2.appId),
15294
+ chapterId: String(row2.chapterId),
15295
+ environment: String(row2.environment),
15296
+ runtime: String(row2.runtime),
15297
+ applicationId: String(row2.applicationId),
15298
+ skuId: String(row2.skuId)
15299
+ });
15300
+ if (charge.seatId) {
15301
+ const seat = await membershipLoad(db, "networkSeat", charge.seatId);
15302
+ if (!seat || seat.environment !== env.ODLA_ENV || !disputedChargeCoversSeat(charge, seat)) return { disposition: "not_applicable" };
15303
+ const receipt2 = await reconcileMembershipDispute(db, { ...scopeOf(seat), eventId: fact.eventId }, now, secret, charge);
15304
+ return { disposition: "applied", membershipId: receipt2.membershipId, seatId: charge.seatId };
15305
+ }
15217
15306
  const rows5 = (await db.query({ networkMembership: { $: { where: { providerCustomerId: charge.customerId, environment: env.ODLA_ENV }, limit: 5 } } })).networkMembership ?? [];
15218
15307
  const member = rows5.find((row2) => disputedChargeCoversMembership(charge, row2));
15219
15308
  if (!member) return { disposition: "not_applicable" };
15220
- const target = {
15221
- appId: String(member.appId),
15222
- chapterId: String(member.chapterId),
15223
- environment: String(member.environment),
15224
- runtime: String(member.runtime),
15225
- applicationId: String(member.applicationId),
15226
- skuId: String(member.skuId)
15227
- };
15228
- const receipt = await reconcileMembershipDispute(db, { ...target, eventId: fact.eventId }, now, secret, charge);
15309
+ const receipt = await reconcileMembershipDispute(db, { ...scopeOf(member), eventId: fact.eventId }, now, secret, charge);
15229
15310
  return { disposition: "applied", membershipId: receipt.membershipId };
15230
15311
  }
15231
15312
 
@@ -15293,11 +15374,11 @@ function safeProjection(projection) {
15293
15374
  amountReceivedCents: seat.amountReceivedCents,
15294
15375
  refundedCents: seat.refundedCents ?? 0
15295
15376
  })),
15296
- disputes: (projection.disputes ?? []).map((dispute) => ({
15297
- ...row2(dispute),
15298
- chargeId: dispute.chargeId ?? null,
15299
- paymentIntentId: dispute.paymentIntentId ?? null,
15300
- reason: dispute.disputeReason ?? null
15377
+ disputes: (projection.disputes ?? []).map((dispute2) => ({
15378
+ ...row2(dispute2),
15379
+ chargeId: dispute2.chargeId ?? null,
15380
+ paymentIntentId: dispute2.paymentIntentId ?? null,
15381
+ reason: dispute2.disputeReason ?? null
15301
15382
  })),
15302
15383
  exceptions: projection.exceptions
15303
15384
  };
@@ -15331,10 +15412,10 @@ var handleNetworkCommercialStripeWebhook = async (req, url, env, ctx) => {
15331
15412
  if (result.status === "collision") {
15332
15413
  console.error("network commercial event collision", JSON.stringify({ eventId: fact.eventId, eventType: fact.eventType }));
15333
15414
  }
15334
- let dispute;
15415
+ let dispute2;
15335
15416
  if (fact.objectKind === "dispute" && result.status !== "collision") {
15336
15417
  try {
15337
- dispute = await applyHostedMembershipDispute(env, ctx, fact, Date.now());
15418
+ dispute2 = await applyHostedMembershipDispute(env, ctx, fact, Date.now());
15338
15419
  } catch (error) {
15339
15420
  console.warn("membership dispute pending", JSON.stringify({ eventId: fact.eventId, code: error.code ?? "unknown" }));
15340
15421
  return json({ error: "membership dispute reconciliation pending", eventId: fact.eventId }, 503);
@@ -15344,7 +15425,7 @@ var handleNetworkCommercialStripeWebhook = async (req, url, env, ctx) => {
15344
15425
  ok: true,
15345
15426
  status: result.status,
15346
15427
  eventId: fact.eventId,
15347
- ...dispute ? { dispute: dispute.disposition } : {},
15428
+ ...dispute2 ? { dispute: dispute2.disposition } : {},
15348
15429
  ...result.status === "replayed" && result.resolvedCollision ? { resolvedCollision: true } : {}
15349
15430
  });
15350
15431
  } catch (error) {
@@ -15542,7 +15623,7 @@ var record3 = (value) => value !== null && typeof value === "object" && !Array.i
15542
15623
  function unknownKeys3(value, allowed2, path, errors) {
15543
15624
  for (const key3 of Object.keys(value)) if (!allowed2.has(key3)) errors.push(`${path}.${key3} is not supported`);
15544
15625
  }
15545
- function text8(value, path, errors, max = MAX_TEXT2, required = true) {
15626
+ function text9(value, path, errors, max = MAX_TEXT2, required = true) {
15546
15627
  if (value === void 0 && !required) return void 0;
15547
15628
  if (typeof value !== "string" || required && !value.trim()) {
15548
15629
  errors.push(`${path} must be a non-empty string`);
@@ -15568,7 +15649,7 @@ function scopeValue(value, path, errors) {
15568
15649
  return { kind: raw2.kind };
15569
15650
  }
15570
15651
  if (raw2.kind === "chapter") {
15571
- const chapterId = text8(raw2.chapterId, `${path}.chapterId`, errors, 63) ?? "";
15652
+ const chapterId = text9(raw2.chapterId, `${path}.chapterId`, errors, 63) ?? "";
15572
15653
  if (chapterId && !APP_ID.test(chapterId)) errors.push(`${path}.chapterId must be a lowercase app slug`);
15573
15654
  return { kind: "chapter", chapterId };
15574
15655
  }
@@ -15582,22 +15663,22 @@ function resourceValue(value, path, errors) {
15582
15663
  return null;
15583
15664
  }
15584
15665
  unknownKeys3(raw2, RESOURCE_KEYS, path, errors);
15585
- const id7 = text8(raw2.id, `${path}.id`, errors, 80) ?? "";
15666
+ const id7 = text9(raw2.id, `${path}.id`, errors, 80) ?? "";
15586
15667
  if (id7 && !ID5.test(id7)) errors.push(`${path}.id must be lowercase kebab-case`);
15587
- const kind = text8(raw2.kind, `${path}.kind`, errors) ?? "";
15668
+ const kind = text9(raw2.kind, `${path}.kind`, errors) ?? "";
15588
15669
  if (!KINDS.has(kind)) errors.push(`${path}.kind is unsupported`);
15589
- const title = text8(raw2.title, `${path}.title`, errors, 240) ?? "";
15590
- const summary = text8(raw2.summary, `${path}.summary`, errors, 1e3, false);
15591
- const status2 = text8(raw2.status, `${path}.status`, errors) ?? "";
15670
+ const title = text9(raw2.title, `${path}.title`, errors, 240) ?? "";
15671
+ const summary = text9(raw2.summary, `${path}.summary`, errors, 1e3, false);
15672
+ const status2 = text9(raw2.status, `${path}.status`, errors) ?? "";
15592
15673
  if (!STATUSES.has(status2)) errors.push(`${path}.status must be draft, published, or archived`);
15593
15674
  const revision = integer4(raw2.revision, `${path}.revision`, errors);
15594
15675
  const scope2 = scopeValue(raw2.scope, `${path}.scope`, errors) ?? { kind: "global" };
15595
- const tags = Array.isArray(raw2.tags) ? raw2.tags.map((tag, index2) => text8(tag, `${path}.tags[${index2}]`, errors, 80) ?? "") : [];
15676
+ const tags = Array.isArray(raw2.tags) ? raw2.tags.map((tag, index2) => text9(tag, `${path}.tags[${index2}]`, errors, 80) ?? "") : [];
15596
15677
  if (!Array.isArray(raw2.tags)) errors.push(`${path}.tags must be an array`);
15597
15678
  if (tags.length > MAX_TAGS) errors.push(`${path}.tags may contain at most ${MAX_TAGS} items`);
15598
15679
  if (new Set(tags).size !== tags.length) errors.push(`${path}.tags must be unique`);
15599
- const bodyMarkdown = text8(raw2.bodyMarkdown, `${path}.bodyMarkdown`, errors, MAX_MARKDOWN3, false);
15600
- const url = text8(raw2.url, `${path}.url`, errors, 2048, false);
15680
+ const bodyMarkdown = text9(raw2.bodyMarkdown, `${path}.bodyMarkdown`, errors, MAX_MARKDOWN3, false);
15681
+ const url = text9(raw2.url, `${path}.url`, errors, 2048, false);
15601
15682
  if (url) {
15602
15683
  try {
15603
15684
  if (new URL(url).protocol !== "https:") errors.push(`${path}.url must use https`);
@@ -15609,9 +15690,9 @@ function resourceValue(value, path, errors) {
15609
15690
  if (raw2.brandAsset !== void 0 && !asset) errors.push(`${path}.brandAsset must be an object`);
15610
15691
  if (asset) unknownKeys3(asset, ASSET_KEYS, `${path}.brandAsset`, errors);
15611
15692
  const brandAsset = asset ? {
15612
- bookId: text8(asset.bookId, `${path}.brandAsset.bookId`, errors, 160) ?? "",
15613
- assetId: text8(asset.assetId, `${path}.brandAsset.assetId`, errors, 160) ?? "",
15614
- ...asset.filename === void 0 ? {} : { filename: text8(asset.filename, `${path}.brandAsset.filename`, errors, 240) ?? "" }
15693
+ bookId: text9(asset.bookId, `${path}.brandAsset.bookId`, errors, 160) ?? "",
15694
+ assetId: text9(asset.assetId, `${path}.brandAsset.assetId`, errors, 160) ?? "",
15695
+ ...asset.filename === void 0 ? {} : { filename: text9(asset.filename, `${path}.brandAsset.filename`, errors, 240) ?? "" }
15615
15696
  } : void 0;
15616
15697
  if ((kind === "document" || kind === "note") && !bodyMarkdown) errors.push(`${path}.bodyMarkdown is required for ${kind}`);
15617
15698
  if ((kind === "link" || kind === "inspiration") && !url) errors.push(`${path}.url is required for ${kind}`);
@@ -15626,17 +15707,17 @@ function resourceValue(value, path, errors) {
15626
15707
  if (provenanceRaw?.promotedFrom !== void 0 && !promotedRaw) errors.push(`${path}.provenance.promotedFrom must be an object`);
15627
15708
  if (promotedRaw) unknownKeys3(promotedRaw, PROMOTION_KEYS, `${path}.provenance.promotedFrom`, errors);
15628
15709
  const promotedFrom = promotedRaw ? {
15629
- chapterId: text8(promotedRaw.chapterId, `${path}.provenance.promotedFrom.chapterId`, errors, 63) ?? "",
15630
- resourceId: text8(promotedRaw.resourceId, `${path}.provenance.promotedFrom.resourceId`, errors, 80) ?? "",
15710
+ chapterId: text9(promotedRaw.chapterId, `${path}.provenance.promotedFrom.chapterId`, errors, 63) ?? "",
15711
+ resourceId: text9(promotedRaw.resourceId, `${path}.provenance.promotedFrom.resourceId`, errors, 80) ?? "",
15631
15712
  resourceRevision: integer4(promotedRaw.resourceRevision, `${path}.provenance.promotedFrom.resourceRevision`, errors),
15632
15713
  documentRevision: integer4(promotedRaw.documentRevision, `${path}.provenance.promotedFrom.documentRevision`, errors),
15633
- promotedBy: text8(promotedRaw.promotedBy, `${path}.provenance.promotedFrom.promotedBy`, errors, 320) ?? "",
15714
+ promotedBy: text9(promotedRaw.promotedBy, `${path}.provenance.promotedFrom.promotedBy`, errors, 320) ?? "",
15634
15715
  promotedAt: integer4(promotedRaw.promotedAt, `${path}.provenance.promotedFrom.promotedAt`, errors, 0)
15635
15716
  } : void 0;
15636
15717
  const provenance2 = {
15637
- createdBy: text8(provenanceRaw?.createdBy, `${path}.provenance.createdBy`, errors, 320) ?? "",
15718
+ createdBy: text9(provenanceRaw?.createdBy, `${path}.provenance.createdBy`, errors, 320) ?? "",
15638
15719
  createdAt: integer4(provenanceRaw?.createdAt, `${path}.provenance.createdAt`, errors, 0),
15639
- updatedBy: text8(provenanceRaw?.updatedBy, `${path}.provenance.updatedBy`, errors, 320) ?? "",
15720
+ updatedBy: text9(provenanceRaw?.updatedBy, `${path}.provenance.updatedBy`, errors, 320) ?? "",
15640
15721
  updatedAt: integer4(provenanceRaw?.updatedAt, `${path}.provenance.updatedAt`, errors, 0),
15641
15722
  ...promotedFrom ? { promotedFrom } : {}
15642
15723
  };
@@ -15655,7 +15736,7 @@ function validateBuilderLibraryDocument(value, options = {}) {
15655
15736
  if (sourceRaw) unknownKeys3(sourceRaw, SOURCE_KEYS2, "document.source", errors);
15656
15737
  const sourceType = sourceRaw?.type;
15657
15738
  if (sourceType !== "chapter" && sourceType !== "built_not_found") errors.push("document.source.type must be chapter or built_not_found");
15658
- const sourceAppId = text8(sourceRaw?.appId, "document.source.appId", errors, 63) ?? "";
15739
+ const sourceAppId = text9(sourceRaw?.appId, "document.source.appId", errors, 63) ?? "";
15659
15740
  if (sourceAppId && !APP_ID.test(sourceAppId)) errors.push("document.source.appId must be a lowercase app slug");
15660
15741
  if (options.sourceType && sourceType !== options.sourceType) errors.push(`document.source.type must be ${options.sourceType}`);
15661
15742
  if (options.sourceAppId && sourceAppId !== options.sourceAppId) errors.push(`document.source.appId must be ${options.sourceAppId}`);
@@ -15663,8 +15744,8 @@ function validateBuilderLibraryDocument(value, options = {}) {
15663
15744
  if (raw2.target !== void 0 && !targetRaw) errors.push("document.target must be an object");
15664
15745
  if (targetRaw) unknownKeys3(targetRaw, TARGET_KEYS2, "document.target", errors);
15665
15746
  const target = targetRaw ? {
15666
- appId: text8(targetRaw.appId, "document.target.appId", errors, 63) ?? "",
15667
- environment: text8(targetRaw.environment, "document.target.environment", errors, 80) ?? ""
15747
+ appId: text9(targetRaw.appId, "document.target.appId", errors, 63) ?? "",
15748
+ environment: text9(targetRaw.environment, "document.target.environment", errors, 80) ?? ""
15668
15749
  } : void 0;
15669
15750
  if (options.targetAppId && target?.appId !== options.targetAppId) errors.push(`document.target.appId must be ${options.targetAppId}`);
15670
15751
  if (!Array.isArray(raw2.resources)) errors.push("document.resources must be an array");
@@ -17189,6 +17270,19 @@ var id5 = () => attr("string", false, true, true);
17189
17270
  var key = (optional = false) => attr("string", optional, false, true);
17190
17271
  var scope = () => ({ appId: key(), chapterId: key(), environment: key(), runtime: key(), applicationId: key(), skuId: key() });
17191
17272
  var times = () => ({ revision: attr("number"), createdAt: attr("number"), updatedAt: attr("number") });
17273
+ var dispute = () => ({
17274
+ disputeId: key(true),
17275
+ disputeStatus: attr("string", true),
17276
+ disputeReason: attr("string", true),
17277
+ disputeAmountCents: attr("number", true),
17278
+ disputedAt: attr("number", true),
17279
+ disputeClosedAt: attr("number", true),
17280
+ disputePriorStatus: attr("string", true),
17281
+ disputeEventId: key(true),
17282
+ rechargePaymentIntentId: key(true),
17283
+ rechargedAt: attr("number", true),
17284
+ rechargeAttempt: attr("json", true)
17285
+ });
17192
17286
  var membershipAuthorityEntities = {
17193
17287
  networkMembership: { attrs: {
17194
17288
  id: id5(),
@@ -17229,19 +17323,7 @@ var membershipAuthorityEntities = {
17229
17323
  ...times(),
17230
17324
  refundRequestedAt: attr("number", true),
17231
17325
  restartId: key(true),
17232
- // A card-network dispute on this membership's payment: Stripe's dispute id and status, the reason the cardholder gave,
17233
- // the status the membership held before it was suspended, and the re-issued charge once an operator collected again.
17234
- disputeId: key(true),
17235
- disputeStatus: attr("string", true),
17236
- disputeReason: attr("string", true),
17237
- disputeAmountCents: attr("number", true),
17238
- disputedAt: attr("number", true),
17239
- disputeClosedAt: attr("number", true),
17240
- disputePriorStatus: attr("string", true),
17241
- disputeEventId: key(true),
17242
- rechargePaymentIntentId: key(true),
17243
- rechargedAt: attr("number", true),
17244
- rechargeAttempt: attr("json", true)
17326
+ ...dispute()
17245
17327
  } },
17246
17328
  membershipRestart: { attrs: {
17247
17329
  id: id5(),
@@ -17317,7 +17399,8 @@ var membershipAuthorityEntities = {
17317
17399
  refundedCents: attr("number", true),
17318
17400
  providerRefundIds: attr("json", true),
17319
17401
  regiftedAt: attr("number", true),
17320
- formerRecipients: attr("json", true)
17402
+ formerRecipients: attr("json", true),
17403
+ ...dispute()
17321
17404
  } },
17322
17405
  networkSeatRecipient: { attrs: { id: id5(), appId: key(), chapterId: key(), environment: key(), runtime: key(), seatId: key(), recipientEmailNormalized: key(), createdAt: attr("number") } },
17323
17406
  membershipEffect: { attrs: {
@@ -17654,7 +17737,7 @@ async function readBoundedJson(response, signal, maxBytes, label) {
17654
17737
  };
17655
17738
  signal.addEventListener("abort", cancel, { once: true });
17656
17739
  let size = 0;
17657
- let text9 = "";
17740
+ let text10 = "";
17658
17741
  const decoder = new TextDecoder();
17659
17742
  try {
17660
17743
  while (true) {
@@ -17664,10 +17747,10 @@ async function readBoundedJson(response, signal, maxBytes, label) {
17664
17747
  if (done) break;
17665
17748
  size += value.byteLength;
17666
17749
  if (size > maxBytes) throw new TypeError(`${label} exceeds limit`);
17667
- text9 += decoder.decode(value, { stream: true });
17750
+ text10 += decoder.decode(value, { stream: true });
17668
17751
  }
17669
17752
  try {
17670
- return JSON.parse(text9 + decoder.decode());
17753
+ return JSON.parse(text10 + decoder.decode());
17671
17754
  } catch {
17672
17755
  throw new TypeError(`invalid ${label}`);
17673
17756
  }