@odla-ai/chapter 0.54.2 → 0.55.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,
@@ -1987,9 +1987,9 @@ var APPLICATION_LABELS = {
1987
1987
  phone: "Phone",
1988
1988
  state: "State"
1989
1989
  };
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;
1990
+ function humanize(id11) {
1991
+ const text6 = id11.replace(/([a-z0-9])([A-Z])/g, "$1 $2").replace(/[_-]+/g, " ").trim();
1992
+ return text6 ? `${text6[0].toUpperCase()}${text6.slice(1)}` : id11;
1993
1993
  }
1994
1994
  function applicationDefs(chapter) {
1995
1995
  try {
@@ -1998,23 +1998,23 @@ function applicationDefs(chapter) {
1998
1998
  return {};
1999
1999
  }
2000
2000
  }
2001
- function applicationType(id10, def) {
2001
+ function applicationType(id11, def) {
2002
2002
  if (def && ["string", "email", "url", "enum"].includes(def.type)) return def.type;
2003
- return id10 === "email" ? "email" : id10 === "linkedin" ? "url" : "string";
2003
+ return id11 === "email" ? "email" : id11 === "linkedin" ? "url" : "string";
2004
2004
  }
2005
2005
  function applicationFlowFields(chapter, application = chapter.application) {
2006
2006
  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);
2007
+ return [...application.required, ...application.optional].map((id11, order2) => {
2008
+ const def = defs[id11];
2009
+ const conditions = application.conditions[id11];
2010
+ const type = applicationType(id11, def);
2011
2011
  return {
2012
- id: id10,
2013
- label: def?.label ?? APPLICATION_LABELS[id10] ?? humanize(id10),
2012
+ id: id11,
2013
+ label: def?.label ?? APPLICATION_LABELS[id11] ?? humanize(id11),
2014
2014
  type,
2015
- required: application.required.includes(id10),
2015
+ required: application.required.includes(id11),
2016
2016
  order: order2,
2017
- maxLength: application.maxLen[id10] ?? application.defaultMaxLen,
2017
+ maxLength: application.maxLen[id11] ?? application.defaultMaxLen,
2018
2018
  ...type === "enum" && def?.options ? { options: [...def.options] } : {},
2019
2019
  ...conditions ? { conditions: { ...conditions } } : {},
2020
2020
  ...type === "email" ? { emailIdentity: "literal" } : {}
@@ -2024,17 +2024,17 @@ function applicationFlowFields(chapter, application = chapter.application) {
2024
2024
  function resolveFormationFlowFields(crm, formation) {
2025
2025
  if (!formation.enabled) return [];
2026
2026
  const def = crm.type(formation.type);
2027
- return [...formation.required, ...formation.optional].map((id10, order2) => {
2028
- const field2 = def.fields[id10];
2027
+ return [...formation.required, ...formation.optional].map((id11, order2) => {
2028
+ const field2 = def.fields[id11];
2029
2029
  return {
2030
- id: id10,
2031
- label: field2.label ?? humanize(id10),
2030
+ id: id11,
2031
+ label: field2.label ?? humanize(id11),
2032
2032
  type: field2.type,
2033
- required: formation.required.includes(id10),
2033
+ required: formation.required.includes(id11),
2034
2034
  order: order2,
2035
- maxLength: formation.maxLen[id10] ?? formation.defaultMaxLen,
2035
+ maxLength: formation.maxLen[id11] ?? formation.defaultMaxLen,
2036
2036
  ...field2.options ? { options: [...field2.options] } : {},
2037
- ...id10 === def.emailField || field2.type === "email" ? { emailIdentity: "literal" } : {}
2037
+ ...id11 === def.emailField || field2.type === "email" ? { emailIdentity: "literal" } : {}
2038
2038
  };
2039
2039
  });
2040
2040
  }
@@ -2338,12 +2338,12 @@ function resolveNetworkPersonRegistry(mode, input, targetIds) {
2338
2338
  if (input.mode !== "shadow") throw new Error('defineChapter.networkPersonRegistry.mode: must be "shadow"');
2339
2339
  const chapterIds = input.chapterIds ? [...input.chapterIds] : [...targetIds];
2340
2340
  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))) {
2341
+ if (new Set(chapterIds).size !== chapterIds.length || chapterIds.some((id11) => !SLUG2.test(id11))) {
2342
2342
  throw new Error("defineChapter.networkPersonRegistry.chapterIds: expected distinct lowercase slugs");
2343
2343
  }
2344
- const unknown = chapterIds.filter((id10) => !targetIds.includes(id10));
2344
+ const unknown = chapterIds.filter((id11) => !targetIds.includes(id11));
2345
2345
  if (unknown.length) {
2346
- throw new Error(`defineChapter.networkPersonRegistry.chapterIds: ${unknown.map((id10) => `"${id10}"`).join(", ")} are not configured network targets`);
2346
+ throw new Error(`defineChapter.networkPersonRegistry.chapterIds: ${unknown.map((id11) => `"${id11}"`).join(", ")} are not configured network targets`);
2347
2347
  }
2348
2348
  return { mode: "shadow", chapterIds };
2349
2349
  }
@@ -2466,9 +2466,9 @@ function isResolvedCrm(x) {
2466
2466
  }
2467
2467
  function defineChapter(config) {
2468
2468
  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)}`);
2469
+ const { id: id11, name } = config;
2470
+ if (typeof id11 !== "string" || !SLUG7.test(id11)) {
2471
+ throw new Error(`defineChapter.id: must be a lowercase slug [a-z0-9-] (2-63 chars) \u2014 got ${JSON.stringify(id11)}`);
2472
2472
  }
2473
2473
  if (typeof name !== "string" || name.trim() === "") throw new Error("defineChapter.name: a non-empty name is required");
2474
2474
  const mode = config.mode ?? "chapter";
@@ -2575,7 +2575,7 @@ function defineChapter(config) {
2575
2575
  };
2576
2576
  const chapter = {
2577
2577
  config,
2578
- id: id10,
2578
+ id: id11,
2579
2579
  name,
2580
2580
  mode,
2581
2581
  brand,
@@ -2990,14 +2990,14 @@ function facts(snapshots) {
2990
2990
  ["payment_intent.updated", snapshots.payments?.objects ?? []]
2991
2991
  ];
2992
2992
  return groups2.flatMap(([eventType, objects]) => objects.map((object2) => {
2993
- const id10 = typeof object2.id === "string" ? object2.id : "invalid";
2993
+ const id11 = typeof object2.id === "string" ? object2.id : "invalid";
2994
2994
  return normalizeNetworkCommercialStripeEvent({
2995
- id: `evt_snapshot_${eventType.replaceAll(".", "_")}_${id10}`,
2995
+ id: `evt_snapshot_${eventType.replaceAll(".", "_")}_${id11}`,
2996
2996
  type: eventType,
2997
2997
  created: typeof object2.created === "number" ? object2.created : 0,
2998
2998
  livemode: object2.livemode === true,
2999
2999
  data: { object: object2 }
3000
- }, `stripe-snapshot:${eventType}:${id10}`);
3000
+ }, `stripe-snapshot:${eventType}:${id11}`);
3001
3001
  }));
3002
3002
  }
3003
3003
  var selected = (row2) => ({
@@ -3706,8 +3706,8 @@ async function hash(value) {
3706
3706
  async function membershipDiscountCouponId(input, policyId) {
3707
3707
  return policyId ? `odla_m_${await hash([input.appId, input.chapterId, input.environment, input.runtime, input.skuId, policyId])}` : void 0;
3708
3708
  }
3709
- async function load(db, ns, id10) {
3710
- return (await db.query({ [ns]: { $: { where: { id: id10 }, limit: 1 } } }))[ns]?.[0];
3709
+ async function load(db, ns, id11) {
3710
+ return (await db.query({ [ns]: { $: { where: { id: id11 }, limit: 1 } } }))[ns]?.[0];
3711
3711
  }
3712
3712
  async function membershipOfferIdForRequest(input) {
3713
3713
  return `offer_${await hash([input.appId, input.chapterId, input.environment, input.runtime, input.applicationId, input.requestId])}`;
@@ -3844,20 +3844,20 @@ var import_stripe2 = require("@odla-ai/stripe");
3844
3844
 
3845
3845
  // src/commercial-evidence-guard.ts
3846
3846
  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 };
3847
+ const ns = "networkCommercialExceptions", id11 = `network-commercial-exception:${eventId}:collision`;
3848
+ const row2 = (await db.query({ [ns]: { $: { where: { id: id11 }, limit: 1 } } }))[ns]?.[0];
3849
+ if (!row2) return { ns, id: id11, exists: false };
3850
3850
  if (row2.status !== "resolved" || row2.resolvedDigest !== digest || typeof digest !== "string") {
3851
3851
  throw Object.assign(new Error(code), { code });
3852
3852
  }
3853
- return { ns, id: id10, exists: true, equals: { status: "resolved", resolvedDigest: digest, updatedAt: row2.updatedAt } };
3853
+ return { ns, id: id11, exists: true, equals: { status: "resolved", resolvedDigest: digest, updatedAt: row2.updatedAt } };
3854
3854
  }
3855
3855
 
3856
3856
  // src/membership-authority-state.ts
3857
3857
  var membershipFail = (code) => {
3858
3858
  throw Object.assign(new Error(code), { code });
3859
3859
  };
3860
- var membershipLoad = async (db, ns, id10) => (await db.query({ [ns]: { $: { where: { id: id10 }, limit: 1 } } }))[ns]?.[0];
3860
+ var membershipLoad = async (db, ns, id11) => (await db.query({ [ns]: { $: { where: { id: id11 }, limit: 1 } } }))[ns]?.[0];
3861
3861
  var membershipScopeKeys = ["appId", "chapterId", "environment", "runtime", "applicationId", "skuId"];
3862
3862
  var membershipScope = (input) => Object.fromEntries(membershipScopeKeys.map((key3) => [key3, input[key3]]));
3863
3863
  function requireMembershipScope(row2, input) {
@@ -3868,18 +3868,18 @@ async function loadMembershipAuthorityState(db, input) {
3868
3868
  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
3869
  return row2;
3870
3870
  }
3871
- var membershipUpdate = (ns, id10, attrs) => ({ t: "update", ns, id: id10, attrs });
3871
+ var membershipUpdate = (ns, id11, attrs) => ({ t: "update", ns, id: id11, attrs });
3872
3872
  var membershipClock = (now) => {
3873
3873
  if (!Number.isSafeInteger(now) || now < 0) membershipFail("invalid_membership_clock");
3874
3874
  };
3875
3875
 
3876
3876
  // src/membership-restart-payment.ts
3877
3877
  async function membershipRestartPayment(db, target, member, now) {
3878
- const id10 = String(member.id), restart = await membershipLoad(db, "membershipRestart", id10);
3878
+ const id11 = String(member.id), restart = await membershipLoad(db, "membershipRestart", id11);
3879
3879
  requireMembershipScope(restart, target);
3880
- const entitlement = await membershipLoad(db, "networkEntitlement", `${id10}:primary`);
3880
+ const entitlement = await membershipLoad(db, "networkEntitlement", `${id11}:primary`);
3881
3881
  requireMembershipScope(entitlement, target);
3882
- if (entitlement.membershipId !== id10 || entitlement.kind !== "primary") membershipFail("membership_restart_conflict");
3882
+ if (entitlement.membershipId !== id11 || entitlement.kind !== "primary") membershipFail("membership_restart_conflict");
3883
3883
  if (restart.status !== "ready" || member.status !== "restarting" || member.restartId !== restart.restartId || member.memberNumber !== restart.memberNumber || !Number.isSafeInteger(member.journeyApprovedAt)) membershipFail("membership_restart_conflict");
3884
3884
  return {
3885
3885
  restartId: String(restart.restartId),
@@ -3896,49 +3896,117 @@ async function membershipRestartPayment(db, target, member, now) {
3896
3896
  graceEndsAt: 0
3897
3897
  },
3898
3898
  ops: [
3899
- membershipUpdate("membershipRestart", id10, { status: "complete" }),
3900
- { t: "retract", ns: "networkMembership", id: id10, attrs: ["refundRequestedAt", "cancellationRequestedAt", "discountPolicyVersionId", "discountSnapshot"] }
3899
+ membershipUpdate("membershipRestart", id11, { status: "complete" }),
3900
+ { t: "retract", ns: "networkMembership", id: id11, attrs: ["refundRequestedAt", "cancellationRequestedAt", "discountPolicyVersionId", "discountSnapshot"] }
3901
3901
  ],
3902
3902
  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 } },
3903
+ { ns: "networkMembership", id: id11, exists: true, equals: { status: "restarting", revision: member.revision, restartId: restart.restartId } },
3904
+ { ns: "membershipRestart", id: id11, exists: true, equals: { status: "ready", restartId: restart.restartId } },
3905
3905
  { ns: "networkEntitlement", id: String(entitlement.id), exists: true, equals: { revision: entitlement.revision, status: entitlement.status } }
3906
3906
  ]
3907
3907
  };
3908
3908
  }
3909
3909
 
3910
+ // src/checkout-named-seat.ts
3911
+ function checkoutNamedSeatSelection(value, purchaserEmail) {
3912
+ const row2 = value;
3913
+ const name = typeof row2?.recipientName === "string" ? row2.recipientName.trim().replace(/\s+/g, " ") : "";
3914
+ const email = normalizeChapterEmail(typeof row2?.recipientEmail === "string" ? row2.recipientEmail : "");
3915
+ const purchaser = normalizeChapterEmail(purchaserEmail);
3916
+ 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) {
3917
+ throw Object.assign(new Error("Enter a different recipient's name and email."), { code: "invalid_named_seat_recipient" });
3918
+ }
3919
+ return { recipientName: name, recipientEmail: email };
3920
+ }
3921
+ async function checkoutNamedSeatIdentity(target, email) {
3922
+ const digest = await crypto.subtle.digest("SHA-256", new TextEncoder().encode(JSON.stringify([
3923
+ target.appId,
3924
+ target.chapterId,
3925
+ target.environment,
3926
+ target.runtime,
3927
+ email
3928
+ ])));
3929
+ return {
3930
+ seatId: (await membershipOfferIdForRequest({ ...target, requestId: "named_seat" })).replace(/^offer_/, "seat_"),
3931
+ membershipId: (await membershipOfferIdForRequest({ ...target, requestId: "checkout_binding" })).replace(/^offer_/, "membership_"),
3932
+ recipientClaimId: `seat-recipient:${[...new Uint8Array(digest)].map((byte) => byte.toString(16).padStart(2, "0")).join("")}`
3933
+ };
3934
+ }
3935
+
3910
3936
  // src/membership-checkout-binding.ts
3911
3937
  var keys = ["appId", "chapterId", "environment", "runtime", "applicationId", "skuId", "offerId", "offerDigest", "quoteDigest", "priceId"];
3912
3938
  var reject2 = (code) => {
3913
3939
  throw Object.assign(new Error(code), { code });
3914
3940
  };
3915
- var load2 = async (db, ns, id10) => (await db.query({ [ns]: { $: { where: { id: id10 }, limit: 1 } } }))[ns]?.[0];
3941
+ var load2 = async (db, ns, id11) => (await db.query({ [ns]: { $: { where: { id: id11 }, limit: 1 } } }))[ns]?.[0];
3916
3942
  async function membershipCheckoutBindingId(input) {
3917
3943
  return (await membershipOfferIdForRequest({ ...input, requestId: "checkout_binding" })).replace(/^offer_/, "checkout_");
3918
3944
  }
3919
3945
  async function verifyMembershipCheckoutBinding(value, input) {
3920
3946
  const row2 = value;
3921
3947
  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 };
3948
+ if (Boolean(row2.namedSeat) !== Boolean(input.namedSeat)) return reject2("membership_checkout_conflict");
3949
+ if (input.namedSeat) {
3950
+ const expected = {
3951
+ ...checkoutNamedSeatSelection(input.namedSeat, input.namedSeat.purchaserEmail),
3952
+ purchaserEmail: normalizeChapterEmail(input.namedSeat.purchaserEmail)
3953
+ };
3954
+ const identity2 = await checkoutNamedSeatIdentity(input, expected.recipientEmail);
3955
+ 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");
3956
+ }
3957
+ return {
3958
+ id: row2.id,
3959
+ ...Object.fromEntries(keys.map((key3) => [key3, input[key3]])),
3960
+ boundAt: row2.boundAt,
3961
+ ...row2.namedSeat ? { namedSeat: row2.namedSeat } : {}
3962
+ };
3923
3963
  }
3924
- async function bindMembershipCheckout(db, input, now) {
3964
+ async function bindMembershipCheckout(db, input, now, seatPriceId) {
3925
3965
  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
3966
  const stateId = `${input.chapterId}:${input.environment}`;
3927
3967
  const state = await load2(db, "membershipAuthorityState", stateId);
3928
3968
  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);
3969
+ const id11 = await membershipCheckoutBindingId(input);
3970
+ const existing = await load2(db, "membershipOfferCheckout", id11);
3931
3971
  if (existing) return verifyMembershipCheckoutBinding(existing, input);
3932
3972
  const row2 = await load2(db, "membershipCheckoutOffer", input.offerId);
3933
3973
  const offer = await verifyIssuedMembershipOffer(row2, { ...input, now, lifetimeMs: 6e4 });
3934
3974
  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 };
3975
+ const restart = await load2(db, "membershipRestart", id11.replace(/^checkout_/, "membership_"));
3976
+ let namedSeat;
3977
+ if (input.namedSeat) {
3978
+ if (!seatPriceId || !/^price_[A-Za-z0-9_]+$/.test(seatPriceId) || seatPriceId === input.priceId || restart) return reject2("named_seat_purchase_unavailable");
3979
+ const selection = checkoutNamedSeatSelection(input.namedSeat, input.namedSeat.purchaserEmail);
3980
+ namedSeat = {
3981
+ ...selection,
3982
+ purchaserEmail: normalizeChapterEmail(input.namedSeat.purchaserEmail),
3983
+ ...await checkoutNamedSeatIdentity(input, selection.recipientEmail),
3984
+ priceId: seatPriceId,
3985
+ amountCents: offer.contract.offering.seatPriceCents
3986
+ };
3987
+ }
3988
+ const receipt = { id: id11, ...Object.fromEntries(keys.map((key3) => [key3, input[key3]])), boundAt: now, ...namedSeat ? { namedSeat } : {} };
3937
3989
  try {
3938
- await db.transact([{ t: "update", ns: "membershipOfferCheckout", id: id10, attrs: receipt }], {
3939
- mutationId: `membership-checkout:${id10}${restart?.restartId ? `:${restart.restartId}` : ""}`,
3990
+ await db.transact([
3991
+ { t: "update", ns: "membershipOfferCheckout", id: id11, attrs: receipt },
3992
+ ...namedSeat ? [{ t: "update", ns: "networkSeatRecipient", id: namedSeat.recipientClaimId, attrs: {
3993
+ id: namedSeat.recipientClaimId,
3994
+ appId: input.appId,
3995
+ chapterId: input.chapterId,
3996
+ environment: input.environment,
3997
+ runtime: input.runtime,
3998
+ seatId: namedSeat.seatId,
3999
+ recipientEmailNormalized: namedSeat.recipientEmail,
4000
+ createdAt: now
4001
+ } }] : []
4002
+ ], {
4003
+ mutationId: `membership-checkout:${id11}${restart?.restartId ? `:${restart.restartId}` : ""}`,
3940
4004
  guards: [
3941
- { ns: "membershipOfferCheckout", id: id10, exists: false },
4005
+ { ns: "membershipOfferCheckout", id: id11, exists: false },
4006
+ ...namedSeat ? [
4007
+ { ns: "networkSeat", id: namedSeat.seatId, exists: false },
4008
+ { ns: "networkSeatRecipient", id: namedSeat.recipientClaimId, exists: false }
4009
+ ] : [],
3942
4010
  { ns: "membershipCheckoutOffer", id: input.offerId, exists: true, equals: { digest: input.offerDigest } },
3943
4011
  { ns: "membershipAuthorityState", id: stateId, exists: true, equals: { mode: "authoritative", revision: state.revision } }
3944
4012
  ]
@@ -3946,11 +4014,78 @@ async function bindMembershipCheckout(db, input, now) {
3946
4014
  } catch (error) {
3947
4015
  if (error.code !== "transact_guard_failed") throw error;
3948
4016
  }
3949
- const saved = await load2(db, "membershipOfferCheckout", id10);
4017
+ const saved = await load2(db, "membershipOfferCheckout", id11);
3950
4018
  if (!saved) return reject2("membership_binding_unconfirmed");
3951
4019
  return verifyMembershipCheckoutBinding(saved, input);
3952
4020
  }
3953
4021
 
4022
+ // src/membership-checkout-seat-payment.ts
4023
+ function checkoutSeatPaymentOperations(target, seat, input) {
4024
+ const { now, paidAt, endsAt } = input;
4025
+ const included = seat.amountCents === 0, entitlementId = `${seat.seatId}:entitlement`;
4026
+ const effectId = `${seat.seatId}:${included ? "invite" : "attach-renewal"}`;
4027
+ return [
4028
+ membershipUpdate("networkSeat", seat.seatId, {
4029
+ id: seat.seatId,
4030
+ ...membershipScope(target),
4031
+ primaryMembershipId: seat.membershipId,
4032
+ purchaserEmailNormalized: seat.purchaserEmail,
4033
+ recipientName: seat.recipientName,
4034
+ recipientEmailNormalized: seat.recipientEmail,
4035
+ recipientClaimId: seat.recipientClaimId,
4036
+ priceId: seat.priceId,
4037
+ primaryPriceId: input.primaryPriceId,
4038
+ amountCents: seat.amountCents,
4039
+ currency: input.offering.currency,
4040
+ interval: input.offering.interval,
4041
+ providerCustomerId: input.customerId,
4042
+ providerSubscriptionId: input.subscriptionId,
4043
+ combinedInvoice: { invoiceId: input.invoiceId, totalCents: input.invoiceTotalCents },
4044
+ status: included ? "pending_acceptance" : "renewal_pending",
4045
+ preparedAt: input.boundAt,
4046
+ purchasedAt: paidAt,
4047
+ termStartedAt: paidAt,
4048
+ termEndsAt: endsAt,
4049
+ acceptanceEndsAt: endsAt,
4050
+ seatPurchaseEndsAt: seatPurchaseDeadline(input.offering, { initialTermStartedAt: paidAt, termEndsAt: endsAt }),
4051
+ offeringSnapshot: input.offering,
4052
+ paymentEventId: input.eventId,
4053
+ ...included ? { entitlementId } : {},
4054
+ revision: 1,
4055
+ createdAt: now,
4056
+ updatedAt: now
4057
+ }),
4058
+ ...included ? [membershipUpdate("networkEntitlement", entitlementId, {
4059
+ id: entitlementId,
4060
+ ...membershipScope(target),
4061
+ membershipId: seat.membershipId,
4062
+ seatId: seat.seatId,
4063
+ kind: "named_seat",
4064
+ status: "pending_acceptance",
4065
+ termStartedAt: paidAt,
4066
+ termEndsAt: endsAt,
4067
+ offeringSnapshot: input.offering,
4068
+ sourceEventId: input.eventId,
4069
+ revision: 1,
4070
+ createdAt: now,
4071
+ updatedAt: now
4072
+ })] : [],
4073
+ membershipUpdate("membershipEffect", effectId, {
4074
+ id: effectId,
4075
+ chapterId: target.chapterId,
4076
+ environment: target.environment,
4077
+ runtime: target.runtime,
4078
+ membershipId: seat.membershipId,
4079
+ seatId: seat.seatId,
4080
+ kind: included ? "invite_named_recipient" : "attach_seat_renewal",
4081
+ status: "pending",
4082
+ attempts: 0,
4083
+ createdAt: now,
4084
+ updatedAt: now
4085
+ })
4086
+ ];
4087
+ }
4088
+
3954
4089
  // src/membership-payment-authority.ts
3955
4090
  async function networkMembershipId(input) {
3956
4091
  return (await membershipCheckoutBindingId(input)).replace(/^checkout_/, "membership_");
@@ -3966,11 +4101,14 @@ async function confirmMembershipPayment(db, input, now) {
3966
4101
  { ...input, offerId: String(binding.offerId), now, lifetimeMs: 6e4 }
3967
4102
  );
3968
4103
  if (binding.offerDigest !== offer.digest) membershipFail("membership_checkout_conflict");
4104
+ const { namedSeat } = await verifyMembershipCheckoutBinding(binding, binding);
4105
+ if (namedSeat && namedSeat.amountCents !== offer.contract.offering.seatPriceCents) membershipFail("membership_checkout_conflict");
4106
+ const totalCents = offer.contract.dueTodayCents + (namedSeat?.amountCents ?? 0);
3969
4107
  const eventRow = await membershipLoad(db, "networkCommercialEvents", `network-commercial-event:${input.eventId}`);
3970
4108
  if (!eventRow) membershipFail("membership_payment_evidence_pending");
3971
4109
  const evidenceGuard = await commercialEvidenceGuard(db, input.eventId, eventRow.digest);
3972
4110
  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({
4111
+ 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
4112
  appId: input.appId,
3975
4113
  chapterId: input.chapterId,
3976
4114
  environment: input.environment,
@@ -3982,14 +4120,14 @@ async function confirmMembershipPayment(db, input, now) {
3982
4120
  const paidAt = fact.invoicePaidAt;
3983
4121
  const endsAt = offer.contract.initialTermEndsAt ?? fact.invoicePeriodEndsAt;
3984
4122
  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);
4123
+ const id11 = await networkMembershipId(input), entitlementId = `${id11}:primary`;
4124
+ let member = await membershipLoad(db, "networkMembership", id11);
3987
4125
  if (!member || member.status === "restarting") {
3988
4126
  const offering = offer.contract.offering;
3989
4127
  const restart = member ? await membershipRestartPayment(db, input, member, now) : null;
3990
4128
  const attrs = {
3991
- id: id10,
3992
- membershipId: id10,
4129
+ id: id11,
4130
+ membershipId: id11,
3993
4131
  ...membershipScope(input),
3994
4132
  baseSkuId: input.skuId,
3995
4133
  status: "paid_pending_approval",
@@ -4005,8 +4143,9 @@ async function confirmMembershipPayment(db, input, now) {
4005
4143
  founding: offer.contract.founding,
4006
4144
  cohort: offer.contract.founding ? offer.contract.discountPolicy.cohortLabel : "standard",
4007
4145
  baseAmountCents: offering.basePriceCents,
4008
- amountCents: offer.contract.dueTodayCents,
4146
+ amountCents: totalCents,
4009
4147
  renewalAmountCents: offer.contract.renewalCents,
4148
+ ...namedSeat ? { namedSeatId: namedSeat.seatId } : {},
4010
4149
  currency: offering.currency,
4011
4150
  interval: offering.interval,
4012
4151
  initialTermStartedAt: paidAt,
@@ -4023,11 +4162,11 @@ async function confirmMembershipPayment(db, input, now) {
4023
4162
  };
4024
4163
  try {
4025
4164
  await db.transact([
4026
- membershipUpdate("networkMembership", id10, attrs),
4165
+ membershipUpdate("networkMembership", id11, attrs),
4027
4166
  membershipUpdate("networkEntitlement", entitlementId, {
4028
4167
  id: entitlementId,
4029
4168
  ...membershipScope(input),
4030
- membershipId: id10,
4169
+ membershipId: id11,
4031
4170
  kind: "primary",
4032
4171
  status: restart?.attrs.status === "active" ? "active" : "pending_approval",
4033
4172
  termStartedAt: paidAt,
@@ -4039,23 +4178,45 @@ async function confirmMembershipPayment(db, input, now) {
4039
4178
  updatedAt: now,
4040
4179
  ...restart?.entitlementAttrs
4041
4180
  }),
4042
- ...restart?.ops ?? []
4043
- ], { mutationId: `membership-paid:${id10}${restart ? `:${restart.restartId}` : ""}`, guards: [
4044
- ...restart?.guards ?? [{ ns: "networkMembership", id: id10, exists: false }],
4181
+ ...restart?.ops ?? [],
4182
+ ...namedSeat ? checkoutSeatPaymentOperations(input, namedSeat, {
4183
+ offering,
4184
+ primaryPriceId: String(binding.priceId),
4185
+ invoiceId: fact.invoiceId,
4186
+ invoiceTotalCents: totalCents,
4187
+ subscriptionId: input.providerSubscriptionId,
4188
+ customerId: input.providerCustomerId,
4189
+ eventId: input.eventId,
4190
+ paidAt: Number(paidAt),
4191
+ endsAt: Number(endsAt),
4192
+ boundAt: Number(binding.boundAt),
4193
+ now
4194
+ }) : []
4195
+ ], { mutationId: `membership-paid:${id11}${restart ? `:${restart.restartId}` : ""}`, guards: [
4196
+ ...restart?.guards ?? [{ ns: "networkMembership", id: id11, exists: false }],
4197
+ ...namedSeat ? [
4198
+ { ns: "networkSeat", id: namedSeat.seatId, exists: false },
4199
+ {
4200
+ ns: "networkSeatRecipient",
4201
+ id: namedSeat.recipientClaimId,
4202
+ exists: true,
4203
+ equals: { seatId: namedSeat.seatId, recipientEmailNormalized: namedSeat.recipientEmail }
4204
+ }
4205
+ ] : [],
4045
4206
  { 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 } },
4207
+ { ns: "membershipOfferCheckout", id: String(binding.id), exists: true, equals: { offerDigest: offer.digest, quoteDigest: binding.quoteDigest } },
4047
4208
  { ns: "networkCommercialEvents", id: String(eventRow.id), exists: true, equals: { digest: eventRow.digest } },
4048
4209
  evidenceGuard
4049
4210
  ] });
4050
4211
  } catch (error) {
4051
4212
  if (error.code !== "transact_guard_failed") throw error;
4052
4213
  }
4053
- member = await membershipLoad(db, "networkMembership", id10);
4214
+ member = await membershipLoad(db, "networkMembership", id11);
4054
4215
  }
4055
4216
  requireMembershipScope(member, input);
4056
4217
  if (member.purchaseOfferDigest !== offer.digest || member.providerInitialInvoiceId !== fact.invoiceId || member.providerSubscriptionId !== fact.subscriptionId || member.providerCustomerId !== fact.customerId) membershipFail("membership_purchase_conflict");
4057
4218
  return {
4058
- membershipId: id10,
4219
+ membershipId: id11,
4059
4220
  status: String(member.status),
4060
4221
  cohort: String(member.cohort),
4061
4222
  initialTermStartedAt: Number(member.initialTermStartedAt),
@@ -4067,9 +4228,9 @@ async function confirmMembershipPayment(db, input, now) {
4067
4228
  // src/membership-cancel-authority.ts
4068
4229
  async function cancelMembershipAtTermEnd(db, target, now, stripeSecret) {
4069
4230
  membershipClock(now);
4070
- const id10 = await networkMembershipId(target);
4231
+ const id11 = await networkMembershipId(target);
4071
4232
  const state = await loadMembershipAuthorityState(db, target);
4072
- const member = await membershipLoad(db, "networkMembership", id10);
4233
+ const member = await membershipLoad(db, "networkMembership", id11);
4073
4234
  requireMembershipScope(member, target);
4074
4235
  if (!["active", "cancel_at_term_end", "payment_grace", "paid_pending_approval", "lapsed"].includes(String(member.status))) {
4075
4236
  membershipFail("membership_cancellation_unavailable");
@@ -4087,7 +4248,7 @@ async function cancelMembershipAtTermEnd(db, target, now, stripeSecret) {
4087
4248
  "POST",
4088
4249
  path,
4089
4250
  { cancel_at_period_end: true, proration_behavior: "none" },
4090
- { idempotencyKey: `membership-cancel-at-term:${id10}:${member.providerSubscriptionId}` }
4251
+ { idempotencyKey: `membership-cancel-at-term:${id11}:${member.providerSubscriptionId}` }
4091
4252
  );
4092
4253
  if (!write.ok || write.body.id !== member.providerSubscriptionId || write.body.cancel_at_period_end !== true) {
4093
4254
  membershipFail("membership_cancellation_pending");
@@ -4095,7 +4256,7 @@ async function cancelMembershipAtTermEnd(db, target, now, stripeSecret) {
4095
4256
  }
4096
4257
  const status2 = Number(member.termEndsAt) <= now ? "lapsed" : member.journeyApprovedAt ? "cancel_at_term_end" : "paid_pending_approval";
4097
4258
  if (member.status !== status2 || member.autoRenew !== false || Number(member.graceEndsAt ?? 0) !== 0) {
4098
- await db.transact([membershipUpdate("networkMembership", id10, {
4259
+ await db.transact([membershipUpdate("networkMembership", id11, {
4099
4260
  status: status2,
4100
4261
  autoRenew: false,
4101
4262
  graceEndsAt: 0,
@@ -4104,12 +4265,12 @@ async function cancelMembershipAtTermEnd(db, target, now, stripeSecret) {
4104
4265
  updatedAt: now
4105
4266
  })], {
4106
4267
  guards: [
4107
- { ns: "networkMembership", id: id10, exists: true, equals: { revision: member.revision } },
4268
+ { ns: "networkMembership", id: id11, exists: true, equals: { revision: member.revision } },
4108
4269
  { ns: "membershipAuthorityState", id: String(state.id), exists: true, equals: { revision: state.revision, mode: "authoritative" } }
4109
4270
  ]
4110
4271
  });
4111
4272
  }
4112
- return { membershipId: id10, status: status2, termEndsAt: Number(member.termEndsAt), graceEndsAt: 0, autoRenew: false };
4273
+ return { membershipId: id11, status: status2, termEndsAt: Number(member.termEndsAt), graceEndsAt: 0, autoRenew: false };
4113
4274
  }
4114
4275
 
4115
4276
  // src/membership-authority-handler.ts
@@ -4148,17 +4309,17 @@ async function namedSeatPrimaryActivation(db, member, now) {
4148
4309
  // src/membership-approval-authority.ts
4149
4310
  async function approveNetworkMembership(db, input, now) {
4150
4311
  membershipClock(now);
4151
- const id10 = await networkMembershipId(input), entitlementId = `${id10}:primary`;
4312
+ const id11 = await networkMembershipId(input), entitlementId = `${id11}:primary`;
4152
4313
  for (let attempt = 0; attempt < 8; attempt++) {
4153
4314
  const state = await loadMembershipAuthorityState(db, input);
4154
- const member = await membershipLoad(db, "networkMembership", id10);
4315
+ const member = await membershipLoad(db, "networkMembership", id11);
4155
4316
  requireMembershipScope(member, input);
4156
4317
  const entitlement = await membershipLoad(db, "networkEntitlement", entitlementId);
4157
4318
  requireMembershipScope(entitlement, input);
4158
- if (entitlement.membershipId !== id10 || entitlement.kind !== "primary") membershipFail("membership_entitlement_conflict");
4319
+ if (entitlement.membershipId !== id11 || entitlement.kind !== "primary") membershipFail("membership_entitlement_conflict");
4159
4320
  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
4321
  return {
4161
- membershipId: id10,
4322
+ membershipId: id11,
4162
4323
  status: String(member.status),
4163
4324
  cohort: String(member.cohort),
4164
4325
  memberNumber: Number(member.memberNumber),
@@ -4178,7 +4339,7 @@ async function approveNetworkMembership(db, input, now) {
4178
4339
  const seatActivation = await namedSeatPrimaryActivation(db, member, now);
4179
4340
  try {
4180
4341
  await db.transact([
4181
- membershipUpdate("networkMembership", id10, {
4342
+ membershipUpdate("networkMembership", id11, {
4182
4343
  status: member.autoRenew ? "active" : "cancel_at_term_end",
4183
4344
  memberNumber,
4184
4345
  journeyApprovedAt: now,
@@ -4193,8 +4354,8 @@ async function approveNetworkMembership(db, input, now) {
4193
4354
  updatedAt: now
4194
4355
  }),
4195
4356
  ...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"] },
4357
+ ], { mutationId: `membership-approve:${id11}`, guards: [
4358
+ { ns: "networkMembership", id: id11, exists: true, equals: { revision: member.revision, status: "paid_pending_approval" }, absent: ["memberNumber"] },
4198
4359
  { ns: "networkEntitlement", id: entitlementId, exists: true, equals: { revision: entitlement.revision, status: "pending_approval" } },
4199
4360
  { ns: "membershipAuthorityState", id: String(state.id), exists: true, equals: { revision: state.revision, mode: "authoritative" } },
4200
4361
  ...seatActivation.guards
@@ -4293,41 +4454,41 @@ async function refundSubscriptionPayment(secretKey, input) {
4293
4454
  // src/membership-refund-authority.ts
4294
4455
  async function refundNetworkMembership(db, input, secretKey, now) {
4295
4456
  membershipClock(now);
4296
- const id10 = await networkMembershipId(input), entitlementId = `${id10}:primary`;
4297
- let state = await loadMembershipAuthorityState(db, input), member = await membershipLoad(db, "networkMembership", id10);
4457
+ const id11 = await networkMembershipId(input), entitlementId = `${id11}:primary`;
4458
+ let state = await loadMembershipAuthorityState(db, input), member = await membershipLoad(db, "networkMembership", id11);
4298
4459
  requireMembershipScope(member, input);
4299
- if (member.status === "refunded") return { membershipId: id10, status: "refunded", refundedCents: Number(member.refundedCents), subscriptionCanceled: true };
4460
+ if (member.status === "refunded") return { membershipId: id11, status: "refunded", refundedCents: Number(member.refundedCents), subscriptionCanceled: true };
4300
4461
  if (!["paid_pending_approval", "active", "cancel_at_term_end", "payment_grace", "lapsed", "refund_pending"].includes(String(member.status))) membershipFail("membership_refund_not_eligible");
4301
4462
  if (member.status !== "refund_pending") {
4302
4463
  const entitlement = await membershipLoad(db, "networkEntitlement", entitlementId);
4303
4464
  requireMembershipScope(entitlement, input);
4304
4465
  await db.transact([
4305
- membershipUpdate("networkMembership", id10, {
4466
+ membershipUpdate("networkMembership", id11, {
4306
4467
  status: "refund_pending",
4307
4468
  refundRequestedAt: now,
4308
4469
  revision: Number(member.revision) + 1,
4309
4470
  updatedAt: now
4310
4471
  }),
4311
4472
  membershipUpdate("networkEntitlement", entitlementId, { status: "closed", revision: Number(entitlement.revision) + 1, updatedAt: now }),
4312
- membershipUpdate("membershipEffect", `${id10}:refund`, {
4313
- id: `${id10}:refund`,
4473
+ membershipUpdate("membershipEffect", `${id11}:refund`, {
4474
+ id: `${id11}:refund`,
4314
4475
  chapterId: input.chapterId,
4315
4476
  environment: input.environment,
4316
4477
  runtime: input.runtime,
4317
- membershipId: id10,
4478
+ membershipId: id11,
4318
4479
  kind: "refund_membership",
4319
4480
  status: "pending",
4320
4481
  attempts: 0,
4321
4482
  createdAt: now,
4322
4483
  updatedAt: now
4323
4484
  })
4324
- ], { mutationId: `membership-refund-request:${id10}`, guards: [
4325
- { ns: "networkMembership", id: id10, exists: true, equals: { revision: member.revision } },
4485
+ ], { mutationId: `membership-refund-request:${id11}`, guards: [
4486
+ { ns: "networkMembership", id: id11, exists: true, equals: { revision: member.revision } },
4326
4487
  { ns: "networkEntitlement", id: entitlementId, exists: true, equals: { revision: entitlement.revision } },
4327
4488
  { ns: "membershipAuthorityState", id: String(state.id), exists: true, equals: { revision: state.revision, mode: "authoritative" } }
4328
4489
  ] });
4329
4490
  }
4330
- member = await membershipLoad(db, "networkMembership", id10);
4491
+ member = await membershipLoad(db, "networkMembership", id11);
4331
4492
  requireMembershipScope(member, input);
4332
4493
  if (member.status !== "refund_pending") membershipFail("membership_refund_retry_required");
4333
4494
  const result = await refundSubscriptionPayment(secretKey, {
@@ -4341,23 +4502,23 @@ async function refundNetworkMembership(db, input, secretKey, now) {
4341
4502
  if (!result.subscriptionCanceled || result.refundedCents !== member.amountCents) membershipFail("membership_refund_unconfirmed");
4342
4503
  for (let attempt = 0; attempt < 8; attempt++) {
4343
4504
  state = await loadMembershipAuthorityState(db, input);
4344
- member = await membershipLoad(db, "networkMembership", id10);
4505
+ member = await membershipLoad(db, "networkMembership", id11);
4345
4506
  requireMembershipScope(member, input);
4346
- if (member.status === "refunded") return { membershipId: id10, status: "refunded", refundedCents: Number(member.refundedCents), subscriptionCanceled: true };
4507
+ if (member.status === "refunded") return { membershipId: id11, status: "refunded", refundedCents: Number(member.refundedCents), subscriptionCanceled: true };
4347
4508
  if (member.status !== "refund_pending") membershipFail("membership_refund_retry_required");
4348
4509
  const counted = member.founding === true && member.memberNumber !== void 0;
4349
4510
  const counts = state.cohortCounts;
4350
4511
  if (!counts || counted && (!Number.isSafeInteger(counts[String(member.cohort)]) || counts[String(member.cohort)] < 1)) membershipFail("invalid_cohort_count");
4351
4512
  try {
4352
4513
  await db.transact([
4353
- membershipUpdate("networkMembership", id10, {
4514
+ membershipUpdate("networkMembership", id11, {
4354
4515
  status: "refunded",
4355
4516
  refundedCents: result.refundedCents,
4356
4517
  autoRenew: false,
4357
4518
  revision: Number(member.revision) + 1,
4358
4519
  updatedAt: now
4359
4520
  }),
4360
- membershipUpdate("membershipEffect", `${id10}:refund`, { status: "complete", updatedAt: now }),
4521
+ membershipUpdate("membershipEffect", `${id11}:refund`, { status: "complete", updatedAt: now }),
4361
4522
  ...counted ? [membershipUpdate("membershipAuthorityState", String(state.id), {
4362
4523
  cohortCounts: { ...counts, [String(member.cohort)]: counts[String(member.cohort)] - 1 },
4363
4524
  revision: Number(state.revision) + 1,
@@ -4368,7 +4529,7 @@ async function refundNetworkMembership(db, input, secretKey, now) {
4368
4529
  chapterId: input.chapterId,
4369
4530
  environment: input.environment,
4370
4531
  runtime: input.runtime,
4371
- membershipId: id10,
4532
+ membershipId: id11,
4372
4533
  seatId: member.namedSeatId,
4373
4534
  kind: "refund_seat_with_primary",
4374
4535
  status: "pending",
@@ -4376,8 +4537,8 @@ async function refundNetworkMembership(db, input, secretKey, now) {
4376
4537
  createdAt: now,
4377
4538
  updatedAt: now
4378
4539
  })] : []
4379
- ], { mutationId: `membership-refunded:${id10}`, guards: [
4380
- { ns: "networkMembership", id: id10, exists: true, equals: { status: "refund_pending", revision: member.revision } },
4540
+ ], { mutationId: `membership-refunded:${id11}`, guards: [
4541
+ { ns: "networkMembership", id: id11, exists: true, equals: { status: "refund_pending", revision: member.revision } },
4381
4542
  { ns: "membershipAuthorityState", id: String(state.id), exists: true, equals: { mode: "authoritative", revision: state.revision } }
4382
4543
  ] });
4383
4544
  } catch (error) {
@@ -4405,8 +4566,8 @@ async function reserveNamedMembershipSeat(db, input, now) {
4405
4566
  const memberId = await networkMembershipId(input), member = await membershipLoad(db, "networkMembership", memberId);
4406
4567
  requireMembershipScope(member, input);
4407
4568
  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);
4569
+ const id11 = (await membershipOfferIdForRequest({ ...input, requestId: "named_seat" })).replace(/^offer_/, "seat_");
4570
+ let seat = await membershipLoad(db, "networkSeat", id11);
4410
4571
  if (!seat) {
4411
4572
  const offering = membershipOfferingVersion(member.offeringSnapshot), claimId = await recipientKey(input, email);
4412
4573
  const purchaseEndsAt = seatPurchaseDeadline(offering, member);
@@ -4414,7 +4575,7 @@ async function reserveNamedMembershipSeat(db, input, now) {
4414
4575
  membershipFail("named_seat_purchase_unavailable");
4415
4576
  }
4416
4577
  const attrs = {
4417
- id: id10,
4578
+ id: id11,
4418
4579
  ...membershipScope(input),
4419
4580
  primaryMembershipId: memberId,
4420
4581
  recipientName: name,
@@ -4439,20 +4600,20 @@ async function reserveNamedMembershipSeat(db, input, now) {
4439
4600
  };
4440
4601
  try {
4441
4602
  await db.transact([
4442
- membershipUpdate("networkSeat", id10, attrs),
4603
+ membershipUpdate("networkSeat", id11, attrs),
4443
4604
  membershipUpdate("networkSeatRecipient", claimId, {
4444
4605
  id: claimId,
4445
4606
  appId: input.appId,
4446
4607
  chapterId: input.chapterId,
4447
4608
  environment: input.environment,
4448
4609
  runtime: input.runtime,
4449
- seatId: id10,
4610
+ seatId: id11,
4450
4611
  recipientEmailNormalized: email,
4451
4612
  createdAt: now
4452
4613
  }),
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 },
4614
+ membershipUpdate("networkMembership", memberId, { namedSeatId: id11, revision: Number(member.revision) + 1, updatedAt: now })
4615
+ ], { mutationId: `named-seat-reserve:${id11}`, guards: [
4616
+ { ns: "networkSeat", id: id11, exists: false },
4456
4617
  { ns: "networkSeatRecipient", id: claimId, exists: false },
4457
4618
  { ns: "networkMembership", id: memberId, exists: true, equals: { revision: member.revision }, absent: ["namedSeatId"] },
4458
4619
  { ns: "membershipAuthorityState", id: String(state.id), exists: true, equals: { mode: "authoritative", revision: state.revision } }
@@ -4460,7 +4621,7 @@ async function reserveNamedMembershipSeat(db, input, now) {
4460
4621
  } catch (error) {
4461
4622
  if (error.code !== "transact_guard_failed") throw error;
4462
4623
  }
4463
- seat = await membershipLoad(db, "networkSeat", id10);
4624
+ seat = await membershipLoad(db, "networkSeat", id11);
4464
4625
  if (!seat && await membershipLoad(db, "networkSeatRecipient", claimId)) membershipFail("named_seat_recipient_unavailable");
4465
4626
  }
4466
4627
  requireMembershipScope(seat, input);
@@ -4470,7 +4631,7 @@ async function reserveNamedMembershipSeat(db, input, now) {
4470
4631
 
4471
4632
  // src/membership-seat-included.ts
4472
4633
  var namedSeatIncluded = (seat) => Number(seat.amountCents) === 0;
4473
- var namedSeatSettled = (seat) => namedSeatIncluded(seat) ? seat.purchasedAt !== void 0 : Boolean(seat.providerPaymentIntentId && seat.providerSubscriptionItemId);
4634
+ var namedSeatSettled = (seat) => namedSeatIncluded(seat) ? seat.purchasedAt !== void 0 : Boolean((seat.providerPaymentIntentId || seat.combinedInvoice) && seat.providerSubscriptionItemId);
4474
4635
  function namedSeatRecipientState(seat) {
4475
4636
  switch (String(seat.status)) {
4476
4637
  case "renewal_pending":
@@ -4568,7 +4729,7 @@ async function grantIncludedNamedSeat(db, input, now) {
4568
4729
  // src/membership-renewal-authority.ts
4569
4730
  async function reconcileMembershipRenewal(db, input, now) {
4570
4731
  membershipClock(now);
4571
- const id10 = await networkMembershipId(input), eventRow = await membershipLoad(db, "networkCommercialEvents", `network-commercial-event:${input.eventId}`);
4732
+ const id11 = await networkMembershipId(input), eventRow = await membershipLoad(db, "networkCommercialEvents", `network-commercial-event:${input.eventId}`);
4572
4733
  const evidenceGuard = await commercialEvidenceGuard(db, input.eventId, eventRow?.digest, "membership_renewal_evidence_pending");
4573
4734
  const fact = eventRow?.fact;
4574
4735
  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 +4742,10 @@ async function reconcileMembershipRenewal(db, input, now) {
4581
4742
  return membershipFail("membership_renewal_evidence_pending");
4582
4743
  }
4583
4744
  for (let attempt = 0; attempt < 8; attempt++) {
4584
- const state = await loadMembershipAuthorityState(db, input), member = await membershipLoad(db, "networkMembership", id10);
4745
+ const state = await loadMembershipAuthorityState(db, input), member = await membershipLoad(db, "networkMembership", id11);
4585
4746
  requireMembershipScope(member, input);
4586
4747
  const receipt = () => ({
4587
- membershipId: id10,
4748
+ membershipId: id11,
4588
4749
  status: String(member.status),
4589
4750
  termEndsAt: Number(member.termEndsAt),
4590
4751
  graceEndsAt: Number(member.graceEndsAt ?? 0),
@@ -4609,13 +4770,13 @@ async function reconcileMembershipRenewal(db, input, now) {
4609
4770
  const paid = fact.eventType === "invoice.payment_succeeded";
4610
4771
  if (!paid && (fact.status !== "open" || !Number.isSafeInteger(fact.amountPaidCents) || Number(fact.amountPaidCents) < 0 || Number(fact.amountPaidCents) >= amount)) membershipFail("membership_renewal_failure_mismatch");
4611
4772
  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`);
4773
+ const entitlement = await membershipLoad(db, "networkEntitlement", `${id11}:primary`);
4613
4774
  requireMembershipScope(entitlement, input);
4614
4775
  const graceEndsAt = paid ? 0 : start + 14 * 864e5;
4615
4776
  const status2 = paid ? member.journeyApprovedAt ? member.autoRenew ? "active" : "cancel_at_term_end" : "paid_pending_approval" : now < graceEndsAt ? "payment_grace" : "lapsed";
4616
4777
  const entitlementStatus = member.journeyApprovedAt && (paid || now < graceEndsAt) ? "active" : member.journeyApprovedAt ? "closed" : "pending_approval";
4617
4778
  const ops = [
4618
- membershipUpdate("networkMembership", id10, {
4779
+ membershipUpdate("networkMembership", id11, {
4619
4780
  status: status2,
4620
4781
  graceEndsAt,
4621
4782
  ...paid ? {
@@ -4626,7 +4787,7 @@ async function reconcileMembershipRenewal(db, input, now) {
4626
4787
  revision: Number(member.revision) + 1,
4627
4788
  updatedAt: now
4628
4789
  }),
4629
- membershipUpdate("networkEntitlement", `${id10}:primary`, {
4790
+ membershipUpdate("networkEntitlement", `${id11}:primary`, {
4630
4791
  status: entitlementStatus,
4631
4792
  ...paid ? { termStartedAt: start, termEndsAt: end } : {},
4632
4793
  sourceEventId: input.eventId,
@@ -4635,8 +4796,8 @@ async function reconcileMembershipRenewal(db, input, now) {
4635
4796
  })
4636
4797
  ];
4637
4798
  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 } },
4799
+ { ns: "networkMembership", id: id11, exists: true, equals: { revision: member.revision } },
4800
+ { ns: "networkEntitlement", id: `${id11}:primary`, exists: true, equals: { revision: entitlement.revision } },
4640
4801
  { ns: "membershipAuthorityState", id: String(state.id), exists: true, equals: { revision: state.revision, mode: "authoritative" } },
4641
4802
  { ns: "networkCommercialEvents", id: String(eventRow.id), exists: true, equals: { digest: eventRow.digest } },
4642
4803
  evidenceGuard
@@ -4661,15 +4822,15 @@ async function reconcileMembershipRenewal(db, input, now) {
4661
4822
  );
4662
4823
  }
4663
4824
  try {
4664
- await db.transact(ops, { mutationId: `membership-renewal:${id10}:${input.eventId}`, guards });
4825
+ await db.transact(ops, { mutationId: `membership-renewal:${id11}:${input.eventId}`, guards });
4665
4826
  } catch (error) {
4666
4827
  if (error.code !== "transact_guard_failed") throw error;
4667
4828
  continue;
4668
4829
  }
4669
- const saved = await membershipLoad(db, "networkMembership", id10);
4830
+ const saved = await membershipLoad(db, "networkMembership", id11);
4670
4831
  requireMembershipScope(saved, input);
4671
4832
  return {
4672
- membershipId: id10,
4833
+ membershipId: id11,
4673
4834
  status: String(saved.status),
4674
4835
  termEndsAt: Number(saved.termEndsAt),
4675
4836
  graceEndsAt: Number(saved.graceEndsAt ?? 0),
@@ -4695,12 +4856,12 @@ async function reconcileMembershipBilling(db, input, now, stripeSecretName = "st
4695
4856
  }).some(([key3, value]) => fact.identity[key3] !== value)) membershipFail("membership_billing_evidence_pending");
4696
4857
  const sk = await getVaultSecret(db, stripeSecretName);
4697
4858
  if (!sk) membershipFail("membership_billing_provider_unavailable");
4698
- const id10 = await networkMembershipId(input);
4859
+ const id11 = await networkMembershipId(input);
4699
4860
  for (let attempt = 0; attempt < 8; attempt++) {
4700
- const state = await loadMembershipAuthorityState(db, input), member = await membershipLoad(db, "networkMembership", id10);
4861
+ const state = await loadMembershipAuthorityState(db, input), member = await membershipLoad(db, "networkMembership", id11);
4701
4862
  requireMembershipScope(member, input);
4702
4863
  const receipt = (value = member) => ({
4703
- membershipId: id10,
4864
+ membershipId: id11,
4704
4865
  status: String(value.status),
4705
4866
  termEndsAt: Number(value.termEndsAt),
4706
4867
  graceEndsAt: Number(value.graceEndsAt ?? 0),
@@ -4721,7 +4882,7 @@ async function reconcileMembershipBilling(db, input, now, stripeSecretName = "st
4721
4882
  });
4722
4883
  await commercialEvidenceGuard(db, input.eventId, row2?.digest, "membership_billing_evidence_pending");
4723
4884
  await refundNetworkMembership(db, input, sk, now);
4724
- const saved2 = await membershipLoad(db, "networkMembership", id10);
4885
+ const saved2 = await membershipLoad(db, "networkMembership", id11);
4725
4886
  requireMembershipScope(saved2, input);
4726
4887
  return receipt(saved2);
4727
4888
  }
@@ -4736,18 +4897,18 @@ async function reconcileMembershipBilling(db, input, now, stripeSecretName = "st
4736
4897
  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
4898
  const graceEndsAt = autoRenew ? Number(member.graceEndsAt ?? 0) : 0;
4738
4899
  if (member.autoRenew === autoRenew && member.status === status2 && Number(member.graceEndsAt ?? 0) === graceEndsAt) return receipt();
4739
- const entitlement = await membershipLoad(db, "networkEntitlement", `${id10}:primary`);
4900
+ const entitlement = await membershipLoad(db, "networkEntitlement", `${id11}:primary`);
4740
4901
  requireMembershipScope(entitlement, input);
4741
4902
  try {
4742
4903
  await db.transact([
4743
- membershipUpdate("networkMembership", id10, { status: status2, autoRenew, graceEndsAt, revision: Number(member.revision) + 1, updatedAt: now }),
4904
+ membershipUpdate("networkMembership", id11, { status: status2, autoRenew, graceEndsAt, revision: Number(member.revision) + 1, updatedAt: now }),
4744
4905
  membershipUpdate("networkEntitlement", String(entitlement.id), {
4745
4906
  status: status2 === "lapsed" ? "closed" : entitlement.status,
4746
4907
  revision: Number(entitlement.revision) + 1,
4747
4908
  updatedAt: now
4748
4909
  })
4749
4910
  ], { guards: [
4750
- { ns: "networkMembership", id: id10, exists: true, equals: { revision: member.revision } },
4911
+ { ns: "networkMembership", id: id11, exists: true, equals: { revision: member.revision } },
4751
4912
  { ns: "networkEntitlement", id: String(entitlement.id), exists: true, equals: { revision: entitlement.revision } },
4752
4913
  { ns: "membershipAuthorityState", id: String(state.id), exists: true, equals: { mode: "authoritative", revision: state.revision } },
4753
4914
  { ns: "networkCommercialEvents", id: String(row2.id), exists: true, equals: { digest: fact.digest } },
@@ -4757,7 +4918,7 @@ async function reconcileMembershipBilling(db, input, now, stripeSecretName = "st
4757
4918
  if (error.code !== "transact_guard_failed") throw error;
4758
4919
  continue;
4759
4920
  }
4760
- const saved = await membershipLoad(db, "networkMembership", id10);
4921
+ const saved = await membershipLoad(db, "networkMembership", id11);
4761
4922
  requireMembershipScope(saved, input);
4762
4923
  return receipt(saved);
4763
4924
  }
@@ -4818,19 +4979,19 @@ async function stopPreviousSubscription(member, target, secret) {
4818
4979
  async function prepareMembershipRestart(db, input, now, stripeSecret) {
4819
4980
  membershipClock(now);
4820
4981
  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]))}`;
4982
+ const id11 = await networkMembershipId(input), bindingId = await membershipCheckoutBindingId(input);
4983
+ const restartId = `restart_${await stripePayloadDigest(JSON.stringify([id11, input.previousSubscriptionId]))}`;
4823
4984
  const state = await loadMembershipAuthorityState(db, input);
4824
- const previous = await membershipLoad(db, "networkMembership", id10);
4985
+ const previous = await membershipLoad(db, "networkMembership", id11);
4825
4986
  requireMembershipScope(previous, input);
4826
4987
  const receipt = {
4827
4988
  applicationId: input.applicationId,
4828
- membershipId: id10,
4989
+ membershipId: id11,
4829
4990
  restartId,
4830
4991
  memberNumber: Number(previous.memberNumber),
4831
4992
  status: "ready"
4832
4993
  };
4833
- const existing = await membershipLoad(db, "membershipRestart", id10);
4994
+ const existing = await membershipLoad(db, "membershipRestart", id11);
4834
4995
  if (existing?.restartId === restartId && existing.status === "ready") {
4835
4996
  requireMembershipScope(existing, input);
4836
4997
  return receipt;
@@ -4846,33 +5007,33 @@ async function prepareMembershipRestart(db, input, now, stripeSecret) {
4846
5007
  membershipUpdate("membershipPurchaseHistory", restartId, {
4847
5008
  id: restartId,
4848
5009
  ...membershipScope(input),
4849
- membershipId: id10,
5010
+ membershipId: id11,
4850
5011
  providerSubscriptionId: input.previousSubscriptionId,
4851
5012
  memberSnapshot: previous,
4852
5013
  bindingSnapshot: binding2,
4853
5014
  archivedAt: now
4854
5015
  }),
4855
- membershipUpdate("networkMembership", id10, { status: "restarting", restartId, revision: Number(previous.revision) + 1, updatedAt: now })
5016
+ membershipUpdate("networkMembership", id11, { status: "restarting", restartId, revision: Number(previous.revision) + 1, updatedAt: now })
4856
5017
  ], { mutationId: `membership-restart-reserve:${restartId}`, guards: [
4857
5018
  ...seats.guards,
4858
- { ns: "networkMembership", id: id10, exists: true, equals: { revision: previous.revision, status: previous.status } },
5019
+ { ns: "networkMembership", id: id11, exists: true, equals: { revision: previous.revision, status: previous.status } },
4859
5020
  { ns: "membershipPurchaseHistory", id: restartId, exists: false },
4860
5021
  { ns: "membershipOfferCheckout", id: bindingId, exists: true, equals: { offerDigest: binding2.offerDigest } },
4861
5022
  { ns: "membershipAuthorityState", id: String(state.id), exists: true, equals: { mode: "authoritative", revision: state.revision } }
4862
5023
  ] });
4863
5024
  } else if (previous.restartId !== restartId) membershipFail("membership_restart_conflict");
4864
- const reserved = await membershipLoad(db, "networkMembership", id10);
5025
+ const reserved = await membershipLoad(db, "networkMembership", id11);
4865
5026
  requireMembershipScope(reserved, input);
4866
5027
  if (reserved.status !== "restarting" || reserved.restartId !== restartId) membershipFail("membership_restart_conflict");
4867
5028
  await stopPreviousSubscription(reserved, input, stripeSecret);
4868
5029
  const binding = await membershipLoad(db, "membershipOfferCheckout", bindingId);
4869
5030
  requireMembershipScope(binding, input);
4870
5031
  await db.transact([
4871
- membershipUpdate("membershipRestart", id10, { id: id10, ...membershipScope(input), ...receipt, previousSubscriptionId: input.previousSubscriptionId, createdAt: now }),
5032
+ membershipUpdate("membershipRestart", id11, { id: id11, ...membershipScope(input), ...receipt, previousSubscriptionId: input.previousSubscriptionId, createdAt: now }),
4872
5033
  { t: "delete", ns: "membershipOfferCheckout", id: bindingId }
4873
5034
  ], { 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 } },
5035
+ { ns: "membershipRestart", id: id11, exists: Boolean(existing), ...existing ? { equals: { restartId: existing.restartId, status: existing.status } } : {} },
5036
+ { ns: "networkMembership", id: id11, exists: true, equals: { revision: reserved.revision, status: "restarting", restartId } },
4876
5037
  { ns: "membershipOfferCheckout", id: bindingId, exists: true, equals: { offerDigest: binding.offerDigest } }
4877
5038
  ] });
4878
5039
  return receipt;
@@ -4882,15 +5043,15 @@ async function prepareMembershipRestart(db, input, now, stripeSecret) {
4882
5043
  async function readMembershipAccess(db, input, now) {
4883
5044
  membershipClock(now);
4884
5045
  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 };
5046
+ const id11 = await networkMembershipId(input), member = await membershipLoad(db, "networkMembership", id11);
5047
+ if (!member) return { membershipId: id11, allowed: false, status: "unpaid", accessEndsAt: 0, revision: 0 };
4887
5048
  requireMembershipScope(member, input);
4888
- const entitlement = await membershipLoad(db, "networkEntitlement", `${id10}:primary`);
5049
+ const entitlement = await membershipLoad(db, "networkEntitlement", `${id11}:primary`);
4889
5050
  requireMembershipScope(entitlement, input);
4890
5051
  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;
5052
+ 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
5053
  return {
4893
- membershipId: id10,
5054
+ membershipId: id11,
4894
5055
  allowed: allowed2,
4895
5056
  status: String(member.status),
4896
5057
  accessEndsAt: Number.isSafeInteger(endsAt) ? endsAt : 0,
@@ -4945,7 +5106,7 @@ async function subscriptionForSeat(sk, seat) {
4945
5106
  currency: seat.currency,
4946
5107
  interval: seat.interval,
4947
5108
  environment: seat.environment
4948
- }, Boolean(seat.providerPaymentIntentId));
5109
+ }, Boolean(seat.providerPaymentIntentId || seat.combinedInvoice));
4949
5110
  if (!productId) reject3();
4950
5111
  const sub = await read(sk, `/v1/subscriptions/${encodeURIComponent(seat.providerSubscriptionId)}?expand[]=discounts`);
4951
5112
  const items = sub.items?.data;
@@ -4989,10 +5150,14 @@ async function createNamedSeatStripePayment(sk, seat, now) {
4989
5150
  return { paymentIntentId: pi.id, clientSecret: pi.client_secret, status: pi.status };
4990
5151
  }
4991
5152
  async function attachNamedSeatStripeRenewal(sk, seat) {
4992
- if (!seat.providerPaymentIntentId || !["renewal_pending", "pending_acceptance", "pending_approval", "active"].includes(seat.status)) reject3();
5153
+ if (!seat.providerPaymentIntentId && !seat.combinedInvoice || !["renewal_pending", "pending_acceptance", "pending_approval", "active"].includes(seat.status)) reject3();
4993
5154
  const sub = await subscriptionForSeat(sk, seat);
4994
5155
  const existing = sub.items.data.find((item2) => item2.metadata?.seatId === seat.id);
4995
- if (existing) return String(existing.id);
5156
+ if (existing) {
5157
+ if (!/^si_[A-Za-z0-9_]+$/.test(existing.id)) reject3();
5158
+ return String(existing.id);
5159
+ }
5160
+ if (seat.combinedInvoice) reject3();
4996
5161
  const result = await (0, import_stripe7.stripeRequest)({ secretKey: sk }, "POST", "/v1/subscription_items", {
4997
5162
  subscription: seat.providerSubscriptionId,
4998
5163
  price: seat.priceId,
@@ -5210,7 +5375,7 @@ async function recordRecipient(db, input, now, approve) {
5210
5375
  const state = await loadMembershipAuthorityState(db, input);
5211
5376
  const seat = await membershipLoad(db, "networkSeat", input.seatId);
5212
5377
  requireMembershipScope(seat, input);
5213
- if (!seat.purchaserUserId || seat.purchaserUserId === input.recipientUserId) membershipFail("named_seat_recipient_must_differ_from_purchaser");
5378
+ if (!seat.purchaserUserId && !seat.purchaserEmailNormalized || seat.purchaserUserId === input.recipientUserId || seat.purchaserEmailNormalized === normalizeChapterEmail(input.verifiedRecipientEmail)) membershipFail("named_seat_recipient_must_differ_from_purchaser");
5214
5379
  const member = await membershipLoad(db, "networkMembership", String(seat.primaryMembershipId));
5215
5380
  requireMembershipScope(member, input);
5216
5381
  const entitlement = await membershipLoad(db, "networkEntitlement", String(seat.entitlementId));
@@ -5268,43 +5433,121 @@ async function recordRecipient(db, input, now, approve) {
5268
5433
  }
5269
5434
 
5270
5435
  // src/membership-seat-refund-stripe.ts
5436
+ var import_stripe9 = require("@odla-ai/stripe");
5437
+
5438
+ // src/membership-checkout-seat-refund.ts
5271
5439
  var import_stripe8 = require("@odla-ai/stripe");
5272
5440
  var fail4 = (code = "named_seat_refund_mismatch") => {
5273
5441
  throw Object.assign(new Error(code), { code });
5274
5442
  };
5443
+ var id8 = (value) => typeof value === "string" ? value : value?.id ?? "";
5444
+ async function refundCheckoutNamedSeat(secretKey, seat) {
5445
+ const invoiceRef = seat.combinedInvoice;
5446
+ 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();
5447
+ const call = async (method, path, params, key3) => {
5448
+ const result = await (0, import_stripe8.stripeRequest)({ secretKey }, method, path, params, key3 ? { idempotencyKey: key3 } : {});
5449
+ if (!result.ok || !result.body) return fail4("named_seat_refund_provider_unavailable");
5450
+ return result.body;
5451
+ };
5452
+ const metadata = { ...stripePartitionMetadata(seat), applicationId: seat.applicationId };
5453
+ const sub = await call("GET", `/v1/subscriptions/${encodeURIComponent(seat.providerSubscriptionId)}`);
5454
+ 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();
5455
+ const invoice = await call("GET", `/v1/invoices/${encodeURIComponent(invoiceRef.invoiceId)}`);
5456
+ 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();
5457
+ let chargeId = id8(invoice.charge), paymentId = id8(invoice.payment_intent);
5458
+ if (!chargeId && !paymentId) {
5459
+ const payments = await call("GET", "/v1/invoice_payments", { invoice: invoice.id, status: "paid", limit: 100 });
5460
+ if (!Array.isArray(payments.data) || payments.data.length !== 1 || payments.has_more !== false) fail4();
5461
+ const payment = payments.data[0];
5462
+ if (id8(payment.invoice) !== invoice.id || payment.status !== "paid" || payment.payment?.type !== "payment_intent") fail4();
5463
+ paymentId = id8(payment.payment.payment_intent);
5464
+ }
5465
+ if (!chargeId) {
5466
+ if (!/^pi_[A-Za-z0-9_]+$/.test(paymentId)) fail4();
5467
+ const payment = await call("GET", `/v1/payment_intents/${encodeURIComponent(paymentId)}`);
5468
+ 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();
5469
+ chargeId = id8(payment.latest_charge);
5470
+ }
5471
+ if (!/^ch_[A-Za-z0-9_]+$/.test(chargeId)) fail4();
5472
+ const charge = await call("GET", `/v1/charges/${encodeURIComponent(chargeId)}`);
5473
+ 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();
5474
+ const items = sub.items.data.filter((item) => item.metadata?.seatId === seat.id);
5475
+ if (items.length > 1) fail4();
5476
+ if (items.length && sub.status !== "canceled") {
5477
+ const item = items[0];
5478
+ if (!/^si_[A-Za-z0-9_]+$/.test(item.id) || item.quantity !== 1 || item.price?.id !== seat.priceId) fail4();
5479
+ const removed = await call("DELETE", `/v1/subscription_items/${encodeURIComponent(item.id)}`, { proration_behavior: "none" });
5480
+ if (removed.id !== item.id || removed.deleted !== true) fail4("named_seat_renewal_removal_unconfirmed");
5481
+ }
5482
+ if (charge.amount_refunded === charge.amount && charge.refunded === true) return { refundedCents: seat.amountCents, refundIds: [] };
5483
+ const list = await call("GET", "/v1/refunds", { charge: chargeId, limit: 100 });
5484
+ if (!Array.isArray(list.data) || list.has_more !== false || list.data.length >= 100) fail4("named_seat_refund_history_incomplete");
5485
+ let refundedCents = 0;
5486
+ const refundIds = [];
5487
+ for (const refund of list.data) {
5488
+ 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();
5489
+ if (["pending", "requires_action"].includes(refund.status)) fail4("named_seat_refund_pending");
5490
+ if (refund.status === "succeeded") {
5491
+ if (refund.metadata?.seatId !== seat.id || refund.metadata?.membershipId !== seat.primaryMembershipId) fail4("named_seat_refund_allocation_ambiguous");
5492
+ refundedCents += refund.amount;
5493
+ refundIds.push(refund.id);
5494
+ }
5495
+ }
5496
+ if (refundedCents !== charge.amount_refunded || refundedCents > seat.amountCents) fail4();
5497
+ if (refundedCents < seat.amountCents) {
5498
+ const remaining = seat.amountCents - refundedCents;
5499
+ const refund = await call("POST", "/v1/refunds", {
5500
+ charge: chargeId,
5501
+ amount: remaining,
5502
+ reason: "requested_by_customer",
5503
+ metadata: { ...metadata, membershipId: seat.primaryMembershipId, seatId: seat.id }
5504
+ }, `combined-seat-refund:${seat.id}:${remaining}`);
5505
+ if (id8(refund.charge) !== chargeId || refund.amount !== remaining || refund.currency !== seat.currency || !/^re_[A-Za-z0-9_]+$/.test(refund.id)) fail4();
5506
+ if (refund.status !== "succeeded") fail4("named_seat_refund_pending");
5507
+ refundedCents += remaining;
5508
+ refundIds.push(refund.id);
5509
+ }
5510
+ return { refundedCents, refundIds };
5511
+ }
5512
+
5513
+ // src/membership-seat-refund-stripe.ts
5514
+ var fail5 = (code = "named_seat_refund_mismatch") => {
5515
+ throw Object.assign(new Error(code), { code });
5516
+ };
5275
5517
  async function refundNamedSeatStripePayment(secretKey, seat) {
5276
- if (!seat.providerPaymentIntentId || seat.status !== "refund_pending") fail4();
5518
+ if (seat.combinedInvoice) return refundCheckoutNamedSeat(secretKey, seat);
5519
+ if (!seat.providerPaymentIntentId || seat.status !== "refund_pending") fail5();
5277
5520
  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");
5521
+ const result = await (0, import_stripe9.stripeRequest)({ secretKey }, method, path, params, idempotencyKey ? { idempotencyKey } : {});
5522
+ if (!result.ok || !result.body) return fail5("named_seat_refund_provider_unavailable");
5280
5523
  return result.body;
5281
5524
  };
5282
5525
  const metadata = { ...stripePartitionMetadata(seat), applicationId: seat.applicationId, membershipId: seat.primaryMembershipId, seatId: seat.id };
5283
5526
  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();
5527
+ 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
5528
  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();
5529
+ 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
5530
  const items = sub.items.data.filter((item) => item.metadata?.seatId === seat.id);
5288
- if (items.length > 1) fail4();
5531
+ if (items.length > 1) fail5();
5289
5532
  if (items.length && sub.status !== "canceled") {
5290
5533
  const item = items[0];
5291
- if (!/^si_[A-Za-z0-9_]+$/.test(item.id) || item.price?.id !== seat.priceId || item.quantity !== 1) fail4();
5534
+ if (!/^si_[A-Za-z0-9_]+$/.test(item.id) || item.price?.id !== seat.priceId || item.quantity !== 1) fail5();
5292
5535
  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");
5536
+ if (removed.id !== item.id || removed.deleted !== true) fail5("named_seat_renewal_removal_unconfirmed");
5294
5537
  }
5295
5538
  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");
5539
+ if (!Array.isArray(list.data) || list.has_more !== false || list.data.length >= 100) fail5("named_seat_refund_history_incomplete");
5297
5540
  let refundedCents = 0, pendingCents = 0;
5298
5541
  const refundIds = [];
5299
5542
  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();
5543
+ 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
5544
  if (refund.status === "succeeded") {
5302
5545
  refundedCents += refund.amount;
5303
5546
  refundIds.push(refund.id);
5304
5547
  } else if (refund.status === "pending" || refund.status === "requires_action") pendingCents += refund.amount;
5305
5548
  }
5306
- if (refundedCents + pendingCents > seat.amountCents) fail4();
5307
- if (pendingCents) fail4("named_seat_refund_pending");
5549
+ if (refundedCents + pendingCents > seat.amountCents) fail5();
5550
+ if (pendingCents) fail5("named_seat_refund_pending");
5308
5551
  if (refundedCents < seat.amountCents) {
5309
5552
  const remaining = seat.amountCents - refundedCents;
5310
5553
  const refund = await call("POST", "/v1/refunds", {
@@ -5313,8 +5556,8 @@ async function refundNamedSeatStripePayment(secretKey, seat) {
5313
5556
  reason: "requested_by_customer",
5314
5557
  metadata
5315
5558
  }, `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");
5559
+ if (refund.payment_intent !== seat.providerPaymentIntentId || refund.amount !== remaining || refund.currency !== seat.currency || !/^re_[A-Za-z0-9_]+$/.test(refund.id)) fail5();
5560
+ if (refund.status !== "succeeded") fail5("named_seat_refund_pending");
5318
5561
  refundedCents += remaining;
5319
5562
  refundIds.push(refund.id);
5320
5563
  }
@@ -5330,7 +5573,7 @@ async function requestNamedSeatRefund(db, input, now) {
5330
5573
  const member = await membershipLoad(db, "networkMembership", String(seat.primaryMembershipId));
5331
5574
  requireMembershipScope(member, input);
5332
5575
  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");
5576
+ 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
5577
  const entitlement = seat.entitlementId ? await membershipLoad(db, "networkEntitlement", String(seat.entitlementId)) : void 0;
5335
5578
  await db.transact([
5336
5579
  membershipUpdate("networkSeat", input.seatId, { status: "refund_pending", refundReason: input.reason, revision: Number(seat.revision) + 1, updatedAt: now }),
@@ -5435,14 +5678,14 @@ var at = (value) => Number.isSafeInteger(value) ? Number(value) : void 0;
5435
5678
  async function readNamedSeatOffer(db, input, now) {
5436
5679
  membershipClock(now);
5437
5680
  await loadMembershipAuthorityState(db, input);
5438
- const id10 = await networkMembershipId(input), member = await membershipLoad(db, "networkMembership", id10);
5681
+ const id11 = await networkMembershipId(input), member = await membershipLoad(db, "networkMembership", id11);
5439
5682
  requireMembershipScope(member, input);
5440
5683
  const offering = membershipOfferingVersion(member.offeringSnapshot);
5441
5684
  const seat = member.namedSeatId ? await membershipLoad(db, "networkSeat", String(member.namedSeatId)) : void 0;
5442
5685
  if (seat) requireMembershipScope(seat, input);
5443
5686
  const purchaseEndsAt = seatPurchaseDeadline(offering, member);
5444
5687
  const offer = {
5445
- membershipId: id10,
5688
+ membershipId: id11,
5446
5689
  amountCents: offering.seatPriceCents,
5447
5690
  currency: offering.currency,
5448
5691
  interval: offering.interval,
@@ -5720,9 +5963,14 @@ function createMembershipAuthorityHandler(options) {
5720
5963
  try {
5721
5964
  input = JSON.parse(verified.body);
5722
5965
  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])))) {
5966
+ 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
5967
  throw new TypeError("invalid request");
5725
5968
  }
5969
+ if (action === "checkouts" && input.namedSeat !== void 0) {
5970
+ const seat = input.namedSeat;
5971
+ 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");
5972
+ checkoutNamedSeatSelection(seat, seat.purchaserEmail);
5973
+ }
5726
5974
  } catch {
5727
5975
  return respond({ error: "invalid_offer_request" }, 400);
5728
5976
  }
@@ -5795,8 +6043,9 @@ function createMembershipAuthorityHandler(options) {
5795
6043
  offerId: String(input.offerId),
5796
6044
  offerDigest: String(input.offerDigest),
5797
6045
  quoteDigest: String(input.quoteDigest),
5798
- priceId: String(input.priceId)
5799
- }, options.now?.() ?? Date.now());
6046
+ priceId: String(input.priceId),
6047
+ ...input.namedSeat ? { namedSeat: input.namedSeat } : {}
6048
+ }, options.now?.() ?? Date.now(), peer.seatPriceId);
5800
6049
  return respond({ ok: true, binding });
5801
6050
  }
5802
6051
  const offerId = await membershipOfferIdForRequest({ ...peer, applicationId: String(input.applicationId), requestId: String(input.requestId) });
@@ -5899,7 +6148,8 @@ async function requestMembershipCheckoutBinding(options) {
5899
6148
  offerId: options.offerId,
5900
6149
  offerDigest: options.offerDigest,
5901
6150
  quoteDigest: options.quoteDigest,
5902
- priceId: options.priceId
6151
+ priceId: options.priceId,
6152
+ ...options.namedSeat ? { namedSeat: options.namedSeat } : {}
5903
6153
  });
5904
6154
  return verifyMembershipCheckoutBinding(result.binding, options);
5905
6155
  }
@@ -6109,7 +6359,7 @@ async function requestMembershipCancellation(options) {
6109
6359
  }
6110
6360
 
6111
6361
  // src/membership-seat-cancel.ts
6112
- var import_stripe9 = require("@odla-ai/stripe");
6362
+ var import_stripe10 = require("@odla-ai/stripe");
6113
6363
  async function cancelUnpaidNamedSeat(db, target, secretKey, now) {
6114
6364
  membershipClock(now);
6115
6365
  const state = await loadMembershipAuthorityState(db, target);
@@ -6137,7 +6387,7 @@ async function cancelUnpaidNamedSeat(db, target, secretKey, now) {
6137
6387
  }
6138
6388
  const metadata = { ...stripePartitionMetadata(target), applicationId: target.applicationId, membershipId: seat.primaryMembershipId, seatId: seat.id };
6139
6389
  const request = async (method, path, params) => {
6140
- const result = await (0, import_stripe9.stripeRequest)(
6390
+ const result = await (0, import_stripe10.stripeRequest)(
6141
6391
  { secretKey },
6142
6392
  method,
6143
6393
  path,
@@ -6150,7 +6400,7 @@ async function cancelUnpaidNamedSeat(db, target, secretKey, now) {
6150
6400
  let payment;
6151
6401
  if (seat.providerPaymentIntentId) payment = await request("GET", `/v1/payment_intents/${encodeURIComponent(String(seat.providerPaymentIntentId))}`);
6152
6402
  else {
6153
- const snapshot = await listStripeObjects((0, import_stripe9.initStripe)({ secretKey }), "/v1/payment_intents", { customer: String(seat.providerCustomerId) });
6403
+ const snapshot = await listStripeObjects((0, import_stripe10.initStripe)({ secretKey }), "/v1/payment_intents", { customer: String(seat.providerCustomerId) });
6154
6404
  const matches2 = snapshot.objects.filter((row2) => row2.metadata?.seatId === target.seatId);
6155
6405
  if (!snapshot.complete || matches2.length !== 1) throw new Error("seat payment creation outcome unresolved");
6156
6406
  payment = matches2[0];
@@ -6163,9 +6413,9 @@ async function cancelUnpaidNamedSeat(db, target, secretKey, now) {
6163
6413
  if (payment.status === "succeeded" || payment.amount_received > 0) throw new Error("paid seat requires payment evidence and full refund");
6164
6414
  if (payment.status !== "canceled") {
6165
6415
  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");
6416
+ const id11 = payment.id;
6417
+ payment = await request("POST", `/v1/payment_intents/${encodeURIComponent(id11)}/cancel`, { cancellation_reason: "abandoned" });
6418
+ if (payment.id !== id11) throw new Error("seat cancellation receipt mismatch");
6169
6419
  }
6170
6420
  if (!valid(payment) || payment.status !== "canceled" || payment.amount_received !== 0) throw new Error("seat cancellation unconfirmed");
6171
6421
  await db.transact([membershipUpdate("networkSeat", target.seatId, { status: "canceled", revision: Number(seat.revision) + 1, updatedAt: now })], {
@@ -6265,11 +6515,11 @@ async function drainMembershipEffects(db, options, now) {
6265
6515
 
6266
6516
  // src/schema-membership-offers.ts
6267
6517
  var attr8 = (type, optional = false, unique = false, indexed = false) => ({ type, optional, unique, indexed });
6268
- var id8 = () => attr8("string", false, true, true);
6518
+ var id9 = () => attr8("string", false, true, true);
6269
6519
  var key = () => attr8("string", false, false, true);
6270
6520
  var membershipOfferEntities = {
6271
6521
  membershipAuthorityState: { attrs: {
6272
- id: id8(),
6522
+ id: id9(),
6273
6523
  chapterId: key(),
6274
6524
  environment: key(),
6275
6525
  mode: key(),
@@ -6279,7 +6529,7 @@ var membershipOfferEntities = {
6279
6529
  updatedAt: attr8("number")
6280
6530
  } },
6281
6531
  membershipCheckoutOffer: { attrs: {
6282
- id: id8(),
6532
+ id: id9(),
6283
6533
  applicationId: key(),
6284
6534
  appId: key(),
6285
6535
  chapterId: key(),
@@ -6294,7 +6544,7 @@ var membershipOfferEntities = {
6294
6544
  authorityRevision: attr8("number")
6295
6545
  } },
6296
6546
  membershipOfferCheckout: { attrs: {
6297
- id: id8(),
6547
+ id: id9(),
6298
6548
  appId: key(),
6299
6549
  chapterId: key(),
6300
6550
  environment: key(),
@@ -6305,20 +6555,21 @@ var membershipOfferEntities = {
6305
6555
  offerDigest: attr8("string"),
6306
6556
  quoteDigest: attr8("string"),
6307
6557
  priceId: attr8("string"),
6308
- boundAt: attr8("number")
6558
+ boundAt: attr8("number"),
6559
+ namedSeat: attr8("json", true)
6309
6560
  } }
6310
6561
  };
6311
6562
  var membershipOfferRules = Object.fromEntries(Object.keys(membershipOfferEntities).map((ns) => [ns, { view: "false", create: "false", update: "false", delete: "false" }]));
6312
6563
 
6313
6564
  // src/schema-membership-authority.ts
6314
6565
  var attr9 = (type, optional = false, unique = false, indexed = false) => ({ type, optional, unique, indexed });
6315
- var id9 = () => attr9("string", false, true, true);
6566
+ var id10 = () => attr9("string", false, true, true);
6316
6567
  var key2 = (optional = false) => attr9("string", optional, false, true);
6317
6568
  var scope = () => ({ appId: key2(), chapterId: key2(), environment: key2(), runtime: key2(), applicationId: key2(), skuId: key2() });
6318
6569
  var times = () => ({ revision: attr9("number"), createdAt: attr9("number"), updatedAt: attr9("number") });
6319
6570
  var membershipAuthorityEntities = {
6320
6571
  networkMembership: { attrs: {
6321
- id: id9(),
6572
+ id: id10(),
6322
6573
  membershipId: key2(),
6323
6574
  ...scope(),
6324
6575
  personId: key2(true),
@@ -6358,7 +6609,7 @@ var membershipAuthorityEntities = {
6358
6609
  restartId: key2(true)
6359
6610
  } },
6360
6611
  membershipRestart: { attrs: {
6361
- id: id9(),
6612
+ id: id10(),
6362
6613
  ...scope(),
6363
6614
  membershipId: key2(),
6364
6615
  restartId: key2(),
@@ -6368,7 +6619,7 @@ var membershipAuthorityEntities = {
6368
6619
  createdAt: attr9("number")
6369
6620
  } },
6370
6621
  membershipPurchaseHistory: { attrs: {
6371
- id: id9(),
6622
+ id: id10(),
6372
6623
  ...scope(),
6373
6624
  membershipId: key2(),
6374
6625
  providerSubscriptionId: key2(),
@@ -6377,7 +6628,7 @@ var membershipAuthorityEntities = {
6377
6628
  archivedAt: attr9("number")
6378
6629
  } },
6379
6630
  networkEntitlement: { attrs: {
6380
- id: id9(),
6631
+ id: id10(),
6381
6632
  ...scope(),
6382
6633
  membershipId: key2(),
6383
6634
  seatId: key2(true),
@@ -6394,10 +6645,12 @@ var membershipAuthorityEntities = {
6394
6645
  journeyApprovedAt: attr9("number", true)
6395
6646
  } },
6396
6647
  networkSeat: { attrs: {
6397
- id: id9(),
6648
+ id: id10(),
6398
6649
  ...scope(),
6399
6650
  primaryMembershipId: key2(),
6400
- purchaserUserId: key2(),
6651
+ purchaserUserId: key2(true),
6652
+ purchaserEmailNormalized: key2(true),
6653
+ combinedInvoice: attr9("json", true),
6401
6654
  recipientName: attr9("string"),
6402
6655
  recipientEmailNormalized: key2(),
6403
6656
  recipientClaimId: key2(),
@@ -6429,9 +6682,9 @@ var membershipAuthorityEntities = {
6429
6682
  refundedCents: attr9("number", true),
6430
6683
  providerRefundIds: attr9("json", true)
6431
6684
  } },
6432
- networkSeatRecipient: { attrs: { id: id9(), appId: key2(), chapterId: key2(), environment: key2(), runtime: key2(), seatId: key2(), recipientEmailNormalized: key2(), createdAt: attr9("number") } },
6685
+ networkSeatRecipient: { attrs: { id: id10(), appId: key2(), chapterId: key2(), environment: key2(), runtime: key2(), seatId: key2(), recipientEmailNormalized: key2(), createdAt: attr9("number") } },
6433
6686
  membershipEffect: { attrs: {
6434
- id: id9(),
6687
+ id: id10(),
6435
6688
  chapterId: key2(),
6436
6689
  environment: key2(),
6437
6690
  runtime: key2(),
@@ -6556,14 +6809,14 @@ function validateMemberContentDocument(input, options = {}) {
6556
6809
  continue;
6557
6810
  }
6558
6811
  unknownKeys(raw2, PAGE_KEYS, path, errors);
6559
- const id10 = textValue(raw2.id, `${path}.id`, errors, 80, true) ?? "";
6812
+ const id11 = textValue(raw2.id, `${path}.id`, errors, 80, true) ?? "";
6560
6813
  const slug = textValue(raw2.slug, `${path}.slug`, errors, 120, true) ?? "";
6561
6814
  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`);
6815
+ if (id11 && !ID.test(id11)) errors.push(`${path}.id must be a lowercase kebab-case id`);
6563
6816
  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}"`);
6817
+ if (ids2.has(id11) || knownParentIds.has(id11)) errors.push(`${path}.id duplicates page "${id11}"`);
6565
6818
  if (slugs.has(slug) || (options.parentPages ?? []).some((page) => page.slug === slug)) errors.push(`${path}.slug duplicates "${slug}"`);
6566
- ids2.add(id10);
6819
+ ids2.add(id11);
6567
6820
  slugs.add(slug);
6568
6821
  const navRaw = record(raw2.navigation);
6569
6822
  let navigation = { section: "General", order: index2 };
@@ -6619,7 +6872,7 @@ function validateMemberContentDocument(input, options = {}) {
6619
6872
  else if (owner === "chapter") upsell = { owner, ...offer ? { offer } : {} };
6620
6873
  }
6621
6874
  }
6622
- pages.push({ id: id10, slug, title, ...summary ? { summary } : {}, navigation, audience, ...teaserMarkdown ? { teaserMarkdown } : {}, bodyMarkdown, ...upsell ? { upsell } : {} });
6875
+ pages.push({ id: id11, slug, title, ...summary ? { summary } : {}, navigation, audience, ...teaserMarkdown ? { teaserMarkdown } : {}, bodyMarkdown, ...upsell ? { upsell } : {} });
6623
6876
  }
6624
6877
  const allIds = /* @__PURE__ */ new Set([...knownParentIds, ...ids2]);
6625
6878
  for (const [index2, page] of pages.entries()) {
@@ -6825,8 +7078,8 @@ function resourceValue(value, path, errors) {
6825
7078
  return null;
6826
7079
  }
6827
7080
  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`);
7081
+ const id11 = text3(raw2.id, `${path}.id`, errors, 80) ?? "";
7082
+ if (id11 && !ID2.test(id11)) errors.push(`${path}.id must be lowercase kebab-case`);
6830
7083
  const kind = text3(raw2.kind, `${path}.kind`, errors) ?? "";
6831
7084
  if (!KINDS.has(kind)) errors.push(`${path}.kind is unsupported`);
6832
7085
  const title = text3(raw2.title, `${path}.title`, errors, 240) ?? "";
@@ -6883,7 +7136,7 @@ function resourceValue(value, path, errors) {
6883
7136
  updatedAt: integer3(provenanceRaw?.updatedAt, `${path}.provenance.updatedAt`, errors, 0),
6884
7137
  ...promotedFrom ? { promotedFrom } : {}
6885
7138
  };
6886
- return { id: id10, kind, title, ...summary ? { summary } : {}, tags, scope: scope2, status: status2, revision, ...bodyMarkdown ? { bodyMarkdown } : {}, ...url ? { url } : {}, ...brandAsset ? { brandAsset } : {}, provenance };
7139
+ return { id: id11, kind, title, ...summary ? { summary } : {}, tags, scope: scope2, status: status2, revision, ...bodyMarkdown ? { bodyMarkdown } : {}, ...url ? { url } : {}, ...brandAsset ? { brandAsset } : {}, provenance };
6887
7140
  }
6888
7141
  function validateBuilderLibraryDocument(value, options = {}) {
6889
7142
  const errors = [];
@@ -6914,7 +7167,7 @@ function validateBuilderLibraryDocument(value, options = {}) {
6914
7167
  const inputs = Array.isArray(raw2.resources) ? raw2.resources : [];
6915
7168
  if (inputs.length > MAX_RESOURCES) errors.push(`document.resources may contain at most ${MAX_RESOURCES} items`);
6916
7169
  const resources = inputs.map((item, index2) => resourceValue(item, `document.resources[${index2}]`, errors)).filter(Boolean);
6917
- const ids2 = resources.map(({ id: id10 }) => id10);
7170
+ const ids2 = resources.map(({ id: id11 }) => id11);
6918
7171
  if (new Set(ids2).size !== ids2.length) errors.push("document.resources ids must be unique");
6919
7172
  for (const resource of resources) {
6920
7173
  if (sourceType === "chapter" && (resource.scope.kind !== "chapter" || resource.scope.chapterId !== sourceAppId)) {
@@ -7244,11 +7497,11 @@ async function sendTemplated(deps, input) {
7244
7497
  const previous = prior[0];
7245
7498
  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
7499
  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("")}`;
7500
+ const id11 = previous ? String(previous.id) : `email_${Array.from(new Uint8Array(hash2), (b) => b.toString(16).padStart(2, "0")).join("")}`;
7248
7501
  const attempt = Number(previous?.deliveryAttempt ?? 0) + 1;
7249
7502
  if (!Number.isSafeInteger(attempt) || attempt < 1) return { sent: false, reason: "delivery-state-invalid" };
7250
7503
  const row2 = {
7251
- id: id10,
7504
+ id: id11,
7252
7505
  groupId: input.group.id,
7253
7506
  to: decision.to,
7254
7507
  template: input.template,
@@ -7266,15 +7519,15 @@ async function sendTemplated(deps, input) {
7266
7519
  if (isRetiredEmailCopy(String(row2.subject), String(row2.body))) return { sent: false, reason: "template-retired" };
7267
7520
  if ((0, import_crm8.validateEmailCopy)({ subject: row2.subject, text: row2.body }, { allowedVariables: [] }).length) return { sent: false, reason: "template-invalid" };
7268
7521
  try {
7269
- const claim = await deps.db.transact([{ t: "update", ns: "emailLog", id: id10, attrs: row2 }], {
7270
- mutationId: `email-claim:${id10}:${attempt}`,
7522
+ const claim = await deps.db.transact([{ t: "update", ns: "emailLog", id: id11, attrs: row2 }], {
7523
+ mutationId: `email-claim:${id11}:${attempt}`,
7271
7524
  guards: [previous ? {
7272
7525
  ns: "emailLog",
7273
- id: id10,
7526
+ id: id11,
7274
7527
  exists: true,
7275
7528
  equals: { deliveryState: "retry_ready", ...previous.deliveryAttempt !== void 0 ? { deliveryAttempt: previous.deliveryAttempt } : {} },
7276
7529
  ...previous.deliveryAttempt === void 0 ? { absent: ["deliveryAttempt"] } : {}
7277
- } : { ns: "emailLog", id: id10, exists: false }]
7530
+ } : { ns: "emailLog", id: id11, exists: false }]
7278
7531
  });
7279
7532
  if (claim.duplicate) return { sent: false, reason: "delivery-uncertain" };
7280
7533
  } catch (error) {
@@ -7294,16 +7547,16 @@ async function sendTemplated(deps, input) {
7294
7547
  if (!response || typeof response.messageId !== "string" || !response.messageId) throw new Error("missing delivery receipt");
7295
7548
  messageId = response.messageId;
7296
7549
  } 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 } }]
7550
+ await deps.db.transact([{ t: "update", ns: "emailLog", id: id11, attrs: { deliveryState: "uncertain", error: "delivery-uncertain" } }], {
7551
+ guards: [{ ns: "emailLog", id: id11, exists: true, equals: { deliveryState: "sending", deliveryAttempt: attempt } }]
7299
7552
  });
7300
7553
  return { sent: false, reason: "delivery-uncertain" };
7301
7554
  }
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}`,
7555
+ await deps.db.transact([{ t: "update", ns: "emailLog", id: id11, attrs: { deliveryState: "sent", error: "", messageId, sentAt: deps.now() } }], {
7556
+ mutationId: `email-confirmed:${id11}:${attempt}`,
7304
7557
  guards: [{
7305
7558
  ns: "emailLog",
7306
- id: id10,
7559
+ id: id11,
7307
7560
  exists: true,
7308
7561
  equals: { deliveryState: "sending", deliveryAttempt: attempt }
7309
7562
  }]
@@ -7359,7 +7612,7 @@ async function reconcileLifecycleDelivery(db, input, now) {
7359
7612
  }
7360
7613
 
7361
7614
  // src/payments.ts
7362
- var import_stripe10 = require("@odla-ai/stripe");
7615
+ var import_stripe11 = require("@odla-ai/stripe");
7363
7616
  function paymentsReady(group, hasSecretKey) {
7364
7617
  return Boolean(group.stripePublishableKey && group.stripePriceId && hasSecretKey);
7365
7618
  }
@@ -7561,14 +7814,14 @@ function validateAdmissionGrantDocument(input) {
7561
7814
  const seenEmails = /* @__PURE__ */ new Set();
7562
7815
  for (const [index2, raw2] of rawGrants.slice(0, MAX_GRANTS).entries()) {
7563
7816
  const grant = raw2 && typeof raw2 === "object" ? raw2 : {};
7564
- const id10 = typeof grant.id === "string" ? grant.id : "";
7817
+ const id11 = typeof grant.id === "string" ? grant.id : "";
7565
7818
  const email = typeof grant.email === "string" ? grant.email.trim() : "";
7566
7819
  const normalized = normalizeAdmissionEmail(email);
7567
7820
  const decision = grant.decision;
7568
7821
  const note = typeof grant.note === "string" ? grant.note.trim() : void 0;
7569
7822
  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}"`);
7823
+ if (!ID3.test(id11)) errors.push(`document.grants[${index2}].id must be an identifier`);
7824
+ if (seenIds.has(id11)) errors.push(`document.grants repeats id "${id11}"`);
7572
7825
  if (!isAdmissionEmail(normalized)) errors.push(`document.grants[${index2}].email must be valid`);
7573
7826
  if (seenEmails.has(normalized)) errors.push(`document.grants repeats email "${normalized}"`);
7574
7827
  if (decision !== "waive_interview" && decision !== "require_interview") {
@@ -7578,10 +7831,10 @@ function validateAdmissionGrantDocument(input) {
7578
7831
  if (expiresAt !== void 0 && (!Number.isFinite(expiresAt) || expiresAt <= 0)) {
7579
7832
  errors.push(`document.grants[${index2}].expiresAt must be epoch milliseconds`);
7580
7833
  }
7581
- seenIds.add(id10);
7834
+ seenIds.add(id11);
7582
7835
  seenEmails.add(normalized);
7583
7836
  grants.push({
7584
- id: id10,
7837
+ id: id11,
7585
7838
  email,
7586
7839
  decision,
7587
7840
  ...note ? { note } : {},
@@ -7653,8 +7906,8 @@ async function materializeAdmissionGrants(db, envelope, previous, receivedAt) {
7653
7906
  const incomingIds = new Set(document.grants.map((grant) => `admission:parent:${document.source.appId}:${grant.id}`));
7654
7907
  const ops = [];
7655
7908
  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: {
7909
+ const id11 = String(row2.id ?? "");
7910
+ if (id11 && !incomingIds.has(id11)) ops.push({ t: "update", ns: "admissionGrants", id: id11, attrs: {
7658
7911
  active: false,
7659
7912
  revokedAt: receivedAt,
7660
7913
  revokedBy: document.source.actor,
@@ -7663,10 +7916,10 @@ async function materializeAdmissionGrants(db, envelope, previous, receivedAt) {
7663
7916
  } });
7664
7917
  }
7665
7918
  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,
7919
+ const id11 = `admission:parent:${document.source.appId}:${grant.id}`;
7920
+ const existing = current.find((row2) => row2.id === id11);
7921
+ ops.push({ t: "update", ns: "admissionGrants", id: id11, attrs: {
7922
+ id: id11,
7670
7923
  sourceGrantId: grant.id,
7671
7924
  sourceScope: "parent",
7672
7925
  sourceId: document.source.appId,
@@ -7687,7 +7940,7 @@ async function materializeAdmissionGrants(db, envelope, previous, receivedAt) {
7687
7940
  ...!grant.note && existing?.note !== void 0 ? ["note"] : [],
7688
7941
  ...grant.expiresAt === void 0 && existing?.expiresAt !== void 0 ? ["expiresAt"] : []
7689
7942
  ];
7690
- if (retract.length) ops.push({ t: "retract", ns: "admissionGrants", id: id10, attrs: retract });
7943
+ if (retract.length) ops.push({ t: "retract", ns: "admissionGrants", id: id11, attrs: retract });
7691
7944
  }
7692
7945
  if (previous?.active) ops.push({ t: "update", ns: "admissionGrantRevisions", id: previous.id, attrs: { active: false } });
7693
7946
  const revisionId = `admission-revision:${document.source.appId}:${document.target.environment}:${document.revision}`;
@@ -7758,10 +8011,10 @@ function validateGiftOfferCatalog(input, tierIds) {
7758
8011
  }
7759
8012
  const allowed2 = /* @__PURE__ */ new Set(["id", "target", "billingMode", "stripePriceId", "priceCents", "currency", "redemptionLifetimeHours", "activeFrom", "activeUntil", "title", "description", "ctaLabel"]);
7760
8013
  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);
8014
+ const id11 = text4(item.id, `${path}.id`, errors, 80);
8015
+ if (id11 && !ID4.test(id11)) errors.push(`${path}.id must be a lowercase kebab-case id`);
8016
+ if (ids2.has(id11)) errors.push(`${path}.id duplicates "${id11}"`);
8017
+ ids2.add(id11);
7765
8018
  const targetRaw = record3(item.target);
7766
8019
  let target = { type: "entitlement", entitlementKey: "invalid" };
7767
8020
  if (targetRaw?.type === "tier") {
@@ -7787,7 +8040,7 @@ function validateGiftOfferCatalog(input, tierIds) {
7787
8040
  if (activeUntil !== void 0 && (!Number.isSafeInteger(activeUntil) || activeUntil < 1)) errors.push(`${path}.activeUntil must be epoch milliseconds`);
7788
8041
  if (activeFrom !== void 0 && activeUntil !== void 0 && activeUntil <= activeFrom) errors.push(`${path}.activeUntil must follow activeFrom`);
7789
8042
  offers.push({
7790
- id: id10,
8043
+ id: id11,
7791
8044
  target,
7792
8045
  billingMode: item.billingMode,
7793
8046
  stripePriceId,
@@ -7831,10 +8084,10 @@ async function ensureProjectedPerson(deps, input, options = {}) {
7831
8084
  const existing = await find();
7832
8085
  if (existing) return { record: existing, created: false };
7833
8086
  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("")}`;
8087
+ const id11 = `person-${Array.from(digest, (byte) => byte.toString(16).padStart(2, "0")).join("")}`;
7835
8088
  try {
7836
8089
  const created = await (0, import_crm9.createRecord)({ ...deps, db: deps.db }, {
7837
- id: id10,
8090
+ id: id11,
7838
8091
  type: "person",
7839
8092
  input: { ...input, email },
7840
8093
  ...options
@@ -8257,9 +8510,9 @@ function subAnnualCents(sub) {
8257
8510
  }
8258
8511
 
8259
8512
  // src/payments-stripe.ts
8260
- var import_stripe11 = require("@odla-ai/stripe");
8513
+ var import_stripe12 = require("@odla-ai/stripe");
8261
8514
  async function stripeCall(sk, method, path, params, idempotencyKey) {
8262
- return (0, import_stripe11.stripeRequest)(
8515
+ return (0, import_stripe12.stripeRequest)(
8263
8516
  { secretKey: sk },
8264
8517
  method,
8265
8518
  path,
@@ -8337,8 +8590,8 @@ async function updateClerkUserMetadata(secretKey, userId, input, fetchImpl = fet
8337
8590
  return res.ok;
8338
8591
  }
8339
8592
  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;
8593
+ const id11 = await existingClerkUserId(secretKey, email, fetchImpl);
8594
+ return id11 ? updateClerkUserMetadata(secretKey, id11, input, fetchImpl) : false;
8342
8595
  }
8343
8596
  async function existingClerkUserId(secretKey, email, fetchImpl) {
8344
8597
  const auth = { authorization: `Bearer ${secretKey}` };
@@ -8411,10 +8664,10 @@ async function clerkGetUserByEmail(secretKey, email, fetchImpl = fetch, source =
8411
8664
  const user = Array.isArray(data) ? data[0] : void 0;
8412
8665
  return user ? toRecord(user, source) : null;
8413
8666
  }
8414
- async function clerkGetUser(secretKey, id10, fetchImpl = fetch, source = "public") {
8415
- const data = await clerkGet(`/v1/users/${encodeURIComponent(id10)}`, secretKey, fetchImpl).catch(() => null);
8667
+ async function clerkGetUser(secretKey, id11, fetchImpl = fetch, source = "public") {
8668
+ const data = await clerkGet(`/v1/users/${encodeURIComponent(id11)}`, secretKey, fetchImpl).catch(() => null);
8416
8669
  const record4 = data ? toRecord(data, source) : null;
8417
- return record4?.id === id10 ? record4 : null;
8670
+ return record4?.id === id11 ? record4 : null;
8418
8671
  }
8419
8672
  async function clerkListUsers(secretKey, fetchImpl = fetch, source = "public") {
8420
8673
  const out = [];
@@ -8430,8 +8683,8 @@ async function clerkListUsers(secretKey, fetchImpl = fetch, source = "public") {
8430
8683
  }
8431
8684
  return out;
8432
8685
  }
8433
- async function clerkSetRole(secretKey, id10, role, fetchImpl = fetch, source = "public") {
8434
- const res = await fetchImpl(`${CLERK_API}/v1/users/${encodeURIComponent(id10)}/metadata`, {
8686
+ async function clerkSetRole(secretKey, id11, role, fetchImpl = fetch, source = "public") {
8687
+ const res = await fetchImpl(`${CLERK_API}/v1/users/${encodeURIComponent(id11)}/metadata`, {
8435
8688
  method: "PATCH",
8436
8689
  headers: { authorization: `Bearer ${secretKey}`, "content-type": "application/json" },
8437
8690
  redirect: "manual",
@@ -8448,7 +8701,7 @@ function planPrivateRoleMigration(input) {
8448
8701
  if (input.environment !== "dev") blockers.push("dev_only");
8449
8702
  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
8703
  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");
8704
+ if (!expected.size || expected.size !== input.expectedRuntimes.length || input.expectedRuntimes.some((id11) => !id11)) blockers.push("runtime_inventory_required");
8452
8705
  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
8706
  for (const runtime of input.expectedRuntimes) {
8454
8707
  const proof = input.runtimes.find((r) => r.runtime === runtime);
@@ -8478,7 +8731,7 @@ function planPrivateRoleMigration(input) {
8478
8731
  if (privateRole === role && !Object.hasOwn(user.publicMetadata, "role")) alreadyCurrent++;
8479
8732
  else changes.push({ userId: user.id, role, publicRole, privateRole });
8480
8733
  }
8481
- if (!input.protectedSuperAdminUserIds.some((id10) => ids2.has(id10))) blockers.push("protected_operator_grant_required");
8734
+ if (!input.protectedSuperAdminUserIds.some((id11) => ids2.has(id11))) blockers.push("protected_operator_grant_required");
8482
8735
  return { ready: blockers.length === 0, blockers, changes, alreadyCurrent, totalUsers: input.users.length };
8483
8736
  }
8484
8737
 
@@ -9056,10 +9309,10 @@ function tiersOf(value, errors) {
9056
9309
  return [];
9057
9310
  }
9058
9311
  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);
9312
+ const id11 = text5(item.id, `${path}.id`, errors, 80);
9313
+ if (id11 && !TIER.test(id11)) errors.push(`${path}.id must be a lowercase kebab-case id`);
9314
+ if (seen.has(id11)) errors.push(`${path}.id duplicates "${id11}"`);
9315
+ seen.add(id11);
9063
9316
  const name = text5(item.name, `${path}.name`, errors, 160);
9064
9317
  if (typeof item.available !== "boolean") errors.push(`${path}.available must be boolean`);
9065
9318
  if (!Number.isInteger(item.sortOrder) || Number(item.sortOrder) < 0) errors.push(`${path}.sortOrder must be a non-negative integer`);
@@ -9080,7 +9333,7 @@ function tiersOf(value, errors) {
9080
9333
  }
9081
9334
  const blurb = item.blurb === void 0 ? void 0 : text5(item.blurb, `${path}.blurb`, errors, 4e3);
9082
9335
  return [{
9083
- id: id10,
9336
+ id: id11,
9084
9337
  name,
9085
9338
  available: item.available === true,
9086
9339
  sortOrder: Number(item.sortOrder) || 0,
@@ -9142,15 +9395,15 @@ function policyOf(value, errors, tiers2) {
9142
9395
  if (input.tierPolicies !== void 0) {
9143
9396
  const overrides = recordOf(input.tierPolicies);
9144
9397
  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}`;
9398
+ else for (const [id11, raw2] of Object.entries(overrides)) {
9399
+ const path = `document.policy.tierPolicies.${id11}`;
9147
9400
  const policy2 = recordOf(raw2);
9148
- if (!tiers2.some((tier) => tier.id === id10) || !policy2) {
9401
+ if (!tiers2.some((tier) => tier.id === id11) || !policy2) {
9149
9402
  errors.push(`${path} must name a declared tier and policy`);
9150
9403
  continue;
9151
9404
  }
9152
9405
  unknownKeys3(policy2, /* @__PURE__ */ new Set(["refundPolicyText", "trustCopy"]), path, errors);
9153
- tierPolicies[id10] = {
9406
+ tierPolicies[id11] = {
9154
9407
  refundPolicyText: text5(policy2.refundPolicyText, `${path}.refundPolicyText`, errors),
9155
9408
  ...policy2.trustCopy !== void 0 ? { trustCopy: text5(policy2.trustCopy, `${path}.trustCopy`, errors) } : {}
9156
9409
  };
@@ -9473,7 +9726,7 @@ var exclusions = (chapterId, paidTierIds) => [
9473
9726
  id: chapterId,
9474
9727
  reason: "provider_identity",
9475
9728
  paths: [
9476
- ...paidTierIds.map((id10) => `tiers[${id10}].stripePriceId`),
9729
+ ...paidTierIds.map((id11) => `tiers[${id11}].stripePriceId`),
9477
9730
  "groups.stripePublishableKey",
9478
9731
  "applications.stripeCustomerId",
9479
9732
  "applications.stripeSubscriptionId",
@@ -9515,9 +9768,9 @@ async function exportChapterPromotion(input) {
9515
9768
  sourceIds: [`signup-control:${document.revision}`, document.source.appId, document.target.chapterId].sort(),
9516
9769
  receiptDigests: [input.digest]
9517
9770
  };
9518
- const artifact = (kind, id10, content) => ({
9771
+ const artifact = (kind, id11, content) => ({
9519
9772
  kind,
9520
- id: id10,
9773
+ id: id11,
9521
9774
  sourceRevision: revision,
9522
9775
  provenance: promotionProvenance,
9523
9776
  content: (0, import_promotion.canonicalPromotionValue)(content)
@@ -9562,7 +9815,7 @@ var MEMBER_SYSTEM_RECORD_OWNERS = {
9562
9815
  provider: "originating-provider",
9563
9816
  protectedRoles: "odla-platform"
9564
9817
  };
9565
- var rows2 = (group) => group.fields.map((id10) => ({ ...group, id: id10 }));
9818
+ var rows2 = (group) => group.fields.map((id11) => ({ ...group, id: id11 }));
9566
9819
  var MEMBER_SYSTEM_AUTHORITY_MATRIX = [
9567
9820
  ...rows2({
9568
9821
  scope: "network",