@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.js CHANGED
@@ -122,8 +122,8 @@ async function submitApplication(db, chapter, fields, opts) {
122
122
  if (app.requireDisclaimerAck && !acked) {
123
123
  return { ok: false, error: "disclaimerAck is required" };
124
124
  }
125
- const id10 = opts.submissionId ? await applicationIdForSubmission(opts.submissionId) : opts.newId();
126
- const row2 = { id: id10, status: chapter.pipeline.initial, createdAt: opts.now };
125
+ const id11 = opts.submissionId ? await applicationIdForSubmission(opts.submissionId) : opts.newId();
126
+ const row2 = { id: id11, status: chapter.pipeline.initial, createdAt: opts.now };
127
127
  for (const f of [...app.required, ...app.optional]) {
128
128
  if (fieldStates[f]?.visible === false) continue;
129
129
  if (typeof fields[f] === "string") row2[f] = fields[f].trim();
@@ -157,12 +157,12 @@ async function submitApplication(db, chapter, fields, opts) {
157
157
  }
158
158
  if (acked) row2.disclaimerAckAt = opts.now;
159
159
  const ops = [
160
- { t: "update", ns: "applications", id: id10, attrs: row2 }
160
+ { t: "update", ns: "applications", id: id11, attrs: row2 }
161
161
  ];
162
162
  if (opts.admissionGrant?.decision === "waive_interview") {
163
163
  ops.push({ t: "update", ns: "admissionGrants", id: opts.admissionGrant.id, attrs: {
164
164
  consumedAt: opts.now,
165
- consumedApplicationId: id10
165
+ consumedApplicationId: id11
166
166
  } });
167
167
  }
168
168
  try {
@@ -170,7 +170,7 @@ async function submitApplication(db, chapter, fields, opts) {
170
170
  ops,
171
171
  opts.submissionId ? { mutationId: `join:${opts.submissionId}` } : void 0
172
172
  );
173
- return { ok: true, id: id10, duplicate, status: chapter.pipeline.initial, disclaimerAckAt: acked ? opts.now : null };
173
+ return { ok: true, id: id11, duplicate, status: chapter.pipeline.initial, disclaimerAckAt: acked ? opts.now : null };
174
174
  } catch (error) {
175
175
  if (error.code === "unique_violation") return {
176
176
  ok: false,
@@ -1705,9 +1705,9 @@ var APPLICATION_LABELS = {
1705
1705
  phone: "Phone",
1706
1706
  state: "State"
1707
1707
  };
1708
- function humanize(id10) {
1709
- const text6 = id10.replace(/([a-z0-9])([A-Z])/g, "$1 $2").replace(/[_-]+/g, " ").trim();
1710
- return text6 ? `${text6[0].toUpperCase()}${text6.slice(1)}` : id10;
1708
+ function humanize(id11) {
1709
+ const text6 = id11.replace(/([a-z0-9])([A-Z])/g, "$1 $2").replace(/[_-]+/g, " ").trim();
1710
+ return text6 ? `${text6[0].toUpperCase()}${text6.slice(1)}` : id11;
1711
1711
  }
1712
1712
  function applicationDefs(chapter) {
1713
1713
  try {
@@ -1716,23 +1716,23 @@ function applicationDefs(chapter) {
1716
1716
  return {};
1717
1717
  }
1718
1718
  }
1719
- function applicationType(id10, def) {
1719
+ function applicationType(id11, def) {
1720
1720
  if (def && ["string", "email", "url", "enum"].includes(def.type)) return def.type;
1721
- return id10 === "email" ? "email" : id10 === "linkedin" ? "url" : "string";
1721
+ return id11 === "email" ? "email" : id11 === "linkedin" ? "url" : "string";
1722
1722
  }
1723
1723
  function applicationFlowFields(chapter, application = chapter.application) {
1724
1724
  const defs = applicationDefs(chapter);
1725
- return [...application.required, ...application.optional].map((id10, order2) => {
1726
- const def = defs[id10];
1727
- const conditions = application.conditions[id10];
1728
- const type = applicationType(id10, def);
1725
+ return [...application.required, ...application.optional].map((id11, order2) => {
1726
+ const def = defs[id11];
1727
+ const conditions = application.conditions[id11];
1728
+ const type = applicationType(id11, def);
1729
1729
  return {
1730
- id: id10,
1731
- label: def?.label ?? APPLICATION_LABELS[id10] ?? humanize(id10),
1730
+ id: id11,
1731
+ label: def?.label ?? APPLICATION_LABELS[id11] ?? humanize(id11),
1732
1732
  type,
1733
- required: application.required.includes(id10),
1733
+ required: application.required.includes(id11),
1734
1734
  order: order2,
1735
- maxLength: application.maxLen[id10] ?? application.defaultMaxLen,
1735
+ maxLength: application.maxLen[id11] ?? application.defaultMaxLen,
1736
1736
  ...type === "enum" && def?.options ? { options: [...def.options] } : {},
1737
1737
  ...conditions ? { conditions: { ...conditions } } : {},
1738
1738
  ...type === "email" ? { emailIdentity: "literal" } : {}
@@ -1742,17 +1742,17 @@ function applicationFlowFields(chapter, application = chapter.application) {
1742
1742
  function resolveFormationFlowFields(crm, formation) {
1743
1743
  if (!formation.enabled) return [];
1744
1744
  const def = crm.type(formation.type);
1745
- return [...formation.required, ...formation.optional].map((id10, order2) => {
1746
- const field2 = def.fields[id10];
1745
+ return [...formation.required, ...formation.optional].map((id11, order2) => {
1746
+ const field2 = def.fields[id11];
1747
1747
  return {
1748
- id: id10,
1749
- label: field2.label ?? humanize(id10),
1748
+ id: id11,
1749
+ label: field2.label ?? humanize(id11),
1750
1750
  type: field2.type,
1751
- required: formation.required.includes(id10),
1751
+ required: formation.required.includes(id11),
1752
1752
  order: order2,
1753
- maxLength: formation.maxLen[id10] ?? formation.defaultMaxLen,
1753
+ maxLength: formation.maxLen[id11] ?? formation.defaultMaxLen,
1754
1754
  ...field2.options ? { options: [...field2.options] } : {},
1755
- ...id10 === def.emailField || field2.type === "email" ? { emailIdentity: "literal" } : {}
1755
+ ...id11 === def.emailField || field2.type === "email" ? { emailIdentity: "literal" } : {}
1756
1756
  };
1757
1757
  });
1758
1758
  }
@@ -2056,12 +2056,12 @@ function resolveNetworkPersonRegistry(mode, input, targetIds) {
2056
2056
  if (input.mode !== "shadow") throw new Error('defineChapter.networkPersonRegistry.mode: must be "shadow"');
2057
2057
  const chapterIds = input.chapterIds ? [...input.chapterIds] : [...targetIds];
2058
2058
  if (chapterIds.length === 0) throw new Error("defineChapter.networkPersonRegistry.chapterIds: at least one chapter is required");
2059
- if (new Set(chapterIds).size !== chapterIds.length || chapterIds.some((id10) => !SLUG2.test(id10))) {
2059
+ if (new Set(chapterIds).size !== chapterIds.length || chapterIds.some((id11) => !SLUG2.test(id11))) {
2060
2060
  throw new Error("defineChapter.networkPersonRegistry.chapterIds: expected distinct lowercase slugs");
2061
2061
  }
2062
- const unknown = chapterIds.filter((id10) => !targetIds.includes(id10));
2062
+ const unknown = chapterIds.filter((id11) => !targetIds.includes(id11));
2063
2063
  if (unknown.length) {
2064
- throw new Error(`defineChapter.networkPersonRegistry.chapterIds: ${unknown.map((id10) => `"${id10}"`).join(", ")} are not configured network targets`);
2064
+ throw new Error(`defineChapter.networkPersonRegistry.chapterIds: ${unknown.map((id11) => `"${id11}"`).join(", ")} are not configured network targets`);
2065
2065
  }
2066
2066
  return { mode: "shadow", chapterIds };
2067
2067
  }
@@ -2184,9 +2184,9 @@ function isResolvedCrm(x) {
2184
2184
  }
2185
2185
  function defineChapter(config) {
2186
2186
  if (!config || typeof config !== "object") throw new Error("defineChapter: a config object is required");
2187
- const { id: id10, name } = config;
2188
- if (typeof id10 !== "string" || !SLUG7.test(id10)) {
2189
- throw new Error(`defineChapter.id: must be a lowercase slug [a-z0-9-] (2-63 chars) \u2014 got ${JSON.stringify(id10)}`);
2187
+ const { id: id11, name } = config;
2188
+ if (typeof id11 !== "string" || !SLUG7.test(id11)) {
2189
+ throw new Error(`defineChapter.id: must be a lowercase slug [a-z0-9-] (2-63 chars) \u2014 got ${JSON.stringify(id11)}`);
2190
2190
  }
2191
2191
  if (typeof name !== "string" || name.trim() === "") throw new Error("defineChapter.name: a non-empty name is required");
2192
2192
  const mode = config.mode ?? "chapter";
@@ -2293,7 +2293,7 @@ function defineChapter(config) {
2293
2293
  };
2294
2294
  const chapter = {
2295
2295
  config,
2296
- id: id10,
2296
+ id: id11,
2297
2297
  name,
2298
2298
  mode,
2299
2299
  brand,
@@ -2708,14 +2708,14 @@ function facts(snapshots) {
2708
2708
  ["payment_intent.updated", snapshots.payments?.objects ?? []]
2709
2709
  ];
2710
2710
  return groups2.flatMap(([eventType, objects]) => objects.map((object2) => {
2711
- const id10 = typeof object2.id === "string" ? object2.id : "invalid";
2711
+ const id11 = typeof object2.id === "string" ? object2.id : "invalid";
2712
2712
  return normalizeNetworkCommercialStripeEvent({
2713
- id: `evt_snapshot_${eventType.replaceAll(".", "_")}_${id10}`,
2713
+ id: `evt_snapshot_${eventType.replaceAll(".", "_")}_${id11}`,
2714
2714
  type: eventType,
2715
2715
  created: typeof object2.created === "number" ? object2.created : 0,
2716
2716
  livemode: object2.livemode === true,
2717
2717
  data: { object: object2 }
2718
- }, `stripe-snapshot:${eventType}:${id10}`);
2718
+ }, `stripe-snapshot:${eventType}:${id11}`);
2719
2719
  }));
2720
2720
  }
2721
2721
  var selected = (row2) => ({
@@ -3424,8 +3424,8 @@ async function hash(value) {
3424
3424
  async function membershipDiscountCouponId(input, policyId) {
3425
3425
  return policyId ? `odla_m_${await hash([input.appId, input.chapterId, input.environment, input.runtime, input.skuId, policyId])}` : void 0;
3426
3426
  }
3427
- async function load(db, ns, id10) {
3428
- return (await db.query({ [ns]: { $: { where: { id: id10 }, limit: 1 } } }))[ns]?.[0];
3427
+ async function load(db, ns, id11) {
3428
+ return (await db.query({ [ns]: { $: { where: { id: id11 }, limit: 1 } } }))[ns]?.[0];
3429
3429
  }
3430
3430
  async function membershipOfferIdForRequest(input) {
3431
3431
  return `offer_${await hash([input.appId, input.chapterId, input.environment, input.runtime, input.applicationId, input.requestId])}`;
@@ -3562,20 +3562,20 @@ import { stripeRequest as stripeRequest2 } from "@odla-ai/stripe";
3562
3562
 
3563
3563
  // src/commercial-evidence-guard.ts
3564
3564
  async function commercialEvidenceGuard(db, eventId, digest, code = "membership_payment_evidence_quarantined") {
3565
- const ns = "networkCommercialExceptions", id10 = `network-commercial-exception:${eventId}:collision`;
3566
- const row2 = (await db.query({ [ns]: { $: { where: { id: id10 }, limit: 1 } } }))[ns]?.[0];
3567
- if (!row2) return { ns, id: id10, exists: false };
3565
+ const ns = "networkCommercialExceptions", id11 = `network-commercial-exception:${eventId}:collision`;
3566
+ const row2 = (await db.query({ [ns]: { $: { where: { id: id11 }, limit: 1 } } }))[ns]?.[0];
3567
+ if (!row2) return { ns, id: id11, exists: false };
3568
3568
  if (row2.status !== "resolved" || row2.resolvedDigest !== digest || typeof digest !== "string") {
3569
3569
  throw Object.assign(new Error(code), { code });
3570
3570
  }
3571
- return { ns, id: id10, exists: true, equals: { status: "resolved", resolvedDigest: digest, updatedAt: row2.updatedAt } };
3571
+ return { ns, id: id11, exists: true, equals: { status: "resolved", resolvedDigest: digest, updatedAt: row2.updatedAt } };
3572
3572
  }
3573
3573
 
3574
3574
  // src/membership-authority-state.ts
3575
3575
  var membershipFail = (code) => {
3576
3576
  throw Object.assign(new Error(code), { code });
3577
3577
  };
3578
- var membershipLoad = async (db, ns, id10) => (await db.query({ [ns]: { $: { where: { id: id10 }, limit: 1 } } }))[ns]?.[0];
3578
+ var membershipLoad = async (db, ns, id11) => (await db.query({ [ns]: { $: { where: { id: id11 }, limit: 1 } } }))[ns]?.[0];
3579
3579
  var membershipScopeKeys = ["appId", "chapterId", "environment", "runtime", "applicationId", "skuId"];
3580
3580
  var membershipScope = (input) => Object.fromEntries(membershipScopeKeys.map((key3) => [key3, input[key3]]));
3581
3581
  function requireMembershipScope(row2, input) {
@@ -3586,18 +3586,18 @@ async function loadMembershipAuthorityState(db, input) {
3586
3586
  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");
3587
3587
  return row2;
3588
3588
  }
3589
- var membershipUpdate = (ns, id10, attrs) => ({ t: "update", ns, id: id10, attrs });
3589
+ var membershipUpdate = (ns, id11, attrs) => ({ t: "update", ns, id: id11, attrs });
3590
3590
  var membershipClock = (now) => {
3591
3591
  if (!Number.isSafeInteger(now) || now < 0) membershipFail("invalid_membership_clock");
3592
3592
  };
3593
3593
 
3594
3594
  // src/membership-restart-payment.ts
3595
3595
  async function membershipRestartPayment(db, target, member, now) {
3596
- const id10 = String(member.id), restart = await membershipLoad(db, "membershipRestart", id10);
3596
+ const id11 = String(member.id), restart = await membershipLoad(db, "membershipRestart", id11);
3597
3597
  requireMembershipScope(restart, target);
3598
- const entitlement = await membershipLoad(db, "networkEntitlement", `${id10}:primary`);
3598
+ const entitlement = await membershipLoad(db, "networkEntitlement", `${id11}:primary`);
3599
3599
  requireMembershipScope(entitlement, target);
3600
- if (entitlement.membershipId !== id10 || entitlement.kind !== "primary") membershipFail("membership_restart_conflict");
3600
+ if (entitlement.membershipId !== id11 || entitlement.kind !== "primary") membershipFail("membership_restart_conflict");
3601
3601
  if (restart.status !== "ready" || member.status !== "restarting" || member.restartId !== restart.restartId || member.memberNumber !== restart.memberNumber || !Number.isSafeInteger(member.journeyApprovedAt)) membershipFail("membership_restart_conflict");
3602
3602
  return {
3603
3603
  restartId: String(restart.restartId),
@@ -3614,49 +3614,117 @@ async function membershipRestartPayment(db, target, member, now) {
3614
3614
  graceEndsAt: 0
3615
3615
  },
3616
3616
  ops: [
3617
- membershipUpdate("membershipRestart", id10, { status: "complete" }),
3618
- { t: "retract", ns: "networkMembership", id: id10, attrs: ["refundRequestedAt", "cancellationRequestedAt", "discountPolicyVersionId", "discountSnapshot"] }
3617
+ membershipUpdate("membershipRestart", id11, { status: "complete" }),
3618
+ { t: "retract", ns: "networkMembership", id: id11, attrs: ["refundRequestedAt", "cancellationRequestedAt", "discountPolicyVersionId", "discountSnapshot"] }
3619
3619
  ],
3620
3620
  guards: [
3621
- { ns: "networkMembership", id: id10, exists: true, equals: { status: "restarting", revision: member.revision, restartId: restart.restartId } },
3622
- { ns: "membershipRestart", id: id10, exists: true, equals: { status: "ready", restartId: restart.restartId } },
3621
+ { ns: "networkMembership", id: id11, exists: true, equals: { status: "restarting", revision: member.revision, restartId: restart.restartId } },
3622
+ { ns: "membershipRestart", id: id11, exists: true, equals: { status: "ready", restartId: restart.restartId } },
3623
3623
  { ns: "networkEntitlement", id: String(entitlement.id), exists: true, equals: { revision: entitlement.revision, status: entitlement.status } }
3624
3624
  ]
3625
3625
  };
3626
3626
  }
3627
3627
 
3628
+ // src/checkout-named-seat.ts
3629
+ function checkoutNamedSeatSelection(value, purchaserEmail) {
3630
+ const row2 = value;
3631
+ const name = typeof row2?.recipientName === "string" ? row2.recipientName.trim().replace(/\s+/g, " ") : "";
3632
+ const email = normalizeChapterEmail(typeof row2?.recipientEmail === "string" ? row2.recipientEmail : "");
3633
+ const purchaser = normalizeChapterEmail(purchaserEmail);
3634
+ 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) {
3635
+ throw Object.assign(new Error("Enter a different recipient's name and email."), { code: "invalid_named_seat_recipient" });
3636
+ }
3637
+ return { recipientName: name, recipientEmail: email };
3638
+ }
3639
+ async function checkoutNamedSeatIdentity(target, email) {
3640
+ const digest = await crypto.subtle.digest("SHA-256", new TextEncoder().encode(JSON.stringify([
3641
+ target.appId,
3642
+ target.chapterId,
3643
+ target.environment,
3644
+ target.runtime,
3645
+ email
3646
+ ])));
3647
+ return {
3648
+ seatId: (await membershipOfferIdForRequest({ ...target, requestId: "named_seat" })).replace(/^offer_/, "seat_"),
3649
+ membershipId: (await membershipOfferIdForRequest({ ...target, requestId: "checkout_binding" })).replace(/^offer_/, "membership_"),
3650
+ recipientClaimId: `seat-recipient:${[...new Uint8Array(digest)].map((byte) => byte.toString(16).padStart(2, "0")).join("")}`
3651
+ };
3652
+ }
3653
+
3628
3654
  // src/membership-checkout-binding.ts
3629
3655
  var keys = ["appId", "chapterId", "environment", "runtime", "applicationId", "skuId", "offerId", "offerDigest", "quoteDigest", "priceId"];
3630
3656
  var reject2 = (code) => {
3631
3657
  throw Object.assign(new Error(code), { code });
3632
3658
  };
3633
- var load2 = async (db, ns, id10) => (await db.query({ [ns]: { $: { where: { id: id10 }, limit: 1 } } }))[ns]?.[0];
3659
+ var load2 = async (db, ns, id11) => (await db.query({ [ns]: { $: { where: { id: id11 }, limit: 1 } } }))[ns]?.[0];
3634
3660
  async function membershipCheckoutBindingId(input) {
3635
3661
  return (await membershipOfferIdForRequest({ ...input, requestId: "checkout_binding" })).replace(/^offer_/, "checkout_");
3636
3662
  }
3637
3663
  async function verifyMembershipCheckoutBinding(value, input) {
3638
3664
  const row2 = value;
3639
3665
  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");
3640
- return { id: row2.id, ...Object.fromEntries(keys.map((key3) => [key3, input[key3]])), boundAt: row2.boundAt };
3666
+ if (Boolean(row2.namedSeat) !== Boolean(input.namedSeat)) return reject2("membership_checkout_conflict");
3667
+ if (input.namedSeat) {
3668
+ const expected = {
3669
+ ...checkoutNamedSeatSelection(input.namedSeat, input.namedSeat.purchaserEmail),
3670
+ purchaserEmail: normalizeChapterEmail(input.namedSeat.purchaserEmail)
3671
+ };
3672
+ const identity2 = await checkoutNamedSeatIdentity(input, expected.recipientEmail);
3673
+ 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");
3674
+ }
3675
+ return {
3676
+ id: row2.id,
3677
+ ...Object.fromEntries(keys.map((key3) => [key3, input[key3]])),
3678
+ boundAt: row2.boundAt,
3679
+ ...row2.namedSeat ? { namedSeat: row2.namedSeat } : {}
3680
+ };
3641
3681
  }
3642
- async function bindMembershipCheckout(db, input, now) {
3682
+ async function bindMembershipCheckout(db, input, now, seatPriceId) {
3643
3683
  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");
3644
3684
  const stateId = `${input.chapterId}:${input.environment}`;
3645
3685
  const state = await load2(db, "membershipAuthorityState", stateId);
3646
3686
  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");
3647
- const id10 = await membershipCheckoutBindingId(input);
3648
- const existing = await load2(db, "membershipOfferCheckout", id10);
3687
+ const id11 = await membershipCheckoutBindingId(input);
3688
+ const existing = await load2(db, "membershipOfferCheckout", id11);
3649
3689
  if (existing) return verifyMembershipCheckoutBinding(existing, input);
3650
3690
  const row2 = await load2(db, "membershipCheckoutOffer", input.offerId);
3651
3691
  const offer = await verifyIssuedMembershipOffer(row2, { ...input, now, lifetimeMs: 6e4 });
3652
3692
  if (offer.digest !== input.offerDigest || offer.expiresAt <= now || offer.issuedAt > now) return reject2("membership_offer_expired");
3653
- const restart = await load2(db, "membershipRestart", id10.replace(/^checkout_/, "membership_"));
3654
- const receipt = { id: id10, ...Object.fromEntries(keys.map((key3) => [key3, input[key3]])), boundAt: now };
3693
+ const restart = await load2(db, "membershipRestart", id11.replace(/^checkout_/, "membership_"));
3694
+ let namedSeat;
3695
+ if (input.namedSeat) {
3696
+ if (!seatPriceId || !/^price_[A-Za-z0-9_]+$/.test(seatPriceId) || seatPriceId === input.priceId || restart) return reject2("named_seat_purchase_unavailable");
3697
+ const selection = checkoutNamedSeatSelection(input.namedSeat, input.namedSeat.purchaserEmail);
3698
+ namedSeat = {
3699
+ ...selection,
3700
+ purchaserEmail: normalizeChapterEmail(input.namedSeat.purchaserEmail),
3701
+ ...await checkoutNamedSeatIdentity(input, selection.recipientEmail),
3702
+ priceId: seatPriceId,
3703
+ amountCents: offer.contract.offering.seatPriceCents
3704
+ };
3705
+ }
3706
+ const receipt = { id: id11, ...Object.fromEntries(keys.map((key3) => [key3, input[key3]])), boundAt: now, ...namedSeat ? { namedSeat } : {} };
3655
3707
  try {
3656
- await db.transact([{ t: "update", ns: "membershipOfferCheckout", id: id10, attrs: receipt }], {
3657
- mutationId: `membership-checkout:${id10}${restart?.restartId ? `:${restart.restartId}` : ""}`,
3708
+ await db.transact([
3709
+ { t: "update", ns: "membershipOfferCheckout", id: id11, attrs: receipt },
3710
+ ...namedSeat ? [{ t: "update", ns: "networkSeatRecipient", id: namedSeat.recipientClaimId, attrs: {
3711
+ id: namedSeat.recipientClaimId,
3712
+ appId: input.appId,
3713
+ chapterId: input.chapterId,
3714
+ environment: input.environment,
3715
+ runtime: input.runtime,
3716
+ seatId: namedSeat.seatId,
3717
+ recipientEmailNormalized: namedSeat.recipientEmail,
3718
+ createdAt: now
3719
+ } }] : []
3720
+ ], {
3721
+ mutationId: `membership-checkout:${id11}${restart?.restartId ? `:${restart.restartId}` : ""}`,
3658
3722
  guards: [
3659
- { ns: "membershipOfferCheckout", id: id10, exists: false },
3723
+ { ns: "membershipOfferCheckout", id: id11, exists: false },
3724
+ ...namedSeat ? [
3725
+ { ns: "networkSeat", id: namedSeat.seatId, exists: false },
3726
+ { ns: "networkSeatRecipient", id: namedSeat.recipientClaimId, exists: false }
3727
+ ] : [],
3660
3728
  { ns: "membershipCheckoutOffer", id: input.offerId, exists: true, equals: { digest: input.offerDigest } },
3661
3729
  { ns: "membershipAuthorityState", id: stateId, exists: true, equals: { mode: "authoritative", revision: state.revision } }
3662
3730
  ]
@@ -3664,11 +3732,78 @@ async function bindMembershipCheckout(db, input, now) {
3664
3732
  } catch (error) {
3665
3733
  if (error.code !== "transact_guard_failed") throw error;
3666
3734
  }
3667
- const saved = await load2(db, "membershipOfferCheckout", id10);
3735
+ const saved = await load2(db, "membershipOfferCheckout", id11);
3668
3736
  if (!saved) return reject2("membership_binding_unconfirmed");
3669
3737
  return verifyMembershipCheckoutBinding(saved, input);
3670
3738
  }
3671
3739
 
3740
+ // src/membership-checkout-seat-payment.ts
3741
+ function checkoutSeatPaymentOperations(target, seat, input) {
3742
+ const { now, paidAt, endsAt } = input;
3743
+ const included = seat.amountCents === 0, entitlementId = `${seat.seatId}:entitlement`;
3744
+ const effectId = `${seat.seatId}:${included ? "invite" : "attach-renewal"}`;
3745
+ return [
3746
+ membershipUpdate("networkSeat", seat.seatId, {
3747
+ id: seat.seatId,
3748
+ ...membershipScope(target),
3749
+ primaryMembershipId: seat.membershipId,
3750
+ purchaserEmailNormalized: seat.purchaserEmail,
3751
+ recipientName: seat.recipientName,
3752
+ recipientEmailNormalized: seat.recipientEmail,
3753
+ recipientClaimId: seat.recipientClaimId,
3754
+ priceId: seat.priceId,
3755
+ primaryPriceId: input.primaryPriceId,
3756
+ amountCents: seat.amountCents,
3757
+ currency: input.offering.currency,
3758
+ interval: input.offering.interval,
3759
+ providerCustomerId: input.customerId,
3760
+ providerSubscriptionId: input.subscriptionId,
3761
+ combinedInvoice: { invoiceId: input.invoiceId, totalCents: input.invoiceTotalCents },
3762
+ status: included ? "pending_acceptance" : "renewal_pending",
3763
+ preparedAt: input.boundAt,
3764
+ purchasedAt: paidAt,
3765
+ termStartedAt: paidAt,
3766
+ termEndsAt: endsAt,
3767
+ acceptanceEndsAt: endsAt,
3768
+ seatPurchaseEndsAt: seatPurchaseDeadline(input.offering, { initialTermStartedAt: paidAt, termEndsAt: endsAt }),
3769
+ offeringSnapshot: input.offering,
3770
+ paymentEventId: input.eventId,
3771
+ ...included ? { entitlementId } : {},
3772
+ revision: 1,
3773
+ createdAt: now,
3774
+ updatedAt: now
3775
+ }),
3776
+ ...included ? [membershipUpdate("networkEntitlement", entitlementId, {
3777
+ id: entitlementId,
3778
+ ...membershipScope(target),
3779
+ membershipId: seat.membershipId,
3780
+ seatId: seat.seatId,
3781
+ kind: "named_seat",
3782
+ status: "pending_acceptance",
3783
+ termStartedAt: paidAt,
3784
+ termEndsAt: endsAt,
3785
+ offeringSnapshot: input.offering,
3786
+ sourceEventId: input.eventId,
3787
+ revision: 1,
3788
+ createdAt: now,
3789
+ updatedAt: now
3790
+ })] : [],
3791
+ membershipUpdate("membershipEffect", effectId, {
3792
+ id: effectId,
3793
+ chapterId: target.chapterId,
3794
+ environment: target.environment,
3795
+ runtime: target.runtime,
3796
+ membershipId: seat.membershipId,
3797
+ seatId: seat.seatId,
3798
+ kind: included ? "invite_named_recipient" : "attach_seat_renewal",
3799
+ status: "pending",
3800
+ attempts: 0,
3801
+ createdAt: now,
3802
+ updatedAt: now
3803
+ })
3804
+ ];
3805
+ }
3806
+
3672
3807
  // src/membership-payment-authority.ts
3673
3808
  async function networkMembershipId(input) {
3674
3809
  return (await membershipCheckoutBindingId(input)).replace(/^checkout_/, "membership_");
@@ -3684,11 +3819,14 @@ async function confirmMembershipPayment(db, input, now) {
3684
3819
  { ...input, offerId: String(binding.offerId), now, lifetimeMs: 6e4 }
3685
3820
  );
3686
3821
  if (binding.offerDigest !== offer.digest) membershipFail("membership_checkout_conflict");
3822
+ const { namedSeat } = await verifyMembershipCheckoutBinding(binding, binding);
3823
+ if (namedSeat && namedSeat.amountCents !== offer.contract.offering.seatPriceCents) membershipFail("membership_checkout_conflict");
3824
+ const totalCents = offer.contract.dueTodayCents + (namedSeat?.amountCents ?? 0);
3687
3825
  const eventRow = await membershipLoad(db, "networkCommercialEvents", `network-commercial-event:${input.eventId}`);
3688
3826
  if (!eventRow) membershipFail("membership_payment_evidence_pending");
3689
3827
  const evidenceGuard = await commercialEvidenceGuard(db, input.eventId, eventRow.digest);
3690
3828
  const fact = eventRow.fact;
3691
- 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({
3829
+ 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({
3692
3830
  appId: input.appId,
3693
3831
  chapterId: input.chapterId,
3694
3832
  environment: input.environment,
@@ -3700,14 +3838,14 @@ async function confirmMembershipPayment(db, input, now) {
3700
3838
  const paidAt = fact.invoicePaidAt;
3701
3839
  const endsAt = offer.contract.initialTermEndsAt ?? fact.invoicePeriodEndsAt;
3702
3840
  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");
3703
- const id10 = await networkMembershipId(input), entitlementId = `${id10}:primary`;
3704
- let member = await membershipLoad(db, "networkMembership", id10);
3841
+ const id11 = await networkMembershipId(input), entitlementId = `${id11}:primary`;
3842
+ let member = await membershipLoad(db, "networkMembership", id11);
3705
3843
  if (!member || member.status === "restarting") {
3706
3844
  const offering = offer.contract.offering;
3707
3845
  const restart = member ? await membershipRestartPayment(db, input, member, now) : null;
3708
3846
  const attrs = {
3709
- id: id10,
3710
- membershipId: id10,
3847
+ id: id11,
3848
+ membershipId: id11,
3711
3849
  ...membershipScope(input),
3712
3850
  baseSkuId: input.skuId,
3713
3851
  status: "paid_pending_approval",
@@ -3723,8 +3861,9 @@ async function confirmMembershipPayment(db, input, now) {
3723
3861
  founding: offer.contract.founding,
3724
3862
  cohort: offer.contract.founding ? offer.contract.discountPolicy.cohortLabel : "standard",
3725
3863
  baseAmountCents: offering.basePriceCents,
3726
- amountCents: offer.contract.dueTodayCents,
3864
+ amountCents: totalCents,
3727
3865
  renewalAmountCents: offer.contract.renewalCents,
3866
+ ...namedSeat ? { namedSeatId: namedSeat.seatId } : {},
3728
3867
  currency: offering.currency,
3729
3868
  interval: offering.interval,
3730
3869
  initialTermStartedAt: paidAt,
@@ -3741,11 +3880,11 @@ async function confirmMembershipPayment(db, input, now) {
3741
3880
  };
3742
3881
  try {
3743
3882
  await db.transact([
3744
- membershipUpdate("networkMembership", id10, attrs),
3883
+ membershipUpdate("networkMembership", id11, attrs),
3745
3884
  membershipUpdate("networkEntitlement", entitlementId, {
3746
3885
  id: entitlementId,
3747
3886
  ...membershipScope(input),
3748
- membershipId: id10,
3887
+ membershipId: id11,
3749
3888
  kind: "primary",
3750
3889
  status: restart?.attrs.status === "active" ? "active" : "pending_approval",
3751
3890
  termStartedAt: paidAt,
@@ -3757,23 +3896,45 @@ async function confirmMembershipPayment(db, input, now) {
3757
3896
  updatedAt: now,
3758
3897
  ...restart?.entitlementAttrs
3759
3898
  }),
3760
- ...restart?.ops ?? []
3761
- ], { mutationId: `membership-paid:${id10}${restart ? `:${restart.restartId}` : ""}`, guards: [
3762
- ...restart?.guards ?? [{ ns: "networkMembership", id: id10, exists: false }],
3899
+ ...restart?.ops ?? [],
3900
+ ...namedSeat ? checkoutSeatPaymentOperations(input, namedSeat, {
3901
+ offering,
3902
+ primaryPriceId: String(binding.priceId),
3903
+ invoiceId: fact.invoiceId,
3904
+ invoiceTotalCents: totalCents,
3905
+ subscriptionId: input.providerSubscriptionId,
3906
+ customerId: input.providerCustomerId,
3907
+ eventId: input.eventId,
3908
+ paidAt: Number(paidAt),
3909
+ endsAt: Number(endsAt),
3910
+ boundAt: Number(binding.boundAt),
3911
+ now
3912
+ }) : []
3913
+ ], { mutationId: `membership-paid:${id11}${restart ? `:${restart.restartId}` : ""}`, guards: [
3914
+ ...restart?.guards ?? [{ ns: "networkMembership", id: id11, exists: false }],
3915
+ ...namedSeat ? [
3916
+ { ns: "networkSeat", id: namedSeat.seatId, exists: false },
3917
+ {
3918
+ ns: "networkSeatRecipient",
3919
+ id: namedSeat.recipientClaimId,
3920
+ exists: true,
3921
+ equals: { seatId: namedSeat.seatId, recipientEmailNormalized: namedSeat.recipientEmail }
3922
+ }
3923
+ ] : [],
3763
3924
  { ns: "membershipAuthorityState", id: String(state.id), exists: true, equals: { mode: "authoritative", revision: state.revision } },
3764
- { ns: "membershipOfferCheckout", id: String(binding.id), exists: true, equals: { offerDigest: offer.digest } },
3925
+ { ns: "membershipOfferCheckout", id: String(binding.id), exists: true, equals: { offerDigest: offer.digest, quoteDigest: binding.quoteDigest } },
3765
3926
  { ns: "networkCommercialEvents", id: String(eventRow.id), exists: true, equals: { digest: eventRow.digest } },
3766
3927
  evidenceGuard
3767
3928
  ] });
3768
3929
  } catch (error) {
3769
3930
  if (error.code !== "transact_guard_failed") throw error;
3770
3931
  }
3771
- member = await membershipLoad(db, "networkMembership", id10);
3932
+ member = await membershipLoad(db, "networkMembership", id11);
3772
3933
  }
3773
3934
  requireMembershipScope(member, input);
3774
3935
  if (member.purchaseOfferDigest !== offer.digest || member.providerInitialInvoiceId !== fact.invoiceId || member.providerSubscriptionId !== fact.subscriptionId || member.providerCustomerId !== fact.customerId) membershipFail("membership_purchase_conflict");
3775
3936
  return {
3776
- membershipId: id10,
3937
+ membershipId: id11,
3777
3938
  status: String(member.status),
3778
3939
  cohort: String(member.cohort),
3779
3940
  initialTermStartedAt: Number(member.initialTermStartedAt),
@@ -3785,9 +3946,9 @@ async function confirmMembershipPayment(db, input, now) {
3785
3946
  // src/membership-cancel-authority.ts
3786
3947
  async function cancelMembershipAtTermEnd(db, target, now, stripeSecret) {
3787
3948
  membershipClock(now);
3788
- const id10 = await networkMembershipId(target);
3949
+ const id11 = await networkMembershipId(target);
3789
3950
  const state = await loadMembershipAuthorityState(db, target);
3790
- const member = await membershipLoad(db, "networkMembership", id10);
3951
+ const member = await membershipLoad(db, "networkMembership", id11);
3791
3952
  requireMembershipScope(member, target);
3792
3953
  if (!["active", "cancel_at_term_end", "payment_grace", "paid_pending_approval", "lapsed"].includes(String(member.status))) {
3793
3954
  membershipFail("membership_cancellation_unavailable");
@@ -3805,7 +3966,7 @@ async function cancelMembershipAtTermEnd(db, target, now, stripeSecret) {
3805
3966
  "POST",
3806
3967
  path,
3807
3968
  { cancel_at_period_end: true, proration_behavior: "none" },
3808
- { idempotencyKey: `membership-cancel-at-term:${id10}:${member.providerSubscriptionId}` }
3969
+ { idempotencyKey: `membership-cancel-at-term:${id11}:${member.providerSubscriptionId}` }
3809
3970
  );
3810
3971
  if (!write.ok || write.body.id !== member.providerSubscriptionId || write.body.cancel_at_period_end !== true) {
3811
3972
  membershipFail("membership_cancellation_pending");
@@ -3813,7 +3974,7 @@ async function cancelMembershipAtTermEnd(db, target, now, stripeSecret) {
3813
3974
  }
3814
3975
  const status2 = Number(member.termEndsAt) <= now ? "lapsed" : member.journeyApprovedAt ? "cancel_at_term_end" : "paid_pending_approval";
3815
3976
  if (member.status !== status2 || member.autoRenew !== false || Number(member.graceEndsAt ?? 0) !== 0) {
3816
- await db.transact([membershipUpdate("networkMembership", id10, {
3977
+ await db.transact([membershipUpdate("networkMembership", id11, {
3817
3978
  status: status2,
3818
3979
  autoRenew: false,
3819
3980
  graceEndsAt: 0,
@@ -3822,12 +3983,12 @@ async function cancelMembershipAtTermEnd(db, target, now, stripeSecret) {
3822
3983
  updatedAt: now
3823
3984
  })], {
3824
3985
  guards: [
3825
- { ns: "networkMembership", id: id10, exists: true, equals: { revision: member.revision } },
3986
+ { ns: "networkMembership", id: id11, exists: true, equals: { revision: member.revision } },
3826
3987
  { ns: "membershipAuthorityState", id: String(state.id), exists: true, equals: { revision: state.revision, mode: "authoritative" } }
3827
3988
  ]
3828
3989
  });
3829
3990
  }
3830
- return { membershipId: id10, status: status2, termEndsAt: Number(member.termEndsAt), graceEndsAt: 0, autoRenew: false };
3991
+ return { membershipId: id11, status: status2, termEndsAt: Number(member.termEndsAt), graceEndsAt: 0, autoRenew: false };
3831
3992
  }
3832
3993
 
3833
3994
  // src/membership-authority-handler.ts
@@ -3866,17 +4027,17 @@ async function namedSeatPrimaryActivation(db, member, now) {
3866
4027
  // src/membership-approval-authority.ts
3867
4028
  async function approveNetworkMembership(db, input, now) {
3868
4029
  membershipClock(now);
3869
- const id10 = await networkMembershipId(input), entitlementId = `${id10}:primary`;
4030
+ const id11 = await networkMembershipId(input), entitlementId = `${id11}:primary`;
3870
4031
  for (let attempt = 0; attempt < 8; attempt++) {
3871
4032
  const state = await loadMembershipAuthorityState(db, input);
3872
- const member = await membershipLoad(db, "networkMembership", id10);
4033
+ const member = await membershipLoad(db, "networkMembership", id11);
3873
4034
  requireMembershipScope(member, input);
3874
4035
  const entitlement = await membershipLoad(db, "networkEntitlement", entitlementId);
3875
4036
  requireMembershipScope(entitlement, input);
3876
- if (entitlement.membershipId !== id10 || entitlement.kind !== "primary") membershipFail("membership_entitlement_conflict");
4037
+ if (entitlement.membershipId !== id11 || entitlement.kind !== "primary") membershipFail("membership_entitlement_conflict");
3877
4038
  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) {
3878
4039
  return {
3879
- membershipId: id10,
4040
+ membershipId: id11,
3880
4041
  status: String(member.status),
3881
4042
  cohort: String(member.cohort),
3882
4043
  memberNumber: Number(member.memberNumber),
@@ -3896,7 +4057,7 @@ async function approveNetworkMembership(db, input, now) {
3896
4057
  const seatActivation = await namedSeatPrimaryActivation(db, member, now);
3897
4058
  try {
3898
4059
  await db.transact([
3899
- membershipUpdate("networkMembership", id10, {
4060
+ membershipUpdate("networkMembership", id11, {
3900
4061
  status: member.autoRenew ? "active" : "cancel_at_term_end",
3901
4062
  memberNumber,
3902
4063
  journeyApprovedAt: now,
@@ -3911,8 +4072,8 @@ async function approveNetworkMembership(db, input, now) {
3911
4072
  updatedAt: now
3912
4073
  }),
3913
4074
  ...seatActivation.ops
3914
- ], { mutationId: `membership-approve:${id10}`, guards: [
3915
- { ns: "networkMembership", id: id10, exists: true, equals: { revision: member.revision, status: "paid_pending_approval" }, absent: ["memberNumber"] },
4075
+ ], { mutationId: `membership-approve:${id11}`, guards: [
4076
+ { ns: "networkMembership", id: id11, exists: true, equals: { revision: member.revision, status: "paid_pending_approval" }, absent: ["memberNumber"] },
3916
4077
  { ns: "networkEntitlement", id: entitlementId, exists: true, equals: { revision: entitlement.revision, status: "pending_approval" } },
3917
4078
  { ns: "membershipAuthorityState", id: String(state.id), exists: true, equals: { revision: state.revision, mode: "authoritative" } },
3918
4079
  ...seatActivation.guards
@@ -4011,41 +4172,41 @@ async function refundSubscriptionPayment(secretKey, input) {
4011
4172
  // src/membership-refund-authority.ts
4012
4173
  async function refundNetworkMembership(db, input, secretKey, now) {
4013
4174
  membershipClock(now);
4014
- const id10 = await networkMembershipId(input), entitlementId = `${id10}:primary`;
4015
- let state = await loadMembershipAuthorityState(db, input), member = await membershipLoad(db, "networkMembership", id10);
4175
+ const id11 = await networkMembershipId(input), entitlementId = `${id11}:primary`;
4176
+ let state = await loadMembershipAuthorityState(db, input), member = await membershipLoad(db, "networkMembership", id11);
4016
4177
  requireMembershipScope(member, input);
4017
- if (member.status === "refunded") return { membershipId: id10, status: "refunded", refundedCents: Number(member.refundedCents), subscriptionCanceled: true };
4178
+ if (member.status === "refunded") return { membershipId: id11, status: "refunded", refundedCents: Number(member.refundedCents), subscriptionCanceled: true };
4018
4179
  if (!["paid_pending_approval", "active", "cancel_at_term_end", "payment_grace", "lapsed", "refund_pending"].includes(String(member.status))) membershipFail("membership_refund_not_eligible");
4019
4180
  if (member.status !== "refund_pending") {
4020
4181
  const entitlement = await membershipLoad(db, "networkEntitlement", entitlementId);
4021
4182
  requireMembershipScope(entitlement, input);
4022
4183
  await db.transact([
4023
- membershipUpdate("networkMembership", id10, {
4184
+ membershipUpdate("networkMembership", id11, {
4024
4185
  status: "refund_pending",
4025
4186
  refundRequestedAt: now,
4026
4187
  revision: Number(member.revision) + 1,
4027
4188
  updatedAt: now
4028
4189
  }),
4029
4190
  membershipUpdate("networkEntitlement", entitlementId, { status: "closed", revision: Number(entitlement.revision) + 1, updatedAt: now }),
4030
- membershipUpdate("membershipEffect", `${id10}:refund`, {
4031
- id: `${id10}:refund`,
4191
+ membershipUpdate("membershipEffect", `${id11}:refund`, {
4192
+ id: `${id11}:refund`,
4032
4193
  chapterId: input.chapterId,
4033
4194
  environment: input.environment,
4034
4195
  runtime: input.runtime,
4035
- membershipId: id10,
4196
+ membershipId: id11,
4036
4197
  kind: "refund_membership",
4037
4198
  status: "pending",
4038
4199
  attempts: 0,
4039
4200
  createdAt: now,
4040
4201
  updatedAt: now
4041
4202
  })
4042
- ], { mutationId: `membership-refund-request:${id10}`, guards: [
4043
- { ns: "networkMembership", id: id10, exists: true, equals: { revision: member.revision } },
4203
+ ], { mutationId: `membership-refund-request:${id11}`, guards: [
4204
+ { ns: "networkMembership", id: id11, exists: true, equals: { revision: member.revision } },
4044
4205
  { ns: "networkEntitlement", id: entitlementId, exists: true, equals: { revision: entitlement.revision } },
4045
4206
  { ns: "membershipAuthorityState", id: String(state.id), exists: true, equals: { revision: state.revision, mode: "authoritative" } }
4046
4207
  ] });
4047
4208
  }
4048
- member = await membershipLoad(db, "networkMembership", id10);
4209
+ member = await membershipLoad(db, "networkMembership", id11);
4049
4210
  requireMembershipScope(member, input);
4050
4211
  if (member.status !== "refund_pending") membershipFail("membership_refund_retry_required");
4051
4212
  const result = await refundSubscriptionPayment(secretKey, {
@@ -4059,23 +4220,23 @@ async function refundNetworkMembership(db, input, secretKey, now) {
4059
4220
  if (!result.subscriptionCanceled || result.refundedCents !== member.amountCents) membershipFail("membership_refund_unconfirmed");
4060
4221
  for (let attempt = 0; attempt < 8; attempt++) {
4061
4222
  state = await loadMembershipAuthorityState(db, input);
4062
- member = await membershipLoad(db, "networkMembership", id10);
4223
+ member = await membershipLoad(db, "networkMembership", id11);
4063
4224
  requireMembershipScope(member, input);
4064
- if (member.status === "refunded") return { membershipId: id10, status: "refunded", refundedCents: Number(member.refundedCents), subscriptionCanceled: true };
4225
+ if (member.status === "refunded") return { membershipId: id11, status: "refunded", refundedCents: Number(member.refundedCents), subscriptionCanceled: true };
4065
4226
  if (member.status !== "refund_pending") membershipFail("membership_refund_retry_required");
4066
4227
  const counted = member.founding === true && member.memberNumber !== void 0;
4067
4228
  const counts = state.cohortCounts;
4068
4229
  if (!counts || counted && (!Number.isSafeInteger(counts[String(member.cohort)]) || counts[String(member.cohort)] < 1)) membershipFail("invalid_cohort_count");
4069
4230
  try {
4070
4231
  await db.transact([
4071
- membershipUpdate("networkMembership", id10, {
4232
+ membershipUpdate("networkMembership", id11, {
4072
4233
  status: "refunded",
4073
4234
  refundedCents: result.refundedCents,
4074
4235
  autoRenew: false,
4075
4236
  revision: Number(member.revision) + 1,
4076
4237
  updatedAt: now
4077
4238
  }),
4078
- membershipUpdate("membershipEffect", `${id10}:refund`, { status: "complete", updatedAt: now }),
4239
+ membershipUpdate("membershipEffect", `${id11}:refund`, { status: "complete", updatedAt: now }),
4079
4240
  ...counted ? [membershipUpdate("membershipAuthorityState", String(state.id), {
4080
4241
  cohortCounts: { ...counts, [String(member.cohort)]: counts[String(member.cohort)] - 1 },
4081
4242
  revision: Number(state.revision) + 1,
@@ -4086,7 +4247,7 @@ async function refundNetworkMembership(db, input, secretKey, now) {
4086
4247
  chapterId: input.chapterId,
4087
4248
  environment: input.environment,
4088
4249
  runtime: input.runtime,
4089
- membershipId: id10,
4250
+ membershipId: id11,
4090
4251
  seatId: member.namedSeatId,
4091
4252
  kind: "refund_seat_with_primary",
4092
4253
  status: "pending",
@@ -4094,8 +4255,8 @@ async function refundNetworkMembership(db, input, secretKey, now) {
4094
4255
  createdAt: now,
4095
4256
  updatedAt: now
4096
4257
  })] : []
4097
- ], { mutationId: `membership-refunded:${id10}`, guards: [
4098
- { ns: "networkMembership", id: id10, exists: true, equals: { status: "refund_pending", revision: member.revision } },
4258
+ ], { mutationId: `membership-refunded:${id11}`, guards: [
4259
+ { ns: "networkMembership", id: id11, exists: true, equals: { status: "refund_pending", revision: member.revision } },
4099
4260
  { ns: "membershipAuthorityState", id: String(state.id), exists: true, equals: { mode: "authoritative", revision: state.revision } }
4100
4261
  ] });
4101
4262
  } catch (error) {
@@ -4123,8 +4284,8 @@ async function reserveNamedMembershipSeat(db, input, now) {
4123
4284
  const memberId = await networkMembershipId(input), member = await membershipLoad(db, "networkMembership", memberId);
4124
4285
  requireMembershipScope(member, input);
4125
4286
  if (!Number.isSafeInteger(member.revision) || !/^price_[A-Za-z0-9_]+$/.test(String(member.providerPriceId)) || member.providerPriceId === input.priceId) membershipFail("named_seat_purchase_unavailable");
4126
- const id10 = (await membershipOfferIdForRequest({ ...input, requestId: "named_seat" })).replace(/^offer_/, "seat_");
4127
- let seat = await membershipLoad(db, "networkSeat", id10);
4287
+ const id11 = (await membershipOfferIdForRequest({ ...input, requestId: "named_seat" })).replace(/^offer_/, "seat_");
4288
+ let seat = await membershipLoad(db, "networkSeat", id11);
4128
4289
  if (!seat) {
4129
4290
  const offering = membershipOfferingVersion(member.offeringSnapshot), claimId = await recipientKey(input, email);
4130
4291
  const purchaseEndsAt = seatPurchaseDeadline(offering, member);
@@ -4132,7 +4293,7 @@ async function reserveNamedMembershipSeat(db, input, now) {
4132
4293
  membershipFail("named_seat_purchase_unavailable");
4133
4294
  }
4134
4295
  const attrs = {
4135
- id: id10,
4296
+ id: id11,
4136
4297
  ...membershipScope(input),
4137
4298
  primaryMembershipId: memberId,
4138
4299
  recipientName: name,
@@ -4157,20 +4318,20 @@ async function reserveNamedMembershipSeat(db, input, now) {
4157
4318
  };
4158
4319
  try {
4159
4320
  await db.transact([
4160
- membershipUpdate("networkSeat", id10, attrs),
4321
+ membershipUpdate("networkSeat", id11, attrs),
4161
4322
  membershipUpdate("networkSeatRecipient", claimId, {
4162
4323
  id: claimId,
4163
4324
  appId: input.appId,
4164
4325
  chapterId: input.chapterId,
4165
4326
  environment: input.environment,
4166
4327
  runtime: input.runtime,
4167
- seatId: id10,
4328
+ seatId: id11,
4168
4329
  recipientEmailNormalized: email,
4169
4330
  createdAt: now
4170
4331
  }),
4171
- membershipUpdate("networkMembership", memberId, { namedSeatId: id10, revision: Number(member.revision) + 1, updatedAt: now })
4172
- ], { mutationId: `named-seat-reserve:${id10}`, guards: [
4173
- { ns: "networkSeat", id: id10, exists: false },
4332
+ membershipUpdate("networkMembership", memberId, { namedSeatId: id11, revision: Number(member.revision) + 1, updatedAt: now })
4333
+ ], { mutationId: `named-seat-reserve:${id11}`, guards: [
4334
+ { ns: "networkSeat", id: id11, exists: false },
4174
4335
  { ns: "networkSeatRecipient", id: claimId, exists: false },
4175
4336
  { ns: "networkMembership", id: memberId, exists: true, equals: { revision: member.revision }, absent: ["namedSeatId"] },
4176
4337
  { ns: "membershipAuthorityState", id: String(state.id), exists: true, equals: { mode: "authoritative", revision: state.revision } }
@@ -4178,7 +4339,7 @@ async function reserveNamedMembershipSeat(db, input, now) {
4178
4339
  } catch (error) {
4179
4340
  if (error.code !== "transact_guard_failed") throw error;
4180
4341
  }
4181
- seat = await membershipLoad(db, "networkSeat", id10);
4342
+ seat = await membershipLoad(db, "networkSeat", id11);
4182
4343
  if (!seat && await membershipLoad(db, "networkSeatRecipient", claimId)) membershipFail("named_seat_recipient_unavailable");
4183
4344
  }
4184
4345
  requireMembershipScope(seat, input);
@@ -4188,7 +4349,7 @@ async function reserveNamedMembershipSeat(db, input, now) {
4188
4349
 
4189
4350
  // src/membership-seat-included.ts
4190
4351
  var namedSeatIncluded = (seat) => Number(seat.amountCents) === 0;
4191
- var namedSeatSettled = (seat) => namedSeatIncluded(seat) ? seat.purchasedAt !== void 0 : Boolean(seat.providerPaymentIntentId && seat.providerSubscriptionItemId);
4352
+ var namedSeatSettled = (seat) => namedSeatIncluded(seat) ? seat.purchasedAt !== void 0 : Boolean((seat.providerPaymentIntentId || seat.combinedInvoice) && seat.providerSubscriptionItemId);
4192
4353
  function namedSeatRecipientState(seat) {
4193
4354
  switch (String(seat.status)) {
4194
4355
  case "renewal_pending":
@@ -4286,7 +4447,7 @@ async function grantIncludedNamedSeat(db, input, now) {
4286
4447
  // src/membership-renewal-authority.ts
4287
4448
  async function reconcileMembershipRenewal(db, input, now) {
4288
4449
  membershipClock(now);
4289
- const id10 = await networkMembershipId(input), eventRow = await membershipLoad(db, "networkCommercialEvents", `network-commercial-event:${input.eventId}`);
4450
+ const id11 = await networkMembershipId(input), eventRow = await membershipLoad(db, "networkCommercialEvents", `network-commercial-event:${input.eventId}`);
4290
4451
  const evidenceGuard = await commercialEvidenceGuard(db, input.eventId, eventRow?.digest, "membership_renewal_evidence_pending");
4291
4452
  const fact = eventRow?.fact;
4292
4453
  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({
@@ -4299,10 +4460,10 @@ async function reconcileMembershipRenewal(db, input, now) {
4299
4460
  return membershipFail("membership_renewal_evidence_pending");
4300
4461
  }
4301
4462
  for (let attempt = 0; attempt < 8; attempt++) {
4302
- const state = await loadMembershipAuthorityState(db, input), member = await membershipLoad(db, "networkMembership", id10);
4463
+ const state = await loadMembershipAuthorityState(db, input), member = await membershipLoad(db, "networkMembership", id11);
4303
4464
  requireMembershipScope(member, input);
4304
4465
  const receipt = () => ({
4305
- membershipId: id10,
4466
+ membershipId: id11,
4306
4467
  status: String(member.status),
4307
4468
  termEndsAt: Number(member.termEndsAt),
4308
4469
  graceEndsAt: Number(member.graceEndsAt ?? 0),
@@ -4327,13 +4488,13 @@ async function reconcileMembershipRenewal(db, input, now) {
4327
4488
  const paid = fact.eventType === "invoice.payment_succeeded";
4328
4489
  if (!paid && (fact.status !== "open" || !Number.isSafeInteger(fact.amountPaidCents) || Number(fact.amountPaidCents) < 0 || Number(fact.amountPaidCents) >= amount)) membershipFail("membership_renewal_failure_mismatch");
4329
4490
  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");
4330
- const entitlement = await membershipLoad(db, "networkEntitlement", `${id10}:primary`);
4491
+ const entitlement = await membershipLoad(db, "networkEntitlement", `${id11}:primary`);
4331
4492
  requireMembershipScope(entitlement, input);
4332
4493
  const graceEndsAt = paid ? 0 : start + 14 * 864e5;
4333
4494
  const status2 = paid ? member.journeyApprovedAt ? member.autoRenew ? "active" : "cancel_at_term_end" : "paid_pending_approval" : now < graceEndsAt ? "payment_grace" : "lapsed";
4334
4495
  const entitlementStatus = member.journeyApprovedAt && (paid || now < graceEndsAt) ? "active" : member.journeyApprovedAt ? "closed" : "pending_approval";
4335
4496
  const ops = [
4336
- membershipUpdate("networkMembership", id10, {
4497
+ membershipUpdate("networkMembership", id11, {
4337
4498
  status: status2,
4338
4499
  graceEndsAt,
4339
4500
  ...paid ? {
@@ -4344,7 +4505,7 @@ async function reconcileMembershipRenewal(db, input, now) {
4344
4505
  revision: Number(member.revision) + 1,
4345
4506
  updatedAt: now
4346
4507
  }),
4347
- membershipUpdate("networkEntitlement", `${id10}:primary`, {
4508
+ membershipUpdate("networkEntitlement", `${id11}:primary`, {
4348
4509
  status: entitlementStatus,
4349
4510
  ...paid ? { termStartedAt: start, termEndsAt: end } : {},
4350
4511
  sourceEventId: input.eventId,
@@ -4353,8 +4514,8 @@ async function reconcileMembershipRenewal(db, input, now) {
4353
4514
  })
4354
4515
  ];
4355
4516
  const guards = [
4356
- { ns: "networkMembership", id: id10, exists: true, equals: { revision: member.revision } },
4357
- { ns: "networkEntitlement", id: `${id10}:primary`, exists: true, equals: { revision: entitlement.revision } },
4517
+ { ns: "networkMembership", id: id11, exists: true, equals: { revision: member.revision } },
4518
+ { ns: "networkEntitlement", id: `${id11}:primary`, exists: true, equals: { revision: entitlement.revision } },
4358
4519
  { ns: "membershipAuthorityState", id: String(state.id), exists: true, equals: { revision: state.revision, mode: "authoritative" } },
4359
4520
  { ns: "networkCommercialEvents", id: String(eventRow.id), exists: true, equals: { digest: eventRow.digest } },
4360
4521
  evidenceGuard
@@ -4379,15 +4540,15 @@ async function reconcileMembershipRenewal(db, input, now) {
4379
4540
  );
4380
4541
  }
4381
4542
  try {
4382
- await db.transact(ops, { mutationId: `membership-renewal:${id10}:${input.eventId}`, guards });
4543
+ await db.transact(ops, { mutationId: `membership-renewal:${id11}:${input.eventId}`, guards });
4383
4544
  } catch (error) {
4384
4545
  if (error.code !== "transact_guard_failed") throw error;
4385
4546
  continue;
4386
4547
  }
4387
- const saved = await membershipLoad(db, "networkMembership", id10);
4548
+ const saved = await membershipLoad(db, "networkMembership", id11);
4388
4549
  requireMembershipScope(saved, input);
4389
4550
  return {
4390
- membershipId: id10,
4551
+ membershipId: id11,
4391
4552
  status: String(saved.status),
4392
4553
  termEndsAt: Number(saved.termEndsAt),
4393
4554
  graceEndsAt: Number(saved.graceEndsAt ?? 0),
@@ -4413,12 +4574,12 @@ async function reconcileMembershipBilling(db, input, now, stripeSecretName = "st
4413
4574
  }).some(([key3, value]) => fact.identity[key3] !== value)) membershipFail("membership_billing_evidence_pending");
4414
4575
  const sk = await getVaultSecret(db, stripeSecretName);
4415
4576
  if (!sk) membershipFail("membership_billing_provider_unavailable");
4416
- const id10 = await networkMembershipId(input);
4577
+ const id11 = await networkMembershipId(input);
4417
4578
  for (let attempt = 0; attempt < 8; attempt++) {
4418
- const state = await loadMembershipAuthorityState(db, input), member = await membershipLoad(db, "networkMembership", id10);
4579
+ const state = await loadMembershipAuthorityState(db, input), member = await membershipLoad(db, "networkMembership", id11);
4419
4580
  requireMembershipScope(member, input);
4420
4581
  const receipt = (value = member) => ({
4421
- membershipId: id10,
4582
+ membershipId: id11,
4422
4583
  status: String(value.status),
4423
4584
  termEndsAt: Number(value.termEndsAt),
4424
4585
  graceEndsAt: Number(value.graceEndsAt ?? 0),
@@ -4439,7 +4600,7 @@ async function reconcileMembershipBilling(db, input, now, stripeSecretName = "st
4439
4600
  });
4440
4601
  await commercialEvidenceGuard(db, input.eventId, row2?.digest, "membership_billing_evidence_pending");
4441
4602
  await refundNetworkMembership(db, input, sk, now);
4442
- const saved2 = await membershipLoad(db, "networkMembership", id10);
4603
+ const saved2 = await membershipLoad(db, "networkMembership", id11);
4443
4604
  requireMembershipScope(saved2, input);
4444
4605
  return receipt(saved2);
4445
4606
  }
@@ -4454,18 +4615,18 @@ async function reconcileMembershipBilling(db, input, now, stripeSecretName = "st
4454
4615
  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";
4455
4616
  const graceEndsAt = autoRenew ? Number(member.graceEndsAt ?? 0) : 0;
4456
4617
  if (member.autoRenew === autoRenew && member.status === status2 && Number(member.graceEndsAt ?? 0) === graceEndsAt) return receipt();
4457
- const entitlement = await membershipLoad(db, "networkEntitlement", `${id10}:primary`);
4618
+ const entitlement = await membershipLoad(db, "networkEntitlement", `${id11}:primary`);
4458
4619
  requireMembershipScope(entitlement, input);
4459
4620
  try {
4460
4621
  await db.transact([
4461
- membershipUpdate("networkMembership", id10, { status: status2, autoRenew, graceEndsAt, revision: Number(member.revision) + 1, updatedAt: now }),
4622
+ membershipUpdate("networkMembership", id11, { status: status2, autoRenew, graceEndsAt, revision: Number(member.revision) + 1, updatedAt: now }),
4462
4623
  membershipUpdate("networkEntitlement", String(entitlement.id), {
4463
4624
  status: status2 === "lapsed" ? "closed" : entitlement.status,
4464
4625
  revision: Number(entitlement.revision) + 1,
4465
4626
  updatedAt: now
4466
4627
  })
4467
4628
  ], { guards: [
4468
- { ns: "networkMembership", id: id10, exists: true, equals: { revision: member.revision } },
4629
+ { ns: "networkMembership", id: id11, exists: true, equals: { revision: member.revision } },
4469
4630
  { ns: "networkEntitlement", id: String(entitlement.id), exists: true, equals: { revision: entitlement.revision } },
4470
4631
  { ns: "membershipAuthorityState", id: String(state.id), exists: true, equals: { mode: "authoritative", revision: state.revision } },
4471
4632
  { ns: "networkCommercialEvents", id: String(row2.id), exists: true, equals: { digest: fact.digest } },
@@ -4475,7 +4636,7 @@ async function reconcileMembershipBilling(db, input, now, stripeSecretName = "st
4475
4636
  if (error.code !== "transact_guard_failed") throw error;
4476
4637
  continue;
4477
4638
  }
4478
- const saved = await membershipLoad(db, "networkMembership", id10);
4639
+ const saved = await membershipLoad(db, "networkMembership", id11);
4479
4640
  requireMembershipScope(saved, input);
4480
4641
  return receipt(saved);
4481
4642
  }
@@ -4536,19 +4697,19 @@ async function stopPreviousSubscription(member, target, secret) {
4536
4697
  async function prepareMembershipRestart(db, input, now, stripeSecret) {
4537
4698
  membershipClock(now);
4538
4699
  if (!/^sub_[A-Za-z0-9_]+$/.test(input.previousSubscriptionId)) membershipFail("membership_restart_conflict");
4539
- const id10 = await networkMembershipId(input), bindingId = await membershipCheckoutBindingId(input);
4540
- const restartId = `restart_${await stripePayloadDigest(JSON.stringify([id10, input.previousSubscriptionId]))}`;
4700
+ const id11 = await networkMembershipId(input), bindingId = await membershipCheckoutBindingId(input);
4701
+ const restartId = `restart_${await stripePayloadDigest(JSON.stringify([id11, input.previousSubscriptionId]))}`;
4541
4702
  const state = await loadMembershipAuthorityState(db, input);
4542
- const previous = await membershipLoad(db, "networkMembership", id10);
4703
+ const previous = await membershipLoad(db, "networkMembership", id11);
4543
4704
  requireMembershipScope(previous, input);
4544
4705
  const receipt = {
4545
4706
  applicationId: input.applicationId,
4546
- membershipId: id10,
4707
+ membershipId: id11,
4547
4708
  restartId,
4548
4709
  memberNumber: Number(previous.memberNumber),
4549
4710
  status: "ready"
4550
4711
  };
4551
- const existing = await membershipLoad(db, "membershipRestart", id10);
4712
+ const existing = await membershipLoad(db, "membershipRestart", id11);
4552
4713
  if (existing?.restartId === restartId && existing.status === "ready") {
4553
4714
  requireMembershipScope(existing, input);
4554
4715
  return receipt;
@@ -4564,33 +4725,33 @@ async function prepareMembershipRestart(db, input, now, stripeSecret) {
4564
4725
  membershipUpdate("membershipPurchaseHistory", restartId, {
4565
4726
  id: restartId,
4566
4727
  ...membershipScope(input),
4567
- membershipId: id10,
4728
+ membershipId: id11,
4568
4729
  providerSubscriptionId: input.previousSubscriptionId,
4569
4730
  memberSnapshot: previous,
4570
4731
  bindingSnapshot: binding2,
4571
4732
  archivedAt: now
4572
4733
  }),
4573
- membershipUpdate("networkMembership", id10, { status: "restarting", restartId, revision: Number(previous.revision) + 1, updatedAt: now })
4734
+ membershipUpdate("networkMembership", id11, { status: "restarting", restartId, revision: Number(previous.revision) + 1, updatedAt: now })
4574
4735
  ], { mutationId: `membership-restart-reserve:${restartId}`, guards: [
4575
4736
  ...seats.guards,
4576
- { ns: "networkMembership", id: id10, exists: true, equals: { revision: previous.revision, status: previous.status } },
4737
+ { ns: "networkMembership", id: id11, exists: true, equals: { revision: previous.revision, status: previous.status } },
4577
4738
  { ns: "membershipPurchaseHistory", id: restartId, exists: false },
4578
4739
  { ns: "membershipOfferCheckout", id: bindingId, exists: true, equals: { offerDigest: binding2.offerDigest } },
4579
4740
  { ns: "membershipAuthorityState", id: String(state.id), exists: true, equals: { mode: "authoritative", revision: state.revision } }
4580
4741
  ] });
4581
4742
  } else if (previous.restartId !== restartId) membershipFail("membership_restart_conflict");
4582
- const reserved = await membershipLoad(db, "networkMembership", id10);
4743
+ const reserved = await membershipLoad(db, "networkMembership", id11);
4583
4744
  requireMembershipScope(reserved, input);
4584
4745
  if (reserved.status !== "restarting" || reserved.restartId !== restartId) membershipFail("membership_restart_conflict");
4585
4746
  await stopPreviousSubscription(reserved, input, stripeSecret);
4586
4747
  const binding = await membershipLoad(db, "membershipOfferCheckout", bindingId);
4587
4748
  requireMembershipScope(binding, input);
4588
4749
  await db.transact([
4589
- membershipUpdate("membershipRestart", id10, { id: id10, ...membershipScope(input), ...receipt, previousSubscriptionId: input.previousSubscriptionId, createdAt: now }),
4750
+ membershipUpdate("membershipRestart", id11, { id: id11, ...membershipScope(input), ...receipt, previousSubscriptionId: input.previousSubscriptionId, createdAt: now }),
4590
4751
  { t: "delete", ns: "membershipOfferCheckout", id: bindingId }
4591
4752
  ], { mutationId: `membership-restart-ready:${restartId}`, guards: [
4592
- { ns: "membershipRestart", id: id10, exists: Boolean(existing), ...existing ? { equals: { restartId: existing.restartId, status: existing.status } } : {} },
4593
- { ns: "networkMembership", id: id10, exists: true, equals: { revision: reserved.revision, status: "restarting", restartId } },
4753
+ { ns: "membershipRestart", id: id11, exists: Boolean(existing), ...existing ? { equals: { restartId: existing.restartId, status: existing.status } } : {} },
4754
+ { ns: "networkMembership", id: id11, exists: true, equals: { revision: reserved.revision, status: "restarting", restartId } },
4594
4755
  { ns: "membershipOfferCheckout", id: bindingId, exists: true, equals: { offerDigest: binding.offerDigest } }
4595
4756
  ] });
4596
4757
  return receipt;
@@ -4600,15 +4761,15 @@ async function prepareMembershipRestart(db, input, now, stripeSecret) {
4600
4761
  async function readMembershipAccess(db, input, now) {
4601
4762
  membershipClock(now);
4602
4763
  await loadMembershipAuthorityState(db, input);
4603
- const id10 = await networkMembershipId(input), member = await membershipLoad(db, "networkMembership", id10);
4604
- if (!member) return { membershipId: id10, allowed: false, status: "unpaid", accessEndsAt: 0, revision: 0 };
4764
+ const id11 = await networkMembershipId(input), member = await membershipLoad(db, "networkMembership", id11);
4765
+ if (!member) return { membershipId: id11, allowed: false, status: "unpaid", accessEndsAt: 0, revision: 0 };
4605
4766
  requireMembershipScope(member, input);
4606
- const entitlement = await membershipLoad(db, "networkEntitlement", `${id10}:primary`);
4767
+ const entitlement = await membershipLoad(db, "networkEntitlement", `${id11}:primary`);
4607
4768
  requireMembershipScope(entitlement, input);
4608
4769
  const endsAt = member.status === "payment_grace" ? Number(member.graceEndsAt) : Number(member.termEndsAt);
4609
- 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;
4770
+ 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;
4610
4771
  return {
4611
- membershipId: id10,
4772
+ membershipId: id11,
4612
4773
  allowed: allowed2,
4613
4774
  status: String(member.status),
4614
4775
  accessEndsAt: Number.isSafeInteger(endsAt) ? endsAt : 0,
@@ -4663,7 +4824,7 @@ async function subscriptionForSeat(sk, seat) {
4663
4824
  currency: seat.currency,
4664
4825
  interval: seat.interval,
4665
4826
  environment: seat.environment
4666
- }, Boolean(seat.providerPaymentIntentId));
4827
+ }, Boolean(seat.providerPaymentIntentId || seat.combinedInvoice));
4667
4828
  if (!productId) reject3();
4668
4829
  const sub = await read(sk, `/v1/subscriptions/${encodeURIComponent(seat.providerSubscriptionId)}?expand[]=discounts`);
4669
4830
  const items = sub.items?.data;
@@ -4707,10 +4868,14 @@ async function createNamedSeatStripePayment(sk, seat, now) {
4707
4868
  return { paymentIntentId: pi.id, clientSecret: pi.client_secret, status: pi.status };
4708
4869
  }
4709
4870
  async function attachNamedSeatStripeRenewal(sk, seat) {
4710
- if (!seat.providerPaymentIntentId || !["renewal_pending", "pending_acceptance", "pending_approval", "active"].includes(seat.status)) reject3();
4871
+ if (!seat.providerPaymentIntentId && !seat.combinedInvoice || !["renewal_pending", "pending_acceptance", "pending_approval", "active"].includes(seat.status)) reject3();
4711
4872
  const sub = await subscriptionForSeat(sk, seat);
4712
4873
  const existing = sub.items.data.find((item2) => item2.metadata?.seatId === seat.id);
4713
- if (existing) return String(existing.id);
4874
+ if (existing) {
4875
+ if (!/^si_[A-Za-z0-9_]+$/.test(existing.id)) reject3();
4876
+ return String(existing.id);
4877
+ }
4878
+ if (seat.combinedInvoice) reject3();
4714
4879
  const result = await stripeRequest7({ secretKey: sk }, "POST", "/v1/subscription_items", {
4715
4880
  subscription: seat.providerSubscriptionId,
4716
4881
  price: seat.priceId,
@@ -4928,7 +5093,7 @@ async function recordRecipient(db, input, now, approve) {
4928
5093
  const state = await loadMembershipAuthorityState(db, input);
4929
5094
  const seat = await membershipLoad(db, "networkSeat", input.seatId);
4930
5095
  requireMembershipScope(seat, input);
4931
- if (!seat.purchaserUserId || seat.purchaserUserId === input.recipientUserId) membershipFail("named_seat_recipient_must_differ_from_purchaser");
5096
+ if (!seat.purchaserUserId && !seat.purchaserEmailNormalized || seat.purchaserUserId === input.recipientUserId || seat.purchaserEmailNormalized === normalizeChapterEmail(input.verifiedRecipientEmail)) membershipFail("named_seat_recipient_must_differ_from_purchaser");
4932
5097
  const member = await membershipLoad(db, "networkMembership", String(seat.primaryMembershipId));
4933
5098
  requireMembershipScope(member, input);
4934
5099
  const entitlement = await membershipLoad(db, "networkEntitlement", String(seat.entitlementId));
@@ -4986,43 +5151,121 @@ async function recordRecipient(db, input, now, approve) {
4986
5151
  }
4987
5152
 
4988
5153
  // src/membership-seat-refund-stripe.ts
5154
+ import { stripeRequest as stripeRequest9 } from "@odla-ai/stripe";
5155
+
5156
+ // src/membership-checkout-seat-refund.ts
4989
5157
  import { stripeRequest as stripeRequest8 } from "@odla-ai/stripe";
4990
5158
  var fail4 = (code = "named_seat_refund_mismatch") => {
4991
5159
  throw Object.assign(new Error(code), { code });
4992
5160
  };
5161
+ var id8 = (value) => typeof value === "string" ? value : value?.id ?? "";
5162
+ async function refundCheckoutNamedSeat(secretKey, seat) {
5163
+ const invoiceRef = seat.combinedInvoice;
5164
+ 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();
5165
+ const call = async (method, path, params, key3) => {
5166
+ const result = await stripeRequest8({ secretKey }, method, path, params, key3 ? { idempotencyKey: key3 } : {});
5167
+ if (!result.ok || !result.body) return fail4("named_seat_refund_provider_unavailable");
5168
+ return result.body;
5169
+ };
5170
+ const metadata = { ...stripePartitionMetadata(seat), applicationId: seat.applicationId };
5171
+ const sub = await call("GET", `/v1/subscriptions/${encodeURIComponent(seat.providerSubscriptionId)}`);
5172
+ 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();
5173
+ const invoice = await call("GET", `/v1/invoices/${encodeURIComponent(invoiceRef.invoiceId)}`);
5174
+ 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();
5175
+ let chargeId = id8(invoice.charge), paymentId = id8(invoice.payment_intent);
5176
+ if (!chargeId && !paymentId) {
5177
+ const payments = await call("GET", "/v1/invoice_payments", { invoice: invoice.id, status: "paid", limit: 100 });
5178
+ if (!Array.isArray(payments.data) || payments.data.length !== 1 || payments.has_more !== false) fail4();
5179
+ const payment = payments.data[0];
5180
+ if (id8(payment.invoice) !== invoice.id || payment.status !== "paid" || payment.payment?.type !== "payment_intent") fail4();
5181
+ paymentId = id8(payment.payment.payment_intent);
5182
+ }
5183
+ if (!chargeId) {
5184
+ if (!/^pi_[A-Za-z0-9_]+$/.test(paymentId)) fail4();
5185
+ const payment = await call("GET", `/v1/payment_intents/${encodeURIComponent(paymentId)}`);
5186
+ 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();
5187
+ chargeId = id8(payment.latest_charge);
5188
+ }
5189
+ if (!/^ch_[A-Za-z0-9_]+$/.test(chargeId)) fail4();
5190
+ const charge = await call("GET", `/v1/charges/${encodeURIComponent(chargeId)}`);
5191
+ 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();
5192
+ const items = sub.items.data.filter((item) => item.metadata?.seatId === seat.id);
5193
+ if (items.length > 1) fail4();
5194
+ if (items.length && sub.status !== "canceled") {
5195
+ const item = items[0];
5196
+ if (!/^si_[A-Za-z0-9_]+$/.test(item.id) || item.quantity !== 1 || item.price?.id !== seat.priceId) fail4();
5197
+ const removed = await call("DELETE", `/v1/subscription_items/${encodeURIComponent(item.id)}`, { proration_behavior: "none" });
5198
+ if (removed.id !== item.id || removed.deleted !== true) fail4("named_seat_renewal_removal_unconfirmed");
5199
+ }
5200
+ if (charge.amount_refunded === charge.amount && charge.refunded === true) return { refundedCents: seat.amountCents, refundIds: [] };
5201
+ const list = await call("GET", "/v1/refunds", { charge: chargeId, limit: 100 });
5202
+ if (!Array.isArray(list.data) || list.has_more !== false || list.data.length >= 100) fail4("named_seat_refund_history_incomplete");
5203
+ let refundedCents = 0;
5204
+ const refundIds = [];
5205
+ for (const refund of list.data) {
5206
+ 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();
5207
+ if (["pending", "requires_action"].includes(refund.status)) fail4("named_seat_refund_pending");
5208
+ if (refund.status === "succeeded") {
5209
+ if (refund.metadata?.seatId !== seat.id || refund.metadata?.membershipId !== seat.primaryMembershipId) fail4("named_seat_refund_allocation_ambiguous");
5210
+ refundedCents += refund.amount;
5211
+ refundIds.push(refund.id);
5212
+ }
5213
+ }
5214
+ if (refundedCents !== charge.amount_refunded || refundedCents > seat.amountCents) fail4();
5215
+ if (refundedCents < seat.amountCents) {
5216
+ const remaining = seat.amountCents - refundedCents;
5217
+ const refund = await call("POST", "/v1/refunds", {
5218
+ charge: chargeId,
5219
+ amount: remaining,
5220
+ reason: "requested_by_customer",
5221
+ metadata: { ...metadata, membershipId: seat.primaryMembershipId, seatId: seat.id }
5222
+ }, `combined-seat-refund:${seat.id}:${remaining}`);
5223
+ if (id8(refund.charge) !== chargeId || refund.amount !== remaining || refund.currency !== seat.currency || !/^re_[A-Za-z0-9_]+$/.test(refund.id)) fail4();
5224
+ if (refund.status !== "succeeded") fail4("named_seat_refund_pending");
5225
+ refundedCents += remaining;
5226
+ refundIds.push(refund.id);
5227
+ }
5228
+ return { refundedCents, refundIds };
5229
+ }
5230
+
5231
+ // src/membership-seat-refund-stripe.ts
5232
+ var fail5 = (code = "named_seat_refund_mismatch") => {
5233
+ throw Object.assign(new Error(code), { code });
5234
+ };
4993
5235
  async function refundNamedSeatStripePayment(secretKey, seat) {
4994
- if (!seat.providerPaymentIntentId || seat.status !== "refund_pending") fail4();
5236
+ if (seat.combinedInvoice) return refundCheckoutNamedSeat(secretKey, seat);
5237
+ if (!seat.providerPaymentIntentId || seat.status !== "refund_pending") fail5();
4995
5238
  const call = async (method, path, params, idempotencyKey) => {
4996
- const result = await stripeRequest8({ secretKey }, method, path, params, idempotencyKey ? { idempotencyKey } : {});
4997
- if (!result.ok || !result.body) return fail4("named_seat_refund_provider_unavailable");
5239
+ const result = await stripeRequest9({ secretKey }, method, path, params, idempotencyKey ? { idempotencyKey } : {});
5240
+ if (!result.ok || !result.body) return fail5("named_seat_refund_provider_unavailable");
4998
5241
  return result.body;
4999
5242
  };
5000
5243
  const metadata = { ...stripePartitionMetadata(seat), applicationId: seat.applicationId, membershipId: seat.primaryMembershipId, seatId: seat.id };
5001
5244
  const payment = await call("GET", `/v1/payment_intents/${encodeURIComponent(seat.providerPaymentIntentId)}`);
5002
- 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();
5245
+ 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();
5003
5246
  const sub = await call("GET", `/v1/subscriptions/${encodeURIComponent(seat.providerSubscriptionId)}`);
5004
- 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();
5247
+ 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();
5005
5248
  const items = sub.items.data.filter((item) => item.metadata?.seatId === seat.id);
5006
- if (items.length > 1) fail4();
5249
+ if (items.length > 1) fail5();
5007
5250
  if (items.length && sub.status !== "canceled") {
5008
5251
  const item = items[0];
5009
- if (!/^si_[A-Za-z0-9_]+$/.test(item.id) || item.price?.id !== seat.priceId || item.quantity !== 1) fail4();
5252
+ if (!/^si_[A-Za-z0-9_]+$/.test(item.id) || item.price?.id !== seat.priceId || item.quantity !== 1) fail5();
5010
5253
  const removed = await call("DELETE", `/v1/subscription_items/${encodeURIComponent(item.id)}`, { proration_behavior: "none" });
5011
- if (removed.id !== item.id || removed.deleted !== true) fail4("named_seat_renewal_removal_unconfirmed");
5254
+ if (removed.id !== item.id || removed.deleted !== true) fail5("named_seat_renewal_removal_unconfirmed");
5012
5255
  }
5013
5256
  const list = await call("GET", "/v1/refunds", { payment_intent: seat.providerPaymentIntentId, limit: 100 });
5014
- if (!Array.isArray(list.data) || list.has_more !== false || list.data.length >= 100) fail4("named_seat_refund_history_incomplete");
5257
+ if (!Array.isArray(list.data) || list.has_more !== false || list.data.length >= 100) fail5("named_seat_refund_history_incomplete");
5015
5258
  let refundedCents = 0, pendingCents = 0;
5016
5259
  const refundIds = [];
5017
5260
  for (const refund of list.data) {
5018
- 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();
5261
+ 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();
5019
5262
  if (refund.status === "succeeded") {
5020
5263
  refundedCents += refund.amount;
5021
5264
  refundIds.push(refund.id);
5022
5265
  } else if (refund.status === "pending" || refund.status === "requires_action") pendingCents += refund.amount;
5023
5266
  }
5024
- if (refundedCents + pendingCents > seat.amountCents) fail4();
5025
- if (pendingCents) fail4("named_seat_refund_pending");
5267
+ if (refundedCents + pendingCents > seat.amountCents) fail5();
5268
+ if (pendingCents) fail5("named_seat_refund_pending");
5026
5269
  if (refundedCents < seat.amountCents) {
5027
5270
  const remaining = seat.amountCents - refundedCents;
5028
5271
  const refund = await call("POST", "/v1/refunds", {
@@ -5031,8 +5274,8 @@ async function refundNamedSeatStripePayment(secretKey, seat) {
5031
5274
  reason: "requested_by_customer",
5032
5275
  metadata
5033
5276
  }, `named-seat-refund:${seat.id}:${remaining}`);
5034
- if (refund.payment_intent !== seat.providerPaymentIntentId || refund.amount !== remaining || refund.currency !== seat.currency || !/^re_[A-Za-z0-9_]+$/.test(refund.id)) fail4();
5035
- if (refund.status !== "succeeded") fail4("named_seat_refund_pending");
5277
+ if (refund.payment_intent !== seat.providerPaymentIntentId || refund.amount !== remaining || refund.currency !== seat.currency || !/^re_[A-Za-z0-9_]+$/.test(refund.id)) fail5();
5278
+ if (refund.status !== "succeeded") fail5("named_seat_refund_pending");
5036
5279
  refundedCents += remaining;
5037
5280
  refundIds.push(refund.id);
5038
5281
  }
@@ -5048,7 +5291,7 @@ async function requestNamedSeatRefund(db, input, now) {
5048
5291
  const member = await membershipLoad(db, "networkMembership", String(seat.primaryMembershipId));
5049
5292
  requireMembershipScope(member, input);
5050
5293
  if (seat.status === "refunded" || seat.status === "refund_pending") return;
5051
- 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");
5294
+ 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");
5052
5295
  const entitlement = seat.entitlementId ? await membershipLoad(db, "networkEntitlement", String(seat.entitlementId)) : void 0;
5053
5296
  await db.transact([
5054
5297
  membershipUpdate("networkSeat", input.seatId, { status: "refund_pending", refundReason: input.reason, revision: Number(seat.revision) + 1, updatedAt: now }),
@@ -5153,14 +5396,14 @@ var at = (value) => Number.isSafeInteger(value) ? Number(value) : void 0;
5153
5396
  async function readNamedSeatOffer(db, input, now) {
5154
5397
  membershipClock(now);
5155
5398
  await loadMembershipAuthorityState(db, input);
5156
- const id10 = await networkMembershipId(input), member = await membershipLoad(db, "networkMembership", id10);
5399
+ const id11 = await networkMembershipId(input), member = await membershipLoad(db, "networkMembership", id11);
5157
5400
  requireMembershipScope(member, input);
5158
5401
  const offering = membershipOfferingVersion(member.offeringSnapshot);
5159
5402
  const seat = member.namedSeatId ? await membershipLoad(db, "networkSeat", String(member.namedSeatId)) : void 0;
5160
5403
  if (seat) requireMembershipScope(seat, input);
5161
5404
  const purchaseEndsAt = seatPurchaseDeadline(offering, member);
5162
5405
  const offer = {
5163
- membershipId: id10,
5406
+ membershipId: id11,
5164
5407
  amountCents: offering.seatPriceCents,
5165
5408
  currency: offering.currency,
5166
5409
  interval: offering.interval,
@@ -5438,9 +5681,14 @@ function createMembershipAuthorityHandler(options) {
5438
5681
  try {
5439
5682
  input = JSON.parse(verified.body);
5440
5683
  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] ?? []];
5441
- 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])))) {
5684
+ 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])))) {
5442
5685
  throw new TypeError("invalid request");
5443
5686
  }
5687
+ if (action === "checkouts" && input.namedSeat !== void 0) {
5688
+ const seat = input.namedSeat;
5689
+ 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");
5690
+ checkoutNamedSeatSelection(seat, seat.purchaserEmail);
5691
+ }
5444
5692
  } catch {
5445
5693
  return respond({ error: "invalid_offer_request" }, 400);
5446
5694
  }
@@ -5513,8 +5761,9 @@ function createMembershipAuthorityHandler(options) {
5513
5761
  offerId: String(input.offerId),
5514
5762
  offerDigest: String(input.offerDigest),
5515
5763
  quoteDigest: String(input.quoteDigest),
5516
- priceId: String(input.priceId)
5517
- }, options.now?.() ?? Date.now());
5764
+ priceId: String(input.priceId),
5765
+ ...input.namedSeat ? { namedSeat: input.namedSeat } : {}
5766
+ }, options.now?.() ?? Date.now(), peer.seatPriceId);
5518
5767
  return respond({ ok: true, binding });
5519
5768
  }
5520
5769
  const offerId = await membershipOfferIdForRequest({ ...peer, applicationId: String(input.applicationId), requestId: String(input.requestId) });
@@ -5617,7 +5866,8 @@ async function requestMembershipCheckoutBinding(options) {
5617
5866
  offerId: options.offerId,
5618
5867
  offerDigest: options.offerDigest,
5619
5868
  quoteDigest: options.quoteDigest,
5620
- priceId: options.priceId
5869
+ priceId: options.priceId,
5870
+ ...options.namedSeat ? { namedSeat: options.namedSeat } : {}
5621
5871
  });
5622
5872
  return verifyMembershipCheckoutBinding(result.binding, options);
5623
5873
  }
@@ -5827,7 +6077,7 @@ async function requestMembershipCancellation(options) {
5827
6077
  }
5828
6078
 
5829
6079
  // src/membership-seat-cancel.ts
5830
- import { initStripe, stripeRequest as stripeRequest9 } from "@odla-ai/stripe";
6080
+ import { initStripe, stripeRequest as stripeRequest10 } from "@odla-ai/stripe";
5831
6081
  async function cancelUnpaidNamedSeat(db, target, secretKey, now) {
5832
6082
  membershipClock(now);
5833
6083
  const state = await loadMembershipAuthorityState(db, target);
@@ -5855,7 +6105,7 @@ async function cancelUnpaidNamedSeat(db, target, secretKey, now) {
5855
6105
  }
5856
6106
  const metadata = { ...stripePartitionMetadata(target), applicationId: target.applicationId, membershipId: seat.primaryMembershipId, seatId: seat.id };
5857
6107
  const request = async (method, path, params) => {
5858
- const result = await stripeRequest9(
6108
+ const result = await stripeRequest10(
5859
6109
  { secretKey },
5860
6110
  method,
5861
6111
  path,
@@ -5881,9 +6131,9 @@ async function cancelUnpaidNamedSeat(db, target, secretKey, now) {
5881
6131
  if (payment.status === "succeeded" || payment.amount_received > 0) throw new Error("paid seat requires payment evidence and full refund");
5882
6132
  if (payment.status !== "canceled") {
5883
6133
  if (!["requires_payment_method", "requires_confirmation", "requires_action", "processing"].includes(payment.status)) throw new Error("seat payment outcome unresolved");
5884
- const id10 = payment.id;
5885
- payment = await request("POST", `/v1/payment_intents/${encodeURIComponent(id10)}/cancel`, { cancellation_reason: "abandoned" });
5886
- if (payment.id !== id10) throw new Error("seat cancellation receipt mismatch");
6134
+ const id11 = payment.id;
6135
+ payment = await request("POST", `/v1/payment_intents/${encodeURIComponent(id11)}/cancel`, { cancellation_reason: "abandoned" });
6136
+ if (payment.id !== id11) throw new Error("seat cancellation receipt mismatch");
5887
6137
  }
5888
6138
  if (!valid(payment) || payment.status !== "canceled" || payment.amount_received !== 0) throw new Error("seat cancellation unconfirmed");
5889
6139
  await db.transact([membershipUpdate("networkSeat", target.seatId, { status: "canceled", revision: Number(seat.revision) + 1, updatedAt: now })], {
@@ -5983,11 +6233,11 @@ async function drainMembershipEffects(db, options, now) {
5983
6233
 
5984
6234
  // src/schema-membership-offers.ts
5985
6235
  var attr8 = (type, optional = false, unique = false, indexed = false) => ({ type, optional, unique, indexed });
5986
- var id8 = () => attr8("string", false, true, true);
6236
+ var id9 = () => attr8("string", false, true, true);
5987
6237
  var key = () => attr8("string", false, false, true);
5988
6238
  var membershipOfferEntities = {
5989
6239
  membershipAuthorityState: { attrs: {
5990
- id: id8(),
6240
+ id: id9(),
5991
6241
  chapterId: key(),
5992
6242
  environment: key(),
5993
6243
  mode: key(),
@@ -5997,7 +6247,7 @@ var membershipOfferEntities = {
5997
6247
  updatedAt: attr8("number")
5998
6248
  } },
5999
6249
  membershipCheckoutOffer: { attrs: {
6000
- id: id8(),
6250
+ id: id9(),
6001
6251
  applicationId: key(),
6002
6252
  appId: key(),
6003
6253
  chapterId: key(),
@@ -6012,7 +6262,7 @@ var membershipOfferEntities = {
6012
6262
  authorityRevision: attr8("number")
6013
6263
  } },
6014
6264
  membershipOfferCheckout: { attrs: {
6015
- id: id8(),
6265
+ id: id9(),
6016
6266
  appId: key(),
6017
6267
  chapterId: key(),
6018
6268
  environment: key(),
@@ -6023,20 +6273,21 @@ var membershipOfferEntities = {
6023
6273
  offerDigest: attr8("string"),
6024
6274
  quoteDigest: attr8("string"),
6025
6275
  priceId: attr8("string"),
6026
- boundAt: attr8("number")
6276
+ boundAt: attr8("number"),
6277
+ namedSeat: attr8("json", true)
6027
6278
  } }
6028
6279
  };
6029
6280
  var membershipOfferRules = Object.fromEntries(Object.keys(membershipOfferEntities).map((ns) => [ns, { view: "false", create: "false", update: "false", delete: "false" }]));
6030
6281
 
6031
6282
  // src/schema-membership-authority.ts
6032
6283
  var attr9 = (type, optional = false, unique = false, indexed = false) => ({ type, optional, unique, indexed });
6033
- var id9 = () => attr9("string", false, true, true);
6284
+ var id10 = () => attr9("string", false, true, true);
6034
6285
  var key2 = (optional = false) => attr9("string", optional, false, true);
6035
6286
  var scope = () => ({ appId: key2(), chapterId: key2(), environment: key2(), runtime: key2(), applicationId: key2(), skuId: key2() });
6036
6287
  var times = () => ({ revision: attr9("number"), createdAt: attr9("number"), updatedAt: attr9("number") });
6037
6288
  var membershipAuthorityEntities = {
6038
6289
  networkMembership: { attrs: {
6039
- id: id9(),
6290
+ id: id10(),
6040
6291
  membershipId: key2(),
6041
6292
  ...scope(),
6042
6293
  personId: key2(true),
@@ -6076,7 +6327,7 @@ var membershipAuthorityEntities = {
6076
6327
  restartId: key2(true)
6077
6328
  } },
6078
6329
  membershipRestart: { attrs: {
6079
- id: id9(),
6330
+ id: id10(),
6080
6331
  ...scope(),
6081
6332
  membershipId: key2(),
6082
6333
  restartId: key2(),
@@ -6086,7 +6337,7 @@ var membershipAuthorityEntities = {
6086
6337
  createdAt: attr9("number")
6087
6338
  } },
6088
6339
  membershipPurchaseHistory: { attrs: {
6089
- id: id9(),
6340
+ id: id10(),
6090
6341
  ...scope(),
6091
6342
  membershipId: key2(),
6092
6343
  providerSubscriptionId: key2(),
@@ -6095,7 +6346,7 @@ var membershipAuthorityEntities = {
6095
6346
  archivedAt: attr9("number")
6096
6347
  } },
6097
6348
  networkEntitlement: { attrs: {
6098
- id: id9(),
6349
+ id: id10(),
6099
6350
  ...scope(),
6100
6351
  membershipId: key2(),
6101
6352
  seatId: key2(true),
@@ -6112,10 +6363,12 @@ var membershipAuthorityEntities = {
6112
6363
  journeyApprovedAt: attr9("number", true)
6113
6364
  } },
6114
6365
  networkSeat: { attrs: {
6115
- id: id9(),
6366
+ id: id10(),
6116
6367
  ...scope(),
6117
6368
  primaryMembershipId: key2(),
6118
- purchaserUserId: key2(),
6369
+ purchaserUserId: key2(true),
6370
+ purchaserEmailNormalized: key2(true),
6371
+ combinedInvoice: attr9("json", true),
6119
6372
  recipientName: attr9("string"),
6120
6373
  recipientEmailNormalized: key2(),
6121
6374
  recipientClaimId: key2(),
@@ -6147,9 +6400,9 @@ var membershipAuthorityEntities = {
6147
6400
  refundedCents: attr9("number", true),
6148
6401
  providerRefundIds: attr9("json", true)
6149
6402
  } },
6150
- networkSeatRecipient: { attrs: { id: id9(), appId: key2(), chapterId: key2(), environment: key2(), runtime: key2(), seatId: key2(), recipientEmailNormalized: key2(), createdAt: attr9("number") } },
6403
+ networkSeatRecipient: { attrs: { id: id10(), appId: key2(), chapterId: key2(), environment: key2(), runtime: key2(), seatId: key2(), recipientEmailNormalized: key2(), createdAt: attr9("number") } },
6151
6404
  membershipEffect: { attrs: {
6152
- id: id9(),
6405
+ id: id10(),
6153
6406
  chapterId: key2(),
6154
6407
  environment: key2(),
6155
6408
  runtime: key2(),
@@ -6274,14 +6527,14 @@ function validateMemberContentDocument(input, options = {}) {
6274
6527
  continue;
6275
6528
  }
6276
6529
  unknownKeys(raw2, PAGE_KEYS, path, errors);
6277
- const id10 = textValue(raw2.id, `${path}.id`, errors, 80, true) ?? "";
6530
+ const id11 = textValue(raw2.id, `${path}.id`, errors, 80, true) ?? "";
6278
6531
  const slug = textValue(raw2.slug, `${path}.slug`, errors, 120, true) ?? "";
6279
6532
  const title = textValue(raw2.title, `${path}.title`, errors, MAX_TEXT, true) ?? "";
6280
- if (id10 && !ID.test(id10)) errors.push(`${path}.id must be a lowercase kebab-case id`);
6533
+ if (id11 && !ID.test(id11)) errors.push(`${path}.id must be a lowercase kebab-case id`);
6281
6534
  if (slug && !SLUG8.test(slug)) errors.push(`${path}.slug must be a lowercase URL slug`);
6282
- if (ids2.has(id10) || knownParentIds.has(id10)) errors.push(`${path}.id duplicates page "${id10}"`);
6535
+ if (ids2.has(id11) || knownParentIds.has(id11)) errors.push(`${path}.id duplicates page "${id11}"`);
6283
6536
  if (slugs.has(slug) || (options.parentPages ?? []).some((page) => page.slug === slug)) errors.push(`${path}.slug duplicates "${slug}"`);
6284
- ids2.add(id10);
6537
+ ids2.add(id11);
6285
6538
  slugs.add(slug);
6286
6539
  const navRaw = record(raw2.navigation);
6287
6540
  let navigation = { section: "General", order: index2 };
@@ -6337,7 +6590,7 @@ function validateMemberContentDocument(input, options = {}) {
6337
6590
  else if (owner === "chapter") upsell = { owner, ...offer ? { offer } : {} };
6338
6591
  }
6339
6592
  }
6340
- pages.push({ id: id10, slug, title, ...summary ? { summary } : {}, navigation, audience, ...teaserMarkdown ? { teaserMarkdown } : {}, bodyMarkdown, ...upsell ? { upsell } : {} });
6593
+ pages.push({ id: id11, slug, title, ...summary ? { summary } : {}, navigation, audience, ...teaserMarkdown ? { teaserMarkdown } : {}, bodyMarkdown, ...upsell ? { upsell } : {} });
6341
6594
  }
6342
6595
  const allIds = /* @__PURE__ */ new Set([...knownParentIds, ...ids2]);
6343
6596
  for (const [index2, page] of pages.entries()) {
@@ -6543,8 +6796,8 @@ function resourceValue(value, path, errors) {
6543
6796
  return null;
6544
6797
  }
6545
6798
  unknownKeys2(raw2, RESOURCE_KEYS, path, errors);
6546
- const id10 = text3(raw2.id, `${path}.id`, errors, 80) ?? "";
6547
- if (id10 && !ID2.test(id10)) errors.push(`${path}.id must be lowercase kebab-case`);
6799
+ const id11 = text3(raw2.id, `${path}.id`, errors, 80) ?? "";
6800
+ if (id11 && !ID2.test(id11)) errors.push(`${path}.id must be lowercase kebab-case`);
6548
6801
  const kind = text3(raw2.kind, `${path}.kind`, errors) ?? "";
6549
6802
  if (!KINDS.has(kind)) errors.push(`${path}.kind is unsupported`);
6550
6803
  const title = text3(raw2.title, `${path}.title`, errors, 240) ?? "";
@@ -6601,7 +6854,7 @@ function resourceValue(value, path, errors) {
6601
6854
  updatedAt: integer3(provenanceRaw?.updatedAt, `${path}.provenance.updatedAt`, errors, 0),
6602
6855
  ...promotedFrom ? { promotedFrom } : {}
6603
6856
  };
6604
- return { id: id10, kind, title, ...summary ? { summary } : {}, tags, scope: scope2, status: status2, revision, ...bodyMarkdown ? { bodyMarkdown } : {}, ...url ? { url } : {}, ...brandAsset ? { brandAsset } : {}, provenance };
6857
+ return { id: id11, kind, title, ...summary ? { summary } : {}, tags, scope: scope2, status: status2, revision, ...bodyMarkdown ? { bodyMarkdown } : {}, ...url ? { url } : {}, ...brandAsset ? { brandAsset } : {}, provenance };
6605
6858
  }
6606
6859
  function validateBuilderLibraryDocument(value, options = {}) {
6607
6860
  const errors = [];
@@ -6632,7 +6885,7 @@ function validateBuilderLibraryDocument(value, options = {}) {
6632
6885
  const inputs = Array.isArray(raw2.resources) ? raw2.resources : [];
6633
6886
  if (inputs.length > MAX_RESOURCES) errors.push(`document.resources may contain at most ${MAX_RESOURCES} items`);
6634
6887
  const resources = inputs.map((item, index2) => resourceValue(item, `document.resources[${index2}]`, errors)).filter(Boolean);
6635
- const ids2 = resources.map(({ id: id10 }) => id10);
6888
+ const ids2 = resources.map(({ id: id11 }) => id11);
6636
6889
  if (new Set(ids2).size !== ids2.length) errors.push("document.resources ids must be unique");
6637
6890
  for (const resource of resources) {
6638
6891
  if (sourceType === "chapter" && (resource.scope.kind !== "chapter" || resource.scope.chapterId !== sourceAppId)) {
@@ -6962,11 +7215,11 @@ async function sendTemplated(deps, input) {
6962
7215
  const previous = prior[0];
6963
7216
  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" };
6964
7217
  const hash2 = await crypto.subtle.digest("SHA-256", new TextEncoder().encode(input.dedupeKey));
6965
- const id10 = previous ? String(previous.id) : `email_${Array.from(new Uint8Array(hash2), (b) => b.toString(16).padStart(2, "0")).join("")}`;
7218
+ const id11 = previous ? String(previous.id) : `email_${Array.from(new Uint8Array(hash2), (b) => b.toString(16).padStart(2, "0")).join("")}`;
6966
7219
  const attempt = Number(previous?.deliveryAttempt ?? 0) + 1;
6967
7220
  if (!Number.isSafeInteger(attempt) || attempt < 1) return { sent: false, reason: "delivery-state-invalid" };
6968
7221
  const row2 = {
6969
- id: id10,
7222
+ id: id11,
6970
7223
  groupId: input.group.id,
6971
7224
  to: decision.to,
6972
7225
  template: input.template,
@@ -6984,15 +7237,15 @@ async function sendTemplated(deps, input) {
6984
7237
  if (isRetiredEmailCopy(String(row2.subject), String(row2.body))) return { sent: false, reason: "template-retired" };
6985
7238
  if (validateEmailCopy3({ subject: row2.subject, text: row2.body }, { allowedVariables: [] }).length) return { sent: false, reason: "template-invalid" };
6986
7239
  try {
6987
- const claim = await deps.db.transact([{ t: "update", ns: "emailLog", id: id10, attrs: row2 }], {
6988
- mutationId: `email-claim:${id10}:${attempt}`,
7240
+ const claim = await deps.db.transact([{ t: "update", ns: "emailLog", id: id11, attrs: row2 }], {
7241
+ mutationId: `email-claim:${id11}:${attempt}`,
6989
7242
  guards: [previous ? {
6990
7243
  ns: "emailLog",
6991
- id: id10,
7244
+ id: id11,
6992
7245
  exists: true,
6993
7246
  equals: { deliveryState: "retry_ready", ...previous.deliveryAttempt !== void 0 ? { deliveryAttempt: previous.deliveryAttempt } : {} },
6994
7247
  ...previous.deliveryAttempt === void 0 ? { absent: ["deliveryAttempt"] } : {}
6995
- } : { ns: "emailLog", id: id10, exists: false }]
7248
+ } : { ns: "emailLog", id: id11, exists: false }]
6996
7249
  });
6997
7250
  if (claim.duplicate) return { sent: false, reason: "delivery-uncertain" };
6998
7251
  } catch (error) {
@@ -7012,16 +7265,16 @@ async function sendTemplated(deps, input) {
7012
7265
  if (!response || typeof response.messageId !== "string" || !response.messageId) throw new Error("missing delivery receipt");
7013
7266
  messageId = response.messageId;
7014
7267
  } catch {
7015
- await deps.db.transact([{ t: "update", ns: "emailLog", id: id10, attrs: { deliveryState: "uncertain", error: "delivery-uncertain" } }], {
7016
- guards: [{ ns: "emailLog", id: id10, exists: true, equals: { deliveryState: "sending", deliveryAttempt: attempt } }]
7268
+ await deps.db.transact([{ t: "update", ns: "emailLog", id: id11, attrs: { deliveryState: "uncertain", error: "delivery-uncertain" } }], {
7269
+ guards: [{ ns: "emailLog", id: id11, exists: true, equals: { deliveryState: "sending", deliveryAttempt: attempt } }]
7017
7270
  });
7018
7271
  return { sent: false, reason: "delivery-uncertain" };
7019
7272
  }
7020
- await deps.db.transact([{ t: "update", ns: "emailLog", id: id10, attrs: { deliveryState: "sent", error: "", messageId, sentAt: deps.now() } }], {
7021
- mutationId: `email-confirmed:${id10}:${attempt}`,
7273
+ await deps.db.transact([{ t: "update", ns: "emailLog", id: id11, attrs: { deliveryState: "sent", error: "", messageId, sentAt: deps.now() } }], {
7274
+ mutationId: `email-confirmed:${id11}:${attempt}`,
7022
7275
  guards: [{
7023
7276
  ns: "emailLog",
7024
- id: id10,
7277
+ id: id11,
7025
7278
  exists: true,
7026
7279
  equals: { deliveryState: "sending", deliveryAttempt: attempt }
7027
7280
  }]
@@ -7279,14 +7532,14 @@ function validateAdmissionGrantDocument(input) {
7279
7532
  const seenEmails = /* @__PURE__ */ new Set();
7280
7533
  for (const [index2, raw2] of rawGrants.slice(0, MAX_GRANTS).entries()) {
7281
7534
  const grant = raw2 && typeof raw2 === "object" ? raw2 : {};
7282
- const id10 = typeof grant.id === "string" ? grant.id : "";
7535
+ const id11 = typeof grant.id === "string" ? grant.id : "";
7283
7536
  const email = typeof grant.email === "string" ? grant.email.trim() : "";
7284
7537
  const normalized = normalizeAdmissionEmail(email);
7285
7538
  const decision = grant.decision;
7286
7539
  const note = typeof grant.note === "string" ? grant.note.trim() : void 0;
7287
7540
  const expiresAt = grant.expiresAt === void 0 ? void 0 : Number(grant.expiresAt);
7288
- if (!ID3.test(id10)) errors.push(`document.grants[${index2}].id must be an identifier`);
7289
- if (seenIds.has(id10)) errors.push(`document.grants repeats id "${id10}"`);
7541
+ if (!ID3.test(id11)) errors.push(`document.grants[${index2}].id must be an identifier`);
7542
+ if (seenIds.has(id11)) errors.push(`document.grants repeats id "${id11}"`);
7290
7543
  if (!isAdmissionEmail(normalized)) errors.push(`document.grants[${index2}].email must be valid`);
7291
7544
  if (seenEmails.has(normalized)) errors.push(`document.grants repeats email "${normalized}"`);
7292
7545
  if (decision !== "waive_interview" && decision !== "require_interview") {
@@ -7296,10 +7549,10 @@ function validateAdmissionGrantDocument(input) {
7296
7549
  if (expiresAt !== void 0 && (!Number.isFinite(expiresAt) || expiresAt <= 0)) {
7297
7550
  errors.push(`document.grants[${index2}].expiresAt must be epoch milliseconds`);
7298
7551
  }
7299
- seenIds.add(id10);
7552
+ seenIds.add(id11);
7300
7553
  seenEmails.add(normalized);
7301
7554
  grants.push({
7302
- id: id10,
7555
+ id: id11,
7303
7556
  email,
7304
7557
  decision,
7305
7558
  ...note ? { note } : {},
@@ -7371,8 +7624,8 @@ async function materializeAdmissionGrants(db, envelope, previous, receivedAt) {
7371
7624
  const incomingIds = new Set(document.grants.map((grant) => `admission:parent:${document.source.appId}:${grant.id}`));
7372
7625
  const ops = [];
7373
7626
  for (const row2 of current) {
7374
- const id10 = String(row2.id ?? "");
7375
- if (id10 && !incomingIds.has(id10)) ops.push({ t: "update", ns: "admissionGrants", id: id10, attrs: {
7627
+ const id11 = String(row2.id ?? "");
7628
+ if (id11 && !incomingIds.has(id11)) ops.push({ t: "update", ns: "admissionGrants", id: id11, attrs: {
7376
7629
  active: false,
7377
7630
  revokedAt: receivedAt,
7378
7631
  revokedBy: document.source.actor,
@@ -7381,10 +7634,10 @@ async function materializeAdmissionGrants(db, envelope, previous, receivedAt) {
7381
7634
  } });
7382
7635
  }
7383
7636
  for (const grant of document.grants) {
7384
- const id10 = `admission:parent:${document.source.appId}:${grant.id}`;
7385
- const existing = current.find((row2) => row2.id === id10);
7386
- ops.push({ t: "update", ns: "admissionGrants", id: id10, attrs: {
7387
- id: id10,
7637
+ const id11 = `admission:parent:${document.source.appId}:${grant.id}`;
7638
+ const existing = current.find((row2) => row2.id === id11);
7639
+ ops.push({ t: "update", ns: "admissionGrants", id: id11, attrs: {
7640
+ id: id11,
7388
7641
  sourceGrantId: grant.id,
7389
7642
  sourceScope: "parent",
7390
7643
  sourceId: document.source.appId,
@@ -7405,7 +7658,7 @@ async function materializeAdmissionGrants(db, envelope, previous, receivedAt) {
7405
7658
  ...!grant.note && existing?.note !== void 0 ? ["note"] : [],
7406
7659
  ...grant.expiresAt === void 0 && existing?.expiresAt !== void 0 ? ["expiresAt"] : []
7407
7660
  ];
7408
- if (retract.length) ops.push({ t: "retract", ns: "admissionGrants", id: id10, attrs: retract });
7661
+ if (retract.length) ops.push({ t: "retract", ns: "admissionGrants", id: id11, attrs: retract });
7409
7662
  }
7410
7663
  if (previous?.active) ops.push({ t: "update", ns: "admissionGrantRevisions", id: previous.id, attrs: { active: false } });
7411
7664
  const revisionId = `admission-revision:${document.source.appId}:${document.target.environment}:${document.revision}`;
@@ -7476,10 +7729,10 @@ function validateGiftOfferCatalog(input, tierIds) {
7476
7729
  }
7477
7730
  const allowed2 = /* @__PURE__ */ new Set(["id", "target", "billingMode", "stripePriceId", "priceCents", "currency", "redemptionLifetimeHours", "activeFrom", "activeUntil", "title", "description", "ctaLabel"]);
7478
7731
  for (const key3 of Object.keys(item)) if (!allowed2.has(key3)) errors.push(`${path}.${key3} is not supported`);
7479
- const id10 = text4(item.id, `${path}.id`, errors, 80);
7480
- if (id10 && !ID4.test(id10)) errors.push(`${path}.id must be a lowercase kebab-case id`);
7481
- if (ids2.has(id10)) errors.push(`${path}.id duplicates "${id10}"`);
7482
- ids2.add(id10);
7732
+ const id11 = text4(item.id, `${path}.id`, errors, 80);
7733
+ if (id11 && !ID4.test(id11)) errors.push(`${path}.id must be a lowercase kebab-case id`);
7734
+ if (ids2.has(id11)) errors.push(`${path}.id duplicates "${id11}"`);
7735
+ ids2.add(id11);
7483
7736
  const targetRaw = record3(item.target);
7484
7737
  let target = { type: "entitlement", entitlementKey: "invalid" };
7485
7738
  if (targetRaw?.type === "tier") {
@@ -7505,7 +7758,7 @@ function validateGiftOfferCatalog(input, tierIds) {
7505
7758
  if (activeUntil !== void 0 && (!Number.isSafeInteger(activeUntil) || activeUntil < 1)) errors.push(`${path}.activeUntil must be epoch milliseconds`);
7506
7759
  if (activeFrom !== void 0 && activeUntil !== void 0 && activeUntil <= activeFrom) errors.push(`${path}.activeUntil must follow activeFrom`);
7507
7760
  offers.push({
7508
- id: id10,
7761
+ id: id11,
7509
7762
  target,
7510
7763
  billingMode: item.billingMode,
7511
7764
  stripePriceId,
@@ -7554,10 +7807,10 @@ async function ensureProjectedPerson(deps, input, options = {}) {
7554
7807
  const existing = await find();
7555
7808
  if (existing) return { record: existing, created: false };
7556
7809
  const digest = new Uint8Array(await crypto.subtle.digest("SHA-256", new TextEncoder().encode(`chapter:person:v1:${email}`)));
7557
- const id10 = `person-${Array.from(digest, (byte) => byte.toString(16).padStart(2, "0")).join("")}`;
7810
+ const id11 = `person-${Array.from(digest, (byte) => byte.toString(16).padStart(2, "0")).join("")}`;
7558
7811
  try {
7559
7812
  const created = await createRecord({ ...deps, db: deps.db }, {
7560
- id: id10,
7813
+ id: id11,
7561
7814
  type: "person",
7562
7815
  input: { ...input, email },
7563
7816
  ...options
@@ -7980,9 +8233,9 @@ function subAnnualCents(sub) {
7980
8233
  }
7981
8234
 
7982
8235
  // src/payments-stripe.ts
7983
- import { stripeRequest as stripeRequest10, verifyStripeSignature as verifyStripeSignature2 } from "@odla-ai/stripe";
8236
+ import { stripeRequest as stripeRequest11, verifyStripeSignature as verifyStripeSignature2 } from "@odla-ai/stripe";
7984
8237
  async function stripeCall(sk, method, path, params, idempotencyKey) {
7985
- return stripeRequest10(
8238
+ return stripeRequest11(
7986
8239
  { secretKey: sk },
7987
8240
  method,
7988
8241
  path,
@@ -8060,8 +8313,8 @@ async function updateClerkUserMetadata(secretKey, userId, input, fetchImpl = fet
8060
8313
  return res.ok;
8061
8314
  }
8062
8315
  async function updateClerkUserMetadataByEmail(secretKey, email, input, fetchImpl = fetch) {
8063
- const id10 = await existingClerkUserId(secretKey, email, fetchImpl);
8064
- return id10 ? updateClerkUserMetadata(secretKey, id10, input, fetchImpl) : false;
8316
+ const id11 = await existingClerkUserId(secretKey, email, fetchImpl);
8317
+ return id11 ? updateClerkUserMetadata(secretKey, id11, input, fetchImpl) : false;
8065
8318
  }
8066
8319
  async function existingClerkUserId(secretKey, email, fetchImpl) {
8067
8320
  const auth = { authorization: `Bearer ${secretKey}` };
@@ -8134,10 +8387,10 @@ async function clerkGetUserByEmail(secretKey, email, fetchImpl = fetch, source =
8134
8387
  const user = Array.isArray(data) ? data[0] : void 0;
8135
8388
  return user ? toRecord(user, source) : null;
8136
8389
  }
8137
- async function clerkGetUser(secretKey, id10, fetchImpl = fetch, source = "public") {
8138
- const data = await clerkGet(`/v1/users/${encodeURIComponent(id10)}`, secretKey, fetchImpl).catch(() => null);
8390
+ async function clerkGetUser(secretKey, id11, fetchImpl = fetch, source = "public") {
8391
+ const data = await clerkGet(`/v1/users/${encodeURIComponent(id11)}`, secretKey, fetchImpl).catch(() => null);
8139
8392
  const record4 = data ? toRecord(data, source) : null;
8140
- return record4?.id === id10 ? record4 : null;
8393
+ return record4?.id === id11 ? record4 : null;
8141
8394
  }
8142
8395
  async function clerkListUsers(secretKey, fetchImpl = fetch, source = "public") {
8143
8396
  const out = [];
@@ -8153,8 +8406,8 @@ async function clerkListUsers(secretKey, fetchImpl = fetch, source = "public") {
8153
8406
  }
8154
8407
  return out;
8155
8408
  }
8156
- async function clerkSetRole(secretKey, id10, role, fetchImpl = fetch, source = "public") {
8157
- const res = await fetchImpl(`${CLERK_API}/v1/users/${encodeURIComponent(id10)}/metadata`, {
8409
+ async function clerkSetRole(secretKey, id11, role, fetchImpl = fetch, source = "public") {
8410
+ const res = await fetchImpl(`${CLERK_API}/v1/users/${encodeURIComponent(id11)}/metadata`, {
8158
8411
  method: "PATCH",
8159
8412
  headers: { authorization: `Bearer ${secretKey}`, "content-type": "application/json" },
8160
8413
  redirect: "manual",
@@ -8171,7 +8424,7 @@ function planPrivateRoleMigration(input) {
8171
8424
  if (input.environment !== "dev") blockers.push("dev_only");
8172
8425
  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");
8173
8426
  const expected = new Set(input.expectedRuntimes);
8174
- if (!expected.size || expected.size !== input.expectedRuntimes.length || input.expectedRuntimes.some((id10) => !id10)) blockers.push("runtime_inventory_required");
8427
+ if (!expected.size || expected.size !== input.expectedRuntimes.length || input.expectedRuntimes.some((id11) => !id11)) blockers.push("runtime_inventory_required");
8175
8428
  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");
8176
8429
  for (const runtime of input.expectedRuntimes) {
8177
8430
  const proof = input.runtimes.find((r) => r.runtime === runtime);
@@ -8201,7 +8454,7 @@ function planPrivateRoleMigration(input) {
8201
8454
  if (privateRole === role && !Object.hasOwn(user.publicMetadata, "role")) alreadyCurrent++;
8202
8455
  else changes.push({ userId: user.id, role, publicRole, privateRole });
8203
8456
  }
8204
- if (!input.protectedSuperAdminUserIds.some((id10) => ids2.has(id10))) blockers.push("protected_operator_grant_required");
8457
+ if (!input.protectedSuperAdminUserIds.some((id11) => ids2.has(id11))) blockers.push("protected_operator_grant_required");
8205
8458
  return { ready: blockers.length === 0, blockers, changes, alreadyCurrent, totalUsers: input.users.length };
8206
8459
  }
8207
8460
 
@@ -8779,10 +9032,10 @@ function tiersOf(value, errors) {
8779
9032
  return [];
8780
9033
  }
8781
9034
  unknownKeys3(item, TIER_KEYS2, path, errors);
8782
- const id10 = text5(item.id, `${path}.id`, errors, 80);
8783
- if (id10 && !TIER.test(id10)) errors.push(`${path}.id must be a lowercase kebab-case id`);
8784
- if (seen.has(id10)) errors.push(`${path}.id duplicates "${id10}"`);
8785
- seen.add(id10);
9035
+ const id11 = text5(item.id, `${path}.id`, errors, 80);
9036
+ if (id11 && !TIER.test(id11)) errors.push(`${path}.id must be a lowercase kebab-case id`);
9037
+ if (seen.has(id11)) errors.push(`${path}.id duplicates "${id11}"`);
9038
+ seen.add(id11);
8786
9039
  const name = text5(item.name, `${path}.name`, errors, 160);
8787
9040
  if (typeof item.available !== "boolean") errors.push(`${path}.available must be boolean`);
8788
9041
  if (!Number.isInteger(item.sortOrder) || Number(item.sortOrder) < 0) errors.push(`${path}.sortOrder must be a non-negative integer`);
@@ -8803,7 +9056,7 @@ function tiersOf(value, errors) {
8803
9056
  }
8804
9057
  const blurb = item.blurb === void 0 ? void 0 : text5(item.blurb, `${path}.blurb`, errors, 4e3);
8805
9058
  return [{
8806
- id: id10,
9059
+ id: id11,
8807
9060
  name,
8808
9061
  available: item.available === true,
8809
9062
  sortOrder: Number(item.sortOrder) || 0,
@@ -8865,15 +9118,15 @@ function policyOf(value, errors, tiers2) {
8865
9118
  if (input.tierPolicies !== void 0) {
8866
9119
  const overrides = recordOf(input.tierPolicies);
8867
9120
  if (!overrides || Object.keys(overrides).length > 20) errors.push("document.policy.tierPolicies must be a bounded tier map");
8868
- else for (const [id10, raw2] of Object.entries(overrides)) {
8869
- const path = `document.policy.tierPolicies.${id10}`;
9121
+ else for (const [id11, raw2] of Object.entries(overrides)) {
9122
+ const path = `document.policy.tierPolicies.${id11}`;
8870
9123
  const policy2 = recordOf(raw2);
8871
- if (!tiers2.some((tier) => tier.id === id10) || !policy2) {
9124
+ if (!tiers2.some((tier) => tier.id === id11) || !policy2) {
8872
9125
  errors.push(`${path} must name a declared tier and policy`);
8873
9126
  continue;
8874
9127
  }
8875
9128
  unknownKeys3(policy2, /* @__PURE__ */ new Set(["refundPolicyText", "trustCopy"]), path, errors);
8876
- tierPolicies[id10] = {
9129
+ tierPolicies[id11] = {
8877
9130
  refundPolicyText: text5(policy2.refundPolicyText, `${path}.refundPolicyText`, errors),
8878
9131
  ...policy2.trustCopy !== void 0 ? { trustCopy: text5(policy2.trustCopy, `${path}.trustCopy`, errors) } : {}
8879
9132
  };
@@ -9198,7 +9451,7 @@ var exclusions = (chapterId, paidTierIds) => [
9198
9451
  id: chapterId,
9199
9452
  reason: "provider_identity",
9200
9453
  paths: [
9201
- ...paidTierIds.map((id10) => `tiers[${id10}].stripePriceId`),
9454
+ ...paidTierIds.map((id11) => `tiers[${id11}].stripePriceId`),
9202
9455
  "groups.stripePublishableKey",
9203
9456
  "applications.stripeCustomerId",
9204
9457
  "applications.stripeSubscriptionId",
@@ -9240,9 +9493,9 @@ async function exportChapterPromotion(input) {
9240
9493
  sourceIds: [`signup-control:${document.revision}`, document.source.appId, document.target.chapterId].sort(),
9241
9494
  receiptDigests: [input.digest]
9242
9495
  };
9243
- const artifact = (kind, id10, content) => ({
9496
+ const artifact = (kind, id11, content) => ({
9244
9497
  kind,
9245
- id: id10,
9498
+ id: id11,
9246
9499
  sourceRevision: revision,
9247
9500
  provenance: promotionProvenance,
9248
9501
  content: canonicalPromotionValue(content)
@@ -9287,7 +9540,7 @@ var MEMBER_SYSTEM_RECORD_OWNERS = {
9287
9540
  provider: "originating-provider",
9288
9541
  protectedRoles: "odla-platform"
9289
9542
  };
9290
- var rows2 = (group) => group.fields.map((id10) => ({ ...group, id: id10 }));
9543
+ var rows2 = (group) => group.fields.map((id11) => ({ ...group, id: id11 }));
9291
9544
  var MEMBER_SYSTEM_AUTHORITY_MATRIX = [
9292
9545
  ...rows2({
9293
9546
  scope: "network",