@odla-ai/chapter 0.54.2 → 0.56.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -253,7 +253,7 @@ __export(src_exports, {
253
253
  slotWindow: () => slotWindow,
254
254
  stageIndex: () => stageIndex,
255
255
  stripeCall: () => stripeCall,
256
- stripeForm: () => import_stripe10.stripeForm,
256
+ stripeForm: () => import_stripe11.stripeForm,
257
257
  subAnnualCents: () => subAnnualCents,
258
258
  submitApplication: () => submitApplication,
259
259
  subscriptionIdempotencyKey: () => subscriptionIdempotencyKey,
@@ -275,7 +275,7 @@ __export(src_exports, {
275
275
  verifyMembershipAccessReceipt: () => verifyMembershipAccessReceipt,
276
276
  verifyMembershipCheckoutBinding: () => verifyMembershipCheckoutBinding,
277
277
  verifyMembershipPaymentReceipt: () => verifyMembershipPaymentReceipt,
278
- verifyStripeSignature: () => import_stripe10.verifyStripeSignature,
278
+ verifyStripeSignature: () => import_stripe11.verifyStripeSignature,
279
279
  webhookMutationId: () => webhookMutationId
280
280
  });
281
281
  module.exports = __toCommonJS(src_exports);
@@ -404,8 +404,8 @@ async function submitApplication(db, chapter, fields, opts) {
404
404
  if (app.requireDisclaimerAck && !acked) {
405
405
  return { ok: false, error: "disclaimerAck is required" };
406
406
  }
407
- const id10 = opts.submissionId ? await applicationIdForSubmission(opts.submissionId) : opts.newId();
408
- const row2 = { id: id10, status: chapter.pipeline.initial, createdAt: opts.now };
407
+ const id11 = opts.submissionId ? await applicationIdForSubmission(opts.submissionId) : opts.newId();
408
+ const row2 = { id: id11, status: chapter.pipeline.initial, createdAt: opts.now };
409
409
  for (const f of [...app.required, ...app.optional]) {
410
410
  if (fieldStates[f]?.visible === false) continue;
411
411
  if (typeof fields[f] === "string") row2[f] = fields[f].trim();
@@ -439,12 +439,12 @@ async function submitApplication(db, chapter, fields, opts) {
439
439
  }
440
440
  if (acked) row2.disclaimerAckAt = opts.now;
441
441
  const ops = [
442
- { t: "update", ns: "applications", id: id10, attrs: row2 }
442
+ { t: "update", ns: "applications", id: id11, attrs: row2 }
443
443
  ];
444
444
  if (opts.admissionGrant?.decision === "waive_interview") {
445
445
  ops.push({ t: "update", ns: "admissionGrants", id: opts.admissionGrant.id, attrs: {
446
446
  consumedAt: opts.now,
447
- consumedApplicationId: id10
447
+ consumedApplicationId: id11
448
448
  } });
449
449
  }
450
450
  try {
@@ -452,7 +452,7 @@ async function submitApplication(db, chapter, fields, opts) {
452
452
  ops,
453
453
  opts.submissionId ? { mutationId: `join:${opts.submissionId}` } : void 0
454
454
  );
455
- return { ok: true, id: id10, duplicate, status: chapter.pipeline.initial, disclaimerAckAt: acked ? opts.now : null };
455
+ return { ok: true, id: id11, duplicate, status: chapter.pipeline.initial, disclaimerAckAt: acked ? opts.now : null };
456
456
  } catch (error) {
457
457
  if (error.code === "unique_violation") return {
458
458
  ok: false,
@@ -841,6 +841,7 @@ var subscriptionCheckoutIntentsEntity = {
841
841
  interval: attr5("string", { indexed: true }),
842
842
  billingTerms: attr5("json", { optional: true }),
843
843
  paymentQuote: attr5("json", { optional: true }),
844
+ paymentReceipt: attr5("json", { optional: true }),
844
845
  membershipOffer: attr5("json", { optional: true }),
845
846
  purchaserEmailNormalized: attr5("string", { indexed: true }),
846
847
  requestedCustomerId: attr5("string", { indexed: true, optional: true }),
@@ -1987,9 +1988,9 @@ var APPLICATION_LABELS = {
1987
1988
  phone: "Phone",
1988
1989
  state: "State"
1989
1990
  };
1990
- function humanize(id10) {
1991
- const text6 = id10.replace(/([a-z0-9])([A-Z])/g, "$1 $2").replace(/[_-]+/g, " ").trim();
1992
- return text6 ? `${text6[0].toUpperCase()}${text6.slice(1)}` : id10;
1991
+ function humanize(id11) {
1992
+ const text6 = id11.replace(/([a-z0-9])([A-Z])/g, "$1 $2").replace(/[_-]+/g, " ").trim();
1993
+ return text6 ? `${text6[0].toUpperCase()}${text6.slice(1)}` : id11;
1993
1994
  }
1994
1995
  function applicationDefs(chapter) {
1995
1996
  try {
@@ -1998,23 +1999,23 @@ function applicationDefs(chapter) {
1998
1999
  return {};
1999
2000
  }
2000
2001
  }
2001
- function applicationType(id10, def) {
2002
+ function applicationType(id11, def) {
2002
2003
  if (def && ["string", "email", "url", "enum"].includes(def.type)) return def.type;
2003
- return id10 === "email" ? "email" : id10 === "linkedin" ? "url" : "string";
2004
+ return id11 === "email" ? "email" : id11 === "linkedin" ? "url" : "string";
2004
2005
  }
2005
2006
  function applicationFlowFields(chapter, application = chapter.application) {
2006
2007
  const defs = applicationDefs(chapter);
2007
- return [...application.required, ...application.optional].map((id10, order2) => {
2008
- const def = defs[id10];
2009
- const conditions = application.conditions[id10];
2010
- const type = applicationType(id10, def);
2008
+ return [...application.required, ...application.optional].map((id11, order2) => {
2009
+ const def = defs[id11];
2010
+ const conditions = application.conditions[id11];
2011
+ const type = applicationType(id11, def);
2011
2012
  return {
2012
- id: id10,
2013
- label: def?.label ?? APPLICATION_LABELS[id10] ?? humanize(id10),
2013
+ id: id11,
2014
+ label: def?.label ?? APPLICATION_LABELS[id11] ?? humanize(id11),
2014
2015
  type,
2015
- required: application.required.includes(id10),
2016
+ required: application.required.includes(id11),
2016
2017
  order: order2,
2017
- maxLength: application.maxLen[id10] ?? application.defaultMaxLen,
2018
+ maxLength: application.maxLen[id11] ?? application.defaultMaxLen,
2018
2019
  ...type === "enum" && def?.options ? { options: [...def.options] } : {},
2019
2020
  ...conditions ? { conditions: { ...conditions } } : {},
2020
2021
  ...type === "email" ? { emailIdentity: "literal" } : {}
@@ -2024,17 +2025,17 @@ function applicationFlowFields(chapter, application = chapter.application) {
2024
2025
  function resolveFormationFlowFields(crm, formation) {
2025
2026
  if (!formation.enabled) return [];
2026
2027
  const def = crm.type(formation.type);
2027
- return [...formation.required, ...formation.optional].map((id10, order2) => {
2028
- const field2 = def.fields[id10];
2028
+ return [...formation.required, ...formation.optional].map((id11, order2) => {
2029
+ const field2 = def.fields[id11];
2029
2030
  return {
2030
- id: id10,
2031
- label: field2.label ?? humanize(id10),
2031
+ id: id11,
2032
+ label: field2.label ?? humanize(id11),
2032
2033
  type: field2.type,
2033
- required: formation.required.includes(id10),
2034
+ required: formation.required.includes(id11),
2034
2035
  order: order2,
2035
- maxLength: formation.maxLen[id10] ?? formation.defaultMaxLen,
2036
+ maxLength: formation.maxLen[id11] ?? formation.defaultMaxLen,
2036
2037
  ...field2.options ? { options: [...field2.options] } : {},
2037
- ...id10 === def.emailField || field2.type === "email" ? { emailIdentity: "literal" } : {}
2038
+ ...id11 === def.emailField || field2.type === "email" ? { emailIdentity: "literal" } : {}
2038
2039
  };
2039
2040
  });
2040
2041
  }
@@ -2338,12 +2339,12 @@ function resolveNetworkPersonRegistry(mode, input, targetIds) {
2338
2339
  if (input.mode !== "shadow") throw new Error('defineChapter.networkPersonRegistry.mode: must be "shadow"');
2339
2340
  const chapterIds = input.chapterIds ? [...input.chapterIds] : [...targetIds];
2340
2341
  if (chapterIds.length === 0) throw new Error("defineChapter.networkPersonRegistry.chapterIds: at least one chapter is required");
2341
- if (new Set(chapterIds).size !== chapterIds.length || chapterIds.some((id10) => !SLUG2.test(id10))) {
2342
+ if (new Set(chapterIds).size !== chapterIds.length || chapterIds.some((id11) => !SLUG2.test(id11))) {
2342
2343
  throw new Error("defineChapter.networkPersonRegistry.chapterIds: expected distinct lowercase slugs");
2343
2344
  }
2344
- const unknown = chapterIds.filter((id10) => !targetIds.includes(id10));
2345
+ const unknown = chapterIds.filter((id11) => !targetIds.includes(id11));
2345
2346
  if (unknown.length) {
2346
- throw new Error(`defineChapter.networkPersonRegistry.chapterIds: ${unknown.map((id10) => `"${id10}"`).join(", ")} are not configured network targets`);
2347
+ throw new Error(`defineChapter.networkPersonRegistry.chapterIds: ${unknown.map((id11) => `"${id11}"`).join(", ")} are not configured network targets`);
2347
2348
  }
2348
2349
  return { mode: "shadow", chapterIds };
2349
2350
  }
@@ -2466,9 +2467,9 @@ function isResolvedCrm(x) {
2466
2467
  }
2467
2468
  function defineChapter(config) {
2468
2469
  if (!config || typeof config !== "object") throw new Error("defineChapter: a config object is required");
2469
- const { id: id10, name } = config;
2470
- if (typeof id10 !== "string" || !SLUG7.test(id10)) {
2471
- throw new Error(`defineChapter.id: must be a lowercase slug [a-z0-9-] (2-63 chars) \u2014 got ${JSON.stringify(id10)}`);
2470
+ const { id: id11, name } = config;
2471
+ if (typeof id11 !== "string" || !SLUG7.test(id11)) {
2472
+ throw new Error(`defineChapter.id: must be a lowercase slug [a-z0-9-] (2-63 chars) \u2014 got ${JSON.stringify(id11)}`);
2472
2473
  }
2473
2474
  if (typeof name !== "string" || name.trim() === "") throw new Error("defineChapter.name: a non-empty name is required");
2474
2475
  const mode = config.mode ?? "chapter";
@@ -2575,7 +2576,7 @@ function defineChapter(config) {
2575
2576
  };
2576
2577
  const chapter = {
2577
2578
  config,
2578
- id: id10,
2579
+ id: id11,
2579
2580
  name,
2580
2581
  mode,
2581
2582
  brand,
@@ -2990,14 +2991,14 @@ function facts(snapshots) {
2990
2991
  ["payment_intent.updated", snapshots.payments?.objects ?? []]
2991
2992
  ];
2992
2993
  return groups2.flatMap(([eventType, objects]) => objects.map((object2) => {
2993
- const id10 = typeof object2.id === "string" ? object2.id : "invalid";
2994
+ const id11 = typeof object2.id === "string" ? object2.id : "invalid";
2994
2995
  return normalizeNetworkCommercialStripeEvent({
2995
- id: `evt_snapshot_${eventType.replaceAll(".", "_")}_${id10}`,
2996
+ id: `evt_snapshot_${eventType.replaceAll(".", "_")}_${id11}`,
2996
2997
  type: eventType,
2997
2998
  created: typeof object2.created === "number" ? object2.created : 0,
2998
2999
  livemode: object2.livemode === true,
2999
3000
  data: { object: object2 }
3000
- }, `stripe-snapshot:${eventType}:${id10}`);
3001
+ }, `stripe-snapshot:${eventType}:${id11}`);
3001
3002
  }));
3002
3003
  }
3003
3004
  var selected = (row2) => ({
@@ -3410,7 +3411,7 @@ function findTier(tiers2, tierId) {
3410
3411
  if (!tierId) return tiers2.length === 1 ? tiers2[0] : null;
3411
3412
  return tiers2.find((tier) => tier.id === tierId) ?? null;
3412
3413
  }
3413
- function joinConfigTiers(tiers2, defaults = {}, founding = /* @__PURE__ */ new Map()) {
3414
+ function joinConfigTiers(tiers2, defaults = {}, founding = /* @__PURE__ */ new Map(), seats = /* @__PURE__ */ new Map()) {
3414
3415
  return tiers2.map((tier) => ({
3415
3416
  id: tier.id,
3416
3417
  name: tier.name,
@@ -3419,7 +3420,8 @@ function joinConfigTiers(tiers2, defaults = {}, founding = /* @__PURE__ */ new M
3419
3420
  interval: tier.interval ?? defaults.interval ?? "year",
3420
3421
  blurb: tier.blurb ?? "",
3421
3422
  free: tierIsFree(tier),
3422
- ...founding.has(tier.id) ? { founding: founding.get(tier.id) } : {}
3423
+ ...founding.has(tier.id) ? { founding: founding.get(tier.id) } : {},
3424
+ ...seats.has(tier.id) ? { seatOffer: seats.get(tier.id) } : {}
3423
3425
  }));
3424
3426
  }
3425
3427
 
@@ -3706,8 +3708,8 @@ async function hash(value) {
3706
3708
  async function membershipDiscountCouponId(input, policyId) {
3707
3709
  return policyId ? `odla_m_${await hash([input.appId, input.chapterId, input.environment, input.runtime, input.skuId, policyId])}` : void 0;
3708
3710
  }
3709
- async function load(db, ns, id10) {
3710
- return (await db.query({ [ns]: { $: { where: { id: id10 }, limit: 1 } } }))[ns]?.[0];
3711
+ async function load(db, ns, id11) {
3712
+ return (await db.query({ [ns]: { $: { where: { id: id11 }, limit: 1 } } }))[ns]?.[0];
3711
3713
  }
3712
3714
  async function membershipOfferIdForRequest(input) {
3713
3715
  return `offer_${await hash([input.appId, input.chapterId, input.environment, input.runtime, input.applicationId, input.requestId])}`;
@@ -3773,6 +3775,7 @@ async function previewMembershipCommercialOffer(db, input) {
3773
3775
  ...contract.founding && contract.discountPolicy ? { cohortLabel: contract.discountPolicy.cohortLabel } : {},
3774
3776
  dueTodayCents: contract.dueTodayCents,
3775
3777
  renewalCents: contract.renewalCents,
3778
+ ...offering.seatPriceCents !== void 0 ? { seatPriceCents: offering.seatPriceCents } : {},
3776
3779
  ...discount ? { discountDuration: discount.duration === "once" ? "first_term" : "while_active" } : {},
3777
3780
  ...contract.initialTermEndsAt !== void 0 ? { initialTermEndsAt: contract.initialTermEndsAt } : {}
3778
3781
  };
@@ -3844,20 +3847,20 @@ var import_stripe2 = require("@odla-ai/stripe");
3844
3847
 
3845
3848
  // src/commercial-evidence-guard.ts
3846
3849
  async function commercialEvidenceGuard(db, eventId, digest, code = "membership_payment_evidence_quarantined") {
3847
- const ns = "networkCommercialExceptions", id10 = `network-commercial-exception:${eventId}:collision`;
3848
- const row2 = (await db.query({ [ns]: { $: { where: { id: id10 }, limit: 1 } } }))[ns]?.[0];
3849
- if (!row2) return { ns, id: id10, exists: false };
3850
+ const ns = "networkCommercialExceptions", id11 = `network-commercial-exception:${eventId}:collision`;
3851
+ const row2 = (await db.query({ [ns]: { $: { where: { id: id11 }, limit: 1 } } }))[ns]?.[0];
3852
+ if (!row2) return { ns, id: id11, exists: false };
3850
3853
  if (row2.status !== "resolved" || row2.resolvedDigest !== digest || typeof digest !== "string") {
3851
3854
  throw Object.assign(new Error(code), { code });
3852
3855
  }
3853
- return { ns, id: id10, exists: true, equals: { status: "resolved", resolvedDigest: digest, updatedAt: row2.updatedAt } };
3856
+ return { ns, id: id11, exists: true, equals: { status: "resolved", resolvedDigest: digest, updatedAt: row2.updatedAt } };
3854
3857
  }
3855
3858
 
3856
3859
  // src/membership-authority-state.ts
3857
3860
  var membershipFail = (code) => {
3858
3861
  throw Object.assign(new Error(code), { code });
3859
3862
  };
3860
- var membershipLoad = async (db, ns, id10) => (await db.query({ [ns]: { $: { where: { id: id10 }, limit: 1 } } }))[ns]?.[0];
3863
+ var membershipLoad = async (db, ns, id11) => (await db.query({ [ns]: { $: { where: { id: id11 }, limit: 1 } } }))[ns]?.[0];
3861
3864
  var membershipScopeKeys = ["appId", "chapterId", "environment", "runtime", "applicationId", "skuId"];
3862
3865
  var membershipScope = (input) => Object.fromEntries(membershipScopeKeys.map((key3) => [key3, input[key3]]));
3863
3866
  function requireMembershipScope(row2, input) {
@@ -3868,18 +3871,18 @@ async function loadMembershipAuthorityState(db, input) {
3868
3871
  if (!row2 || row2.mode !== "authoritative" || row2.chapterId !== input.chapterId || row2.environment !== input.environment || !Number.isSafeInteger(row2.revision) || Number(row2.revision) < 1) return membershipFail("membership_authority_unavailable");
3869
3872
  return row2;
3870
3873
  }
3871
- var membershipUpdate = (ns, id10, attrs) => ({ t: "update", ns, id: id10, attrs });
3874
+ var membershipUpdate = (ns, id11, attrs) => ({ t: "update", ns, id: id11, attrs });
3872
3875
  var membershipClock = (now) => {
3873
3876
  if (!Number.isSafeInteger(now) || now < 0) membershipFail("invalid_membership_clock");
3874
3877
  };
3875
3878
 
3876
3879
  // src/membership-restart-payment.ts
3877
3880
  async function membershipRestartPayment(db, target, member, now) {
3878
- const id10 = String(member.id), restart = await membershipLoad(db, "membershipRestart", id10);
3881
+ const id11 = String(member.id), restart = await membershipLoad(db, "membershipRestart", id11);
3879
3882
  requireMembershipScope(restart, target);
3880
- const entitlement = await membershipLoad(db, "networkEntitlement", `${id10}:primary`);
3883
+ const entitlement = await membershipLoad(db, "networkEntitlement", `${id11}:primary`);
3881
3884
  requireMembershipScope(entitlement, target);
3882
- if (entitlement.membershipId !== id10 || entitlement.kind !== "primary") membershipFail("membership_restart_conflict");
3885
+ if (entitlement.membershipId !== id11 || entitlement.kind !== "primary") membershipFail("membership_restart_conflict");
3883
3886
  if (restart.status !== "ready" || member.status !== "restarting" || member.restartId !== restart.restartId || member.memberNumber !== restart.memberNumber || !Number.isSafeInteger(member.journeyApprovedAt)) membershipFail("membership_restart_conflict");
3884
3887
  return {
3885
3888
  restartId: String(restart.restartId),
@@ -3896,49 +3899,117 @@ async function membershipRestartPayment(db, target, member, now) {
3896
3899
  graceEndsAt: 0
3897
3900
  },
3898
3901
  ops: [
3899
- membershipUpdate("membershipRestart", id10, { status: "complete" }),
3900
- { t: "retract", ns: "networkMembership", id: id10, attrs: ["refundRequestedAt", "cancellationRequestedAt", "discountPolicyVersionId", "discountSnapshot"] }
3902
+ membershipUpdate("membershipRestart", id11, { status: "complete" }),
3903
+ { t: "retract", ns: "networkMembership", id: id11, attrs: ["refundRequestedAt", "cancellationRequestedAt", "discountPolicyVersionId", "discountSnapshot"] }
3901
3904
  ],
3902
3905
  guards: [
3903
- { ns: "networkMembership", id: id10, exists: true, equals: { status: "restarting", revision: member.revision, restartId: restart.restartId } },
3904
- { ns: "membershipRestart", id: id10, exists: true, equals: { status: "ready", restartId: restart.restartId } },
3906
+ { ns: "networkMembership", id: id11, exists: true, equals: { status: "restarting", revision: member.revision, restartId: restart.restartId } },
3907
+ { ns: "membershipRestart", id: id11, exists: true, equals: { status: "ready", restartId: restart.restartId } },
3905
3908
  { ns: "networkEntitlement", id: String(entitlement.id), exists: true, equals: { revision: entitlement.revision, status: entitlement.status } }
3906
3909
  ]
3907
3910
  };
3908
3911
  }
3909
3912
 
3913
+ // src/checkout-named-seat.ts
3914
+ function checkoutNamedSeatSelection(value, purchaserEmail) {
3915
+ const row2 = value;
3916
+ const name = typeof row2?.recipientName === "string" ? row2.recipientName.trim().replace(/\s+/g, " ") : "";
3917
+ const email = normalizeChapterEmail(typeof row2?.recipientEmail === "string" ? row2.recipientEmail : "");
3918
+ const purchaser = normalizeChapterEmail(purchaserEmail);
3919
+ if (!row2 || typeof row2 !== "object" || Array.isArray(row2) || name.length < 2 || name.length > 160 || /[\u0000-\u001f\u007f]/.test(row2.recipientName) || email.length > 254 || !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email) || !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(purchaser) || email === purchaser) {
3920
+ throw Object.assign(new Error("Enter a different recipient's name and email."), { code: "invalid_named_seat_recipient" });
3921
+ }
3922
+ return { recipientName: name, recipientEmail: email };
3923
+ }
3924
+ async function checkoutNamedSeatIdentity(target, email) {
3925
+ const digest = await crypto.subtle.digest("SHA-256", new TextEncoder().encode(JSON.stringify([
3926
+ target.appId,
3927
+ target.chapterId,
3928
+ target.environment,
3929
+ target.runtime,
3930
+ email
3931
+ ])));
3932
+ return {
3933
+ seatId: (await membershipOfferIdForRequest({ ...target, requestId: "named_seat" })).replace(/^offer_/, "seat_"),
3934
+ membershipId: (await membershipOfferIdForRequest({ ...target, requestId: "checkout_binding" })).replace(/^offer_/, "membership_"),
3935
+ recipientClaimId: `seat-recipient:${[...new Uint8Array(digest)].map((byte) => byte.toString(16).padStart(2, "0")).join("")}`
3936
+ };
3937
+ }
3938
+
3910
3939
  // src/membership-checkout-binding.ts
3911
3940
  var keys = ["appId", "chapterId", "environment", "runtime", "applicationId", "skuId", "offerId", "offerDigest", "quoteDigest", "priceId"];
3912
3941
  var reject2 = (code) => {
3913
3942
  throw Object.assign(new Error(code), { code });
3914
3943
  };
3915
- var load2 = async (db, ns, id10) => (await db.query({ [ns]: { $: { where: { id: id10 }, limit: 1 } } }))[ns]?.[0];
3944
+ var load2 = async (db, ns, id11) => (await db.query({ [ns]: { $: { where: { id: id11 }, limit: 1 } } }))[ns]?.[0];
3916
3945
  async function membershipCheckoutBindingId(input) {
3917
3946
  return (await membershipOfferIdForRequest({ ...input, requestId: "checkout_binding" })).replace(/^offer_/, "checkout_");
3918
3947
  }
3919
3948
  async function verifyMembershipCheckoutBinding(value, input) {
3920
3949
  const row2 = value;
3921
3950
  if (!row2 || typeof row2 !== "object" || row2.id !== await membershipCheckoutBindingId(input) || keys.some((key3) => row2[key3] !== input[key3]) || !Number.isSafeInteger(row2.boundAt) || row2.boundAt < 0) return reject2("membership_checkout_conflict");
3922
- return { id: row2.id, ...Object.fromEntries(keys.map((key3) => [key3, input[key3]])), boundAt: row2.boundAt };
3951
+ if (Boolean(row2.namedSeat) !== Boolean(input.namedSeat)) return reject2("membership_checkout_conflict");
3952
+ if (input.namedSeat) {
3953
+ const expected = {
3954
+ ...checkoutNamedSeatSelection(input.namedSeat, input.namedSeat.purchaserEmail),
3955
+ purchaserEmail: normalizeChapterEmail(input.namedSeat.purchaserEmail)
3956
+ };
3957
+ const identity2 = await checkoutNamedSeatIdentity(input, expected.recipientEmail);
3958
+ if (!row2.namedSeat || Object.entries({ ...expected, ...identity2 }).some(([key3, value2]) => row2.namedSeat[key3] !== value2) || !/^price_[A-Za-z0-9_]+$/.test(row2.namedSeat.priceId) || row2.namedSeat.priceId === input.priceId || !Number.isSafeInteger(row2.namedSeat.amountCents) || row2.namedSeat.amountCents < 0) return reject2("membership_checkout_conflict");
3959
+ }
3960
+ return {
3961
+ id: row2.id,
3962
+ ...Object.fromEntries(keys.map((key3) => [key3, input[key3]])),
3963
+ boundAt: row2.boundAt,
3964
+ ...row2.namedSeat ? { namedSeat: row2.namedSeat } : {}
3965
+ };
3923
3966
  }
3924
- async function bindMembershipCheckout(db, input, now) {
3967
+ async function bindMembershipCheckout(db, input, now, seatPriceId) {
3925
3968
  if (!Number.isSafeInteger(now) || now < 0 || !/^[a-f0-9]{64}$/.test(input.offerDigest) || !/^[a-f0-9]{64}$/.test(input.quoteDigest) || !/^price_[A-Za-z0-9_]+$/.test(input.priceId)) return reject2("invalid_membership_checkout");
3926
3969
  const stateId = `${input.chapterId}:${input.environment}`;
3927
3970
  const state = await load2(db, "membershipAuthorityState", stateId);
3928
3971
  if (!state || state.mode !== "authoritative" || state.chapterId !== input.chapterId || state.environment !== input.environment || !Number.isSafeInteger(state.revision) || Number(state.revision) < 1) return reject2("membership_authority_unavailable");
3929
- const id10 = await membershipCheckoutBindingId(input);
3930
- const existing = await load2(db, "membershipOfferCheckout", id10);
3972
+ const id11 = await membershipCheckoutBindingId(input);
3973
+ const existing = await load2(db, "membershipOfferCheckout", id11);
3931
3974
  if (existing) return verifyMembershipCheckoutBinding(existing, input);
3932
3975
  const row2 = await load2(db, "membershipCheckoutOffer", input.offerId);
3933
3976
  const offer = await verifyIssuedMembershipOffer(row2, { ...input, now, lifetimeMs: 6e4 });
3934
3977
  if (offer.digest !== input.offerDigest || offer.expiresAt <= now || offer.issuedAt > now) return reject2("membership_offer_expired");
3935
- const restart = await load2(db, "membershipRestart", id10.replace(/^checkout_/, "membership_"));
3936
- const receipt = { id: id10, ...Object.fromEntries(keys.map((key3) => [key3, input[key3]])), boundAt: now };
3978
+ const restart = await load2(db, "membershipRestart", id11.replace(/^checkout_/, "membership_"));
3979
+ let namedSeat;
3980
+ if (input.namedSeat) {
3981
+ if (!seatPriceId || !/^price_[A-Za-z0-9_]+$/.test(seatPriceId) || seatPriceId === input.priceId || restart) return reject2("named_seat_purchase_unavailable");
3982
+ const selection = checkoutNamedSeatSelection(input.namedSeat, input.namedSeat.purchaserEmail);
3983
+ namedSeat = {
3984
+ ...selection,
3985
+ purchaserEmail: normalizeChapterEmail(input.namedSeat.purchaserEmail),
3986
+ ...await checkoutNamedSeatIdentity(input, selection.recipientEmail),
3987
+ priceId: seatPriceId,
3988
+ amountCents: offer.contract.offering.seatPriceCents
3989
+ };
3990
+ }
3991
+ const receipt = { id: id11, ...Object.fromEntries(keys.map((key3) => [key3, input[key3]])), boundAt: now, ...namedSeat ? { namedSeat } : {} };
3937
3992
  try {
3938
- await db.transact([{ t: "update", ns: "membershipOfferCheckout", id: id10, attrs: receipt }], {
3939
- mutationId: `membership-checkout:${id10}${restart?.restartId ? `:${restart.restartId}` : ""}`,
3993
+ await db.transact([
3994
+ { t: "update", ns: "membershipOfferCheckout", id: id11, attrs: receipt },
3995
+ ...namedSeat ? [{ t: "update", ns: "networkSeatRecipient", id: namedSeat.recipientClaimId, attrs: {
3996
+ id: namedSeat.recipientClaimId,
3997
+ appId: input.appId,
3998
+ chapterId: input.chapterId,
3999
+ environment: input.environment,
4000
+ runtime: input.runtime,
4001
+ seatId: namedSeat.seatId,
4002
+ recipientEmailNormalized: namedSeat.recipientEmail,
4003
+ createdAt: now
4004
+ } }] : []
4005
+ ], {
4006
+ mutationId: `membership-checkout:${id11}${restart?.restartId ? `:${restart.restartId}` : ""}`,
3940
4007
  guards: [
3941
- { ns: "membershipOfferCheckout", id: id10, exists: false },
4008
+ { ns: "membershipOfferCheckout", id: id11, exists: false },
4009
+ ...namedSeat ? [
4010
+ { ns: "networkSeat", id: namedSeat.seatId, exists: false },
4011
+ { ns: "networkSeatRecipient", id: namedSeat.recipientClaimId, exists: false }
4012
+ ] : [],
3942
4013
  { ns: "membershipCheckoutOffer", id: input.offerId, exists: true, equals: { digest: input.offerDigest } },
3943
4014
  { ns: "membershipAuthorityState", id: stateId, exists: true, equals: { mode: "authoritative", revision: state.revision } }
3944
4015
  ]
@@ -3946,11 +4017,78 @@ async function bindMembershipCheckout(db, input, now) {
3946
4017
  } catch (error) {
3947
4018
  if (error.code !== "transact_guard_failed") throw error;
3948
4019
  }
3949
- const saved = await load2(db, "membershipOfferCheckout", id10);
4020
+ const saved = await load2(db, "membershipOfferCheckout", id11);
3950
4021
  if (!saved) return reject2("membership_binding_unconfirmed");
3951
4022
  return verifyMembershipCheckoutBinding(saved, input);
3952
4023
  }
3953
4024
 
4025
+ // src/membership-checkout-seat-payment.ts
4026
+ function checkoutSeatPaymentOperations(target, seat, input) {
4027
+ const { now, paidAt, endsAt } = input;
4028
+ const included = seat.amountCents === 0, entitlementId = `${seat.seatId}:entitlement`;
4029
+ const effectId = `${seat.seatId}:${included ? "invite" : "attach-renewal"}`;
4030
+ return [
4031
+ membershipUpdate("networkSeat", seat.seatId, {
4032
+ id: seat.seatId,
4033
+ ...membershipScope(target),
4034
+ primaryMembershipId: seat.membershipId,
4035
+ purchaserEmailNormalized: seat.purchaserEmail,
4036
+ recipientName: seat.recipientName,
4037
+ recipientEmailNormalized: seat.recipientEmail,
4038
+ recipientClaimId: seat.recipientClaimId,
4039
+ priceId: seat.priceId,
4040
+ primaryPriceId: input.primaryPriceId,
4041
+ amountCents: seat.amountCents,
4042
+ currency: input.offering.currency,
4043
+ interval: input.offering.interval,
4044
+ providerCustomerId: input.customerId,
4045
+ providerSubscriptionId: input.subscriptionId,
4046
+ combinedInvoice: { invoiceId: input.invoiceId, totalCents: input.invoiceTotalCents },
4047
+ status: included ? "pending_acceptance" : "renewal_pending",
4048
+ preparedAt: input.boundAt,
4049
+ purchasedAt: paidAt,
4050
+ termStartedAt: paidAt,
4051
+ termEndsAt: endsAt,
4052
+ acceptanceEndsAt: endsAt,
4053
+ seatPurchaseEndsAt: seatPurchaseDeadline(input.offering, { initialTermStartedAt: paidAt, termEndsAt: endsAt }),
4054
+ offeringSnapshot: input.offering,
4055
+ paymentEventId: input.eventId,
4056
+ ...included ? { entitlementId } : {},
4057
+ revision: 1,
4058
+ createdAt: now,
4059
+ updatedAt: now
4060
+ }),
4061
+ ...included ? [membershipUpdate("networkEntitlement", entitlementId, {
4062
+ id: entitlementId,
4063
+ ...membershipScope(target),
4064
+ membershipId: seat.membershipId,
4065
+ seatId: seat.seatId,
4066
+ kind: "named_seat",
4067
+ status: "pending_acceptance",
4068
+ termStartedAt: paidAt,
4069
+ termEndsAt: endsAt,
4070
+ offeringSnapshot: input.offering,
4071
+ sourceEventId: input.eventId,
4072
+ revision: 1,
4073
+ createdAt: now,
4074
+ updatedAt: now
4075
+ })] : [],
4076
+ membershipUpdate("membershipEffect", effectId, {
4077
+ id: effectId,
4078
+ chapterId: target.chapterId,
4079
+ environment: target.environment,
4080
+ runtime: target.runtime,
4081
+ membershipId: seat.membershipId,
4082
+ seatId: seat.seatId,
4083
+ kind: included ? "invite_named_recipient" : "attach_seat_renewal",
4084
+ status: "pending",
4085
+ attempts: 0,
4086
+ createdAt: now,
4087
+ updatedAt: now
4088
+ })
4089
+ ];
4090
+ }
4091
+
3954
4092
  // src/membership-payment-authority.ts
3955
4093
  async function networkMembershipId(input) {
3956
4094
  return (await membershipCheckoutBindingId(input)).replace(/^checkout_/, "membership_");
@@ -3966,11 +4104,14 @@ async function confirmMembershipPayment(db, input, now) {
3966
4104
  { ...input, offerId: String(binding.offerId), now, lifetimeMs: 6e4 }
3967
4105
  );
3968
4106
  if (binding.offerDigest !== offer.digest) membershipFail("membership_checkout_conflict");
4107
+ const { namedSeat } = await verifyMembershipCheckoutBinding(binding, binding);
4108
+ if (namedSeat && namedSeat.amountCents !== offer.contract.offering.seatPriceCents) membershipFail("membership_checkout_conflict");
4109
+ const totalCents = offer.contract.dueTodayCents + (namedSeat?.amountCents ?? 0);
3969
4110
  const eventRow = await membershipLoad(db, "networkCommercialEvents", `network-commercial-event:${input.eventId}`);
3970
4111
  if (!eventRow) membershipFail("membership_payment_evidence_pending");
3971
4112
  const evidenceGuard = await commercialEvidenceGuard(db, input.eventId, eventRow.digest);
3972
4113
  const fact = eventRow.fact;
3973
- if (!fact || !fact.identity || !/^[a-f0-9]{64}$/.test(String(eventRow.digest)) || fact.digest !== eventRow.digest || fact.eventId !== input.eventId || fact.eventType !== "invoice.payment_succeeded" || fact.objectKind !== "invoice" || fact.billingReason !== "subscription_create" || fact.livemode !== (input.environment === "prod") || fact.status !== "paid" || fact.applicationId !== input.applicationId || fact.subscriptionId !== input.providerSubscriptionId || fact.customerId !== input.providerCustomerId || !/^in_[A-Za-z0-9_]+$/.test(fact.invoiceId ?? "") || fact.currency !== offer.contract.offering.currency || fact.invoiceTotalCents !== offer.contract.dueTodayCents || fact.amountPaidCents !== offer.contract.dueTodayCents || Object.entries({
4114
+ if (!fact || !fact.identity || !/^[a-f0-9]{64}$/.test(String(eventRow.digest)) || fact.digest !== eventRow.digest || fact.eventId !== input.eventId || fact.eventType !== "invoice.payment_succeeded" || fact.objectKind !== "invoice" || fact.billingReason !== "subscription_create" || fact.livemode !== (input.environment === "prod") || fact.status !== "paid" || fact.applicationId !== input.applicationId || fact.subscriptionId !== input.providerSubscriptionId || fact.customerId !== input.providerCustomerId || !/^in_[A-Za-z0-9_]+$/.test(fact.invoiceId ?? "") || fact.currency !== offer.contract.offering.currency || fact.invoiceTotalCents !== totalCents || fact.amountPaidCents !== totalCents || Object.entries({
3974
4115
  appId: input.appId,
3975
4116
  chapterId: input.chapterId,
3976
4117
  environment: input.environment,
@@ -3982,14 +4123,14 @@ async function confirmMembershipPayment(db, input, now) {
3982
4123
  const paidAt = fact.invoicePaidAt;
3983
4124
  const endsAt = offer.contract.initialTermEndsAt ?? fact.invoicePeriodEndsAt;
3984
4125
  if (!Number.isSafeInteger(paidAt) || Number(paidAt) < Math.floor(Number(binding.boundAt) / 1e3) * 1e3 || Number(paidAt) > now + 3e4 || !Number.isSafeInteger(endsAt) || Number(endsAt) <= Number(paidAt)) membershipFail("membership_payment_period_mismatch");
3985
- const id10 = await networkMembershipId(input), entitlementId = `${id10}:primary`;
3986
- let member = await membershipLoad(db, "networkMembership", id10);
4126
+ const id11 = await networkMembershipId(input), entitlementId = `${id11}:primary`;
4127
+ let member = await membershipLoad(db, "networkMembership", id11);
3987
4128
  if (!member || member.status === "restarting") {
3988
4129
  const offering = offer.contract.offering;
3989
4130
  const restart = member ? await membershipRestartPayment(db, input, member, now) : null;
3990
4131
  const attrs = {
3991
- id: id10,
3992
- membershipId: id10,
4132
+ id: id11,
4133
+ membershipId: id11,
3993
4134
  ...membershipScope(input),
3994
4135
  baseSkuId: input.skuId,
3995
4136
  status: "paid_pending_approval",
@@ -4005,8 +4146,9 @@ async function confirmMembershipPayment(db, input, now) {
4005
4146
  founding: offer.contract.founding,
4006
4147
  cohort: offer.contract.founding ? offer.contract.discountPolicy.cohortLabel : "standard",
4007
4148
  baseAmountCents: offering.basePriceCents,
4008
- amountCents: offer.contract.dueTodayCents,
4149
+ amountCents: totalCents,
4009
4150
  renewalAmountCents: offer.contract.renewalCents,
4151
+ ...namedSeat ? { namedSeatId: namedSeat.seatId } : {},
4010
4152
  currency: offering.currency,
4011
4153
  interval: offering.interval,
4012
4154
  initialTermStartedAt: paidAt,
@@ -4023,11 +4165,11 @@ async function confirmMembershipPayment(db, input, now) {
4023
4165
  };
4024
4166
  try {
4025
4167
  await db.transact([
4026
- membershipUpdate("networkMembership", id10, attrs),
4168
+ membershipUpdate("networkMembership", id11, attrs),
4027
4169
  membershipUpdate("networkEntitlement", entitlementId, {
4028
4170
  id: entitlementId,
4029
4171
  ...membershipScope(input),
4030
- membershipId: id10,
4172
+ membershipId: id11,
4031
4173
  kind: "primary",
4032
4174
  status: restart?.attrs.status === "active" ? "active" : "pending_approval",
4033
4175
  termStartedAt: paidAt,
@@ -4039,23 +4181,45 @@ async function confirmMembershipPayment(db, input, now) {
4039
4181
  updatedAt: now,
4040
4182
  ...restart?.entitlementAttrs
4041
4183
  }),
4042
- ...restart?.ops ?? []
4043
- ], { mutationId: `membership-paid:${id10}${restart ? `:${restart.restartId}` : ""}`, guards: [
4044
- ...restart?.guards ?? [{ ns: "networkMembership", id: id10, exists: false }],
4184
+ ...restart?.ops ?? [],
4185
+ ...namedSeat ? checkoutSeatPaymentOperations(input, namedSeat, {
4186
+ offering,
4187
+ primaryPriceId: String(binding.priceId),
4188
+ invoiceId: fact.invoiceId,
4189
+ invoiceTotalCents: totalCents,
4190
+ subscriptionId: input.providerSubscriptionId,
4191
+ customerId: input.providerCustomerId,
4192
+ eventId: input.eventId,
4193
+ paidAt: Number(paidAt),
4194
+ endsAt: Number(endsAt),
4195
+ boundAt: Number(binding.boundAt),
4196
+ now
4197
+ }) : []
4198
+ ], { mutationId: `membership-paid:${id11}${restart ? `:${restart.restartId}` : ""}`, guards: [
4199
+ ...restart?.guards ?? [{ ns: "networkMembership", id: id11, exists: false }],
4200
+ ...namedSeat ? [
4201
+ { ns: "networkSeat", id: namedSeat.seatId, exists: false },
4202
+ {
4203
+ ns: "networkSeatRecipient",
4204
+ id: namedSeat.recipientClaimId,
4205
+ exists: true,
4206
+ equals: { seatId: namedSeat.seatId, recipientEmailNormalized: namedSeat.recipientEmail }
4207
+ }
4208
+ ] : [],
4045
4209
  { ns: "membershipAuthorityState", id: String(state.id), exists: true, equals: { mode: "authoritative", revision: state.revision } },
4046
- { ns: "membershipOfferCheckout", id: String(binding.id), exists: true, equals: { offerDigest: offer.digest } },
4210
+ { ns: "membershipOfferCheckout", id: String(binding.id), exists: true, equals: { offerDigest: offer.digest, quoteDigest: binding.quoteDigest } },
4047
4211
  { ns: "networkCommercialEvents", id: String(eventRow.id), exists: true, equals: { digest: eventRow.digest } },
4048
4212
  evidenceGuard
4049
4213
  ] });
4050
4214
  } catch (error) {
4051
4215
  if (error.code !== "transact_guard_failed") throw error;
4052
4216
  }
4053
- member = await membershipLoad(db, "networkMembership", id10);
4217
+ member = await membershipLoad(db, "networkMembership", id11);
4054
4218
  }
4055
4219
  requireMembershipScope(member, input);
4056
4220
  if (member.purchaseOfferDigest !== offer.digest || member.providerInitialInvoiceId !== fact.invoiceId || member.providerSubscriptionId !== fact.subscriptionId || member.providerCustomerId !== fact.customerId) membershipFail("membership_purchase_conflict");
4057
4221
  return {
4058
- membershipId: id10,
4222
+ membershipId: id11,
4059
4223
  status: String(member.status),
4060
4224
  cohort: String(member.cohort),
4061
4225
  initialTermStartedAt: Number(member.initialTermStartedAt),
@@ -4067,9 +4231,9 @@ async function confirmMembershipPayment(db, input, now) {
4067
4231
  // src/membership-cancel-authority.ts
4068
4232
  async function cancelMembershipAtTermEnd(db, target, now, stripeSecret) {
4069
4233
  membershipClock(now);
4070
- const id10 = await networkMembershipId(target);
4234
+ const id11 = await networkMembershipId(target);
4071
4235
  const state = await loadMembershipAuthorityState(db, target);
4072
- const member = await membershipLoad(db, "networkMembership", id10);
4236
+ const member = await membershipLoad(db, "networkMembership", id11);
4073
4237
  requireMembershipScope(member, target);
4074
4238
  if (!["active", "cancel_at_term_end", "payment_grace", "paid_pending_approval", "lapsed"].includes(String(member.status))) {
4075
4239
  membershipFail("membership_cancellation_unavailable");
@@ -4087,7 +4251,7 @@ async function cancelMembershipAtTermEnd(db, target, now, stripeSecret) {
4087
4251
  "POST",
4088
4252
  path,
4089
4253
  { cancel_at_period_end: true, proration_behavior: "none" },
4090
- { idempotencyKey: `membership-cancel-at-term:${id10}:${member.providerSubscriptionId}` }
4254
+ { idempotencyKey: `membership-cancel-at-term:${id11}:${member.providerSubscriptionId}` }
4091
4255
  );
4092
4256
  if (!write.ok || write.body.id !== member.providerSubscriptionId || write.body.cancel_at_period_end !== true) {
4093
4257
  membershipFail("membership_cancellation_pending");
@@ -4095,7 +4259,7 @@ async function cancelMembershipAtTermEnd(db, target, now, stripeSecret) {
4095
4259
  }
4096
4260
  const status2 = Number(member.termEndsAt) <= now ? "lapsed" : member.journeyApprovedAt ? "cancel_at_term_end" : "paid_pending_approval";
4097
4261
  if (member.status !== status2 || member.autoRenew !== false || Number(member.graceEndsAt ?? 0) !== 0) {
4098
- await db.transact([membershipUpdate("networkMembership", id10, {
4262
+ await db.transact([membershipUpdate("networkMembership", id11, {
4099
4263
  status: status2,
4100
4264
  autoRenew: false,
4101
4265
  graceEndsAt: 0,
@@ -4104,12 +4268,12 @@ async function cancelMembershipAtTermEnd(db, target, now, stripeSecret) {
4104
4268
  updatedAt: now
4105
4269
  })], {
4106
4270
  guards: [
4107
- { ns: "networkMembership", id: id10, exists: true, equals: { revision: member.revision } },
4271
+ { ns: "networkMembership", id: id11, exists: true, equals: { revision: member.revision } },
4108
4272
  { ns: "membershipAuthorityState", id: String(state.id), exists: true, equals: { revision: state.revision, mode: "authoritative" } }
4109
4273
  ]
4110
4274
  });
4111
4275
  }
4112
- return { membershipId: id10, status: status2, termEndsAt: Number(member.termEndsAt), graceEndsAt: 0, autoRenew: false };
4276
+ return { membershipId: id11, status: status2, termEndsAt: Number(member.termEndsAt), graceEndsAt: 0, autoRenew: false };
4113
4277
  }
4114
4278
 
4115
4279
  // src/membership-authority-handler.ts
@@ -4148,17 +4312,17 @@ async function namedSeatPrimaryActivation(db, member, now) {
4148
4312
  // src/membership-approval-authority.ts
4149
4313
  async function approveNetworkMembership(db, input, now) {
4150
4314
  membershipClock(now);
4151
- const id10 = await networkMembershipId(input), entitlementId = `${id10}:primary`;
4315
+ const id11 = await networkMembershipId(input), entitlementId = `${id11}:primary`;
4152
4316
  for (let attempt = 0; attempt < 8; attempt++) {
4153
4317
  const state = await loadMembershipAuthorityState(db, input);
4154
- const member = await membershipLoad(db, "networkMembership", id10);
4318
+ const member = await membershipLoad(db, "networkMembership", id11);
4155
4319
  requireMembershipScope(member, input);
4156
4320
  const entitlement = await membershipLoad(db, "networkEntitlement", entitlementId);
4157
4321
  requireMembershipScope(entitlement, input);
4158
- if (entitlement.membershipId !== id10 || entitlement.kind !== "primary") membershipFail("membership_entitlement_conflict");
4322
+ if (entitlement.membershipId !== id11 || entitlement.kind !== "primary") membershipFail("membership_entitlement_conflict");
4159
4323
  if (["active", "cancel_at_term_end"].includes(String(member.status)) && entitlement.status === "active" && Number(member.refundedCents) === 0 && Number.isSafeInteger(member.memberNumber) && Number(member.memberNumber) > 0 && Number(member.termEndsAt) > now) {
4160
4324
  return {
4161
- membershipId: id10,
4325
+ membershipId: id11,
4162
4326
  status: String(member.status),
4163
4327
  cohort: String(member.cohort),
4164
4328
  memberNumber: Number(member.memberNumber),
@@ -4178,7 +4342,7 @@ async function approveNetworkMembership(db, input, now) {
4178
4342
  const seatActivation = await namedSeatPrimaryActivation(db, member, now);
4179
4343
  try {
4180
4344
  await db.transact([
4181
- membershipUpdate("networkMembership", id10, {
4345
+ membershipUpdate("networkMembership", id11, {
4182
4346
  status: member.autoRenew ? "active" : "cancel_at_term_end",
4183
4347
  memberNumber,
4184
4348
  journeyApprovedAt: now,
@@ -4193,8 +4357,8 @@ async function approveNetworkMembership(db, input, now) {
4193
4357
  updatedAt: now
4194
4358
  }),
4195
4359
  ...seatActivation.ops
4196
- ], { mutationId: `membership-approve:${id10}`, guards: [
4197
- { ns: "networkMembership", id: id10, exists: true, equals: { revision: member.revision, status: "paid_pending_approval" }, absent: ["memberNumber"] },
4360
+ ], { mutationId: `membership-approve:${id11}`, guards: [
4361
+ { ns: "networkMembership", id: id11, exists: true, equals: { revision: member.revision, status: "paid_pending_approval" }, absent: ["memberNumber"] },
4198
4362
  { ns: "networkEntitlement", id: entitlementId, exists: true, equals: { revision: entitlement.revision, status: "pending_approval" } },
4199
4363
  { ns: "membershipAuthorityState", id: String(state.id), exists: true, equals: { revision: state.revision, mode: "authoritative" } },
4200
4364
  ...seatActivation.guards
@@ -4293,41 +4457,41 @@ async function refundSubscriptionPayment(secretKey, input) {
4293
4457
  // src/membership-refund-authority.ts
4294
4458
  async function refundNetworkMembership(db, input, secretKey, now) {
4295
4459
  membershipClock(now);
4296
- const id10 = await networkMembershipId(input), entitlementId = `${id10}:primary`;
4297
- let state = await loadMembershipAuthorityState(db, input), member = await membershipLoad(db, "networkMembership", id10);
4460
+ const id11 = await networkMembershipId(input), entitlementId = `${id11}:primary`;
4461
+ let state = await loadMembershipAuthorityState(db, input), member = await membershipLoad(db, "networkMembership", id11);
4298
4462
  requireMembershipScope(member, input);
4299
- if (member.status === "refunded") return { membershipId: id10, status: "refunded", refundedCents: Number(member.refundedCents), subscriptionCanceled: true };
4463
+ if (member.status === "refunded") return { membershipId: id11, status: "refunded", refundedCents: Number(member.refundedCents), subscriptionCanceled: true };
4300
4464
  if (!["paid_pending_approval", "active", "cancel_at_term_end", "payment_grace", "lapsed", "refund_pending"].includes(String(member.status))) membershipFail("membership_refund_not_eligible");
4301
4465
  if (member.status !== "refund_pending") {
4302
4466
  const entitlement = await membershipLoad(db, "networkEntitlement", entitlementId);
4303
4467
  requireMembershipScope(entitlement, input);
4304
4468
  await db.transact([
4305
- membershipUpdate("networkMembership", id10, {
4469
+ membershipUpdate("networkMembership", id11, {
4306
4470
  status: "refund_pending",
4307
4471
  refundRequestedAt: now,
4308
4472
  revision: Number(member.revision) + 1,
4309
4473
  updatedAt: now
4310
4474
  }),
4311
4475
  membershipUpdate("networkEntitlement", entitlementId, { status: "closed", revision: Number(entitlement.revision) + 1, updatedAt: now }),
4312
- membershipUpdate("membershipEffect", `${id10}:refund`, {
4313
- id: `${id10}:refund`,
4476
+ membershipUpdate("membershipEffect", `${id11}:refund`, {
4477
+ id: `${id11}:refund`,
4314
4478
  chapterId: input.chapterId,
4315
4479
  environment: input.environment,
4316
4480
  runtime: input.runtime,
4317
- membershipId: id10,
4481
+ membershipId: id11,
4318
4482
  kind: "refund_membership",
4319
4483
  status: "pending",
4320
4484
  attempts: 0,
4321
4485
  createdAt: now,
4322
4486
  updatedAt: now
4323
4487
  })
4324
- ], { mutationId: `membership-refund-request:${id10}`, guards: [
4325
- { ns: "networkMembership", id: id10, exists: true, equals: { revision: member.revision } },
4488
+ ], { mutationId: `membership-refund-request:${id11}`, guards: [
4489
+ { ns: "networkMembership", id: id11, exists: true, equals: { revision: member.revision } },
4326
4490
  { ns: "networkEntitlement", id: entitlementId, exists: true, equals: { revision: entitlement.revision } },
4327
4491
  { ns: "membershipAuthorityState", id: String(state.id), exists: true, equals: { revision: state.revision, mode: "authoritative" } }
4328
4492
  ] });
4329
4493
  }
4330
- member = await membershipLoad(db, "networkMembership", id10);
4494
+ member = await membershipLoad(db, "networkMembership", id11);
4331
4495
  requireMembershipScope(member, input);
4332
4496
  if (member.status !== "refund_pending") membershipFail("membership_refund_retry_required");
4333
4497
  const result = await refundSubscriptionPayment(secretKey, {
@@ -4341,23 +4505,23 @@ async function refundNetworkMembership(db, input, secretKey, now) {
4341
4505
  if (!result.subscriptionCanceled || result.refundedCents !== member.amountCents) membershipFail("membership_refund_unconfirmed");
4342
4506
  for (let attempt = 0; attempt < 8; attempt++) {
4343
4507
  state = await loadMembershipAuthorityState(db, input);
4344
- member = await membershipLoad(db, "networkMembership", id10);
4508
+ member = await membershipLoad(db, "networkMembership", id11);
4345
4509
  requireMembershipScope(member, input);
4346
- if (member.status === "refunded") return { membershipId: id10, status: "refunded", refundedCents: Number(member.refundedCents), subscriptionCanceled: true };
4510
+ if (member.status === "refunded") return { membershipId: id11, status: "refunded", refundedCents: Number(member.refundedCents), subscriptionCanceled: true };
4347
4511
  if (member.status !== "refund_pending") membershipFail("membership_refund_retry_required");
4348
4512
  const counted = member.founding === true && member.memberNumber !== void 0;
4349
4513
  const counts = state.cohortCounts;
4350
4514
  if (!counts || counted && (!Number.isSafeInteger(counts[String(member.cohort)]) || counts[String(member.cohort)] < 1)) membershipFail("invalid_cohort_count");
4351
4515
  try {
4352
4516
  await db.transact([
4353
- membershipUpdate("networkMembership", id10, {
4517
+ membershipUpdate("networkMembership", id11, {
4354
4518
  status: "refunded",
4355
4519
  refundedCents: result.refundedCents,
4356
4520
  autoRenew: false,
4357
4521
  revision: Number(member.revision) + 1,
4358
4522
  updatedAt: now
4359
4523
  }),
4360
- membershipUpdate("membershipEffect", `${id10}:refund`, { status: "complete", updatedAt: now }),
4524
+ membershipUpdate("membershipEffect", `${id11}:refund`, { status: "complete", updatedAt: now }),
4361
4525
  ...counted ? [membershipUpdate("membershipAuthorityState", String(state.id), {
4362
4526
  cohortCounts: { ...counts, [String(member.cohort)]: counts[String(member.cohort)] - 1 },
4363
4527
  revision: Number(state.revision) + 1,
@@ -4368,7 +4532,7 @@ async function refundNetworkMembership(db, input, secretKey, now) {
4368
4532
  chapterId: input.chapterId,
4369
4533
  environment: input.environment,
4370
4534
  runtime: input.runtime,
4371
- membershipId: id10,
4535
+ membershipId: id11,
4372
4536
  seatId: member.namedSeatId,
4373
4537
  kind: "refund_seat_with_primary",
4374
4538
  status: "pending",
@@ -4376,8 +4540,8 @@ async function refundNetworkMembership(db, input, secretKey, now) {
4376
4540
  createdAt: now,
4377
4541
  updatedAt: now
4378
4542
  })] : []
4379
- ], { mutationId: `membership-refunded:${id10}`, guards: [
4380
- { ns: "networkMembership", id: id10, exists: true, equals: { status: "refund_pending", revision: member.revision } },
4543
+ ], { mutationId: `membership-refunded:${id11}`, guards: [
4544
+ { ns: "networkMembership", id: id11, exists: true, equals: { status: "refund_pending", revision: member.revision } },
4381
4545
  { ns: "membershipAuthorityState", id: String(state.id), exists: true, equals: { mode: "authoritative", revision: state.revision } }
4382
4546
  ] });
4383
4547
  } catch (error) {
@@ -4405,8 +4569,8 @@ async function reserveNamedMembershipSeat(db, input, now) {
4405
4569
  const memberId = await networkMembershipId(input), member = await membershipLoad(db, "networkMembership", memberId);
4406
4570
  requireMembershipScope(member, input);
4407
4571
  if (!Number.isSafeInteger(member.revision) || !/^price_[A-Za-z0-9_]+$/.test(String(member.providerPriceId)) || member.providerPriceId === input.priceId) membershipFail("named_seat_purchase_unavailable");
4408
- const id10 = (await membershipOfferIdForRequest({ ...input, requestId: "named_seat" })).replace(/^offer_/, "seat_");
4409
- let seat = await membershipLoad(db, "networkSeat", id10);
4572
+ const id11 = (await membershipOfferIdForRequest({ ...input, requestId: "named_seat" })).replace(/^offer_/, "seat_");
4573
+ let seat = await membershipLoad(db, "networkSeat", id11);
4410
4574
  if (!seat) {
4411
4575
  const offering = membershipOfferingVersion(member.offeringSnapshot), claimId = await recipientKey(input, email);
4412
4576
  const purchaseEndsAt = seatPurchaseDeadline(offering, member);
@@ -4414,7 +4578,7 @@ async function reserveNamedMembershipSeat(db, input, now) {
4414
4578
  membershipFail("named_seat_purchase_unavailable");
4415
4579
  }
4416
4580
  const attrs = {
4417
- id: id10,
4581
+ id: id11,
4418
4582
  ...membershipScope(input),
4419
4583
  primaryMembershipId: memberId,
4420
4584
  recipientName: name,
@@ -4439,20 +4603,20 @@ async function reserveNamedMembershipSeat(db, input, now) {
4439
4603
  };
4440
4604
  try {
4441
4605
  await db.transact([
4442
- membershipUpdate("networkSeat", id10, attrs),
4606
+ membershipUpdate("networkSeat", id11, attrs),
4443
4607
  membershipUpdate("networkSeatRecipient", claimId, {
4444
4608
  id: claimId,
4445
4609
  appId: input.appId,
4446
4610
  chapterId: input.chapterId,
4447
4611
  environment: input.environment,
4448
4612
  runtime: input.runtime,
4449
- seatId: id10,
4613
+ seatId: id11,
4450
4614
  recipientEmailNormalized: email,
4451
4615
  createdAt: now
4452
4616
  }),
4453
- membershipUpdate("networkMembership", memberId, { namedSeatId: id10, revision: Number(member.revision) + 1, updatedAt: now })
4454
- ], { mutationId: `named-seat-reserve:${id10}`, guards: [
4455
- { ns: "networkSeat", id: id10, exists: false },
4617
+ membershipUpdate("networkMembership", memberId, { namedSeatId: id11, revision: Number(member.revision) + 1, updatedAt: now })
4618
+ ], { mutationId: `named-seat-reserve:${id11}`, guards: [
4619
+ { ns: "networkSeat", id: id11, exists: false },
4456
4620
  { ns: "networkSeatRecipient", id: claimId, exists: false },
4457
4621
  { ns: "networkMembership", id: memberId, exists: true, equals: { revision: member.revision }, absent: ["namedSeatId"] },
4458
4622
  { ns: "membershipAuthorityState", id: String(state.id), exists: true, equals: { mode: "authoritative", revision: state.revision } }
@@ -4460,7 +4624,7 @@ async function reserveNamedMembershipSeat(db, input, now) {
4460
4624
  } catch (error) {
4461
4625
  if (error.code !== "transact_guard_failed") throw error;
4462
4626
  }
4463
- seat = await membershipLoad(db, "networkSeat", id10);
4627
+ seat = await membershipLoad(db, "networkSeat", id11);
4464
4628
  if (!seat && await membershipLoad(db, "networkSeatRecipient", claimId)) membershipFail("named_seat_recipient_unavailable");
4465
4629
  }
4466
4630
  requireMembershipScope(seat, input);
@@ -4470,7 +4634,7 @@ async function reserveNamedMembershipSeat(db, input, now) {
4470
4634
 
4471
4635
  // src/membership-seat-included.ts
4472
4636
  var namedSeatIncluded = (seat) => Number(seat.amountCents) === 0;
4473
- var namedSeatSettled = (seat) => namedSeatIncluded(seat) ? seat.purchasedAt !== void 0 : Boolean(seat.providerPaymentIntentId && seat.providerSubscriptionItemId);
4637
+ var namedSeatSettled = (seat) => namedSeatIncluded(seat) ? seat.purchasedAt !== void 0 : Boolean((seat.providerPaymentIntentId || seat.combinedInvoice) && seat.providerSubscriptionItemId);
4474
4638
  function namedSeatRecipientState(seat) {
4475
4639
  switch (String(seat.status)) {
4476
4640
  case "renewal_pending":
@@ -4568,7 +4732,7 @@ async function grantIncludedNamedSeat(db, input, now) {
4568
4732
  // src/membership-renewal-authority.ts
4569
4733
  async function reconcileMembershipRenewal(db, input, now) {
4570
4734
  membershipClock(now);
4571
- const id10 = await networkMembershipId(input), eventRow = await membershipLoad(db, "networkCommercialEvents", `network-commercial-event:${input.eventId}`);
4735
+ const id11 = await networkMembershipId(input), eventRow = await membershipLoad(db, "networkCommercialEvents", `network-commercial-event:${input.eventId}`);
4572
4736
  const evidenceGuard = await commercialEvidenceGuard(db, input.eventId, eventRow?.digest, "membership_renewal_evidence_pending");
4573
4737
  const fact = eventRow?.fact;
4574
4738
  if (!fact || !/^evt_[A-Za-z0-9_]+$/.test(input.eventId) || !/^[a-f0-9]{64}$/.test(String(eventRow.digest)) || fact.digest !== eventRow.digest || fact.eventId !== input.eventId || fact.objectKind !== "invoice" || !["invoice.payment_succeeded", "invoice.payment_failed"].includes(fact.eventType) || fact.billingReason !== "subscription_cycle" || fact.livemode !== (input.environment === "prod") || fact.applicationId !== input.applicationId || Object.entries({
@@ -4581,10 +4745,10 @@ async function reconcileMembershipRenewal(db, input, now) {
4581
4745
  return membershipFail("membership_renewal_evidence_pending");
4582
4746
  }
4583
4747
  for (let attempt = 0; attempt < 8; attempt++) {
4584
- const state = await loadMembershipAuthorityState(db, input), member = await membershipLoad(db, "networkMembership", id10);
4748
+ const state = await loadMembershipAuthorityState(db, input), member = await membershipLoad(db, "networkMembership", id11);
4585
4749
  requireMembershipScope(member, input);
4586
4750
  const receipt = () => ({
4587
- membershipId: id10,
4751
+ membershipId: id11,
4588
4752
  status: String(member.status),
4589
4753
  termEndsAt: Number(member.termEndsAt),
4590
4754
  graceEndsAt: Number(member.graceEndsAt ?? 0),
@@ -4609,13 +4773,13 @@ async function reconcileMembershipRenewal(db, input, now) {
4609
4773
  const paid = fact.eventType === "invoice.payment_succeeded";
4610
4774
  if (!paid && (fact.status !== "open" || !Number.isSafeInteger(fact.amountPaidCents) || Number(fact.amountPaidCents) < 0 || Number(fact.amountPaidCents) >= amount)) membershipFail("membership_renewal_failure_mismatch");
4611
4775
  if (paid && (fact.status !== "paid" || fact.amountPaidCents !== amount || !Number.isSafeInteger(fact.invoicePaidAt) || Number(fact.invoicePaidAt) < start || Number(fact.invoicePaidAt) > now + 3e4)) membershipFail("membership_renewal_unpaid");
4612
- const entitlement = await membershipLoad(db, "networkEntitlement", `${id10}:primary`);
4776
+ const entitlement = await membershipLoad(db, "networkEntitlement", `${id11}:primary`);
4613
4777
  requireMembershipScope(entitlement, input);
4614
4778
  const graceEndsAt = paid ? 0 : start + 14 * 864e5;
4615
4779
  const status2 = paid ? member.journeyApprovedAt ? member.autoRenew ? "active" : "cancel_at_term_end" : "paid_pending_approval" : now < graceEndsAt ? "payment_grace" : "lapsed";
4616
4780
  const entitlementStatus = member.journeyApprovedAt && (paid || now < graceEndsAt) ? "active" : member.journeyApprovedAt ? "closed" : "pending_approval";
4617
4781
  const ops = [
4618
- membershipUpdate("networkMembership", id10, {
4782
+ membershipUpdate("networkMembership", id11, {
4619
4783
  status: status2,
4620
4784
  graceEndsAt,
4621
4785
  ...paid ? {
@@ -4626,7 +4790,7 @@ async function reconcileMembershipRenewal(db, input, now) {
4626
4790
  revision: Number(member.revision) + 1,
4627
4791
  updatedAt: now
4628
4792
  }),
4629
- membershipUpdate("networkEntitlement", `${id10}:primary`, {
4793
+ membershipUpdate("networkEntitlement", `${id11}:primary`, {
4630
4794
  status: entitlementStatus,
4631
4795
  ...paid ? { termStartedAt: start, termEndsAt: end } : {},
4632
4796
  sourceEventId: input.eventId,
@@ -4635,8 +4799,8 @@ async function reconcileMembershipRenewal(db, input, now) {
4635
4799
  })
4636
4800
  ];
4637
4801
  const guards = [
4638
- { ns: "networkMembership", id: id10, exists: true, equals: { revision: member.revision } },
4639
- { ns: "networkEntitlement", id: `${id10}:primary`, exists: true, equals: { revision: entitlement.revision } },
4802
+ { ns: "networkMembership", id: id11, exists: true, equals: { revision: member.revision } },
4803
+ { ns: "networkEntitlement", id: `${id11}:primary`, exists: true, equals: { revision: entitlement.revision } },
4640
4804
  { ns: "membershipAuthorityState", id: String(state.id), exists: true, equals: { revision: state.revision, mode: "authoritative" } },
4641
4805
  { ns: "networkCommercialEvents", id: String(eventRow.id), exists: true, equals: { digest: eventRow.digest } },
4642
4806
  evidenceGuard
@@ -4661,15 +4825,15 @@ async function reconcileMembershipRenewal(db, input, now) {
4661
4825
  );
4662
4826
  }
4663
4827
  try {
4664
- await db.transact(ops, { mutationId: `membership-renewal:${id10}:${input.eventId}`, guards });
4828
+ await db.transact(ops, { mutationId: `membership-renewal:${id11}:${input.eventId}`, guards });
4665
4829
  } catch (error) {
4666
4830
  if (error.code !== "transact_guard_failed") throw error;
4667
4831
  continue;
4668
4832
  }
4669
- const saved = await membershipLoad(db, "networkMembership", id10);
4833
+ const saved = await membershipLoad(db, "networkMembership", id11);
4670
4834
  requireMembershipScope(saved, input);
4671
4835
  return {
4672
- membershipId: id10,
4836
+ membershipId: id11,
4673
4837
  status: String(saved.status),
4674
4838
  termEndsAt: Number(saved.termEndsAt),
4675
4839
  graceEndsAt: Number(saved.graceEndsAt ?? 0),
@@ -4695,12 +4859,12 @@ async function reconcileMembershipBilling(db, input, now, stripeSecretName = "st
4695
4859
  }).some(([key3, value]) => fact.identity[key3] !== value)) membershipFail("membership_billing_evidence_pending");
4696
4860
  const sk = await getVaultSecret(db, stripeSecretName);
4697
4861
  if (!sk) membershipFail("membership_billing_provider_unavailable");
4698
- const id10 = await networkMembershipId(input);
4862
+ const id11 = await networkMembershipId(input);
4699
4863
  for (let attempt = 0; attempt < 8; attempt++) {
4700
- const state = await loadMembershipAuthorityState(db, input), member = await membershipLoad(db, "networkMembership", id10);
4864
+ const state = await loadMembershipAuthorityState(db, input), member = await membershipLoad(db, "networkMembership", id11);
4701
4865
  requireMembershipScope(member, input);
4702
4866
  const receipt = (value = member) => ({
4703
- membershipId: id10,
4867
+ membershipId: id11,
4704
4868
  status: String(value.status),
4705
4869
  termEndsAt: Number(value.termEndsAt),
4706
4870
  graceEndsAt: Number(value.graceEndsAt ?? 0),
@@ -4721,7 +4885,7 @@ async function reconcileMembershipBilling(db, input, now, stripeSecretName = "st
4721
4885
  });
4722
4886
  await commercialEvidenceGuard(db, input.eventId, row2?.digest, "membership_billing_evidence_pending");
4723
4887
  await refundNetworkMembership(db, input, sk, now);
4724
- const saved2 = await membershipLoad(db, "networkMembership", id10);
4888
+ const saved2 = await membershipLoad(db, "networkMembership", id11);
4725
4889
  requireMembershipScope(saved2, input);
4726
4890
  return receipt(saved2);
4727
4891
  }
@@ -4736,18 +4900,18 @@ async function reconcileMembershipBilling(db, input, now, stripeSecretName = "st
4736
4900
  const status2 = autoRenew ? member.status === "cancel_at_term_end" ? "active" : String(member.status) : now >= Number(member.termEndsAt) ? "lapsed" : member.journeyApprovedAt ? "cancel_at_term_end" : "paid_pending_approval";
4737
4901
  const graceEndsAt = autoRenew ? Number(member.graceEndsAt ?? 0) : 0;
4738
4902
  if (member.autoRenew === autoRenew && member.status === status2 && Number(member.graceEndsAt ?? 0) === graceEndsAt) return receipt();
4739
- const entitlement = await membershipLoad(db, "networkEntitlement", `${id10}:primary`);
4903
+ const entitlement = await membershipLoad(db, "networkEntitlement", `${id11}:primary`);
4740
4904
  requireMembershipScope(entitlement, input);
4741
4905
  try {
4742
4906
  await db.transact([
4743
- membershipUpdate("networkMembership", id10, { status: status2, autoRenew, graceEndsAt, revision: Number(member.revision) + 1, updatedAt: now }),
4907
+ membershipUpdate("networkMembership", id11, { status: status2, autoRenew, graceEndsAt, revision: Number(member.revision) + 1, updatedAt: now }),
4744
4908
  membershipUpdate("networkEntitlement", String(entitlement.id), {
4745
4909
  status: status2 === "lapsed" ? "closed" : entitlement.status,
4746
4910
  revision: Number(entitlement.revision) + 1,
4747
4911
  updatedAt: now
4748
4912
  })
4749
4913
  ], { guards: [
4750
- { ns: "networkMembership", id: id10, exists: true, equals: { revision: member.revision } },
4914
+ { ns: "networkMembership", id: id11, exists: true, equals: { revision: member.revision } },
4751
4915
  { ns: "networkEntitlement", id: String(entitlement.id), exists: true, equals: { revision: entitlement.revision } },
4752
4916
  { ns: "membershipAuthorityState", id: String(state.id), exists: true, equals: { mode: "authoritative", revision: state.revision } },
4753
4917
  { ns: "networkCommercialEvents", id: String(row2.id), exists: true, equals: { digest: fact.digest } },
@@ -4757,7 +4921,7 @@ async function reconcileMembershipBilling(db, input, now, stripeSecretName = "st
4757
4921
  if (error.code !== "transact_guard_failed") throw error;
4758
4922
  continue;
4759
4923
  }
4760
- const saved = await membershipLoad(db, "networkMembership", id10);
4924
+ const saved = await membershipLoad(db, "networkMembership", id11);
4761
4925
  requireMembershipScope(saved, input);
4762
4926
  return receipt(saved);
4763
4927
  }
@@ -4818,19 +4982,19 @@ async function stopPreviousSubscription(member, target, secret) {
4818
4982
  async function prepareMembershipRestart(db, input, now, stripeSecret) {
4819
4983
  membershipClock(now);
4820
4984
  if (!/^sub_[A-Za-z0-9_]+$/.test(input.previousSubscriptionId)) membershipFail("membership_restart_conflict");
4821
- const id10 = await networkMembershipId(input), bindingId = await membershipCheckoutBindingId(input);
4822
- const restartId = `restart_${await stripePayloadDigest(JSON.stringify([id10, input.previousSubscriptionId]))}`;
4985
+ const id11 = await networkMembershipId(input), bindingId = await membershipCheckoutBindingId(input);
4986
+ const restartId = `restart_${await stripePayloadDigest(JSON.stringify([id11, input.previousSubscriptionId]))}`;
4823
4987
  const state = await loadMembershipAuthorityState(db, input);
4824
- const previous = await membershipLoad(db, "networkMembership", id10);
4988
+ const previous = await membershipLoad(db, "networkMembership", id11);
4825
4989
  requireMembershipScope(previous, input);
4826
4990
  const receipt = {
4827
4991
  applicationId: input.applicationId,
4828
- membershipId: id10,
4992
+ membershipId: id11,
4829
4993
  restartId,
4830
4994
  memberNumber: Number(previous.memberNumber),
4831
4995
  status: "ready"
4832
4996
  };
4833
- const existing = await membershipLoad(db, "membershipRestart", id10);
4997
+ const existing = await membershipLoad(db, "membershipRestart", id11);
4834
4998
  if (existing?.restartId === restartId && existing.status === "ready") {
4835
4999
  requireMembershipScope(existing, input);
4836
5000
  return receipt;
@@ -4846,33 +5010,33 @@ async function prepareMembershipRestart(db, input, now, stripeSecret) {
4846
5010
  membershipUpdate("membershipPurchaseHistory", restartId, {
4847
5011
  id: restartId,
4848
5012
  ...membershipScope(input),
4849
- membershipId: id10,
5013
+ membershipId: id11,
4850
5014
  providerSubscriptionId: input.previousSubscriptionId,
4851
5015
  memberSnapshot: previous,
4852
5016
  bindingSnapshot: binding2,
4853
5017
  archivedAt: now
4854
5018
  }),
4855
- membershipUpdate("networkMembership", id10, { status: "restarting", restartId, revision: Number(previous.revision) + 1, updatedAt: now })
5019
+ membershipUpdate("networkMembership", id11, { status: "restarting", restartId, revision: Number(previous.revision) + 1, updatedAt: now })
4856
5020
  ], { mutationId: `membership-restart-reserve:${restartId}`, guards: [
4857
5021
  ...seats.guards,
4858
- { ns: "networkMembership", id: id10, exists: true, equals: { revision: previous.revision, status: previous.status } },
5022
+ { ns: "networkMembership", id: id11, exists: true, equals: { revision: previous.revision, status: previous.status } },
4859
5023
  { ns: "membershipPurchaseHistory", id: restartId, exists: false },
4860
5024
  { ns: "membershipOfferCheckout", id: bindingId, exists: true, equals: { offerDigest: binding2.offerDigest } },
4861
5025
  { ns: "membershipAuthorityState", id: String(state.id), exists: true, equals: { mode: "authoritative", revision: state.revision } }
4862
5026
  ] });
4863
5027
  } else if (previous.restartId !== restartId) membershipFail("membership_restart_conflict");
4864
- const reserved = await membershipLoad(db, "networkMembership", id10);
5028
+ const reserved = await membershipLoad(db, "networkMembership", id11);
4865
5029
  requireMembershipScope(reserved, input);
4866
5030
  if (reserved.status !== "restarting" || reserved.restartId !== restartId) membershipFail("membership_restart_conflict");
4867
5031
  await stopPreviousSubscription(reserved, input, stripeSecret);
4868
5032
  const binding = await membershipLoad(db, "membershipOfferCheckout", bindingId);
4869
5033
  requireMembershipScope(binding, input);
4870
5034
  await db.transact([
4871
- membershipUpdate("membershipRestart", id10, { id: id10, ...membershipScope(input), ...receipt, previousSubscriptionId: input.previousSubscriptionId, createdAt: now }),
5035
+ membershipUpdate("membershipRestart", id11, { id: id11, ...membershipScope(input), ...receipt, previousSubscriptionId: input.previousSubscriptionId, createdAt: now }),
4872
5036
  { t: "delete", ns: "membershipOfferCheckout", id: bindingId }
4873
5037
  ], { mutationId: `membership-restart-ready:${restartId}`, guards: [
4874
- { ns: "membershipRestart", id: id10, exists: Boolean(existing), ...existing ? { equals: { restartId: existing.restartId, status: existing.status } } : {} },
4875
- { ns: "networkMembership", id: id10, exists: true, equals: { revision: reserved.revision, status: "restarting", restartId } },
5038
+ { ns: "membershipRestart", id: id11, exists: Boolean(existing), ...existing ? { equals: { restartId: existing.restartId, status: existing.status } } : {} },
5039
+ { ns: "networkMembership", id: id11, exists: true, equals: { revision: reserved.revision, status: "restarting", restartId } },
4876
5040
  { ns: "membershipOfferCheckout", id: bindingId, exists: true, equals: { offerDigest: binding.offerDigest } }
4877
5041
  ] });
4878
5042
  return receipt;
@@ -4882,15 +5046,15 @@ async function prepareMembershipRestart(db, input, now, stripeSecret) {
4882
5046
  async function readMembershipAccess(db, input, now) {
4883
5047
  membershipClock(now);
4884
5048
  await loadMembershipAuthorityState(db, input);
4885
- const id10 = await networkMembershipId(input), member = await membershipLoad(db, "networkMembership", id10);
4886
- if (!member) return { membershipId: id10, allowed: false, status: "unpaid", accessEndsAt: 0, revision: 0 };
5049
+ const id11 = await networkMembershipId(input), member = await membershipLoad(db, "networkMembership", id11);
5050
+ if (!member) return { membershipId: id11, allowed: false, status: "unpaid", accessEndsAt: 0, revision: 0 };
4887
5051
  requireMembershipScope(member, input);
4888
- const entitlement = await membershipLoad(db, "networkEntitlement", `${id10}:primary`);
5052
+ const entitlement = await membershipLoad(db, "networkEntitlement", `${id11}:primary`);
4889
5053
  requireMembershipScope(entitlement, input);
4890
5054
  const endsAt = member.status === "payment_grace" ? Number(member.graceEndsAt) : Number(member.termEndsAt);
4891
- const allowed2 = ["active", "cancel_at_term_end", "payment_grace"].includes(String(member.status)) && member.journeyApprovedAt !== void 0 && Number.isSafeInteger(member.memberNumber) && Number(member.memberNumber) > 0 && member.refundedCents === 0 && entitlement.status === "active" && entitlement.membershipId === id10 && entitlement.kind === "primary" && Number.isSafeInteger(endsAt) && endsAt > now && Number(member.termStartedAt) <= now;
5055
+ const allowed2 = ["active", "cancel_at_term_end", "payment_grace"].includes(String(member.status)) && member.journeyApprovedAt !== void 0 && Number.isSafeInteger(member.memberNumber) && Number(member.memberNumber) > 0 && member.refundedCents === 0 && entitlement.status === "active" && entitlement.membershipId === id11 && entitlement.kind === "primary" && Number.isSafeInteger(endsAt) && endsAt > now && Number(member.termStartedAt) <= now;
4892
5056
  return {
4893
- membershipId: id10,
5057
+ membershipId: id11,
4894
5058
  allowed: allowed2,
4895
5059
  status: String(member.status),
4896
5060
  accessEndsAt: Number.isSafeInteger(endsAt) ? endsAt : 0,
@@ -4945,7 +5109,7 @@ async function subscriptionForSeat(sk, seat) {
4945
5109
  currency: seat.currency,
4946
5110
  interval: seat.interval,
4947
5111
  environment: seat.environment
4948
- }, Boolean(seat.providerPaymentIntentId));
5112
+ }, Boolean(seat.providerPaymentIntentId || seat.combinedInvoice));
4949
5113
  if (!productId) reject3();
4950
5114
  const sub = await read(sk, `/v1/subscriptions/${encodeURIComponent(seat.providerSubscriptionId)}?expand[]=discounts`);
4951
5115
  const items = sub.items?.data;
@@ -4989,10 +5153,14 @@ async function createNamedSeatStripePayment(sk, seat, now) {
4989
5153
  return { paymentIntentId: pi.id, clientSecret: pi.client_secret, status: pi.status };
4990
5154
  }
4991
5155
  async function attachNamedSeatStripeRenewal(sk, seat) {
4992
- if (!seat.providerPaymentIntentId || !["renewal_pending", "pending_acceptance", "pending_approval", "active"].includes(seat.status)) reject3();
5156
+ if (!seat.providerPaymentIntentId && !seat.combinedInvoice || !["renewal_pending", "pending_acceptance", "pending_approval", "active"].includes(seat.status)) reject3();
4993
5157
  const sub = await subscriptionForSeat(sk, seat);
4994
5158
  const existing = sub.items.data.find((item2) => item2.metadata?.seatId === seat.id);
4995
- if (existing) return String(existing.id);
5159
+ if (existing) {
5160
+ if (!/^si_[A-Za-z0-9_]+$/.test(existing.id)) reject3();
5161
+ return String(existing.id);
5162
+ }
5163
+ if (seat.combinedInvoice) reject3();
4996
5164
  const result = await (0, import_stripe7.stripeRequest)({ secretKey: sk }, "POST", "/v1/subscription_items", {
4997
5165
  subscription: seat.providerSubscriptionId,
4998
5166
  price: seat.priceId,
@@ -5210,7 +5378,7 @@ async function recordRecipient(db, input, now, approve) {
5210
5378
  const state = await loadMembershipAuthorityState(db, input);
5211
5379
  const seat = await membershipLoad(db, "networkSeat", input.seatId);
5212
5380
  requireMembershipScope(seat, input);
5213
- if (!seat.purchaserUserId || seat.purchaserUserId === input.recipientUserId) membershipFail("named_seat_recipient_must_differ_from_purchaser");
5381
+ if (!seat.purchaserUserId && !seat.purchaserEmailNormalized || seat.purchaserUserId === input.recipientUserId || seat.purchaserEmailNormalized === normalizeChapterEmail(input.verifiedRecipientEmail)) membershipFail("named_seat_recipient_must_differ_from_purchaser");
5214
5382
  const member = await membershipLoad(db, "networkMembership", String(seat.primaryMembershipId));
5215
5383
  requireMembershipScope(member, input);
5216
5384
  const entitlement = await membershipLoad(db, "networkEntitlement", String(seat.entitlementId));
@@ -5268,43 +5436,121 @@ async function recordRecipient(db, input, now, approve) {
5268
5436
  }
5269
5437
 
5270
5438
  // src/membership-seat-refund-stripe.ts
5439
+ var import_stripe9 = require("@odla-ai/stripe");
5440
+
5441
+ // src/membership-checkout-seat-refund.ts
5271
5442
  var import_stripe8 = require("@odla-ai/stripe");
5272
5443
  var fail4 = (code = "named_seat_refund_mismatch") => {
5273
5444
  throw Object.assign(new Error(code), { code });
5274
5445
  };
5446
+ var id8 = (value) => typeof value === "string" ? value : value?.id ?? "";
5447
+ async function refundCheckoutNamedSeat(secretKey, seat) {
5448
+ const invoiceRef = seat.combinedInvoice;
5449
+ 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();
5450
+ const call = async (method, path, params, key3) => {
5451
+ const result = await (0, import_stripe8.stripeRequest)({ secretKey }, method, path, params, key3 ? { idempotencyKey: key3 } : {});
5452
+ if (!result.ok || !result.body) return fail4("named_seat_refund_provider_unavailable");
5453
+ return result.body;
5454
+ };
5455
+ const metadata = { ...stripePartitionMetadata(seat), applicationId: seat.applicationId };
5456
+ const sub = await call("GET", `/v1/subscriptions/${encodeURIComponent(seat.providerSubscriptionId)}`);
5457
+ if (sub.id !== seat.providerSubscriptionId || id8(sub.customer) !== seat.providerCustomerId || sub.livemode !== (seat.environment === "prod") || !Object.entries(metadata).every(([key3, value]) => sub.metadata?.[key3] === value) || !Array.isArray(sub.items?.data) || sub.items.has_more !== false) fail4();
5458
+ const invoice = await call("GET", `/v1/invoices/${encodeURIComponent(invoiceRef.invoiceId)}`);
5459
+ if (invoice.id !== invoiceRef.invoiceId || id8(invoice.subscription ?? invoice.parent?.subscription_details?.subscription) !== sub.id || id8(invoice.customer) !== seat.providerCustomerId || invoice.status !== "paid" || invoice.billing_reason !== "subscription_create" || invoice.total !== invoiceRef.totalCents || invoice.amount_paid !== invoiceRef.totalCents || invoice.currency !== seat.currency || invoice.livemode !== (seat.environment === "prod")) fail4();
5460
+ let chargeId = id8(invoice.charge), paymentId = id8(invoice.payment_intent);
5461
+ if (!chargeId && !paymentId) {
5462
+ const payments = await call("GET", "/v1/invoice_payments", { invoice: invoice.id, status: "paid", limit: 100 });
5463
+ if (!Array.isArray(payments.data) || payments.data.length !== 1 || payments.has_more !== false) fail4();
5464
+ const payment = payments.data[0];
5465
+ if (id8(payment.invoice) !== invoice.id || payment.status !== "paid" || payment.payment?.type !== "payment_intent") fail4();
5466
+ paymentId = id8(payment.payment.payment_intent);
5467
+ }
5468
+ if (!chargeId) {
5469
+ if (!/^pi_[A-Za-z0-9_]+$/.test(paymentId)) fail4();
5470
+ const payment = await call("GET", `/v1/payment_intents/${encodeURIComponent(paymentId)}`);
5471
+ if (payment.id !== paymentId || id8(payment.customer) !== seat.providerCustomerId || payment.status !== "succeeded" || payment.amount_received !== invoiceRef.totalCents || payment.currency !== seat.currency || payment.livemode !== (seat.environment === "prod")) fail4();
5472
+ chargeId = id8(payment.latest_charge);
5473
+ }
5474
+ if (!/^ch_[A-Za-z0-9_]+$/.test(chargeId)) fail4();
5475
+ const charge = await call("GET", `/v1/charges/${encodeURIComponent(chargeId)}`);
5476
+ if (charge.id !== chargeId || id8(charge.customer) !== seat.providerCustomerId || charge.status !== "succeeded" || charge.livemode !== (seat.environment === "prod") || charge.amount !== invoiceRef.totalCents || charge.currency !== seat.currency || charge.invoice != null && id8(charge.invoice) !== invoice.id || paymentId && id8(charge.payment_intent) !== paymentId || !Number.isSafeInteger(charge.amount_refunded) || charge.amount_refunded < 0 || charge.amount_refunded > charge.amount) fail4();
5477
+ const items = sub.items.data.filter((item) => item.metadata?.seatId === seat.id);
5478
+ if (items.length > 1) fail4();
5479
+ if (items.length && sub.status !== "canceled") {
5480
+ const item = items[0];
5481
+ if (!/^si_[A-Za-z0-9_]+$/.test(item.id) || item.quantity !== 1 || item.price?.id !== seat.priceId) fail4();
5482
+ const removed = await call("DELETE", `/v1/subscription_items/${encodeURIComponent(item.id)}`, { proration_behavior: "none" });
5483
+ if (removed.id !== item.id || removed.deleted !== true) fail4("named_seat_renewal_removal_unconfirmed");
5484
+ }
5485
+ if (charge.amount_refunded === charge.amount && charge.refunded === true) return { refundedCents: seat.amountCents, refundIds: [] };
5486
+ const list = await call("GET", "/v1/refunds", { charge: chargeId, limit: 100 });
5487
+ if (!Array.isArray(list.data) || list.has_more !== false || list.data.length >= 100) fail4("named_seat_refund_history_incomplete");
5488
+ let refundedCents = 0;
5489
+ const refundIds = [];
5490
+ for (const refund of list.data) {
5491
+ if (id8(refund.charge) !== chargeId || refund.currency !== seat.currency || !Number.isSafeInteger(refund.amount) || refund.amount <= 0 || !/^re_[A-Za-z0-9_]+$/.test(refund.id) || !["succeeded", "pending", "requires_action", "failed", "canceled"].includes(refund.status)) fail4();
5492
+ if (["pending", "requires_action"].includes(refund.status)) fail4("named_seat_refund_pending");
5493
+ if (refund.status === "succeeded") {
5494
+ if (refund.metadata?.seatId !== seat.id || refund.metadata?.membershipId !== seat.primaryMembershipId) fail4("named_seat_refund_allocation_ambiguous");
5495
+ refundedCents += refund.amount;
5496
+ refundIds.push(refund.id);
5497
+ }
5498
+ }
5499
+ if (refundedCents !== charge.amount_refunded || refundedCents > seat.amountCents) fail4();
5500
+ if (refundedCents < seat.amountCents) {
5501
+ const remaining = seat.amountCents - refundedCents;
5502
+ const refund = await call("POST", "/v1/refunds", {
5503
+ charge: chargeId,
5504
+ amount: remaining,
5505
+ reason: "requested_by_customer",
5506
+ metadata: { ...metadata, membershipId: seat.primaryMembershipId, seatId: seat.id }
5507
+ }, `combined-seat-refund:${seat.id}:${remaining}`);
5508
+ if (id8(refund.charge) !== chargeId || refund.amount !== remaining || refund.currency !== seat.currency || !/^re_[A-Za-z0-9_]+$/.test(refund.id)) fail4();
5509
+ if (refund.status !== "succeeded") fail4("named_seat_refund_pending");
5510
+ refundedCents += remaining;
5511
+ refundIds.push(refund.id);
5512
+ }
5513
+ return { refundedCents, refundIds };
5514
+ }
5515
+
5516
+ // src/membership-seat-refund-stripe.ts
5517
+ var fail5 = (code = "named_seat_refund_mismatch") => {
5518
+ throw Object.assign(new Error(code), { code });
5519
+ };
5275
5520
  async function refundNamedSeatStripePayment(secretKey, seat) {
5276
- if (!seat.providerPaymentIntentId || seat.status !== "refund_pending") fail4();
5521
+ if (seat.combinedInvoice) return refundCheckoutNamedSeat(secretKey, seat);
5522
+ if (!seat.providerPaymentIntentId || seat.status !== "refund_pending") fail5();
5277
5523
  const call = async (method, path, params, idempotencyKey) => {
5278
- const result = await (0, import_stripe8.stripeRequest)({ secretKey }, method, path, params, idempotencyKey ? { idempotencyKey } : {});
5279
- if (!result.ok || !result.body) return fail4("named_seat_refund_provider_unavailable");
5524
+ const result = await (0, import_stripe9.stripeRequest)({ secretKey }, method, path, params, idempotencyKey ? { idempotencyKey } : {});
5525
+ if (!result.ok || !result.body) return fail5("named_seat_refund_provider_unavailable");
5280
5526
  return result.body;
5281
5527
  };
5282
5528
  const metadata = { ...stripePartitionMetadata(seat), applicationId: seat.applicationId, membershipId: seat.primaryMembershipId, seatId: seat.id };
5283
5529
  const payment = await call("GET", `/v1/payment_intents/${encodeURIComponent(seat.providerPaymentIntentId)}`);
5284
- if (payment.id !== seat.providerPaymentIntentId || payment.customer !== seat.providerCustomerId || payment.amount !== seat.amountCents || payment.amount_received !== seat.amountCents || payment.currency !== seat.currency || payment.livemode !== (seat.environment === "prod") || payment.status !== "succeeded" || !Object.entries(metadata).every(([key3, value]) => payment.metadata?.[key3] === value)) fail4();
5530
+ if (payment.id !== seat.providerPaymentIntentId || payment.customer !== seat.providerCustomerId || payment.amount !== seat.amountCents || payment.amount_received !== seat.amountCents || payment.currency !== seat.currency || payment.livemode !== (seat.environment === "prod") || payment.status !== "succeeded" || !Object.entries(metadata).every(([key3, value]) => payment.metadata?.[key3] === value)) fail5();
5285
5531
  const sub = await call("GET", `/v1/subscriptions/${encodeURIComponent(seat.providerSubscriptionId)}`);
5286
- if (sub.id !== seat.providerSubscriptionId || sub.customer !== seat.providerCustomerId || sub.livemode !== (seat.environment === "prod") || !Object.entries({ ...stripePartitionMetadata(seat), applicationId: seat.applicationId }).every(([key3, value]) => sub.metadata?.[key3] === value) || sub.items?.has_more !== false || !Array.isArray(sub.items?.data)) fail4();
5532
+ if (sub.id !== seat.providerSubscriptionId || sub.customer !== seat.providerCustomerId || sub.livemode !== (seat.environment === "prod") || !Object.entries({ ...stripePartitionMetadata(seat), applicationId: seat.applicationId }).every(([key3, value]) => sub.metadata?.[key3] === value) || sub.items?.has_more !== false || !Array.isArray(sub.items?.data)) fail5();
5287
5533
  const items = sub.items.data.filter((item) => item.metadata?.seatId === seat.id);
5288
- if (items.length > 1) fail4();
5534
+ if (items.length > 1) fail5();
5289
5535
  if (items.length && sub.status !== "canceled") {
5290
5536
  const item = items[0];
5291
- if (!/^si_[A-Za-z0-9_]+$/.test(item.id) || item.price?.id !== seat.priceId || item.quantity !== 1) fail4();
5537
+ if (!/^si_[A-Za-z0-9_]+$/.test(item.id) || item.price?.id !== seat.priceId || item.quantity !== 1) fail5();
5292
5538
  const removed = await call("DELETE", `/v1/subscription_items/${encodeURIComponent(item.id)}`, { proration_behavior: "none" });
5293
- if (removed.id !== item.id || removed.deleted !== true) fail4("named_seat_renewal_removal_unconfirmed");
5539
+ if (removed.id !== item.id || removed.deleted !== true) fail5("named_seat_renewal_removal_unconfirmed");
5294
5540
  }
5295
5541
  const list = await call("GET", "/v1/refunds", { payment_intent: seat.providerPaymentIntentId, limit: 100 });
5296
- if (!Array.isArray(list.data) || list.has_more !== false || list.data.length >= 100) fail4("named_seat_refund_history_incomplete");
5542
+ if (!Array.isArray(list.data) || list.has_more !== false || list.data.length >= 100) fail5("named_seat_refund_history_incomplete");
5297
5543
  let refundedCents = 0, pendingCents = 0;
5298
5544
  const refundIds = [];
5299
5545
  for (const refund of list.data) {
5300
- if (!/^re_[A-Za-z0-9_]+$/.test(refund.id) || refund.payment_intent !== seat.providerPaymentIntentId || refund.currency !== seat.currency || !Number.isSafeInteger(refund.amount) || refund.amount <= 0 || !["succeeded", "pending", "requires_action", "failed", "canceled"].includes(refund.status)) fail4();
5546
+ if (!/^re_[A-Za-z0-9_]+$/.test(refund.id) || refund.payment_intent !== seat.providerPaymentIntentId || refund.currency !== seat.currency || !Number.isSafeInteger(refund.amount) || refund.amount <= 0 || !["succeeded", "pending", "requires_action", "failed", "canceled"].includes(refund.status)) fail5();
5301
5547
  if (refund.status === "succeeded") {
5302
5548
  refundedCents += refund.amount;
5303
5549
  refundIds.push(refund.id);
5304
5550
  } else if (refund.status === "pending" || refund.status === "requires_action") pendingCents += refund.amount;
5305
5551
  }
5306
- if (refundedCents + pendingCents > seat.amountCents) fail4();
5307
- if (pendingCents) fail4("named_seat_refund_pending");
5552
+ if (refundedCents + pendingCents > seat.amountCents) fail5();
5553
+ if (pendingCents) fail5("named_seat_refund_pending");
5308
5554
  if (refundedCents < seat.amountCents) {
5309
5555
  const remaining = seat.amountCents - refundedCents;
5310
5556
  const refund = await call("POST", "/v1/refunds", {
@@ -5313,8 +5559,8 @@ async function refundNamedSeatStripePayment(secretKey, seat) {
5313
5559
  reason: "requested_by_customer",
5314
5560
  metadata
5315
5561
  }, `named-seat-refund:${seat.id}:${remaining}`);
5316
- if (refund.payment_intent !== seat.providerPaymentIntentId || refund.amount !== remaining || refund.currency !== seat.currency || !/^re_[A-Za-z0-9_]+$/.test(refund.id)) fail4();
5317
- if (refund.status !== "succeeded") fail4("named_seat_refund_pending");
5562
+ if (refund.payment_intent !== seat.providerPaymentIntentId || refund.amount !== remaining || refund.currency !== seat.currency || !/^re_[A-Za-z0-9_]+$/.test(refund.id)) fail5();
5563
+ if (refund.status !== "succeeded") fail5("named_seat_refund_pending");
5318
5564
  refundedCents += remaining;
5319
5565
  refundIds.push(refund.id);
5320
5566
  }
@@ -5330,7 +5576,7 @@ async function requestNamedSeatRefund(db, input, now) {
5330
5576
  const member = await membershipLoad(db, "networkMembership", String(seat.primaryMembershipId));
5331
5577
  requireMembershipScope(member, input);
5332
5578
  if (seat.status === "refunded" || seat.status === "refund_pending") return;
5333
- if (!seat.purchasedAt || !seat.providerPaymentIntentId && !namedSeatIncluded(seat) || input.reason === "declined" && seat.journeyApprovedAt !== void 0 || input.reason === "primary_closed" && !["refunded", "declined"].includes(String(member.status))) membershipFail("named_seat_refund_not_eligible");
5579
+ if (!seat.purchasedAt || !seat.providerPaymentIntentId && !seat.combinedInvoice && !namedSeatIncluded(seat) || input.reason === "declined" && seat.journeyApprovedAt !== void 0 || input.reason === "primary_closed" && !["refunded", "declined"].includes(String(member.status))) membershipFail("named_seat_refund_not_eligible");
5334
5580
  const entitlement = seat.entitlementId ? await membershipLoad(db, "networkEntitlement", String(seat.entitlementId)) : void 0;
5335
5581
  await db.transact([
5336
5582
  membershipUpdate("networkSeat", input.seatId, { status: "refund_pending", refundReason: input.reason, revision: Number(seat.revision) + 1, updatedAt: now }),
@@ -5435,14 +5681,14 @@ var at = (value) => Number.isSafeInteger(value) ? Number(value) : void 0;
5435
5681
  async function readNamedSeatOffer(db, input, now) {
5436
5682
  membershipClock(now);
5437
5683
  await loadMembershipAuthorityState(db, input);
5438
- const id10 = await networkMembershipId(input), member = await membershipLoad(db, "networkMembership", id10);
5684
+ const id11 = await networkMembershipId(input), member = await membershipLoad(db, "networkMembership", id11);
5439
5685
  requireMembershipScope(member, input);
5440
5686
  const offering = membershipOfferingVersion(member.offeringSnapshot);
5441
5687
  const seat = member.namedSeatId ? await membershipLoad(db, "networkSeat", String(member.namedSeatId)) : void 0;
5442
5688
  if (seat) requireMembershipScope(seat, input);
5443
5689
  const purchaseEndsAt = seatPurchaseDeadline(offering, member);
5444
5690
  const offer = {
5445
- membershipId: id10,
5691
+ membershipId: id11,
5446
5692
  amountCents: offering.seatPriceCents,
5447
5693
  currency: offering.currency,
5448
5694
  interval: offering.interval,
@@ -5720,9 +5966,14 @@ function createMembershipAuthorityHandler(options) {
5720
5966
  try {
5721
5967
  input = JSON.parse(verified.body);
5722
5968
  const fields = action === "seat-invitation" ? MEMBERSHIP_SEAT_ACTION_FIELDS[action] : action === "offer-preview" ? ["skuId"] : ["applicationId", "skuId", ...action === "offers" ? ["requestId"] : action === "checkouts" ? ["offerId", "offerDigest", "quoteDigest", "priceId"] : action === "payments" ? ["eventId", "providerSubscriptionId", "providerCustomerId"] : action === "renewals" ? ["eventId"] : action === "restarts" ? ["previousSubscriptionId"] : MEMBERSHIP_SEAT_ACTION_FIELDS[action] ?? []];
5723
- if (!input || typeof input !== "object" || Array.isArray(input) || Object.keys(input).some((key3) => !fields.includes(key3)) || !fields.every((key3) => typeof input[key3] === "string" && (["recipientName", "recipientEmail", "purchaserEmail", "verifiedRecipientEmail"].includes(key3) ? input[key3].length > 0 && input[key3].length <= 254 && !/[\u0000-\u001f\u007f]/.test(input[key3]) : SAFE.test(input[key3])))) {
5969
+ if (!input || typeof input !== "object" || Array.isArray(input) || Object.keys(input).some((key3) => !fields.includes(key3) && !(action === "checkouts" && key3 === "namedSeat")) || !fields.every((key3) => typeof input[key3] === "string" && (["recipientName", "recipientEmail", "purchaserEmail", "verifiedRecipientEmail"].includes(key3) ? input[key3].length > 0 && input[key3].length <= 254 && !/[\u0000-\u001f\u007f]/.test(input[key3]) : SAFE.test(input[key3])))) {
5724
5970
  throw new TypeError("invalid request");
5725
5971
  }
5972
+ if (action === "checkouts" && input.namedSeat !== void 0) {
5973
+ const seat = input.namedSeat;
5974
+ if (!seat || Object.keys(seat).some((key3) => !["recipientName", "recipientEmail", "purchaserEmail"].includes(key3)) || typeof seat.purchaserEmail !== "string" || seat.purchaserEmail.length > 254) throw new TypeError("invalid seat");
5975
+ checkoutNamedSeatSelection(seat, seat.purchaserEmail);
5976
+ }
5726
5977
  } catch {
5727
5978
  return respond({ error: "invalid_offer_request" }, 400);
5728
5979
  }
@@ -5795,8 +6046,9 @@ function createMembershipAuthorityHandler(options) {
5795
6046
  offerId: String(input.offerId),
5796
6047
  offerDigest: String(input.offerDigest),
5797
6048
  quoteDigest: String(input.quoteDigest),
5798
- priceId: String(input.priceId)
5799
- }, options.now?.() ?? Date.now());
6049
+ priceId: String(input.priceId),
6050
+ ...input.namedSeat ? { namedSeat: input.namedSeat } : {}
6051
+ }, options.now?.() ?? Date.now(), peer.seatPriceId);
5800
6052
  return respond({ ok: true, binding });
5801
6053
  }
5802
6054
  const offerId = await membershipOfferIdForRequest({ ...peer, applicationId: String(input.applicationId), requestId: String(input.requestId) });
@@ -5889,7 +6141,7 @@ async function requestMembershipCommercialOffer(options) {
5889
6141
  async function requestMembershipOfferPreview(options) {
5890
6142
  const result = await requestAuthority(options, "offer-preview", { skuId: options.skuId });
5891
6143
  const preview = result.preview;
5892
- if (!preview || preview.skuId !== options.skuId || typeof preview.founding !== "boolean" || !Number.isSafeInteger(preview.basePriceCents) || !Number.isSafeInteger(preview.dueTodayCents) || preview.dueTodayCents < 0 || preview.dueTodayCents > preview.basePriceCents || !Number.isSafeInteger(preview.renewalCents) || !/^[a-z]{3}$/.test(preview.currency) || !["month", "year"].includes(preview.interval) || preview.cohortLabel !== void 0 && typeof preview.cohortLabel !== "string" || preview.discountDuration !== void 0 && !["first_term", "while_active"].includes(preview.discountDuration)) throw new Error("invalid offer preview");
6144
+ if (!preview || preview.skuId !== options.skuId || typeof preview.founding !== "boolean" || !Number.isSafeInteger(preview.basePriceCents) || !Number.isSafeInteger(preview.dueTodayCents) || preview.dueTodayCents < 0 || preview.dueTodayCents > preview.basePriceCents || !Number.isSafeInteger(preview.renewalCents) || !/^[a-z]{3}$/.test(preview.currency) || !["month", "year"].includes(preview.interval) || preview.cohortLabel !== void 0 && typeof preview.cohortLabel !== "string" || preview.seatPriceCents !== void 0 && (!Number.isSafeInteger(preview.seatPriceCents) || preview.seatPriceCents < 0) || preview.discountDuration !== void 0 && !["first_term", "while_active"].includes(preview.discountDuration)) throw new Error("invalid offer preview");
5893
6145
  return preview;
5894
6146
  }
5895
6147
  async function requestMembershipCheckoutBinding(options) {
@@ -5899,7 +6151,8 @@ async function requestMembershipCheckoutBinding(options) {
5899
6151
  offerId: options.offerId,
5900
6152
  offerDigest: options.offerDigest,
5901
6153
  quoteDigest: options.quoteDigest,
5902
- priceId: options.priceId
6154
+ priceId: options.priceId,
6155
+ ...options.namedSeat ? { namedSeat: options.namedSeat } : {}
5903
6156
  });
5904
6157
  return verifyMembershipCheckoutBinding(result.binding, options);
5905
6158
  }
@@ -6109,7 +6362,7 @@ async function requestMembershipCancellation(options) {
6109
6362
  }
6110
6363
 
6111
6364
  // src/membership-seat-cancel.ts
6112
- var import_stripe9 = require("@odla-ai/stripe");
6365
+ var import_stripe10 = require("@odla-ai/stripe");
6113
6366
  async function cancelUnpaidNamedSeat(db, target, secretKey, now) {
6114
6367
  membershipClock(now);
6115
6368
  const state = await loadMembershipAuthorityState(db, target);
@@ -6137,7 +6390,7 @@ async function cancelUnpaidNamedSeat(db, target, secretKey, now) {
6137
6390
  }
6138
6391
  const metadata = { ...stripePartitionMetadata(target), applicationId: target.applicationId, membershipId: seat.primaryMembershipId, seatId: seat.id };
6139
6392
  const request = async (method, path, params) => {
6140
- const result = await (0, import_stripe9.stripeRequest)(
6393
+ const result = await (0, import_stripe10.stripeRequest)(
6141
6394
  { secretKey },
6142
6395
  method,
6143
6396
  path,
@@ -6150,7 +6403,7 @@ async function cancelUnpaidNamedSeat(db, target, secretKey, now) {
6150
6403
  let payment;
6151
6404
  if (seat.providerPaymentIntentId) payment = await request("GET", `/v1/payment_intents/${encodeURIComponent(String(seat.providerPaymentIntentId))}`);
6152
6405
  else {
6153
- const snapshot = await listStripeObjects((0, import_stripe9.initStripe)({ secretKey }), "/v1/payment_intents", { customer: String(seat.providerCustomerId) });
6406
+ const snapshot = await listStripeObjects((0, import_stripe10.initStripe)({ secretKey }), "/v1/payment_intents", { customer: String(seat.providerCustomerId) });
6154
6407
  const matches2 = snapshot.objects.filter((row2) => row2.metadata?.seatId === target.seatId);
6155
6408
  if (!snapshot.complete || matches2.length !== 1) throw new Error("seat payment creation outcome unresolved");
6156
6409
  payment = matches2[0];
@@ -6163,9 +6416,9 @@ async function cancelUnpaidNamedSeat(db, target, secretKey, now) {
6163
6416
  if (payment.status === "succeeded" || payment.amount_received > 0) throw new Error("paid seat requires payment evidence and full refund");
6164
6417
  if (payment.status !== "canceled") {
6165
6418
  if (!["requires_payment_method", "requires_confirmation", "requires_action", "processing"].includes(payment.status)) throw new Error("seat payment outcome unresolved");
6166
- const id10 = payment.id;
6167
- payment = await request("POST", `/v1/payment_intents/${encodeURIComponent(id10)}/cancel`, { cancellation_reason: "abandoned" });
6168
- if (payment.id !== id10) throw new Error("seat cancellation receipt mismatch");
6419
+ const id11 = payment.id;
6420
+ payment = await request("POST", `/v1/payment_intents/${encodeURIComponent(id11)}/cancel`, { cancellation_reason: "abandoned" });
6421
+ if (payment.id !== id11) throw new Error("seat cancellation receipt mismatch");
6169
6422
  }
6170
6423
  if (!valid(payment) || payment.status !== "canceled" || payment.amount_received !== 0) throw new Error("seat cancellation unconfirmed");
6171
6424
  await db.transact([membershipUpdate("networkSeat", target.seatId, { status: "canceled", revision: Number(seat.revision) + 1, updatedAt: now })], {
@@ -6265,11 +6518,11 @@ async function drainMembershipEffects(db, options, now) {
6265
6518
 
6266
6519
  // src/schema-membership-offers.ts
6267
6520
  var attr8 = (type, optional = false, unique = false, indexed = false) => ({ type, optional, unique, indexed });
6268
- var id8 = () => attr8("string", false, true, true);
6521
+ var id9 = () => attr8("string", false, true, true);
6269
6522
  var key = () => attr8("string", false, false, true);
6270
6523
  var membershipOfferEntities = {
6271
6524
  membershipAuthorityState: { attrs: {
6272
- id: id8(),
6525
+ id: id9(),
6273
6526
  chapterId: key(),
6274
6527
  environment: key(),
6275
6528
  mode: key(),
@@ -6279,7 +6532,7 @@ var membershipOfferEntities = {
6279
6532
  updatedAt: attr8("number")
6280
6533
  } },
6281
6534
  membershipCheckoutOffer: { attrs: {
6282
- id: id8(),
6535
+ id: id9(),
6283
6536
  applicationId: key(),
6284
6537
  appId: key(),
6285
6538
  chapterId: key(),
@@ -6294,7 +6547,7 @@ var membershipOfferEntities = {
6294
6547
  authorityRevision: attr8("number")
6295
6548
  } },
6296
6549
  membershipOfferCheckout: { attrs: {
6297
- id: id8(),
6550
+ id: id9(),
6298
6551
  appId: key(),
6299
6552
  chapterId: key(),
6300
6553
  environment: key(),
@@ -6305,20 +6558,21 @@ var membershipOfferEntities = {
6305
6558
  offerDigest: attr8("string"),
6306
6559
  quoteDigest: attr8("string"),
6307
6560
  priceId: attr8("string"),
6308
- boundAt: attr8("number")
6561
+ boundAt: attr8("number"),
6562
+ namedSeat: attr8("json", true)
6309
6563
  } }
6310
6564
  };
6311
6565
  var membershipOfferRules = Object.fromEntries(Object.keys(membershipOfferEntities).map((ns) => [ns, { view: "false", create: "false", update: "false", delete: "false" }]));
6312
6566
 
6313
6567
  // src/schema-membership-authority.ts
6314
6568
  var attr9 = (type, optional = false, unique = false, indexed = false) => ({ type, optional, unique, indexed });
6315
- var id9 = () => attr9("string", false, true, true);
6569
+ var id10 = () => attr9("string", false, true, true);
6316
6570
  var key2 = (optional = false) => attr9("string", optional, false, true);
6317
6571
  var scope = () => ({ appId: key2(), chapterId: key2(), environment: key2(), runtime: key2(), applicationId: key2(), skuId: key2() });
6318
6572
  var times = () => ({ revision: attr9("number"), createdAt: attr9("number"), updatedAt: attr9("number") });
6319
6573
  var membershipAuthorityEntities = {
6320
6574
  networkMembership: { attrs: {
6321
- id: id9(),
6575
+ id: id10(),
6322
6576
  membershipId: key2(),
6323
6577
  ...scope(),
6324
6578
  personId: key2(true),
@@ -6358,7 +6612,7 @@ var membershipAuthorityEntities = {
6358
6612
  restartId: key2(true)
6359
6613
  } },
6360
6614
  membershipRestart: { attrs: {
6361
- id: id9(),
6615
+ id: id10(),
6362
6616
  ...scope(),
6363
6617
  membershipId: key2(),
6364
6618
  restartId: key2(),
@@ -6368,7 +6622,7 @@ var membershipAuthorityEntities = {
6368
6622
  createdAt: attr9("number")
6369
6623
  } },
6370
6624
  membershipPurchaseHistory: { attrs: {
6371
- id: id9(),
6625
+ id: id10(),
6372
6626
  ...scope(),
6373
6627
  membershipId: key2(),
6374
6628
  providerSubscriptionId: key2(),
@@ -6377,7 +6631,7 @@ var membershipAuthorityEntities = {
6377
6631
  archivedAt: attr9("number")
6378
6632
  } },
6379
6633
  networkEntitlement: { attrs: {
6380
- id: id9(),
6634
+ id: id10(),
6381
6635
  ...scope(),
6382
6636
  membershipId: key2(),
6383
6637
  seatId: key2(true),
@@ -6394,10 +6648,12 @@ var membershipAuthorityEntities = {
6394
6648
  journeyApprovedAt: attr9("number", true)
6395
6649
  } },
6396
6650
  networkSeat: { attrs: {
6397
- id: id9(),
6651
+ id: id10(),
6398
6652
  ...scope(),
6399
6653
  primaryMembershipId: key2(),
6400
- purchaserUserId: key2(),
6654
+ purchaserUserId: key2(true),
6655
+ purchaserEmailNormalized: key2(true),
6656
+ combinedInvoice: attr9("json", true),
6401
6657
  recipientName: attr9("string"),
6402
6658
  recipientEmailNormalized: key2(),
6403
6659
  recipientClaimId: key2(),
@@ -6429,9 +6685,9 @@ var membershipAuthorityEntities = {
6429
6685
  refundedCents: attr9("number", true),
6430
6686
  providerRefundIds: attr9("json", true)
6431
6687
  } },
6432
- networkSeatRecipient: { attrs: { id: id9(), appId: key2(), chapterId: key2(), environment: key2(), runtime: key2(), seatId: key2(), recipientEmailNormalized: key2(), createdAt: attr9("number") } },
6688
+ networkSeatRecipient: { attrs: { id: id10(), appId: key2(), chapterId: key2(), environment: key2(), runtime: key2(), seatId: key2(), recipientEmailNormalized: key2(), createdAt: attr9("number") } },
6433
6689
  membershipEffect: { attrs: {
6434
- id: id9(),
6690
+ id: id10(),
6435
6691
  chapterId: key2(),
6436
6692
  environment: key2(),
6437
6693
  runtime: key2(),
@@ -6556,14 +6812,14 @@ function validateMemberContentDocument(input, options = {}) {
6556
6812
  continue;
6557
6813
  }
6558
6814
  unknownKeys(raw2, PAGE_KEYS, path, errors);
6559
- const id10 = textValue(raw2.id, `${path}.id`, errors, 80, true) ?? "";
6815
+ const id11 = textValue(raw2.id, `${path}.id`, errors, 80, true) ?? "";
6560
6816
  const slug = textValue(raw2.slug, `${path}.slug`, errors, 120, true) ?? "";
6561
6817
  const title = textValue(raw2.title, `${path}.title`, errors, MAX_TEXT, true) ?? "";
6562
- if (id10 && !ID.test(id10)) errors.push(`${path}.id must be a lowercase kebab-case id`);
6818
+ if (id11 && !ID.test(id11)) errors.push(`${path}.id must be a lowercase kebab-case id`);
6563
6819
  if (slug && !SLUG8.test(slug)) errors.push(`${path}.slug must be a lowercase URL slug`);
6564
- if (ids2.has(id10) || knownParentIds.has(id10)) errors.push(`${path}.id duplicates page "${id10}"`);
6820
+ if (ids2.has(id11) || knownParentIds.has(id11)) errors.push(`${path}.id duplicates page "${id11}"`);
6565
6821
  if (slugs.has(slug) || (options.parentPages ?? []).some((page) => page.slug === slug)) errors.push(`${path}.slug duplicates "${slug}"`);
6566
- ids2.add(id10);
6822
+ ids2.add(id11);
6567
6823
  slugs.add(slug);
6568
6824
  const navRaw = record(raw2.navigation);
6569
6825
  let navigation = { section: "General", order: index2 };
@@ -6619,7 +6875,7 @@ function validateMemberContentDocument(input, options = {}) {
6619
6875
  else if (owner === "chapter") upsell = { owner, ...offer ? { offer } : {} };
6620
6876
  }
6621
6877
  }
6622
- pages.push({ id: id10, slug, title, ...summary ? { summary } : {}, navigation, audience, ...teaserMarkdown ? { teaserMarkdown } : {}, bodyMarkdown, ...upsell ? { upsell } : {} });
6878
+ pages.push({ id: id11, slug, title, ...summary ? { summary } : {}, navigation, audience, ...teaserMarkdown ? { teaserMarkdown } : {}, bodyMarkdown, ...upsell ? { upsell } : {} });
6623
6879
  }
6624
6880
  const allIds = /* @__PURE__ */ new Set([...knownParentIds, ...ids2]);
6625
6881
  for (const [index2, page] of pages.entries()) {
@@ -6825,8 +7081,8 @@ function resourceValue(value, path, errors) {
6825
7081
  return null;
6826
7082
  }
6827
7083
  unknownKeys2(raw2, RESOURCE_KEYS, path, errors);
6828
- const id10 = text3(raw2.id, `${path}.id`, errors, 80) ?? "";
6829
- if (id10 && !ID2.test(id10)) errors.push(`${path}.id must be lowercase kebab-case`);
7084
+ const id11 = text3(raw2.id, `${path}.id`, errors, 80) ?? "";
7085
+ if (id11 && !ID2.test(id11)) errors.push(`${path}.id must be lowercase kebab-case`);
6830
7086
  const kind = text3(raw2.kind, `${path}.kind`, errors) ?? "";
6831
7087
  if (!KINDS.has(kind)) errors.push(`${path}.kind is unsupported`);
6832
7088
  const title = text3(raw2.title, `${path}.title`, errors, 240) ?? "";
@@ -6883,7 +7139,7 @@ function resourceValue(value, path, errors) {
6883
7139
  updatedAt: integer3(provenanceRaw?.updatedAt, `${path}.provenance.updatedAt`, errors, 0),
6884
7140
  ...promotedFrom ? { promotedFrom } : {}
6885
7141
  };
6886
- return { id: id10, kind, title, ...summary ? { summary } : {}, tags, scope: scope2, status: status2, revision, ...bodyMarkdown ? { bodyMarkdown } : {}, ...url ? { url } : {}, ...brandAsset ? { brandAsset } : {}, provenance };
7142
+ return { id: id11, kind, title, ...summary ? { summary } : {}, tags, scope: scope2, status: status2, revision, ...bodyMarkdown ? { bodyMarkdown } : {}, ...url ? { url } : {}, ...brandAsset ? { brandAsset } : {}, provenance };
6887
7143
  }
6888
7144
  function validateBuilderLibraryDocument(value, options = {}) {
6889
7145
  const errors = [];
@@ -6914,7 +7170,7 @@ function validateBuilderLibraryDocument(value, options = {}) {
6914
7170
  const inputs = Array.isArray(raw2.resources) ? raw2.resources : [];
6915
7171
  if (inputs.length > MAX_RESOURCES) errors.push(`document.resources may contain at most ${MAX_RESOURCES} items`);
6916
7172
  const resources = inputs.map((item, index2) => resourceValue(item, `document.resources[${index2}]`, errors)).filter(Boolean);
6917
- const ids2 = resources.map(({ id: id10 }) => id10);
7173
+ const ids2 = resources.map(({ id: id11 }) => id11);
6918
7174
  if (new Set(ids2).size !== ids2.length) errors.push("document.resources ids must be unique");
6919
7175
  for (const resource of resources) {
6920
7176
  if (sourceType === "chapter" && (resource.scope.kind !== "chapter" || resource.scope.chapterId !== sourceAppId)) {
@@ -7168,7 +7424,7 @@ async function frozenCheckoutVariables(db, app) {
7168
7424
  const intent = (await db.query({ subscriptionCheckoutIntents: { $: { where: { id: String(app.id) }, limit: 1 } } })).subscriptionCheckoutIntents?.[0];
7169
7425
  const offer = intent?.membershipOffer, contract = offer?.contract;
7170
7426
  const offering = contract?.offering, quote = intent?.paymentQuote;
7171
- const amount = contract?.dueTodayCents ?? intent?.priceCents;
7427
+ const amount = quote?.lineItems?.dueTodayCents ?? contract?.dueTodayCents ?? intent?.priceCents;
7172
7428
  const currency = offering?.currency ?? intent?.currency;
7173
7429
  let formatted = "";
7174
7430
  if (Number.isSafeInteger(amount) && Number(amount) > 0 && typeof currency === "string" && /^[a-z]{3}$/.test(currency)) {
@@ -7244,11 +7500,11 @@ async function sendTemplated(deps, input) {
7244
7500
  const previous = prior[0];
7245
7501
  if (previous && (previous.groupId !== input.group.id || previous.template !== input.template || previous.to !== decision.to || previous.transport !== decision.transport || previous.applicationId !== input.applicationId)) return { sent: false, reason: "delivery-identity-conflict" };
7246
7502
  const hash2 = await crypto.subtle.digest("SHA-256", new TextEncoder().encode(input.dedupeKey));
7247
- const id10 = previous ? String(previous.id) : `email_${Array.from(new Uint8Array(hash2), (b) => b.toString(16).padStart(2, "0")).join("")}`;
7503
+ const id11 = previous ? String(previous.id) : `email_${Array.from(new Uint8Array(hash2), (b) => b.toString(16).padStart(2, "0")).join("")}`;
7248
7504
  const attempt = Number(previous?.deliveryAttempt ?? 0) + 1;
7249
7505
  if (!Number.isSafeInteger(attempt) || attempt < 1) return { sent: false, reason: "delivery-state-invalid" };
7250
7506
  const row2 = {
7251
- id: id10,
7507
+ id: id11,
7252
7508
  groupId: input.group.id,
7253
7509
  to: decision.to,
7254
7510
  template: input.template,
@@ -7266,15 +7522,15 @@ async function sendTemplated(deps, input) {
7266
7522
  if (isRetiredEmailCopy(String(row2.subject), String(row2.body))) return { sent: false, reason: "template-retired" };
7267
7523
  if ((0, import_crm8.validateEmailCopy)({ subject: row2.subject, text: row2.body }, { allowedVariables: [] }).length) return { sent: false, reason: "template-invalid" };
7268
7524
  try {
7269
- const claim = await deps.db.transact([{ t: "update", ns: "emailLog", id: id10, attrs: row2 }], {
7270
- mutationId: `email-claim:${id10}:${attempt}`,
7525
+ const claim = await deps.db.transact([{ t: "update", ns: "emailLog", id: id11, attrs: row2 }], {
7526
+ mutationId: `email-claim:${id11}:${attempt}`,
7271
7527
  guards: [previous ? {
7272
7528
  ns: "emailLog",
7273
- id: id10,
7529
+ id: id11,
7274
7530
  exists: true,
7275
7531
  equals: { deliveryState: "retry_ready", ...previous.deliveryAttempt !== void 0 ? { deliveryAttempt: previous.deliveryAttempt } : {} },
7276
7532
  ...previous.deliveryAttempt === void 0 ? { absent: ["deliveryAttempt"] } : {}
7277
- } : { ns: "emailLog", id: id10, exists: false }]
7533
+ } : { ns: "emailLog", id: id11, exists: false }]
7278
7534
  });
7279
7535
  if (claim.duplicate) return { sent: false, reason: "delivery-uncertain" };
7280
7536
  } catch (error) {
@@ -7294,16 +7550,16 @@ async function sendTemplated(deps, input) {
7294
7550
  if (!response || typeof response.messageId !== "string" || !response.messageId) throw new Error("missing delivery receipt");
7295
7551
  messageId = response.messageId;
7296
7552
  } catch {
7297
- await deps.db.transact([{ t: "update", ns: "emailLog", id: id10, attrs: { deliveryState: "uncertain", error: "delivery-uncertain" } }], {
7298
- guards: [{ ns: "emailLog", id: id10, exists: true, equals: { deliveryState: "sending", deliveryAttempt: attempt } }]
7553
+ await deps.db.transact([{ t: "update", ns: "emailLog", id: id11, attrs: { deliveryState: "uncertain", error: "delivery-uncertain" } }], {
7554
+ guards: [{ ns: "emailLog", id: id11, exists: true, equals: { deliveryState: "sending", deliveryAttempt: attempt } }]
7299
7555
  });
7300
7556
  return { sent: false, reason: "delivery-uncertain" };
7301
7557
  }
7302
- await deps.db.transact([{ t: "update", ns: "emailLog", id: id10, attrs: { deliveryState: "sent", error: "", messageId, sentAt: deps.now() } }], {
7303
- mutationId: `email-confirmed:${id10}:${attempt}`,
7558
+ await deps.db.transact([{ t: "update", ns: "emailLog", id: id11, attrs: { deliveryState: "sent", error: "", messageId, sentAt: deps.now() } }], {
7559
+ mutationId: `email-confirmed:${id11}:${attempt}`,
7304
7560
  guards: [{
7305
7561
  ns: "emailLog",
7306
- id: id10,
7562
+ id: id11,
7307
7563
  exists: true,
7308
7564
  equals: { deliveryState: "sending", deliveryAttempt: attempt }
7309
7565
  }]
@@ -7359,7 +7615,7 @@ async function reconcileLifecycleDelivery(db, input, now) {
7359
7615
  }
7360
7616
 
7361
7617
  // src/payments.ts
7362
- var import_stripe10 = require("@odla-ai/stripe");
7618
+ var import_stripe11 = require("@odla-ai/stripe");
7363
7619
  function paymentsReady(group, hasSecretKey) {
7364
7620
  return Boolean(group.stripePublishableKey && group.stripePriceId && hasSecretKey);
7365
7621
  }
@@ -7561,14 +7817,14 @@ function validateAdmissionGrantDocument(input) {
7561
7817
  const seenEmails = /* @__PURE__ */ new Set();
7562
7818
  for (const [index2, raw2] of rawGrants.slice(0, MAX_GRANTS).entries()) {
7563
7819
  const grant = raw2 && typeof raw2 === "object" ? raw2 : {};
7564
- const id10 = typeof grant.id === "string" ? grant.id : "";
7820
+ const id11 = typeof grant.id === "string" ? grant.id : "";
7565
7821
  const email = typeof grant.email === "string" ? grant.email.trim() : "";
7566
7822
  const normalized = normalizeAdmissionEmail(email);
7567
7823
  const decision = grant.decision;
7568
7824
  const note = typeof grant.note === "string" ? grant.note.trim() : void 0;
7569
7825
  const expiresAt = grant.expiresAt === void 0 ? void 0 : Number(grant.expiresAt);
7570
- if (!ID3.test(id10)) errors.push(`document.grants[${index2}].id must be an identifier`);
7571
- if (seenIds.has(id10)) errors.push(`document.grants repeats id "${id10}"`);
7826
+ if (!ID3.test(id11)) errors.push(`document.grants[${index2}].id must be an identifier`);
7827
+ if (seenIds.has(id11)) errors.push(`document.grants repeats id "${id11}"`);
7572
7828
  if (!isAdmissionEmail(normalized)) errors.push(`document.grants[${index2}].email must be valid`);
7573
7829
  if (seenEmails.has(normalized)) errors.push(`document.grants repeats email "${normalized}"`);
7574
7830
  if (decision !== "waive_interview" && decision !== "require_interview") {
@@ -7578,10 +7834,10 @@ function validateAdmissionGrantDocument(input) {
7578
7834
  if (expiresAt !== void 0 && (!Number.isFinite(expiresAt) || expiresAt <= 0)) {
7579
7835
  errors.push(`document.grants[${index2}].expiresAt must be epoch milliseconds`);
7580
7836
  }
7581
- seenIds.add(id10);
7837
+ seenIds.add(id11);
7582
7838
  seenEmails.add(normalized);
7583
7839
  grants.push({
7584
- id: id10,
7840
+ id: id11,
7585
7841
  email,
7586
7842
  decision,
7587
7843
  ...note ? { note } : {},
@@ -7653,8 +7909,8 @@ async function materializeAdmissionGrants(db, envelope, previous, receivedAt) {
7653
7909
  const incomingIds = new Set(document.grants.map((grant) => `admission:parent:${document.source.appId}:${grant.id}`));
7654
7910
  const ops = [];
7655
7911
  for (const row2 of current) {
7656
- const id10 = String(row2.id ?? "");
7657
- if (id10 && !incomingIds.has(id10)) ops.push({ t: "update", ns: "admissionGrants", id: id10, attrs: {
7912
+ const id11 = String(row2.id ?? "");
7913
+ if (id11 && !incomingIds.has(id11)) ops.push({ t: "update", ns: "admissionGrants", id: id11, attrs: {
7658
7914
  active: false,
7659
7915
  revokedAt: receivedAt,
7660
7916
  revokedBy: document.source.actor,
@@ -7663,10 +7919,10 @@ async function materializeAdmissionGrants(db, envelope, previous, receivedAt) {
7663
7919
  } });
7664
7920
  }
7665
7921
  for (const grant of document.grants) {
7666
- const id10 = `admission:parent:${document.source.appId}:${grant.id}`;
7667
- const existing = current.find((row2) => row2.id === id10);
7668
- ops.push({ t: "update", ns: "admissionGrants", id: id10, attrs: {
7669
- id: id10,
7922
+ const id11 = `admission:parent:${document.source.appId}:${grant.id}`;
7923
+ const existing = current.find((row2) => row2.id === id11);
7924
+ ops.push({ t: "update", ns: "admissionGrants", id: id11, attrs: {
7925
+ id: id11,
7670
7926
  sourceGrantId: grant.id,
7671
7927
  sourceScope: "parent",
7672
7928
  sourceId: document.source.appId,
@@ -7687,7 +7943,7 @@ async function materializeAdmissionGrants(db, envelope, previous, receivedAt) {
7687
7943
  ...!grant.note && existing?.note !== void 0 ? ["note"] : [],
7688
7944
  ...grant.expiresAt === void 0 && existing?.expiresAt !== void 0 ? ["expiresAt"] : []
7689
7945
  ];
7690
- if (retract.length) ops.push({ t: "retract", ns: "admissionGrants", id: id10, attrs: retract });
7946
+ if (retract.length) ops.push({ t: "retract", ns: "admissionGrants", id: id11, attrs: retract });
7691
7947
  }
7692
7948
  if (previous?.active) ops.push({ t: "update", ns: "admissionGrantRevisions", id: previous.id, attrs: { active: false } });
7693
7949
  const revisionId = `admission-revision:${document.source.appId}:${document.target.environment}:${document.revision}`;
@@ -7758,10 +8014,10 @@ function validateGiftOfferCatalog(input, tierIds) {
7758
8014
  }
7759
8015
  const allowed2 = /* @__PURE__ */ new Set(["id", "target", "billingMode", "stripePriceId", "priceCents", "currency", "redemptionLifetimeHours", "activeFrom", "activeUntil", "title", "description", "ctaLabel"]);
7760
8016
  for (const key3 of Object.keys(item)) if (!allowed2.has(key3)) errors.push(`${path}.${key3} is not supported`);
7761
- const id10 = text4(item.id, `${path}.id`, errors, 80);
7762
- if (id10 && !ID4.test(id10)) errors.push(`${path}.id must be a lowercase kebab-case id`);
7763
- if (ids2.has(id10)) errors.push(`${path}.id duplicates "${id10}"`);
7764
- ids2.add(id10);
8017
+ const id11 = text4(item.id, `${path}.id`, errors, 80);
8018
+ if (id11 && !ID4.test(id11)) errors.push(`${path}.id must be a lowercase kebab-case id`);
8019
+ if (ids2.has(id11)) errors.push(`${path}.id duplicates "${id11}"`);
8020
+ ids2.add(id11);
7765
8021
  const targetRaw = record3(item.target);
7766
8022
  let target = { type: "entitlement", entitlementKey: "invalid" };
7767
8023
  if (targetRaw?.type === "tier") {
@@ -7787,7 +8043,7 @@ function validateGiftOfferCatalog(input, tierIds) {
7787
8043
  if (activeUntil !== void 0 && (!Number.isSafeInteger(activeUntil) || activeUntil < 1)) errors.push(`${path}.activeUntil must be epoch milliseconds`);
7788
8044
  if (activeFrom !== void 0 && activeUntil !== void 0 && activeUntil <= activeFrom) errors.push(`${path}.activeUntil must follow activeFrom`);
7789
8045
  offers.push({
7790
- id: id10,
8046
+ id: id11,
7791
8047
  target,
7792
8048
  billingMode: item.billingMode,
7793
8049
  stripePriceId,
@@ -7831,10 +8087,10 @@ async function ensureProjectedPerson(deps, input, options = {}) {
7831
8087
  const existing = await find();
7832
8088
  if (existing) return { record: existing, created: false };
7833
8089
  const digest = new Uint8Array(await crypto.subtle.digest("SHA-256", new TextEncoder().encode(`chapter:person:v1:${email}`)));
7834
- const id10 = `person-${Array.from(digest, (byte) => byte.toString(16).padStart(2, "0")).join("")}`;
8090
+ const id11 = `person-${Array.from(digest, (byte) => byte.toString(16).padStart(2, "0")).join("")}`;
7835
8091
  try {
7836
8092
  const created = await (0, import_crm9.createRecord)({ ...deps, db: deps.db }, {
7837
- id: id10,
8093
+ id: id11,
7838
8094
  type: "person",
7839
8095
  input: { ...input, email },
7840
8096
  ...options
@@ -8257,9 +8513,9 @@ function subAnnualCents(sub) {
8257
8513
  }
8258
8514
 
8259
8515
  // src/payments-stripe.ts
8260
- var import_stripe11 = require("@odla-ai/stripe");
8516
+ var import_stripe12 = require("@odla-ai/stripe");
8261
8517
  async function stripeCall(sk, method, path, params, idempotencyKey) {
8262
- return (0, import_stripe11.stripeRequest)(
8518
+ return (0, import_stripe12.stripeRequest)(
8263
8519
  { secretKey: sk },
8264
8520
  method,
8265
8521
  path,
@@ -8337,8 +8593,8 @@ async function updateClerkUserMetadata(secretKey, userId, input, fetchImpl = fet
8337
8593
  return res.ok;
8338
8594
  }
8339
8595
  async function updateClerkUserMetadataByEmail(secretKey, email, input, fetchImpl = fetch) {
8340
- const id10 = await existingClerkUserId(secretKey, email, fetchImpl);
8341
- return id10 ? updateClerkUserMetadata(secretKey, id10, input, fetchImpl) : false;
8596
+ const id11 = await existingClerkUserId(secretKey, email, fetchImpl);
8597
+ return id11 ? updateClerkUserMetadata(secretKey, id11, input, fetchImpl) : false;
8342
8598
  }
8343
8599
  async function existingClerkUserId(secretKey, email, fetchImpl) {
8344
8600
  const auth = { authorization: `Bearer ${secretKey}` };
@@ -8411,10 +8667,10 @@ async function clerkGetUserByEmail(secretKey, email, fetchImpl = fetch, source =
8411
8667
  const user = Array.isArray(data) ? data[0] : void 0;
8412
8668
  return user ? toRecord(user, source) : null;
8413
8669
  }
8414
- async function clerkGetUser(secretKey, id10, fetchImpl = fetch, source = "public") {
8415
- const data = await clerkGet(`/v1/users/${encodeURIComponent(id10)}`, secretKey, fetchImpl).catch(() => null);
8670
+ async function clerkGetUser(secretKey, id11, fetchImpl = fetch, source = "public") {
8671
+ const data = await clerkGet(`/v1/users/${encodeURIComponent(id11)}`, secretKey, fetchImpl).catch(() => null);
8416
8672
  const record4 = data ? toRecord(data, source) : null;
8417
- return record4?.id === id10 ? record4 : null;
8673
+ return record4?.id === id11 ? record4 : null;
8418
8674
  }
8419
8675
  async function clerkListUsers(secretKey, fetchImpl = fetch, source = "public") {
8420
8676
  const out = [];
@@ -8430,8 +8686,8 @@ async function clerkListUsers(secretKey, fetchImpl = fetch, source = "public") {
8430
8686
  }
8431
8687
  return out;
8432
8688
  }
8433
- async function clerkSetRole(secretKey, id10, role, fetchImpl = fetch, source = "public") {
8434
- const res = await fetchImpl(`${CLERK_API}/v1/users/${encodeURIComponent(id10)}/metadata`, {
8689
+ async function clerkSetRole(secretKey, id11, role, fetchImpl = fetch, source = "public") {
8690
+ const res = await fetchImpl(`${CLERK_API}/v1/users/${encodeURIComponent(id11)}/metadata`, {
8435
8691
  method: "PATCH",
8436
8692
  headers: { authorization: `Bearer ${secretKey}`, "content-type": "application/json" },
8437
8693
  redirect: "manual",
@@ -8448,7 +8704,7 @@ function planPrivateRoleMigration(input) {
8448
8704
  if (input.environment !== "dev") blockers.push("dev_only");
8449
8705
  if (!input.ladder.length || new Set(input.ladder).size !== input.ladder.length || input.ladder.some((role) => !role || role === "super_admin")) blockers.push("invalid_role_ladder");
8450
8706
  const expected = new Set(input.expectedRuntimes);
8451
- if (!expected.size || expected.size !== input.expectedRuntimes.length || input.expectedRuntimes.some((id10) => !id10)) blockers.push("runtime_inventory_required");
8707
+ if (!expected.size || expected.size !== input.expectedRuntimes.length || input.expectedRuntimes.some((id11) => !id11)) blockers.push("runtime_inventory_required");
8452
8708
  if (input.runtimes.length !== expected.size || new Set(input.runtimes.map((r) => r.runtime)).size !== expected.size || input.runtimes.some((r) => !expected.has(r.runtime))) blockers.push("runtime_inventory_mismatch");
8453
8709
  for (const runtime of input.expectedRuntimes) {
8454
8710
  const proof = input.runtimes.find((r) => r.runtime === runtime);
@@ -8478,7 +8734,7 @@ function planPrivateRoleMigration(input) {
8478
8734
  if (privateRole === role && !Object.hasOwn(user.publicMetadata, "role")) alreadyCurrent++;
8479
8735
  else changes.push({ userId: user.id, role, publicRole, privateRole });
8480
8736
  }
8481
- if (!input.protectedSuperAdminUserIds.some((id10) => ids2.has(id10))) blockers.push("protected_operator_grant_required");
8737
+ if (!input.protectedSuperAdminUserIds.some((id11) => ids2.has(id11))) blockers.push("protected_operator_grant_required");
8482
8738
  return { ready: blockers.length === 0, blockers, changes, alreadyCurrent, totalUsers: input.users.length };
8483
8739
  }
8484
8740
 
@@ -9056,10 +9312,10 @@ function tiersOf(value, errors) {
9056
9312
  return [];
9057
9313
  }
9058
9314
  unknownKeys3(item, TIER_KEYS2, path, errors);
9059
- const id10 = text5(item.id, `${path}.id`, errors, 80);
9060
- if (id10 && !TIER.test(id10)) errors.push(`${path}.id must be a lowercase kebab-case id`);
9061
- if (seen.has(id10)) errors.push(`${path}.id duplicates "${id10}"`);
9062
- seen.add(id10);
9315
+ const id11 = text5(item.id, `${path}.id`, errors, 80);
9316
+ if (id11 && !TIER.test(id11)) errors.push(`${path}.id must be a lowercase kebab-case id`);
9317
+ if (seen.has(id11)) errors.push(`${path}.id duplicates "${id11}"`);
9318
+ seen.add(id11);
9063
9319
  const name = text5(item.name, `${path}.name`, errors, 160);
9064
9320
  if (typeof item.available !== "boolean") errors.push(`${path}.available must be boolean`);
9065
9321
  if (!Number.isInteger(item.sortOrder) || Number(item.sortOrder) < 0) errors.push(`${path}.sortOrder must be a non-negative integer`);
@@ -9080,7 +9336,7 @@ function tiersOf(value, errors) {
9080
9336
  }
9081
9337
  const blurb = item.blurb === void 0 ? void 0 : text5(item.blurb, `${path}.blurb`, errors, 4e3);
9082
9338
  return [{
9083
- id: id10,
9339
+ id: id11,
9084
9340
  name,
9085
9341
  available: item.available === true,
9086
9342
  sortOrder: Number(item.sortOrder) || 0,
@@ -9142,15 +9398,15 @@ function policyOf(value, errors, tiers2) {
9142
9398
  if (input.tierPolicies !== void 0) {
9143
9399
  const overrides = recordOf(input.tierPolicies);
9144
9400
  if (!overrides || Object.keys(overrides).length > 20) errors.push("document.policy.tierPolicies must be a bounded tier map");
9145
- else for (const [id10, raw2] of Object.entries(overrides)) {
9146
- const path = `document.policy.tierPolicies.${id10}`;
9401
+ else for (const [id11, raw2] of Object.entries(overrides)) {
9402
+ const path = `document.policy.tierPolicies.${id11}`;
9147
9403
  const policy2 = recordOf(raw2);
9148
- if (!tiers2.some((tier) => tier.id === id10) || !policy2) {
9404
+ if (!tiers2.some((tier) => tier.id === id11) || !policy2) {
9149
9405
  errors.push(`${path} must name a declared tier and policy`);
9150
9406
  continue;
9151
9407
  }
9152
9408
  unknownKeys3(policy2, /* @__PURE__ */ new Set(["refundPolicyText", "trustCopy"]), path, errors);
9153
- tierPolicies[id10] = {
9409
+ tierPolicies[id11] = {
9154
9410
  refundPolicyText: text5(policy2.refundPolicyText, `${path}.refundPolicyText`, errors),
9155
9411
  ...policy2.trustCopy !== void 0 ? { trustCopy: text5(policy2.trustCopy, `${path}.trustCopy`, errors) } : {}
9156
9412
  };
@@ -9473,7 +9729,7 @@ var exclusions = (chapterId, paidTierIds) => [
9473
9729
  id: chapterId,
9474
9730
  reason: "provider_identity",
9475
9731
  paths: [
9476
- ...paidTierIds.map((id10) => `tiers[${id10}].stripePriceId`),
9732
+ ...paidTierIds.map((id11) => `tiers[${id11}].stripePriceId`),
9477
9733
  "groups.stripePublishableKey",
9478
9734
  "applications.stripeCustomerId",
9479
9735
  "applications.stripeSubscriptionId",
@@ -9515,9 +9771,9 @@ async function exportChapterPromotion(input) {
9515
9771
  sourceIds: [`signup-control:${document.revision}`, document.source.appId, document.target.chapterId].sort(),
9516
9772
  receiptDigests: [input.digest]
9517
9773
  };
9518
- const artifact = (kind, id10, content) => ({
9774
+ const artifact = (kind, id11, content) => ({
9519
9775
  kind,
9520
- id: id10,
9776
+ id: id11,
9521
9777
  sourceRevision: revision,
9522
9778
  provenance: promotionProvenance,
9523
9779
  content: (0, import_promotion.canonicalPromotionValue)(content)
@@ -9562,7 +9818,7 @@ var MEMBER_SYSTEM_RECORD_OWNERS = {
9562
9818
  provider: "originating-provider",
9563
9819
  protectedRoles: "odla-platform"
9564
9820
  };
9565
- var rows2 = (group) => group.fields.map((id10) => ({ ...group, id: id10 }));
9821
+ var rows2 = (group) => group.fields.map((id11) => ({ ...group, id: id11 }));
9566
9822
  var MEMBER_SYSTEM_AUTHORITY_MATRIX = [
9567
9823
  ...rows2({
9568
9824
  scope: "network",