@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.
package/dist/index.cjs CHANGED
@@ -1997,8 +1997,8 @@ var APPLICATION_LABELS = {
1997
1997
  state: "State"
1998
1998
  };
1999
1999
  function humanize(id11) {
2000
- const text7 = id11.replace(/([a-z0-9])([A-Z])/g, "$1 $2").replace(/[_-]+/g, " ").trim();
2001
- return text7 ? `${text7[0].toUpperCase()}${text7.slice(1)}` : id11;
2000
+ const text8 = id11.replace(/([a-z0-9])([A-Z])/g, "$1 $2").replace(/[_-]+/g, " ").trim();
2001
+ return text8 ? `${text8[0].toUpperCase()}${text8.slice(1)}` : id11;
2002
2002
  }
2003
2003
  function applicationDefs(chapter) {
2004
2004
  try {
@@ -4665,6 +4665,9 @@ function namedSeatRecipientState(seat) {
4665
4665
  return seat.refundReason === "declined" ? "declined" : "refunded";
4666
4666
  case "canceled":
4667
4667
  return "canceled";
4668
+ // A card-network dispute on the seat's own payment pauses the recipient until it is won or re-charged.
4669
+ case "payment_disputed":
4670
+ return "disputed";
4668
4671
  default:
4669
4672
  return "payment_pending";
4670
4673
  }
@@ -4860,12 +4863,87 @@ async function reconcileMembershipRenewal(db, input, now) {
4860
4863
 
4861
4864
  // src/membership-dispute-authority.ts
4862
4865
  var import_stripe4 = require("@odla-ai/stripe");
4866
+
4867
+ // src/membership-dispute-transition.ts
4863
4868
  var MEMBERSHIP_DISPUTED_STATUS = "payment_disputed";
4864
4869
  var DISPUTE_WON_STATUSES = ["won", "warning_closed"];
4865
4870
  var CLOSED_STATUSES = [...DISPUTE_WON_STATUSES, "lost"];
4866
- var TERMINAL = ["refunded", "refund_pending", "canceled", "declined", "restarting", "superseded"];
4871
+ function disputeTransition(row2, fact, eventId, now, terminal) {
4872
+ const status2 = String(fact.status ?? "unknown"), closed = CLOSED_STATUSES.includes(status2);
4873
+ const sameDispute = row2.disputeId === fact.objectId;
4874
+ if (sameDispute && Number(row2.disputeClosedAt ?? 0) > 0 && !closed) return null;
4875
+ const won = DISPUTE_WON_STATUSES.includes(status2);
4876
+ const suspended = row2.status === MEMBERSHIP_DISPUTED_STATUS, done = terminal.includes(String(row2.status));
4877
+ const restored = String(row2.disputePriorStatus ?? (row2.journeyApprovedAt ? "active" : "paid_pending_approval"));
4878
+ const next = done ? String(row2.status) : won ? suspended ? restored : String(row2.status) : MEMBERSHIP_DISPUTED_STATUS;
4879
+ return {
4880
+ status: next,
4881
+ disputeId: fact.objectId,
4882
+ disputeStatus: status2,
4883
+ disputeEventId: eventId,
4884
+ disputeClosedAt: closed ? now : 0,
4885
+ ...fact.disputeReason ? { disputeReason: fact.disputeReason } : {},
4886
+ ...fact.amountCents !== void 0 ? { disputeAmountCents: fact.amountCents } : {},
4887
+ ...sameDispute ? {} : { disputedAt: now },
4888
+ ...next === MEMBERSHIP_DISPUTED_STATUS && !suspended ? { disputePriorStatus: String(row2.status) } : {},
4889
+ revision: Number(row2.revision) + 1,
4890
+ updatedAt: now
4891
+ };
4892
+ }
4893
+
4894
+ // src/membership-seat-dispute.ts
4867
4895
  var text3 = (value) => typeof value === "string" && value ? value : void 0;
4868
- var refOf = (value) => typeof value === "string" ? text3(value) : text3(value?.id);
4896
+ var TERMINAL = ["payment_pending", "payment_processing", "payment_canceling", "refund_pending", "refunded", "canceled"];
4897
+ var namedSeatDisputeStanding = (seat, applied) => ({ seatId: String(seat.id), status: String(seat.status), ...text3(seat.disputeStatus) ? { disputeStatus: text3(seat.disputeStatus) } : {}, applied });
4898
+ function disputedChargeCoversSeat(charge, seat) {
4899
+ return charge.seatId === seat.id && charge.customerId === seat.providerCustomerId && charge.paymentIntentId !== void 0 && (charge.paymentIntentId === seat.providerPaymentIntentId || charge.paymentIntentId === seat.rechargePaymentIntentId);
4900
+ }
4901
+ async function reconcileNamedSeatDispute(db, input, now, charge, evidence) {
4902
+ if (!charge.seatId) membershipFail("membership_dispute_scope_mismatch");
4903
+ const membership = async (applied, seat) => {
4904
+ const id11 = await networkMembershipId(input), member = await membershipLoad(db, "networkMembership", id11);
4905
+ requireMembershipScope(member, input);
4906
+ return {
4907
+ membershipId: id11,
4908
+ status: String(member.status),
4909
+ termEndsAt: Number(member.termEndsAt),
4910
+ graceEndsAt: Number(member.graceEndsAt ?? 0),
4911
+ autoRenew: member.autoRenew === true,
4912
+ applied,
4913
+ ...text3(member.disputeStatus) ? { disputeStatus: text3(member.disputeStatus) } : {},
4914
+ ...seat ? { seat: namedSeatDisputeStanding(seat, applied) } : {}
4915
+ };
4916
+ };
4917
+ for (let attempt = 0; attempt < 8; attempt++) {
4918
+ const state = await loadMembershipAuthorityState(db, input), seat = await membershipLoad(db, "networkSeat", charge.seatId);
4919
+ if (!seat) return membership(false);
4920
+ requireMembershipScope(seat, input);
4921
+ if (charge.customerId !== seat.providerCustomerId) membershipFail("membership_dispute_scope_mismatch");
4922
+ if (!disputedChargeCoversSeat(charge, seat) || seat.disputeEventId === input.eventId) return membership(seat.disputeEventId === input.eventId, seat);
4923
+ const attrs = disputeTransition(seat, evidence.fact, input.eventId, now, TERMINAL);
4924
+ if (!attrs) return membership(false, seat);
4925
+ try {
4926
+ await db.transact([membershipUpdate("networkSeat", String(seat.id), attrs)], { mutationId: `seat-dispute:${seat.id}:${input.eventId}`, guards: [
4927
+ { ns: "networkSeat", id: String(seat.id), exists: true, equals: { revision: seat.revision } },
4928
+ { ns: "membershipAuthorityState", id: String(state.id), exists: true, equals: { mode: "authoritative", revision: state.revision } },
4929
+ { ns: "networkCommercialEvents", id: String(evidence.row.id), exists: true, equals: { digest: evidence.fact.digest } },
4930
+ evidence.guard
4931
+ ] });
4932
+ } catch (error) {
4933
+ if (error.code !== "transact_guard_failed") throw error;
4934
+ continue;
4935
+ }
4936
+ const saved = await membershipLoad(db, "networkSeat", String(seat.id));
4937
+ requireMembershipScope(saved, input);
4938
+ return membership(true, saved);
4939
+ }
4940
+ return membershipFail("membership_dispute_retry_required");
4941
+ }
4942
+
4943
+ // src/membership-dispute-authority.ts
4944
+ var TERMINAL2 = ["refunded", "refund_pending", "canceled", "declined", "restarting", "superseded"];
4945
+ var text4 = (value) => typeof value === "string" && value ? value : void 0;
4946
+ var refOf = (value) => typeof value === "string" ? text4(value) : text4(value?.id);
4869
4947
  async function readDisputedCharge(secretKey, chargeId, fetch2) {
4870
4948
  if (!/^ch_[A-Za-z0-9]+$/.test(chargeId)) membershipFail("invalid_dispute_charge");
4871
4949
  const config = { secretKey, ...fetch2 ? { fetch: fetch2 } : {} };
@@ -4879,7 +4957,7 @@ async function readDisputedCharge(secretKey, chargeId, fetch2) {
4879
4957
  if (!invoice.ok || invoice.body.id !== invoiceId) membershipFail("membership_dispute_provider_unavailable");
4880
4958
  subscriptionId = refOf(invoice.body.subscription) ?? refOf(invoice.body.parent?.subscription_details?.subscription);
4881
4959
  }
4882
- const seatId = text3(charge.body.metadata?.seatId);
4960
+ const seatId = text4(charge.body.metadata?.seatId);
4883
4961
  return {
4884
4962
  chargeId,
4885
4963
  customerId,
@@ -4905,37 +4983,23 @@ async function reconcileMembershipDispute(db, input, now, secretKey, resolved) {
4905
4983
  termEndsAt: Number(value.termEndsAt),
4906
4984
  graceEndsAt: Number(value.graceEndsAt ?? 0),
4907
4985
  autoRenew: value.autoRenew === true,
4908
- ...text3(value.disputeStatus) ? { disputeStatus: text3(value.disputeStatus) } : {},
4986
+ ...text4(value.disputeStatus) ? { disputeStatus: text4(value.disputeStatus) } : {},
4909
4987
  applied
4910
4988
  });
4911
4989
  const known = await membershipLoad(db, "networkMembership", id11);
4912
4990
  requireMembershipScope(known, input);
4913
4991
  if (known.disputeEventId === input.eventId) return receipt(known);
4992
+ const seatKnown = known.namedSeatId ? await membershipLoad(db, "networkSeat", String(known.namedSeatId)) : void 0;
4993
+ if (seatKnown?.disputeEventId === input.eventId) return { ...receipt(known, true), seat: namedSeatDisputeStanding(seatKnown, true) };
4914
4994
  const charge = resolved && resolved.chargeId === fact.chargeId ? resolved : await readDisputedCharge(secretKey, fact.chargeId);
4995
+ if (charge.customerId !== known.providerCustomerId) membershipFail("membership_dispute_scope_mismatch");
4996
+ if (charge.seatId) return reconcileNamedSeatDispute(db, input, now, charge, { row: row2, fact, guard: evidenceGuard });
4915
4997
  for (let attempt = 0; attempt < 8; attempt++) {
4916
4998
  const state = await loadMembershipAuthorityState(db, input), member = await membershipLoad(db, "networkMembership", id11);
4917
4999
  requireMembershipScope(member, input);
4918
- if (charge.customerId !== member.providerCustomerId) membershipFail("membership_dispute_scope_mismatch");
4919
5000
  if (!disputedChargeCoversMembership(charge, member) || member.disputeEventId === input.eventId) return receipt(member);
4920
- const status2 = String(fact.status ?? "unknown"), closed = CLOSED_STATUSES.includes(status2);
4921
- const sameDispute = member.disputeId === fact.objectId;
4922
- if (sameDispute && Number(member.disputeClosedAt ?? 0) > 0 && !closed) return receipt(member, false);
4923
- const won = DISPUTE_WON_STATUSES.includes(status2);
4924
- const suspended = member.status === MEMBERSHIP_DISPUTED_STATUS, terminal = TERMINAL.includes(String(member.status));
4925
- const next = terminal ? String(member.status) : won ? suspended ? String(member.disputePriorStatus ?? (member.journeyApprovedAt ? "active" : "paid_pending_approval")) : String(member.status) : MEMBERSHIP_DISPUTED_STATUS;
4926
- const attrs = {
4927
- status: next,
4928
- disputeId: fact.objectId,
4929
- disputeStatus: status2,
4930
- disputeEventId: input.eventId,
4931
- disputeClosedAt: closed ? now : 0,
4932
- ...fact.disputeReason ? { disputeReason: fact.disputeReason } : {},
4933
- ...fact.amountCents !== void 0 ? { disputeAmountCents: fact.amountCents } : {},
4934
- ...sameDispute ? {} : { disputedAt: now },
4935
- ...next === MEMBERSHIP_DISPUTED_STATUS && !suspended ? { disputePriorStatus: String(member.status) } : {},
4936
- revision: Number(member.revision) + 1,
4937
- updatedAt: now
4938
- };
5001
+ const attrs = disputeTransition(member, fact, input.eventId, now, TERMINAL2);
5002
+ if (!attrs) return receipt(member, false);
4939
5003
  try {
4940
5004
  await db.transact([membershipUpdate("networkMembership", id11, attrs)], { mutationId: `membership-dispute:${id11}:${input.eventId}`, guards: [
4941
5005
  { ns: "networkMembership", id: id11, exists: true, equals: { revision: member.revision } },
@@ -6339,7 +6403,7 @@ async function readBoundedJson(response, signal, maxBytes, label) {
6339
6403
  };
6340
6404
  signal.addEventListener("abort", cancel, { once: true });
6341
6405
  let size = 0;
6342
- let text7 = "";
6406
+ let text8 = "";
6343
6407
  const decoder = new TextDecoder();
6344
6408
  try {
6345
6409
  while (true) {
@@ -6349,10 +6413,10 @@ async function readBoundedJson(response, signal, maxBytes, label) {
6349
6413
  if (done) break;
6350
6414
  size += value.byteLength;
6351
6415
  if (size > maxBytes) throw new TypeError(`${label} exceeds limit`);
6352
- text7 += decoder.decode(value, { stream: true });
6416
+ text8 += decoder.decode(value, { stream: true });
6353
6417
  }
6354
6418
  try {
6355
- return JSON.parse(text7 + decoder.decode());
6419
+ return JSON.parse(text8 + decoder.decode());
6356
6420
  } catch {
6357
6421
  throw new TypeError(`invalid ${label}`);
6358
6422
  }
@@ -6839,6 +6903,19 @@ var id10 = () => attr9("string", false, true, true);
6839
6903
  var key2 = (optional = false) => attr9("string", optional, false, true);
6840
6904
  var scope = () => ({ appId: key2(), chapterId: key2(), environment: key2(), runtime: key2(), applicationId: key2(), skuId: key2() });
6841
6905
  var times = () => ({ revision: attr9("number"), createdAt: attr9("number"), updatedAt: attr9("number") });
6906
+ var dispute = () => ({
6907
+ disputeId: key2(true),
6908
+ disputeStatus: attr9("string", true),
6909
+ disputeReason: attr9("string", true),
6910
+ disputeAmountCents: attr9("number", true),
6911
+ disputedAt: attr9("number", true),
6912
+ disputeClosedAt: attr9("number", true),
6913
+ disputePriorStatus: attr9("string", true),
6914
+ disputeEventId: key2(true),
6915
+ rechargePaymentIntentId: key2(true),
6916
+ rechargedAt: attr9("number", true),
6917
+ rechargeAttempt: attr9("json", true)
6918
+ });
6842
6919
  var membershipAuthorityEntities = {
6843
6920
  networkMembership: { attrs: {
6844
6921
  id: id10(),
@@ -6879,19 +6956,7 @@ var membershipAuthorityEntities = {
6879
6956
  ...times(),
6880
6957
  refundRequestedAt: attr9("number", true),
6881
6958
  restartId: key2(true),
6882
- // A card-network dispute on this membership's payment: Stripe's dispute id and status, the reason the cardholder gave,
6883
- // the status the membership held before it was suspended, and the re-issued charge once an operator collected again.
6884
- disputeId: key2(true),
6885
- disputeStatus: attr9("string", true),
6886
- disputeReason: attr9("string", true),
6887
- disputeAmountCents: attr9("number", true),
6888
- disputedAt: attr9("number", true),
6889
- disputeClosedAt: attr9("number", true),
6890
- disputePriorStatus: attr9("string", true),
6891
- disputeEventId: key2(true),
6892
- rechargePaymentIntentId: key2(true),
6893
- rechargedAt: attr9("number", true),
6894
- rechargeAttempt: attr9("json", true)
6959
+ ...dispute()
6895
6960
  } },
6896
6961
  membershipRestart: { attrs: {
6897
6962
  id: id10(),
@@ -6967,7 +7032,8 @@ var membershipAuthorityEntities = {
6967
7032
  refundedCents: attr9("number", true),
6968
7033
  providerRefundIds: attr9("json", true),
6969
7034
  regiftedAt: attr9("number", true),
6970
- formerRecipients: attr9("json", true)
7035
+ formerRecipients: attr9("json", true),
7036
+ ...dispute()
6971
7037
  } },
6972
7038
  networkSeatRecipient: { attrs: { id: id10(), appId: key2(), chapterId: key2(), environment: key2(), runtime: key2(), seatId: key2(), recipientEmailNormalized: key2(), createdAt: attr9("number") } },
6973
7039
  membershipEffect: { attrs: {
@@ -7327,7 +7393,7 @@ var record2 = (value) => value !== null && typeof value === "object" && !Array.i
7327
7393
  function unknownKeys2(value, allowed2, path, errors) {
7328
7394
  for (const key3 of Object.keys(value)) if (!allowed2.has(key3)) errors.push(`${path}.${key3} is not supported`);
7329
7395
  }
7330
- function text4(value, path, errors, max = MAX_TEXT2, required = true) {
7396
+ function text5(value, path, errors, max = MAX_TEXT2, required = true) {
7331
7397
  if (value === void 0 && !required) return void 0;
7332
7398
  if (typeof value !== "string" || required && !value.trim()) {
7333
7399
  errors.push(`${path} must be a non-empty string`);
@@ -7353,7 +7419,7 @@ function scopeValue(value, path, errors) {
7353
7419
  return { kind: raw2.kind };
7354
7420
  }
7355
7421
  if (raw2.kind === "chapter") {
7356
- const chapterId = text4(raw2.chapterId, `${path}.chapterId`, errors, 63) ?? "";
7422
+ const chapterId = text5(raw2.chapterId, `${path}.chapterId`, errors, 63) ?? "";
7357
7423
  if (chapterId && !APP_ID.test(chapterId)) errors.push(`${path}.chapterId must be a lowercase app slug`);
7358
7424
  return { kind: "chapter", chapterId };
7359
7425
  }
@@ -7367,22 +7433,22 @@ function resourceValue(value, path, errors) {
7367
7433
  return null;
7368
7434
  }
7369
7435
  unknownKeys2(raw2, RESOURCE_KEYS, path, errors);
7370
- const id11 = text4(raw2.id, `${path}.id`, errors, 80) ?? "";
7436
+ const id11 = text5(raw2.id, `${path}.id`, errors, 80) ?? "";
7371
7437
  if (id11 && !ID2.test(id11)) errors.push(`${path}.id must be lowercase kebab-case`);
7372
- const kind = text4(raw2.kind, `${path}.kind`, errors) ?? "";
7438
+ const kind = text5(raw2.kind, `${path}.kind`, errors) ?? "";
7373
7439
  if (!KINDS.has(kind)) errors.push(`${path}.kind is unsupported`);
7374
- const title = text4(raw2.title, `${path}.title`, errors, 240) ?? "";
7375
- const summary = text4(raw2.summary, `${path}.summary`, errors, 1e3, false);
7376
- const status2 = text4(raw2.status, `${path}.status`, errors) ?? "";
7440
+ const title = text5(raw2.title, `${path}.title`, errors, 240) ?? "";
7441
+ const summary = text5(raw2.summary, `${path}.summary`, errors, 1e3, false);
7442
+ const status2 = text5(raw2.status, `${path}.status`, errors) ?? "";
7377
7443
  if (!STATUSES.has(status2)) errors.push(`${path}.status must be draft, published, or archived`);
7378
7444
  const revision = integer3(raw2.revision, `${path}.revision`, errors);
7379
7445
  const scope2 = scopeValue(raw2.scope, `${path}.scope`, errors) ?? { kind: "global" };
7380
- const tags = Array.isArray(raw2.tags) ? raw2.tags.map((tag, index2) => text4(tag, `${path}.tags[${index2}]`, errors, 80) ?? "") : [];
7446
+ const tags = Array.isArray(raw2.tags) ? raw2.tags.map((tag, index2) => text5(tag, `${path}.tags[${index2}]`, errors, 80) ?? "") : [];
7381
7447
  if (!Array.isArray(raw2.tags)) errors.push(`${path}.tags must be an array`);
7382
7448
  if (tags.length > MAX_TAGS) errors.push(`${path}.tags may contain at most ${MAX_TAGS} items`);
7383
7449
  if (new Set(tags).size !== tags.length) errors.push(`${path}.tags must be unique`);
7384
- const bodyMarkdown = text4(raw2.bodyMarkdown, `${path}.bodyMarkdown`, errors, MAX_MARKDOWN2, false);
7385
- const url = text4(raw2.url, `${path}.url`, errors, 2048, false);
7450
+ const bodyMarkdown = text5(raw2.bodyMarkdown, `${path}.bodyMarkdown`, errors, MAX_MARKDOWN2, false);
7451
+ const url = text5(raw2.url, `${path}.url`, errors, 2048, false);
7386
7452
  if (url) {
7387
7453
  try {
7388
7454
  if (new URL(url).protocol !== "https:") errors.push(`${path}.url must use https`);
@@ -7394,9 +7460,9 @@ function resourceValue(value, path, errors) {
7394
7460
  if (raw2.brandAsset !== void 0 && !asset) errors.push(`${path}.brandAsset must be an object`);
7395
7461
  if (asset) unknownKeys2(asset, ASSET_KEYS, `${path}.brandAsset`, errors);
7396
7462
  const brandAsset = asset ? {
7397
- bookId: text4(asset.bookId, `${path}.brandAsset.bookId`, errors, 160) ?? "",
7398
- assetId: text4(asset.assetId, `${path}.brandAsset.assetId`, errors, 160) ?? "",
7399
- ...asset.filename === void 0 ? {} : { filename: text4(asset.filename, `${path}.brandAsset.filename`, errors, 240) ?? "" }
7463
+ bookId: text5(asset.bookId, `${path}.brandAsset.bookId`, errors, 160) ?? "",
7464
+ assetId: text5(asset.assetId, `${path}.brandAsset.assetId`, errors, 160) ?? "",
7465
+ ...asset.filename === void 0 ? {} : { filename: text5(asset.filename, `${path}.brandAsset.filename`, errors, 240) ?? "" }
7400
7466
  } : void 0;
7401
7467
  if ((kind === "document" || kind === "note") && !bodyMarkdown) errors.push(`${path}.bodyMarkdown is required for ${kind}`);
7402
7468
  if ((kind === "link" || kind === "inspiration") && !url) errors.push(`${path}.url is required for ${kind}`);
@@ -7411,17 +7477,17 @@ function resourceValue(value, path, errors) {
7411
7477
  if (provenanceRaw?.promotedFrom !== void 0 && !promotedRaw) errors.push(`${path}.provenance.promotedFrom must be an object`);
7412
7478
  if (promotedRaw) unknownKeys2(promotedRaw, PROMOTION_KEYS, `${path}.provenance.promotedFrom`, errors);
7413
7479
  const promotedFrom = promotedRaw ? {
7414
- chapterId: text4(promotedRaw.chapterId, `${path}.provenance.promotedFrom.chapterId`, errors, 63) ?? "",
7415
- resourceId: text4(promotedRaw.resourceId, `${path}.provenance.promotedFrom.resourceId`, errors, 80) ?? "",
7480
+ chapterId: text5(promotedRaw.chapterId, `${path}.provenance.promotedFrom.chapterId`, errors, 63) ?? "",
7481
+ resourceId: text5(promotedRaw.resourceId, `${path}.provenance.promotedFrom.resourceId`, errors, 80) ?? "",
7416
7482
  resourceRevision: integer3(promotedRaw.resourceRevision, `${path}.provenance.promotedFrom.resourceRevision`, errors),
7417
7483
  documentRevision: integer3(promotedRaw.documentRevision, `${path}.provenance.promotedFrom.documentRevision`, errors),
7418
- promotedBy: text4(promotedRaw.promotedBy, `${path}.provenance.promotedFrom.promotedBy`, errors, 320) ?? "",
7484
+ promotedBy: text5(promotedRaw.promotedBy, `${path}.provenance.promotedFrom.promotedBy`, errors, 320) ?? "",
7419
7485
  promotedAt: integer3(promotedRaw.promotedAt, `${path}.provenance.promotedFrom.promotedAt`, errors, 0)
7420
7486
  } : void 0;
7421
7487
  const provenance = {
7422
- createdBy: text4(provenanceRaw?.createdBy, `${path}.provenance.createdBy`, errors, 320) ?? "",
7488
+ createdBy: text5(provenanceRaw?.createdBy, `${path}.provenance.createdBy`, errors, 320) ?? "",
7423
7489
  createdAt: integer3(provenanceRaw?.createdAt, `${path}.provenance.createdAt`, errors, 0),
7424
- updatedBy: text4(provenanceRaw?.updatedBy, `${path}.provenance.updatedBy`, errors, 320) ?? "",
7490
+ updatedBy: text5(provenanceRaw?.updatedBy, `${path}.provenance.updatedBy`, errors, 320) ?? "",
7425
7491
  updatedAt: integer3(provenanceRaw?.updatedAt, `${path}.provenance.updatedAt`, errors, 0),
7426
7492
  ...promotedFrom ? { promotedFrom } : {}
7427
7493
  };
@@ -7440,7 +7506,7 @@ function validateBuilderLibraryDocument(value, options = {}) {
7440
7506
  if (sourceRaw) unknownKeys2(sourceRaw, SOURCE_KEYS2, "document.source", errors);
7441
7507
  const sourceType = sourceRaw?.type;
7442
7508
  if (sourceType !== "chapter" && sourceType !== "built_not_found") errors.push("document.source.type must be chapter or built_not_found");
7443
- const sourceAppId = text4(sourceRaw?.appId, "document.source.appId", errors, 63) ?? "";
7509
+ const sourceAppId = text5(sourceRaw?.appId, "document.source.appId", errors, 63) ?? "";
7444
7510
  if (sourceAppId && !APP_ID.test(sourceAppId)) errors.push("document.source.appId must be a lowercase app slug");
7445
7511
  if (options.sourceType && sourceType !== options.sourceType) errors.push(`document.source.type must be ${options.sourceType}`);
7446
7512
  if (options.sourceAppId && sourceAppId !== options.sourceAppId) errors.push(`document.source.appId must be ${options.sourceAppId}`);
@@ -7448,8 +7514,8 @@ function validateBuilderLibraryDocument(value, options = {}) {
7448
7514
  if (raw2.target !== void 0 && !targetRaw) errors.push("document.target must be an object");
7449
7515
  if (targetRaw) unknownKeys2(targetRaw, TARGET_KEYS2, "document.target", errors);
7450
7516
  const target = targetRaw ? {
7451
- appId: text4(targetRaw.appId, "document.target.appId", errors, 63) ?? "",
7452
- environment: text4(targetRaw.environment, "document.target.environment", errors, 80) ?? ""
7517
+ appId: text5(targetRaw.appId, "document.target.appId", errors, 63) ?? "",
7518
+ environment: text5(targetRaw.environment, "document.target.environment", errors, 80) ?? ""
7453
7519
  } : void 0;
7454
7520
  if (options.targetAppId && target?.appId !== options.targetAppId) errors.push(`document.target.appId must be ${options.targetAppId}`);
7455
7521
  if (!Array.isArray(raw2.resources)) errors.push("document.resources must be an array");
@@ -7544,9 +7610,9 @@ async function createBuilderLibraryEnvelope(document) {
7544
7610
  }
7545
7611
 
7546
7612
  // src/email-retirement.ts
7547
- function isRetiredEmailCopy(subject, text7) {
7613
+ function isRetiredEmailCopy(subject, text8) {
7548
7614
  const subjectCopy = subject.trimStart().replace(/^\[dev\]\s*/i, "");
7549
- const bodyCopy = text7.trimStart().replace(/^\(dev redirect; original recipient: [^\r\n]*\)\r?\n\r?\n/, "");
7615
+ const bodyCopy = text8.trimStart().replace(/^\(dev redirect; original recipient: [^\r\n]*\)\r?\n\r?\n/, "");
7550
7616
  return /^\(\s*unused\s*:/i.test(subjectCopy) || /^Unused\.\s*Switched off\b/i.test(bodyCopy);
7551
7617
  }
7552
7618
 
@@ -7624,10 +7690,10 @@ function planDelivery(input) {
7624
7690
  if (isRetiredEmailCopy(renderedSubject, renderedText)) return { deliver: false, reason: "template-retired" };
7625
7691
  if ((0, import_crm5.validateEmailCopy)({ subject: renderedSubject, text: renderedText }, { allowedVariables: [] }).length) return { deliver: false, reason: "template-invalid" };
7626
7692
  const subject = (redirect ? "[dev] " : "") + renderedSubject;
7627
- const text7 = redirect ? `(dev redirect; original recipient: ${input.to})
7693
+ const text8 = redirect ? `(dev redirect; original recipient: ${input.to})
7628
7694
 
7629
7695
  ` + renderedText : renderedText;
7630
- return { deliver: true, transport, to, subject, text: text7, redirected: redirect };
7696
+ return { deliver: true, transport, to, subject, text: text8, redirected: redirect };
7631
7697
  }
7632
7698
 
7633
7699
  // src/email-readiness.ts
@@ -8283,7 +8349,7 @@ var ID4 = /^[a-z][a-z0-9-]{1,79}$/;
8283
8349
  var PRICE = /^price_[A-Za-z0-9]{12,}$/;
8284
8350
  var CURRENCY = /^[a-z]{3}$/;
8285
8351
  var record3 = (value) => value && typeof value === "object" && !Array.isArray(value) ? value : null;
8286
- var text5 = (value, path, errors, max) => {
8352
+ var text6 = (value, path, errors, max) => {
8287
8353
  if (typeof value !== "string" || !value.trim()) {
8288
8354
  errors.push(`${path} is required`);
8289
8355
  return "";
@@ -8311,23 +8377,23 @@ function validateGiftOfferCatalog(input, tierIds) {
8311
8377
  }
8312
8378
  const allowed2 = /* @__PURE__ */ new Set(["id", "target", "billingMode", "stripePriceId", "priceCents", "currency", "redemptionLifetimeHours", "activeFrom", "activeUntil", "title", "description", "ctaLabel"]);
8313
8379
  for (const key3 of Object.keys(item)) if (!allowed2.has(key3)) errors.push(`${path}.${key3} is not supported`);
8314
- const id11 = text5(item.id, `${path}.id`, errors, 80);
8380
+ const id11 = text6(item.id, `${path}.id`, errors, 80);
8315
8381
  if (id11 && !ID4.test(id11)) errors.push(`${path}.id must be a lowercase kebab-case id`);
8316
8382
  if (ids2.has(id11)) errors.push(`${path}.id duplicates "${id11}"`);
8317
8383
  ids2.add(id11);
8318
8384
  const targetRaw = record3(item.target);
8319
8385
  let target = { type: "entitlement", entitlementKey: "invalid" };
8320
8386
  if (targetRaw?.type === "tier") {
8321
- const tierId = text5(targetRaw.tierId, `${path}.target.tierId`, errors, 80);
8387
+ const tierId = text6(targetRaw.tierId, `${path}.target.tierId`, errors, 80);
8322
8388
  if (!tierIds.has(tierId)) errors.push(`${path}.target.tierId must reference a declared tier`);
8323
8389
  target = { type: "tier", tierId };
8324
8390
  } else if (targetRaw?.type === "entitlement") {
8325
- const entitlementKey = text5(targetRaw.entitlementKey, `${path}.target.entitlementKey`, errors, 80);
8391
+ const entitlementKey = text6(targetRaw.entitlementKey, `${path}.target.entitlementKey`, errors, 80);
8326
8392
  if (entitlementKey && !ID4.test(entitlementKey)) errors.push(`${path}.target.entitlementKey must be a lowercase kebab-case id`);
8327
8393
  target = { type: "entitlement", entitlementKey };
8328
8394
  } else errors.push(`${path}.target.type must be tier or entitlement`);
8329
8395
  if (item.billingMode !== "one_time" && item.billingMode !== "subscription") errors.push(`${path}.billingMode must be one_time or subscription`);
8330
- const stripePriceId = text5(item.stripePriceId, `${path}.stripePriceId`, errors, 128);
8396
+ const stripePriceId = text6(item.stripePriceId, `${path}.stripePriceId`, errors, 128);
8331
8397
  if (stripePriceId && !PRICE.test(stripePriceId)) errors.push(`${path}.stripePriceId must be a real Stripe Price id`);
8332
8398
  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`);
8333
8399
  const currency = typeof item.currency === "string" ? item.currency.trim().toLowerCase() : "";
@@ -8349,9 +8415,9 @@ function validateGiftOfferCatalog(input, tierIds) {
8349
8415
  redemptionLifetimeHours: lifetime,
8350
8416
  ...activeFrom !== void 0 ? { activeFrom } : {},
8351
8417
  ...activeUntil !== void 0 ? { activeUntil } : {},
8352
- title: text5(item.title, `${path}.title`, errors, 160),
8353
- description: text5(item.description, `${path}.description`, errors, 2e3),
8354
- ctaLabel: text5(item.ctaLabel, `${path}.ctaLabel`, errors, 120)
8418
+ title: text6(item.title, `${path}.title`, errors, 160),
8419
+ description: text6(item.description, `${path}.description`, errors, 2e3),
8420
+ ctaLabel: text6(item.ctaLabel, `${path}.ctaLabel`, errors, 120)
8355
8421
  });
8356
8422
  }
8357
8423
  return errors.length ? { ok: false, errors } : { ok: true, value: { schemaVersion: 1, offers } };
@@ -9434,10 +9500,10 @@ function boundedString(value, path, errors, max, required = false) {
9434
9500
  errors.push(`${path} must be a string`);
9435
9501
  return void 0;
9436
9502
  }
9437
- const text7 = value.trim();
9438
- if (required && !text7) errors.push(`${path} is required`);
9439
- if (text7.length > max) errors.push(`${path} must be at most ${max.toLocaleString("en-US")} characters`);
9440
- return text7;
9503
+ const text8 = value.trim();
9504
+ if (required && !text8) errors.push(`${path} is required`);
9505
+ if (text8.length > max) errors.push(`${path} must be at most ${max.toLocaleString("en-US")} characters`);
9506
+ return text8;
9441
9507
  }
9442
9508
  function validateMembershipPageDocument(input, options = {}) {
9443
9509
  const errors = [];
@@ -9486,8 +9552,8 @@ function validateMembershipPageDocument(input, options = {}) {
9486
9552
  else {
9487
9553
  if (item.benefits.length > MAX_BENEFITS) errors.push(`${path}.benefits supports at most ${MAX_BENEFITS} entries`);
9488
9554
  benefits = item.benefits.slice(0, MAX_BENEFITS).flatMap((benefit, benefitIndex) => {
9489
- const text7 = boundedString(benefit, `${path}.benefits[${benefitIndex}]`, errors, 2e3, true);
9490
- return text7 ? [text7] : [];
9555
+ const text8 = boundedString(benefit, `${path}.benefits[${benefitIndex}]`, errors, 2e3, true);
9556
+ return text8 ? [text8] : [];
9491
9557
  });
9492
9558
  }
9493
9559
  }
@@ -9586,7 +9652,7 @@ var recordOf = (value) => value !== null && typeof value === "object" && !Array.
9586
9652
  function unknownKeys3(value, allowed2, path, errors) {
9587
9653
  for (const key3 of Object.keys(value)) if (!allowed2.has(key3)) errors.push(`${path}.${key3} is not supported`);
9588
9654
  }
9589
- function text6(value, path, errors, max = 65536) {
9655
+ function text7(value, path, errors, max = 65536) {
9590
9656
  if (typeof value !== "string" || !value.trim()) {
9591
9657
  errors.push(`${path} is required`);
9592
9658
  return "";
@@ -9609,11 +9675,11 @@ function tiersOf(value, errors) {
9609
9675
  return [];
9610
9676
  }
9611
9677
  unknownKeys3(item, TIER_KEYS2, path, errors);
9612
- const id11 = text6(item.id, `${path}.id`, errors, 80);
9678
+ const id11 = text7(item.id, `${path}.id`, errors, 80);
9613
9679
  if (id11 && !TIER.test(id11)) errors.push(`${path}.id must be a lowercase kebab-case id`);
9614
9680
  if (seen.has(id11)) errors.push(`${path}.id duplicates "${id11}"`);
9615
9681
  seen.add(id11);
9616
- const name = text6(item.name, `${path}.name`, errors, 160);
9682
+ const name = text7(item.name, `${path}.name`, errors, 160);
9617
9683
  if (typeof item.available !== "boolean") errors.push(`${path}.available must be boolean`);
9618
9684
  if (!Number.isInteger(item.sortOrder) || Number(item.sortOrder) < 0) errors.push(`${path}.sortOrder must be a non-negative integer`);
9619
9685
  if (!Number.isInteger(item.priceCents) || Number(item.priceCents) < 0 || Number(item.priceCents) > 1e8) {
@@ -9631,7 +9697,7 @@ function tiersOf(value, errors) {
9631
9697
  if (item.interval !== void 0 && item.interval !== "year" && item.interval !== "month") {
9632
9698
  errors.push(`${path}.interval must be year or month`);
9633
9699
  }
9634
- const blurb = item.blurb === void 0 ? void 0 : text6(item.blurb, `${path}.blurb`, errors, 4e3);
9700
+ const blurb = item.blurb === void 0 ? void 0 : text7(item.blurb, `${path}.blurb`, errors, 4e3);
9635
9701
  return [{
9636
9702
  id: id11,
9637
9703
  name,
@@ -9689,7 +9755,7 @@ function policyOf(value, errors, tiers2) {
9689
9755
  const result = {};
9690
9756
  for (const key3 of allowed2) {
9691
9757
  if (input[key3] === void 0 && key3 !== "disclaimerText" && key3 !== "refundPolicyText") continue;
9692
- result[key3] = text6(input[key3], `document.policy.${key3}`, errors);
9758
+ result[key3] = text7(input[key3], `document.policy.${key3}`, errors);
9693
9759
  }
9694
9760
  const tierPolicies = {};
9695
9761
  if (input.tierPolicies !== void 0) {
@@ -9704,8 +9770,8 @@ function policyOf(value, errors, tiers2) {
9704
9770
  }
9705
9771
  unknownKeys3(policy2, /* @__PURE__ */ new Set(["refundPolicyText", "trustCopy"]), path, errors);
9706
9772
  tierPolicies[id11] = {
9707
- refundPolicyText: text6(policy2.refundPolicyText, `${path}.refundPolicyText`, errors),
9708
- ...policy2.trustCopy !== void 0 ? { trustCopy: text6(policy2.trustCopy, `${path}.trustCopy`, errors) } : {}
9773
+ refundPolicyText: text7(policy2.refundPolicyText, `${path}.refundPolicyText`, errors),
9774
+ ...policy2.trustCopy !== void 0 ? { trustCopy: text7(policy2.trustCopy, `${path}.trustCopy`, errors) } : {}
9709
9775
  };
9710
9776
  }
9711
9777
  }
@@ -9745,9 +9811,9 @@ function validateSignupControlDocument(input) {
9745
9811
  if (!Number.isSafeInteger(root.generatedAt) || Number(root.generatedAt) < 1) errors.push("document.generatedAt must be epoch milliseconds");
9746
9812
  const source = recordOf(root.source);
9747
9813
  const target = recordOf(root.target);
9748
- const appId = text6(source?.appId, "document.source.appId", errors, 63);
9749
- const chapterId = text6(target?.chapterId, "document.target.chapterId", errors, 63);
9750
- const environment = text6(target?.environment, "document.target.environment", errors, 63);
9814
+ const appId = text7(source?.appId, "document.source.appId", errors, 63);
9815
+ const chapterId = text7(target?.chapterId, "document.target.chapterId", errors, 63);
9816
+ const environment = text7(target?.environment, "document.target.environment", errors, 63);
9751
9817
  if (appId && !SLUG9.test(appId)) errors.push("document.source.appId must be a lowercase app slug");
9752
9818
  if (chapterId && !SLUG9.test(chapterId)) errors.push("document.target.chapterId must be a lowercase app slug");
9753
9819
  if (environment && !ENVIRONMENT.test(environment)) errors.push("document.target.environment is invalid");
@@ -9924,9 +9990,9 @@ function markdownBenefits(lines, path, errors) {
9924
9990
  errors.push(`${path} line ${index2 + 1} must be a Markdown bullet`);
9925
9991
  continue;
9926
9992
  }
9927
- const text7 = (match[1] ?? "").trim();
9928
- if (text7.length > 2e3) errors.push(`${path} line ${index2 + 1} must be at most 2,000 characters`);
9929
- if (text7) benefits.push(text7);
9993
+ const text8 = (match[1] ?? "").trim();
9994
+ if (text8.length > 2e3) errors.push(`${path} line ${index2 + 1} must be at most 2,000 characters`);
9995
+ if (text8) benefits.push(text8);
9930
9996
  }
9931
9997
  if (benefits.length > MAX_BENEFITS2) errors.push(`${path} supports at most ${MAX_BENEFITS2} entries`);
9932
9998
  return benefits.slice(0, MAX_BENEFITS2);