@odla-ai/chapter 0.58.0 → 0.59.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1560,7 +1560,9 @@ var DEFAULT_JOIN_COPY = {
1560
1560
  failed: "The booking could not be completed.",
1561
1561
  loading: "Loading available times\u2026",
1562
1562
  book: "Book this time",
1563
- booking: "Booking\u2026"
1563
+ booking: "Booking\u2026",
1564
+ pickDate: "Pick a date",
1565
+ pickTime: "Pick a time"
1564
1566
  },
1565
1567
  payment: {
1566
1568
  setupFailed: "Payment could not be set up. Please try again.",
@@ -1712,8 +1714,8 @@ var APPLICATION_LABELS = {
1712
1714
  state: "State"
1713
1715
  };
1714
1716
  function humanize(id11) {
1715
- const text6 = id11.replace(/([a-z0-9])([A-Z])/g, "$1 $2").replace(/[_-]+/g, " ").trim();
1716
- return text6 ? `${text6[0].toUpperCase()}${text6.slice(1)}` : id11;
1717
+ const text7 = id11.replace(/([a-z0-9])([A-Z])/g, "$1 $2").replace(/[_-]+/g, " ").trim();
1718
+ return text7 ? `${text7[0].toUpperCase()}${text7.slice(1)}` : id11;
1717
1719
  }
1718
1720
  function applicationDefs(chapter) {
1719
1721
  try {
@@ -2512,6 +2514,7 @@ function normalizeNetworkCommercialStripeEvent(input, digest) {
2512
2514
  else if (eventType.startsWith("refund.")) objectKind = "refund";
2513
2515
  else if (eventType === "charge.refunded") objectKind = "refund";
2514
2516
  else if (eventType.startsWith("payment_intent.")) objectKind = "payment_intent";
2517
+ else if (eventType.startsWith("charge.dispute.")) objectKind = "dispute";
2515
2518
  const customerId = refId(object2.customer, "cus_");
2516
2519
  const subscriptionId = objectKind === "subscription" ? providerId(objectId, "sub_") : subscriptionRef(object2);
2517
2520
  const invoiceId = objectKind === "invoice" ? providerId(objectId, "in_") : refId(object2.invoice, "in_");
@@ -2521,6 +2524,7 @@ function normalizeNetworkCommercialStripeEvent(input, digest) {
2521
2524
  const amountCents = integer(eventType === "charge.refunded" ? object2.amount_refunded : object2.amount);
2522
2525
  const currency = text(object2.currency)?.toLowerCase();
2523
2526
  const status2 = text(object2.status);
2527
+ const disputeReason = objectKind === "dispute" ? text(object2.reason) : void 0;
2524
2528
  return {
2525
2529
  eventId,
2526
2530
  digest,
@@ -2539,6 +2543,7 @@ function normalizeNetworkCommercialStripeEvent(input, digest) {
2539
2543
  ...invoiceId ? { invoiceId } : {},
2540
2544
  ...chargeId ? { chargeId } : {},
2541
2545
  ...paymentIntentId ? { paymentIntentId } : {},
2546
+ ...disputeReason ? { disputeReason } : {},
2542
2547
  ...price,
2543
2548
  ...objectKind === "subscription" && commercialSeatItems(object2) ? { seatSubscriptionItems: commercialSeatItems(object2) } : {},
2544
2549
  ...status2 ? { status: status2 } : {},
@@ -2581,6 +2586,7 @@ function ownRef(fact) {
2581
2586
  if (fact.objectKind === "subscription") return `subscription:${fact.objectId}`;
2582
2587
  if (fact.objectKind === "invoice") return `invoice:${fact.objectId}`;
2583
2588
  if (fact.objectKind === "payment_intent") return `payment_intent:${fact.objectId}`;
2589
+ if (fact.objectKind === "dispute") return `dispute:${fact.objectId}`;
2584
2590
  if (fact.eventType.startsWith("refund.")) return `refund:${fact.objectId}`;
2585
2591
  return fact.eventType === "charge.refunded" ? `charge:${fact.objectId}` : null;
2586
2592
  }
@@ -2636,6 +2642,7 @@ function projectNetworkCommercialEvents(input, sources) {
2636
2642
  continue;
2637
2643
  }
2638
2644
  const source = assignments.get(fact.eventId);
2645
+ if (!source && fact.objectKind === "dispute") continue;
2639
2646
  if (!source) {
2640
2647
  const direct = directSource(fact, sources);
2641
2648
  const keys2 = new Set(objectRefs(fact).flatMap((ref) => [...refs.get(ref) ?? []]));
@@ -2652,6 +2659,7 @@ function projectNetworkCommercialEvents(input, sources) {
2652
2659
  const subscriptions = rows3.filter((row2) => row2.objectKind === "subscription");
2653
2660
  const invoices = rows3.filter((row2) => row2.objectKind === "invoice");
2654
2661
  const seatPayments = rows3.filter((row2) => row2.objectKind === "payment_intent" && row2.seatId && row2.membershipId && !row2.invoiceId);
2662
+ const disputes = rows3.filter((row2) => row2.objectKind === "dispute");
2655
2663
  const refunds = rows3.filter((row2) => row2.objectKind === "refund" && row2.eventType.startsWith("refund."));
2656
2664
  for (const charge of rows3.filter((row2) => row2.eventType === "charge.refunded")) {
2657
2665
  if (!refunds.some((refund) => refund.chargeId === charge.objectId)) {
@@ -2672,6 +2680,7 @@ function projectNetworkCommercialEvents(input, sources) {
2672
2680
  for (const row2 of refunds) add(row2, "refundedCents", row2.amountCents ?? 0);
2673
2681
  return {
2674
2682
  ...seatPayments.length ? { seatPayments, seats: projectCommercialSeats(seatPayments, subscriptions, refunds) } : {},
2683
+ ...disputes.length ? { disputes } : {},
2675
2684
  customers,
2676
2685
  subscriptions,
2677
2686
  invoices,
@@ -4275,7 +4284,7 @@ async function refundNetworkMembership(db, input, secretKey, now) {
4275
4284
  }
4276
4285
 
4277
4286
  // src/membership-provider-lifecycle.ts
4278
- import { stripeRequest as stripeRequest4 } from "@odla-ai/stripe";
4287
+ import { stripeRequest as stripeRequest5 } from "@odla-ai/stripe";
4279
4288
 
4280
4289
  // src/membership-seat-reservation.ts
4281
4290
  async function namedSeatRecipientClaimId(input, email) {
@@ -4566,12 +4575,113 @@ async function reconcileMembershipRenewal(db, input, now) {
4566
4575
  return membershipFail("membership_renewal_retry_required");
4567
4576
  }
4568
4577
 
4578
+ // src/membership-dispute-authority.ts
4579
+ import { stripeRequest as stripeRequest4 } from "@odla-ai/stripe";
4580
+ var MEMBERSHIP_DISPUTED_STATUS = "payment_disputed";
4581
+ var DISPUTE_WON_STATUSES = ["won", "warning_closed"];
4582
+ var CLOSED_STATUSES = [...DISPUTE_WON_STATUSES, "lost"];
4583
+ var TERMINAL = ["refunded", "refund_pending", "canceled", "declined", "restarting", "superseded"];
4584
+ var text3 = (value) => typeof value === "string" && value ? value : void 0;
4585
+ var refOf = (value) => typeof value === "string" ? text3(value) : text3(value?.id);
4586
+ async function readDisputedCharge(secretKey, chargeId, fetch2) {
4587
+ if (!/^ch_[A-Za-z0-9]+$/.test(chargeId)) membershipFail("invalid_dispute_charge");
4588
+ const config = { secretKey, ...fetch2 ? { fetch: fetch2 } : {} };
4589
+ const charge = await stripeRequest4(config, "GET", `/v1/charges/${chargeId}`);
4590
+ if (!charge.ok || charge.body.id !== chargeId) membershipFail("membership_dispute_provider_unavailable");
4591
+ const customerId = refOf(charge.body.customer), invoiceId = refOf(charge.body.invoice), paymentIntentId = refOf(charge.body.payment_intent);
4592
+ if (!customerId) membershipFail("membership_dispute_provider_unavailable");
4593
+ let subscriptionId;
4594
+ if (invoiceId) {
4595
+ const invoice = await stripeRequest4(config, "GET", `/v1/invoices/${invoiceId}`);
4596
+ if (!invoice.ok || invoice.body.id !== invoiceId) membershipFail("membership_dispute_provider_unavailable");
4597
+ subscriptionId = refOf(invoice.body.subscription) ?? refOf(invoice.body.parent?.subscription_details?.subscription);
4598
+ }
4599
+ const seatId = text3(charge.body.metadata?.seatId);
4600
+ return {
4601
+ chargeId,
4602
+ customerId,
4603
+ ...invoiceId ? { invoiceId } : {},
4604
+ ...subscriptionId ? { subscriptionId } : {},
4605
+ ...paymentIntentId ? { paymentIntentId } : {},
4606
+ ...seatId ? { seatId } : {}
4607
+ };
4608
+ }
4609
+ function disputedChargeCoversMembership(charge, member) {
4610
+ return charge.customerId === member.providerCustomerId && !charge.seatId && (charge.invoiceId !== void 0 && charge.invoiceId === member.providerInitialInvoiceId || charge.subscriptionId !== void 0 && charge.subscriptionId === member.providerSubscriptionId || charge.paymentIntentId !== void 0 && charge.paymentIntentId === member.rechargePaymentIntentId);
4611
+ }
4612
+ async function reconcileMembershipDispute(db, input, now, secretKey, resolved) {
4613
+ membershipClock(now);
4614
+ const row2 = await membershipLoad(db, "networkCommercialEvents", `network-commercial-event:${input.eventId}`);
4615
+ const fact = row2?.fact;
4616
+ const evidenceGuard = await commercialEvidenceGuard(db, input.eventId, row2?.digest, "membership_billing_evidence_pending");
4617
+ if (!fact || fact.objectKind !== "dispute" || fact.eventId !== input.eventId || fact.digest !== row2.digest || !fact.chargeId || !fact.objectId || fact.livemode !== (input.environment === "prod")) membershipFail("membership_billing_evidence_pending");
4618
+ const id11 = await networkMembershipId(input);
4619
+ const receipt = (value, applied = value.disputeEventId === input.eventId) => ({
4620
+ membershipId: id11,
4621
+ status: String(value.status),
4622
+ termEndsAt: Number(value.termEndsAt),
4623
+ graceEndsAt: Number(value.graceEndsAt ?? 0),
4624
+ autoRenew: value.autoRenew === true,
4625
+ ...text3(value.disputeStatus) ? { disputeStatus: text3(value.disputeStatus) } : {},
4626
+ applied
4627
+ });
4628
+ const known = await membershipLoad(db, "networkMembership", id11);
4629
+ requireMembershipScope(known, input);
4630
+ if (known.disputeEventId === input.eventId) return receipt(known);
4631
+ const charge = resolved && resolved.chargeId === fact.chargeId ? resolved : await readDisputedCharge(secretKey, fact.chargeId);
4632
+ for (let attempt = 0; attempt < 8; attempt++) {
4633
+ const state = await loadMembershipAuthorityState(db, input), member = await membershipLoad(db, "networkMembership", id11);
4634
+ requireMembershipScope(member, input);
4635
+ if (charge.customerId !== member.providerCustomerId) membershipFail("membership_dispute_scope_mismatch");
4636
+ if (!disputedChargeCoversMembership(charge, member) || member.disputeEventId === input.eventId) return receipt(member);
4637
+ const status2 = String(fact.status ?? "unknown"), closed = CLOSED_STATUSES.includes(status2);
4638
+ const sameDispute = member.disputeId === fact.objectId;
4639
+ if (sameDispute && Number(member.disputeClosedAt ?? 0) > 0 && !closed) return receipt(member, false);
4640
+ const won = DISPUTE_WON_STATUSES.includes(status2);
4641
+ const suspended = member.status === MEMBERSHIP_DISPUTED_STATUS, terminal = TERMINAL.includes(String(member.status));
4642
+ const next = terminal ? String(member.status) : won ? suspended ? String(member.disputePriorStatus ?? (member.journeyApprovedAt ? "active" : "paid_pending_approval")) : String(member.status) : MEMBERSHIP_DISPUTED_STATUS;
4643
+ const attrs = {
4644
+ status: next,
4645
+ disputeId: fact.objectId,
4646
+ disputeStatus: status2,
4647
+ disputeEventId: input.eventId,
4648
+ disputeClosedAt: closed ? now : 0,
4649
+ ...fact.disputeReason ? { disputeReason: fact.disputeReason } : {},
4650
+ ...fact.amountCents !== void 0 ? { disputeAmountCents: fact.amountCents } : {},
4651
+ ...sameDispute ? {} : { disputedAt: now },
4652
+ ...next === MEMBERSHIP_DISPUTED_STATUS && !suspended ? { disputePriorStatus: String(member.status) } : {},
4653
+ revision: Number(member.revision) + 1,
4654
+ updatedAt: now
4655
+ };
4656
+ try {
4657
+ await db.transact([membershipUpdate("networkMembership", id11, attrs)], { mutationId: `membership-dispute:${id11}:${input.eventId}`, guards: [
4658
+ { ns: "networkMembership", id: id11, exists: true, equals: { revision: member.revision } },
4659
+ { ns: "membershipAuthorityState", id: String(state.id), exists: true, equals: { mode: "authoritative", revision: state.revision } },
4660
+ { ns: "networkCommercialEvents", id: String(row2.id), exists: true, equals: { digest: fact.digest } },
4661
+ evidenceGuard
4662
+ ] });
4663
+ } catch (error) {
4664
+ if (error.code !== "transact_guard_failed") throw error;
4665
+ continue;
4666
+ }
4667
+ const saved = await membershipLoad(db, "networkMembership", id11);
4668
+ requireMembershipScope(saved, input);
4669
+ return receipt(saved, true);
4670
+ }
4671
+ return membershipFail("membership_dispute_retry_required");
4672
+ }
4673
+
4569
4674
  // src/membership-provider-lifecycle.ts
4570
4675
  async function reconcileMembershipBilling(db, input, now, stripeSecretName = "stripe_secret_key") {
4571
4676
  membershipClock(now);
4572
4677
  const row2 = await membershipLoad(db, "networkCommercialEvents", `network-commercial-event:${input.eventId}`);
4573
4678
  const fact = row2?.fact;
4574
4679
  if (fact?.objectKind === "invoice") return reconcileMembershipRenewal(db, input, now);
4680
+ if (fact?.objectKind === "dispute") {
4681
+ const secret = await getVaultSecret(db, stripeSecretName);
4682
+ if (!secret) membershipFail("membership_billing_provider_unavailable");
4683
+ return reconcileMembershipDispute(db, input, now, secret);
4684
+ }
4575
4685
  const evidenceGuard = await commercialEvidenceGuard(db, input.eventId, row2?.digest, "membership_billing_evidence_pending");
4576
4686
  if (!fact || fact.eventId !== input.eventId || fact.digest !== row2.digest || !/^[a-f0-9]{64}$/.test(fact.digest) || !["customer.subscription.updated", "customer.subscription.deleted", "charge.refunded"].includes(fact.eventType) || fact.livemode !== (input.environment === "prod") || fact.applicationId !== input.applicationId || !fact.identity || Object.entries({
4577
4687
  appId: input.appId,
@@ -4614,7 +4724,7 @@ async function reconcileMembershipBilling(db, input, now, stripeSecretName = "st
4614
4724
  }
4615
4725
  if (fact.subscriptionId !== member.providerSubscriptionId || fact.objectKind !== "subscription") membershipFail("membership_billing_scope_mismatch");
4616
4726
  if (["refund_pending", "refunded", "declined", "canceled", "restarting"].includes(String(member.status))) return receipt();
4617
- const result = await stripeRequest4({ secretKey: sk }, "GET", `/v1/subscriptions/${encodeURIComponent(String(member.providerSubscriptionId))}`);
4727
+ const result = await stripeRequest5({ secretKey: sk }, "GET", `/v1/subscriptions/${encodeURIComponent(String(member.providerSubscriptionId))}`);
4618
4728
  const sub = result.body;
4619
4729
  if (!result.ok || sub.id !== member.providerSubscriptionId || sub.customer !== member.providerCustomerId || sub.livemode !== (input.environment === "prod") || typeof sub.cancel_at_period_end !== "boolean" || !["active", "trialing", "past_due", "unpaid", "canceled"].includes(sub.status) || !Object.entries({ ...stripePartitionMetadata(input), applicationId: input.applicationId }).every(([key3, value]) => sub.metadata?.[key3] === value)) {
4620
4730
  membershipFail("membership_billing_provider_mismatch");
@@ -4671,7 +4781,7 @@ async function expireRestartedMembershipSeat(db, target, member, now) {
4671
4781
  }
4672
4782
 
4673
4783
  // src/membership-restart-authority.ts
4674
- import { stripeRequest as stripeRequest5 } from "@odla-ai/stripe";
4784
+ import { stripeRequest as stripeRequest6 } from "@odla-ai/stripe";
4675
4785
 
4676
4786
  // src/stripe-event-digest.ts
4677
4787
  async function stripePayloadDigest(value) {
@@ -4686,14 +4796,14 @@ function membershipCanRestart(member, now) {
4686
4796
  }
4687
4797
  async function stopPreviousSubscription(member, target, secret) {
4688
4798
  const path = `/v1/subscriptions/${encodeURIComponent(String(member.providerSubscriptionId))}`;
4689
- const result = await stripeRequest5({ secretKey: secret }, "GET", path);
4799
+ const result = await stripeRequest6({ secretKey: secret }, "GET", path);
4690
4800
  const sub = result.body;
4691
4801
  if (!result.ok || sub.id !== member.providerSubscriptionId || sub.customer !== member.providerCustomerId || sub.livemode !== (target.environment === "prod") || !Object.entries({
4692
4802
  ...stripePartitionMetadata(target),
4693
4803
  applicationId: target.applicationId
4694
4804
  }).every(([key3, value]) => sub.metadata?.[key3] === value)) membershipFail("membership_restart_provider_mismatch");
4695
4805
  if (sub.status === "canceled") return;
4696
- const canceled = await stripeRequest5(
4806
+ const canceled = await stripeRequest6(
4697
4807
  { secretKey: secret },
4698
4808
  "DELETE",
4699
4809
  path,
@@ -4794,16 +4904,16 @@ async function verifyMembershipAccessReceipt(value, input, now) {
4794
4904
  }
4795
4905
 
4796
4906
  // src/membership-seat-stripe.ts
4797
- import { stripeRequest as stripeRequest7 } from "@odla-ai/stripe";
4907
+ import { stripeRequest as stripeRequest8 } from "@odla-ai/stripe";
4798
4908
 
4799
4909
  // src/subscription-provider-contract.ts
4800
- import { stripeRequest as stripeRequest6 } from "@odla-ai/stripe";
4910
+ import { stripeRequest as stripeRequest7 } from "@odla-ai/stripe";
4801
4911
  function subscriptionContractMismatch() {
4802
4912
  throw Object.assign(new Error("Stripe subscription does not match the checkout contract"), { code: "stripe_contract_mismatch" });
4803
4913
  }
4804
4914
  async function verifySubscriptionPrice(secretKey, input, resume = false) {
4805
4915
  if (!/^price_[A-Za-z0-9]+$/.test(input.priceId) || !Number.isSafeInteger(input.priceCents) || input.priceCents <= 0 || !/^[a-z]{3}$/.test(input.currency) || !["month", "year"].includes(input.interval)) subscriptionContractMismatch();
4806
- const result = await stripeRequest6({ secretKey }, "GET", `/v1/prices/${encodeURIComponent(input.priceId)}`);
4916
+ const result = await stripeRequest7({ secretKey }, "GET", `/v1/prices/${encodeURIComponent(input.priceId)}`);
4807
4917
  const price = result.body;
4808
4918
  if (!result.ok || !price || price.id !== input.priceId || !resume && price.active !== true || price.unit_amount !== input.priceCents || price.currency !== input.currency || price.type !== "recurring" || price.billing_scheme !== "per_unit" || price.custom_unit_amount || price.transform_quantity || price.recurring?.interval !== input.interval || price.recurring?.interval_count !== 1 || price.recurring?.usage_type !== "licensed" || price.livemode !== (input.environment === "prod")) subscriptionContractMismatch();
4809
4919
  return typeof price.product === "string" ? price.product : void 0;
@@ -4821,7 +4931,7 @@ var metadataFor = (seat) => ({
4821
4931
  });
4822
4932
  var matches = (value, expected) => value && Object.entries(expected).every(([key3, item]) => value[key3] === item);
4823
4933
  async function read(sk, path, params) {
4824
- const response = await stripeRequest7({ secretKey: sk }, "GET", path, params);
4934
+ const response = await stripeRequest8({ secretKey: sk }, "GET", path, params);
4825
4935
  if (!response.ok) return reject3();
4826
4936
  return response.body;
4827
4937
  }
@@ -4858,11 +4968,11 @@ async function createNamedSeatStripePayment(sk, seat, now) {
4858
4968
  }
4859
4969
  await subscriptionForSeat(sk, seat);
4860
4970
  const metadata = metadataFor(seat);
4861
- const result = seat.providerPaymentIntentId ? await stripeRequest7(
4971
+ const result = seat.providerPaymentIntentId ? await stripeRequest8(
4862
4972
  { secretKey: sk },
4863
4973
  "GET",
4864
4974
  `/v1/payment_intents/${encodeURIComponent(seat.providerPaymentIntentId)}`
4865
- ) : await stripeRequest7({ secretKey: sk }, "POST", "/v1/payment_intents", {
4975
+ ) : await stripeRequest8({ secretKey: sk }, "POST", "/v1/payment_intents", {
4866
4976
  amount: seat.amountCents,
4867
4977
  currency: seat.currency,
4868
4978
  customer: seat.providerCustomerId,
@@ -4884,7 +4994,7 @@ async function attachNamedSeatStripeRenewal(sk, seat) {
4884
4994
  return String(existing.id);
4885
4995
  }
4886
4996
  if (seat.combinedInvoice) reject3();
4887
- const result = await stripeRequest7({ secretKey: sk }, "POST", "/v1/subscription_items", {
4997
+ const result = await stripeRequest8({ secretKey: sk }, "POST", "/v1/subscription_items", {
4888
4998
  subscription: seat.providerSubscriptionId,
4889
4999
  price: seat.priceId,
4890
5000
  quantity: 1,
@@ -5159,10 +5269,10 @@ async function recordRecipient(db, input, now, approve) {
5159
5269
  }
5160
5270
 
5161
5271
  // src/membership-seat-refund-stripe.ts
5162
- import { stripeRequest as stripeRequest9 } from "@odla-ai/stripe";
5272
+ import { stripeRequest as stripeRequest10 } from "@odla-ai/stripe";
5163
5273
 
5164
5274
  // src/membership-checkout-seat-refund.ts
5165
- import { stripeRequest as stripeRequest8 } from "@odla-ai/stripe";
5275
+ import { stripeRequest as stripeRequest9 } from "@odla-ai/stripe";
5166
5276
  var fail4 = (code = "named_seat_refund_mismatch") => {
5167
5277
  throw Object.assign(new Error(code), { code });
5168
5278
  };
@@ -5171,7 +5281,7 @@ async function refundCheckoutNamedSeat(secretKey, seat) {
5171
5281
  const invoiceRef = seat.combinedInvoice;
5172
5282
  if (!invoiceRef || !/^in_[A-Za-z0-9_]+$/.test(invoiceRef.invoiceId) || !Number.isSafeInteger(invoiceRef.totalCents) || invoiceRef.totalCents <= seat.amountCents || seat.amountCents <= 0 || seat.status !== "refund_pending") fail4();
5173
5283
  const call = async (method, path, params, key3) => {
5174
- const result = await stripeRequest8({ secretKey }, method, path, params, key3 ? { idempotencyKey: key3 } : {});
5284
+ const result = await stripeRequest9({ secretKey }, method, path, params, key3 ? { idempotencyKey: key3 } : {});
5175
5285
  if (!result.ok || !result.body) return fail4("named_seat_refund_provider_unavailable");
5176
5286
  return result.body;
5177
5287
  };
@@ -5244,7 +5354,7 @@ async function refundNamedSeatStripePayment(secretKey, seat) {
5244
5354
  if (seat.combinedInvoice) return refundCheckoutNamedSeat(secretKey, seat);
5245
5355
  if (!seat.providerPaymentIntentId || seat.status !== "refund_pending") fail5();
5246
5356
  const call = async (method, path, params, idempotencyKey) => {
5247
- const result = await stripeRequest9({ secretKey }, method, path, params, idempotencyKey ? { idempotencyKey } : {});
5357
+ const result = await stripeRequest10({ secretKey }, method, path, params, idempotencyKey ? { idempotencyKey } : {});
5248
5358
  if (!result.ok || !result.body) return fail5("named_seat_refund_provider_unavailable");
5249
5359
  return result.body;
5250
5360
  };
@@ -5946,7 +6056,7 @@ async function readBoundedJson(response, signal, maxBytes, label) {
5946
6056
  };
5947
6057
  signal.addEventListener("abort", cancel, { once: true });
5948
6058
  let size = 0;
5949
- let text6 = "";
6059
+ let text7 = "";
5950
6060
  const decoder = new TextDecoder();
5951
6061
  try {
5952
6062
  while (true) {
@@ -5956,10 +6066,10 @@ async function readBoundedJson(response, signal, maxBytes, label) {
5956
6066
  if (done) break;
5957
6067
  size += value.byteLength;
5958
6068
  if (size > maxBytes) throw new TypeError(`${label} exceeds limit`);
5959
- text6 += decoder.decode(value, { stream: true });
6069
+ text7 += decoder.decode(value, { stream: true });
5960
6070
  }
5961
6071
  try {
5962
- return JSON.parse(text6 + decoder.decode());
6072
+ return JSON.parse(text7 + decoder.decode());
5963
6073
  } catch {
5964
6074
  throw new TypeError(`invalid ${label}`);
5965
6075
  }
@@ -6173,7 +6283,7 @@ async function requestMembershipRefund(options) {
6173
6283
  async function requestMembershipRenewal(options) {
6174
6284
  const result = await requestAuthority(options, "renewals", { applicationId: options.applicationId, skuId: options.skuId, eventId: options.eventId });
6175
6285
  const row2 = result.renewal;
6176
- if (!row2 || row2.membershipId !== await networkMembershipId(options) || !Number.isSafeInteger(row2.termEndsAt) || row2.termEndsAt <= 0 || !Number.isSafeInteger(row2.graceEndsAt) || row2.graceEndsAt < 0 || typeof row2.autoRenew !== "boolean" || !["active", "paid_pending_approval", "cancel_at_term_end", "payment_grace", "lapsed", "refund_pending", "refunded", "canceled", "declined", "restarting"].includes(row2.status)) {
6286
+ if (!row2 || row2.membershipId !== await networkMembershipId(options) || !Number.isSafeInteger(row2.termEndsAt) || row2.termEndsAt <= 0 || !Number.isSafeInteger(row2.graceEndsAt) || row2.graceEndsAt < 0 || typeof row2.autoRenew !== "boolean" || !["active", "paid_pending_approval", "cancel_at_term_end", "payment_grace", "lapsed", "refund_pending", "refunded", "canceled", "declined", "restarting", "payment_disputed"].includes(row2.status)) {
6177
6287
  throw new Error("invalid membership renewal receipt");
6178
6288
  }
6179
6289
  return { membershipId: row2.membershipId, status: row2.status, termEndsAt: row2.termEndsAt, graceEndsAt: row2.graceEndsAt, autoRenew: row2.autoRenew };
@@ -6233,7 +6343,7 @@ async function requestMembershipCancellation(options) {
6233
6343
  }
6234
6344
 
6235
6345
  // src/membership-seat-cancel.ts
6236
- import { initStripe, stripeRequest as stripeRequest10 } from "@odla-ai/stripe";
6346
+ import { initStripe, stripeRequest as stripeRequest11 } from "@odla-ai/stripe";
6237
6347
  async function cancelUnpaidNamedSeat(db, target, secretKey, now) {
6238
6348
  membershipClock(now);
6239
6349
  const state = await loadMembershipAuthorityState(db, target);
@@ -6261,7 +6371,7 @@ async function cancelUnpaidNamedSeat(db, target, secretKey, now) {
6261
6371
  }
6262
6372
  const metadata = { ...stripePartitionMetadata(target), applicationId: target.applicationId, membershipId: seat.primaryMembershipId, seatId: seat.id };
6263
6373
  const request = async (method, path, params) => {
6264
- const result = await stripeRequest10(
6374
+ const result = await stripeRequest11(
6265
6375
  { secretKey },
6266
6376
  method,
6267
6377
  path,
@@ -6485,7 +6595,20 @@ var membershipAuthorityEntities = {
6485
6595
  graceEndsAt: attr9("number", true),
6486
6596
  ...times(),
6487
6597
  refundRequestedAt: attr9("number", true),
6488
- restartId: key2(true)
6598
+ restartId: key2(true),
6599
+ // A card-network dispute on this membership's payment: Stripe's dispute id and status, the reason the cardholder gave,
6600
+ // the status the membership held before it was suspended, and the re-issued charge once an operator collected again.
6601
+ disputeId: key2(true),
6602
+ disputeStatus: attr9("string", true),
6603
+ disputeReason: attr9("string", true),
6604
+ disputeAmountCents: attr9("number", true),
6605
+ disputedAt: attr9("number", true),
6606
+ disputeClosedAt: attr9("number", true),
6607
+ disputePriorStatus: attr9("string", true),
6608
+ disputeEventId: key2(true),
6609
+ rechargePaymentIntentId: key2(true),
6610
+ rechargedAt: attr9("number", true),
6611
+ rechargeAttempt: attr9("json", true)
6489
6612
  } },
6490
6613
  membershipRestart: { attrs: {
6491
6614
  id: id10(),
@@ -6921,7 +7044,7 @@ var record2 = (value) => value !== null && typeof value === "object" && !Array.i
6921
7044
  function unknownKeys2(value, allowed2, path, errors) {
6922
7045
  for (const key3 of Object.keys(value)) if (!allowed2.has(key3)) errors.push(`${path}.${key3} is not supported`);
6923
7046
  }
6924
- function text3(value, path, errors, max = MAX_TEXT2, required = true) {
7047
+ function text4(value, path, errors, max = MAX_TEXT2, required = true) {
6925
7048
  if (value === void 0 && !required) return void 0;
6926
7049
  if (typeof value !== "string" || required && !value.trim()) {
6927
7050
  errors.push(`${path} must be a non-empty string`);
@@ -6947,7 +7070,7 @@ function scopeValue(value, path, errors) {
6947
7070
  return { kind: raw2.kind };
6948
7071
  }
6949
7072
  if (raw2.kind === "chapter") {
6950
- const chapterId = text3(raw2.chapterId, `${path}.chapterId`, errors, 63) ?? "";
7073
+ const chapterId = text4(raw2.chapterId, `${path}.chapterId`, errors, 63) ?? "";
6951
7074
  if (chapterId && !APP_ID.test(chapterId)) errors.push(`${path}.chapterId must be a lowercase app slug`);
6952
7075
  return { kind: "chapter", chapterId };
6953
7076
  }
@@ -6961,22 +7084,22 @@ function resourceValue(value, path, errors) {
6961
7084
  return null;
6962
7085
  }
6963
7086
  unknownKeys2(raw2, RESOURCE_KEYS, path, errors);
6964
- const id11 = text3(raw2.id, `${path}.id`, errors, 80) ?? "";
7087
+ const id11 = text4(raw2.id, `${path}.id`, errors, 80) ?? "";
6965
7088
  if (id11 && !ID2.test(id11)) errors.push(`${path}.id must be lowercase kebab-case`);
6966
- const kind = text3(raw2.kind, `${path}.kind`, errors) ?? "";
7089
+ const kind = text4(raw2.kind, `${path}.kind`, errors) ?? "";
6967
7090
  if (!KINDS.has(kind)) errors.push(`${path}.kind is unsupported`);
6968
- const title = text3(raw2.title, `${path}.title`, errors, 240) ?? "";
6969
- const summary = text3(raw2.summary, `${path}.summary`, errors, 1e3, false);
6970
- const status2 = text3(raw2.status, `${path}.status`, errors) ?? "";
7091
+ const title = text4(raw2.title, `${path}.title`, errors, 240) ?? "";
7092
+ const summary = text4(raw2.summary, `${path}.summary`, errors, 1e3, false);
7093
+ const status2 = text4(raw2.status, `${path}.status`, errors) ?? "";
6971
7094
  if (!STATUSES.has(status2)) errors.push(`${path}.status must be draft, published, or archived`);
6972
7095
  const revision = integer3(raw2.revision, `${path}.revision`, errors);
6973
7096
  const scope2 = scopeValue(raw2.scope, `${path}.scope`, errors) ?? { kind: "global" };
6974
- const tags = Array.isArray(raw2.tags) ? raw2.tags.map((tag, index2) => text3(tag, `${path}.tags[${index2}]`, errors, 80) ?? "") : [];
7097
+ const tags = Array.isArray(raw2.tags) ? raw2.tags.map((tag, index2) => text4(tag, `${path}.tags[${index2}]`, errors, 80) ?? "") : [];
6975
7098
  if (!Array.isArray(raw2.tags)) errors.push(`${path}.tags must be an array`);
6976
7099
  if (tags.length > MAX_TAGS) errors.push(`${path}.tags may contain at most ${MAX_TAGS} items`);
6977
7100
  if (new Set(tags).size !== tags.length) errors.push(`${path}.tags must be unique`);
6978
- const bodyMarkdown = text3(raw2.bodyMarkdown, `${path}.bodyMarkdown`, errors, MAX_MARKDOWN2, false);
6979
- const url = text3(raw2.url, `${path}.url`, errors, 2048, false);
7101
+ const bodyMarkdown = text4(raw2.bodyMarkdown, `${path}.bodyMarkdown`, errors, MAX_MARKDOWN2, false);
7102
+ const url = text4(raw2.url, `${path}.url`, errors, 2048, false);
6980
7103
  if (url) {
6981
7104
  try {
6982
7105
  if (new URL(url).protocol !== "https:") errors.push(`${path}.url must use https`);
@@ -6988,9 +7111,9 @@ function resourceValue(value, path, errors) {
6988
7111
  if (raw2.brandAsset !== void 0 && !asset) errors.push(`${path}.brandAsset must be an object`);
6989
7112
  if (asset) unknownKeys2(asset, ASSET_KEYS, `${path}.brandAsset`, errors);
6990
7113
  const brandAsset = asset ? {
6991
- bookId: text3(asset.bookId, `${path}.brandAsset.bookId`, errors, 160) ?? "",
6992
- assetId: text3(asset.assetId, `${path}.brandAsset.assetId`, errors, 160) ?? "",
6993
- ...asset.filename === void 0 ? {} : { filename: text3(asset.filename, `${path}.brandAsset.filename`, errors, 240) ?? "" }
7114
+ bookId: text4(asset.bookId, `${path}.brandAsset.bookId`, errors, 160) ?? "",
7115
+ assetId: text4(asset.assetId, `${path}.brandAsset.assetId`, errors, 160) ?? "",
7116
+ ...asset.filename === void 0 ? {} : { filename: text4(asset.filename, `${path}.brandAsset.filename`, errors, 240) ?? "" }
6994
7117
  } : void 0;
6995
7118
  if ((kind === "document" || kind === "note") && !bodyMarkdown) errors.push(`${path}.bodyMarkdown is required for ${kind}`);
6996
7119
  if ((kind === "link" || kind === "inspiration") && !url) errors.push(`${path}.url is required for ${kind}`);
@@ -7005,17 +7128,17 @@ function resourceValue(value, path, errors) {
7005
7128
  if (provenanceRaw?.promotedFrom !== void 0 && !promotedRaw) errors.push(`${path}.provenance.promotedFrom must be an object`);
7006
7129
  if (promotedRaw) unknownKeys2(promotedRaw, PROMOTION_KEYS, `${path}.provenance.promotedFrom`, errors);
7007
7130
  const promotedFrom = promotedRaw ? {
7008
- chapterId: text3(promotedRaw.chapterId, `${path}.provenance.promotedFrom.chapterId`, errors, 63) ?? "",
7009
- resourceId: text3(promotedRaw.resourceId, `${path}.provenance.promotedFrom.resourceId`, errors, 80) ?? "",
7131
+ chapterId: text4(promotedRaw.chapterId, `${path}.provenance.promotedFrom.chapterId`, errors, 63) ?? "",
7132
+ resourceId: text4(promotedRaw.resourceId, `${path}.provenance.promotedFrom.resourceId`, errors, 80) ?? "",
7010
7133
  resourceRevision: integer3(promotedRaw.resourceRevision, `${path}.provenance.promotedFrom.resourceRevision`, errors),
7011
7134
  documentRevision: integer3(promotedRaw.documentRevision, `${path}.provenance.promotedFrom.documentRevision`, errors),
7012
- promotedBy: text3(promotedRaw.promotedBy, `${path}.provenance.promotedFrom.promotedBy`, errors, 320) ?? "",
7135
+ promotedBy: text4(promotedRaw.promotedBy, `${path}.provenance.promotedFrom.promotedBy`, errors, 320) ?? "",
7013
7136
  promotedAt: integer3(promotedRaw.promotedAt, `${path}.provenance.promotedFrom.promotedAt`, errors, 0)
7014
7137
  } : void 0;
7015
7138
  const provenance = {
7016
- createdBy: text3(provenanceRaw?.createdBy, `${path}.provenance.createdBy`, errors, 320) ?? "",
7139
+ createdBy: text4(provenanceRaw?.createdBy, `${path}.provenance.createdBy`, errors, 320) ?? "",
7017
7140
  createdAt: integer3(provenanceRaw?.createdAt, `${path}.provenance.createdAt`, errors, 0),
7018
- updatedBy: text3(provenanceRaw?.updatedBy, `${path}.provenance.updatedBy`, errors, 320) ?? "",
7141
+ updatedBy: text4(provenanceRaw?.updatedBy, `${path}.provenance.updatedBy`, errors, 320) ?? "",
7019
7142
  updatedAt: integer3(provenanceRaw?.updatedAt, `${path}.provenance.updatedAt`, errors, 0),
7020
7143
  ...promotedFrom ? { promotedFrom } : {}
7021
7144
  };
@@ -7034,7 +7157,7 @@ function validateBuilderLibraryDocument(value, options = {}) {
7034
7157
  if (sourceRaw) unknownKeys2(sourceRaw, SOURCE_KEYS2, "document.source", errors);
7035
7158
  const sourceType = sourceRaw?.type;
7036
7159
  if (sourceType !== "chapter" && sourceType !== "built_not_found") errors.push("document.source.type must be chapter or built_not_found");
7037
- const sourceAppId = text3(sourceRaw?.appId, "document.source.appId", errors, 63) ?? "";
7160
+ const sourceAppId = text4(sourceRaw?.appId, "document.source.appId", errors, 63) ?? "";
7038
7161
  if (sourceAppId && !APP_ID.test(sourceAppId)) errors.push("document.source.appId must be a lowercase app slug");
7039
7162
  if (options.sourceType && sourceType !== options.sourceType) errors.push(`document.source.type must be ${options.sourceType}`);
7040
7163
  if (options.sourceAppId && sourceAppId !== options.sourceAppId) errors.push(`document.source.appId must be ${options.sourceAppId}`);
@@ -7042,8 +7165,8 @@ function validateBuilderLibraryDocument(value, options = {}) {
7042
7165
  if (raw2.target !== void 0 && !targetRaw) errors.push("document.target must be an object");
7043
7166
  if (targetRaw) unknownKeys2(targetRaw, TARGET_KEYS2, "document.target", errors);
7044
7167
  const target = targetRaw ? {
7045
- appId: text3(targetRaw.appId, "document.target.appId", errors, 63) ?? "",
7046
- environment: text3(targetRaw.environment, "document.target.environment", errors, 80) ?? ""
7168
+ appId: text4(targetRaw.appId, "document.target.appId", errors, 63) ?? "",
7169
+ environment: text4(targetRaw.environment, "document.target.environment", errors, 80) ?? ""
7047
7170
  } : void 0;
7048
7171
  if (options.targetAppId && target?.appId !== options.targetAppId) errors.push(`document.target.appId must be ${options.targetAppId}`);
7049
7172
  if (!Array.isArray(raw2.resources)) errors.push("document.resources must be an array");
@@ -7138,9 +7261,9 @@ async function createBuilderLibraryEnvelope(document) {
7138
7261
  }
7139
7262
 
7140
7263
  // src/email-retirement.ts
7141
- function isRetiredEmailCopy(subject, text6) {
7264
+ function isRetiredEmailCopy(subject, text7) {
7142
7265
  const subjectCopy = subject.trimStart().replace(/^\[dev\]\s*/i, "");
7143
- const bodyCopy = text6.trimStart().replace(/^\(dev redirect; original recipient: [^\r\n]*\)\r?\n\r?\n/, "");
7266
+ const bodyCopy = text7.trimStart().replace(/^\(dev redirect; original recipient: [^\r\n]*\)\r?\n\r?\n/, "");
7144
7267
  return /^\(\s*unused\s*:/i.test(subjectCopy) || /^Unused\.\s*Switched off\b/i.test(bodyCopy);
7145
7268
  }
7146
7269
 
@@ -7218,10 +7341,10 @@ function planDelivery(input) {
7218
7341
  if (isRetiredEmailCopy(renderedSubject, renderedText)) return { deliver: false, reason: "template-retired" };
7219
7342
  if (validateEmailCopy({ subject: renderedSubject, text: renderedText }, { allowedVariables: [] }).length) return { deliver: false, reason: "template-invalid" };
7220
7343
  const subject = (redirect ? "[dev] " : "") + renderedSubject;
7221
- const text6 = redirect ? `(dev redirect; original recipient: ${input.to})
7344
+ const text7 = redirect ? `(dev redirect; original recipient: ${input.to})
7222
7345
 
7223
7346
  ` + renderedText : renderedText;
7224
- return { deliver: true, transport, to, subject, text: text6, redirected: redirect };
7347
+ return { deliver: true, transport, to, subject, text: text7, redirected: redirect };
7225
7348
  }
7226
7349
 
7227
7350
  // src/email-readiness.ts
@@ -7556,6 +7679,17 @@ function findGiftRef(obj) {
7556
7679
  }
7557
7680
  function normalizeWebhookEvent(event2, options = {}) {
7558
7681
  const obj = event2.data?.object ?? {};
7682
+ if (event2.type.startsWith("charge.dispute.")) {
7683
+ const chargeId = typeof obj.charge === "string" ? obj.charge : String(obj.charge?.id ?? "");
7684
+ if (!/^ch_[A-Za-z0-9]+$/.test(chargeId) || typeof obj.id !== "string" || !obj.id) return { kind: "ignored", type: event2.type };
7685
+ return {
7686
+ kind: "disputed",
7687
+ chargeId,
7688
+ disputeId: obj.id,
7689
+ disputeStatus: typeof obj.status === "string" ? obj.status : "unknown",
7690
+ ...typeof obj.payment_intent === "string" ? { paymentIntentId: obj.payment_intent } : {}
7691
+ };
7692
+ }
7559
7693
  if (event2.type === "charge.refunded" && (!Number.isSafeInteger(obj.amount) || Number(obj.amount) <= 0 || obj.amount_refunded !== obj.amount || obj.refunded !== true)) return { kind: "ignored", type: event2.type };
7560
7694
  const gift = findGiftRef(obj);
7561
7695
  if (gift.giftIntentId) {
@@ -7866,7 +8000,7 @@ var ID4 = /^[a-z][a-z0-9-]{1,79}$/;
7866
8000
  var PRICE = /^price_[A-Za-z0-9]{12,}$/;
7867
8001
  var CURRENCY = /^[a-z]{3}$/;
7868
8002
  var record3 = (value) => value && typeof value === "object" && !Array.isArray(value) ? value : null;
7869
- var text4 = (value, path, errors, max) => {
8003
+ var text5 = (value, path, errors, max) => {
7870
8004
  if (typeof value !== "string" || !value.trim()) {
7871
8005
  errors.push(`${path} is required`);
7872
8006
  return "";
@@ -7894,23 +8028,23 @@ function validateGiftOfferCatalog(input, tierIds) {
7894
8028
  }
7895
8029
  const allowed2 = /* @__PURE__ */ new Set(["id", "target", "billingMode", "stripePriceId", "priceCents", "currency", "redemptionLifetimeHours", "activeFrom", "activeUntil", "title", "description", "ctaLabel"]);
7896
8030
  for (const key3 of Object.keys(item)) if (!allowed2.has(key3)) errors.push(`${path}.${key3} is not supported`);
7897
- const id11 = text4(item.id, `${path}.id`, errors, 80);
8031
+ const id11 = text5(item.id, `${path}.id`, errors, 80);
7898
8032
  if (id11 && !ID4.test(id11)) errors.push(`${path}.id must be a lowercase kebab-case id`);
7899
8033
  if (ids2.has(id11)) errors.push(`${path}.id duplicates "${id11}"`);
7900
8034
  ids2.add(id11);
7901
8035
  const targetRaw = record3(item.target);
7902
8036
  let target = { type: "entitlement", entitlementKey: "invalid" };
7903
8037
  if (targetRaw?.type === "tier") {
7904
- const tierId = text4(targetRaw.tierId, `${path}.target.tierId`, errors, 80);
8038
+ const tierId = text5(targetRaw.tierId, `${path}.target.tierId`, errors, 80);
7905
8039
  if (!tierIds.has(tierId)) errors.push(`${path}.target.tierId must reference a declared tier`);
7906
8040
  target = { type: "tier", tierId };
7907
8041
  } else if (targetRaw?.type === "entitlement") {
7908
- const entitlementKey = text4(targetRaw.entitlementKey, `${path}.target.entitlementKey`, errors, 80);
8042
+ const entitlementKey = text5(targetRaw.entitlementKey, `${path}.target.entitlementKey`, errors, 80);
7909
8043
  if (entitlementKey && !ID4.test(entitlementKey)) errors.push(`${path}.target.entitlementKey must be a lowercase kebab-case id`);
7910
8044
  target = { type: "entitlement", entitlementKey };
7911
8045
  } else errors.push(`${path}.target.type must be tier or entitlement`);
7912
8046
  if (item.billingMode !== "one_time" && item.billingMode !== "subscription") errors.push(`${path}.billingMode must be one_time or subscription`);
7913
- const stripePriceId = text4(item.stripePriceId, `${path}.stripePriceId`, errors, 128);
8047
+ const stripePriceId = text5(item.stripePriceId, `${path}.stripePriceId`, errors, 128);
7914
8048
  if (stripePriceId && !PRICE.test(stripePriceId)) errors.push(`${path}.stripePriceId must be a real Stripe Price id`);
7915
8049
  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`);
7916
8050
  const currency = typeof item.currency === "string" ? item.currency.trim().toLowerCase() : "";
@@ -7932,9 +8066,9 @@ function validateGiftOfferCatalog(input, tierIds) {
7932
8066
  redemptionLifetimeHours: lifetime,
7933
8067
  ...activeFrom !== void 0 ? { activeFrom } : {},
7934
8068
  ...activeUntil !== void 0 ? { activeUntil } : {},
7935
- title: text4(item.title, `${path}.title`, errors, 160),
7936
- description: text4(item.description, `${path}.description`, errors, 2e3),
7937
- ctaLabel: text4(item.ctaLabel, `${path}.ctaLabel`, errors, 120)
8069
+ title: text5(item.title, `${path}.title`, errors, 160),
8070
+ description: text5(item.description, `${path}.description`, errors, 2e3),
8071
+ ctaLabel: text5(item.ctaLabel, `${path}.ctaLabel`, errors, 120)
7938
8072
  });
7939
8073
  }
7940
8074
  return errors.length ? { ok: false, errors } : { ok: true, value: { schemaVersion: 1, offers } };
@@ -8398,9 +8532,9 @@ function subAnnualCents(sub) {
8398
8532
  }
8399
8533
 
8400
8534
  // src/payments-stripe.ts
8401
- import { stripeRequest as stripeRequest11, verifyStripeSignature as verifyStripeSignature2 } from "@odla-ai/stripe";
8535
+ import { stripeRequest as stripeRequest12, verifyStripeSignature as verifyStripeSignature2 } from "@odla-ai/stripe";
8402
8536
  async function stripeCall(sk, method, path, params, idempotencyKey) {
8403
- return stripeRequest11(
8537
+ return stripeRequest12(
8404
8538
  { secretKey: sk },
8405
8539
  method,
8406
8540
  path,
@@ -9022,10 +9156,10 @@ function boundedString(value, path, errors, max, required = false) {
9022
9156
  errors.push(`${path} must be a string`);
9023
9157
  return void 0;
9024
9158
  }
9025
- const text6 = value.trim();
9026
- if (required && !text6) errors.push(`${path} is required`);
9027
- if (text6.length > max) errors.push(`${path} must be at most ${max.toLocaleString("en-US")} characters`);
9028
- return text6;
9159
+ const text7 = value.trim();
9160
+ if (required && !text7) errors.push(`${path} is required`);
9161
+ if (text7.length > max) errors.push(`${path} must be at most ${max.toLocaleString("en-US")} characters`);
9162
+ return text7;
9029
9163
  }
9030
9164
  function validateMembershipPageDocument(input, options = {}) {
9031
9165
  const errors = [];
@@ -9074,8 +9208,8 @@ function validateMembershipPageDocument(input, options = {}) {
9074
9208
  else {
9075
9209
  if (item.benefits.length > MAX_BENEFITS) errors.push(`${path}.benefits supports at most ${MAX_BENEFITS} entries`);
9076
9210
  benefits = item.benefits.slice(0, MAX_BENEFITS).flatMap((benefit, benefitIndex) => {
9077
- const text6 = boundedString(benefit, `${path}.benefits[${benefitIndex}]`, errors, 2e3, true);
9078
- return text6 ? [text6] : [];
9211
+ const text7 = boundedString(benefit, `${path}.benefits[${benefitIndex}]`, errors, 2e3, true);
9212
+ return text7 ? [text7] : [];
9079
9213
  });
9080
9214
  }
9081
9215
  }
@@ -9174,7 +9308,7 @@ var recordOf = (value) => value !== null && typeof value === "object" && !Array.
9174
9308
  function unknownKeys3(value, allowed2, path, errors) {
9175
9309
  for (const key3 of Object.keys(value)) if (!allowed2.has(key3)) errors.push(`${path}.${key3} is not supported`);
9176
9310
  }
9177
- function text5(value, path, errors, max = 65536) {
9311
+ function text6(value, path, errors, max = 65536) {
9178
9312
  if (typeof value !== "string" || !value.trim()) {
9179
9313
  errors.push(`${path} is required`);
9180
9314
  return "";
@@ -9197,11 +9331,11 @@ function tiersOf(value, errors) {
9197
9331
  return [];
9198
9332
  }
9199
9333
  unknownKeys3(item, TIER_KEYS2, path, errors);
9200
- const id11 = text5(item.id, `${path}.id`, errors, 80);
9334
+ const id11 = text6(item.id, `${path}.id`, errors, 80);
9201
9335
  if (id11 && !TIER.test(id11)) errors.push(`${path}.id must be a lowercase kebab-case id`);
9202
9336
  if (seen.has(id11)) errors.push(`${path}.id duplicates "${id11}"`);
9203
9337
  seen.add(id11);
9204
- const name = text5(item.name, `${path}.name`, errors, 160);
9338
+ const name = text6(item.name, `${path}.name`, errors, 160);
9205
9339
  if (typeof item.available !== "boolean") errors.push(`${path}.available must be boolean`);
9206
9340
  if (!Number.isInteger(item.sortOrder) || Number(item.sortOrder) < 0) errors.push(`${path}.sortOrder must be a non-negative integer`);
9207
9341
  if (!Number.isInteger(item.priceCents) || Number(item.priceCents) < 0 || Number(item.priceCents) > 1e8) {
@@ -9219,7 +9353,7 @@ function tiersOf(value, errors) {
9219
9353
  if (item.interval !== void 0 && item.interval !== "year" && item.interval !== "month") {
9220
9354
  errors.push(`${path}.interval must be year or month`);
9221
9355
  }
9222
- const blurb = item.blurb === void 0 ? void 0 : text5(item.blurb, `${path}.blurb`, errors, 4e3);
9356
+ const blurb = item.blurb === void 0 ? void 0 : text6(item.blurb, `${path}.blurb`, errors, 4e3);
9223
9357
  return [{
9224
9358
  id: id11,
9225
9359
  name,
@@ -9277,7 +9411,7 @@ function policyOf(value, errors, tiers2) {
9277
9411
  const result = {};
9278
9412
  for (const key3 of allowed2) {
9279
9413
  if (input[key3] === void 0 && key3 !== "disclaimerText" && key3 !== "refundPolicyText") continue;
9280
- result[key3] = text5(input[key3], `document.policy.${key3}`, errors);
9414
+ result[key3] = text6(input[key3], `document.policy.${key3}`, errors);
9281
9415
  }
9282
9416
  const tierPolicies = {};
9283
9417
  if (input.tierPolicies !== void 0) {
@@ -9292,8 +9426,8 @@ function policyOf(value, errors, tiers2) {
9292
9426
  }
9293
9427
  unknownKeys3(policy2, /* @__PURE__ */ new Set(["refundPolicyText", "trustCopy"]), path, errors);
9294
9428
  tierPolicies[id11] = {
9295
- refundPolicyText: text5(policy2.refundPolicyText, `${path}.refundPolicyText`, errors),
9296
- ...policy2.trustCopy !== void 0 ? { trustCopy: text5(policy2.trustCopy, `${path}.trustCopy`, errors) } : {}
9429
+ refundPolicyText: text6(policy2.refundPolicyText, `${path}.refundPolicyText`, errors),
9430
+ ...policy2.trustCopy !== void 0 ? { trustCopy: text6(policy2.trustCopy, `${path}.trustCopy`, errors) } : {}
9297
9431
  };
9298
9432
  }
9299
9433
  }
@@ -9333,9 +9467,9 @@ function validateSignupControlDocument(input) {
9333
9467
  if (!Number.isSafeInteger(root.generatedAt) || Number(root.generatedAt) < 1) errors.push("document.generatedAt must be epoch milliseconds");
9334
9468
  const source = recordOf(root.source);
9335
9469
  const target = recordOf(root.target);
9336
- const appId = text5(source?.appId, "document.source.appId", errors, 63);
9337
- const chapterId = text5(target?.chapterId, "document.target.chapterId", errors, 63);
9338
- const environment = text5(target?.environment, "document.target.environment", errors, 63);
9470
+ const appId = text6(source?.appId, "document.source.appId", errors, 63);
9471
+ const chapterId = text6(target?.chapterId, "document.target.chapterId", errors, 63);
9472
+ const environment = text6(target?.environment, "document.target.environment", errors, 63);
9339
9473
  if (appId && !SLUG9.test(appId)) errors.push("document.source.appId must be a lowercase app slug");
9340
9474
  if (chapterId && !SLUG9.test(chapterId)) errors.push("document.target.chapterId must be a lowercase app slug");
9341
9475
  if (environment && !ENVIRONMENT.test(environment)) errors.push("document.target.environment is invalid");
@@ -9512,9 +9646,9 @@ function markdownBenefits(lines, path, errors) {
9512
9646
  errors.push(`${path} line ${index2 + 1} must be a Markdown bullet`);
9513
9647
  continue;
9514
9648
  }
9515
- const text6 = (match[1] ?? "").trim();
9516
- if (text6.length > 2e3) errors.push(`${path} line ${index2 + 1} must be at most 2,000 characters`);
9517
- if (text6) benefits.push(text6);
9649
+ const text7 = (match[1] ?? "").trim();
9650
+ if (text7.length > 2e3) errors.push(`${path} line ${index2 + 1} must be at most 2,000 characters`);
9651
+ if (text7) benefits.push(text7);
9518
9652
  }
9519
9653
  if (benefits.length > MAX_BENEFITS2) errors.push(`${path} supports at most ${MAX_BENEFITS2} entries`);
9520
9654
  return benefits.slice(0, MAX_BENEFITS2);