@odla-ai/chapter 0.39.0 → 0.39.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -22,6 +22,11 @@ var index_exports = {};
22
22
  __export(index_exports, {
23
23
  ADMISSION_GRANTS_PATH: () => ADMISSION_GRANTS_PATH,
24
24
  ADMISSION_GRANTS_SCHEMA_VERSION: () => ADMISSION_GRANTS_SCHEMA_VERSION,
25
+ BUILDER_LIBRARY_ADMIN_PATH: () => BUILDER_LIBRARY_ADMIN_PATH,
26
+ BUILDER_LIBRARY_CONTROL_PATH: () => BUILDER_LIBRARY_CONTROL_PATH,
27
+ BUILDER_LIBRARY_MANIFEST_PATH: () => BUILDER_LIBRARY_MANIFEST_PATH,
28
+ BUILDER_LIBRARY_PROMOTE_PATH: () => BUILDER_LIBRARY_PROMOTE_PATH,
29
+ BUILDER_LIBRARY_SCHEMA_VERSION: () => BUILDER_LIBRARY_SCHEMA_VERSION,
25
30
  CHAPTER_FLOW_FIELDS_SCHEMA_VERSION: () => CHAPTER_FLOW_FIELDS_SCHEMA_VERSION,
26
31
  CHAPTER_PROMOTION_ADAPTER: () => CHAPTER_PROMOTION_ADAPTER,
27
32
  DEFAULT_CHAPTER_COPY: () => DEFAULT_CHAPTER_COPY,
@@ -46,13 +51,17 @@ __export(index_exports, {
46
51
  bookingDecision: () => bookingDecision,
47
52
  brandTokens: () => brandTokens,
48
53
  bucketSeries: () => bucketSeries,
54
+ buildBuilderLibraryManifest: () => buildBuilderLibraryManifest,
49
55
  buildGroupSeed: () => buildGroupSeed,
50
56
  buildTierSeeds: () => buildTierSeeds,
57
+ builderLibraryDigest: () => builderLibraryDigest,
51
58
  canApprove: () => canApprove,
52
59
  canBook: () => canBook,
53
60
  canChangeRole: () => canChangeRole,
54
61
  canTransition: () => canTransition,
62
+ canViewBuilderLibraryResource: () => canViewBuilderLibraryResource,
55
63
  canceledPatch: () => canceledPatch,
64
+ canonicalBuilderLibrary: () => canonicalBuilderLibrary,
56
65
  canonicalMemberContent: () => canonicalMemberContent,
57
66
  canonicalSignupControl: () => canonicalSignupControl,
58
67
  chapterBrandFromTokens: () => chapterBrandFromTokens,
@@ -68,6 +77,7 @@ __export(index_exports, {
68
77
  clerkUserRequest: () => clerkUserRequest,
69
78
  composeMemberContent: () => composeMemberContent,
70
79
  createAdmissionGrantEnvelope: () => createAdmissionGrantEnvelope,
80
+ createBuilderLibraryEnvelope: () => createBuilderLibraryEnvelope,
71
81
  createChapterIntegration: () => createChapterIntegration,
72
82
  createClerkInvitation: () => createClerkInvitation,
73
83
  createClerkUser: () => createClerkUser,
@@ -123,8 +133,10 @@ __export(index_exports, {
123
133
  personInputFromApp: () => personInputFromApp,
124
134
  planDelivery: () => planDelivery,
125
135
  projectApplicant: () => projectApplicant,
136
+ projectBuilderLibraryDelivery: () => projectBuilderLibraryDelivery,
126
137
  projectMemberContent: () => projectMemberContent,
127
138
  projectSharedRecord: () => projectSharedRecord,
139
+ promoteBuilderLibraryResource: () => promoteBuilderLibraryResource,
128
140
  publicGiftOffers: () => publicGiftOffers,
129
141
  reconcileMeetings: () => reconcileMeetings,
130
142
  refundedPatch: () => refundedPatch,
@@ -135,6 +147,7 @@ __export(index_exports, {
135
147
  renewalPatch: () => renewalPatch,
136
148
  resolveApplication: () => resolveApplication,
137
149
  resolveAuth: () => resolveAuth,
150
+ resolveBuilderLibrary: () => resolveBuilderLibrary,
138
151
  resolveChapterCopy: () => resolveChapterCopy,
139
152
  resolveJoinContinuation: () => resolveJoinContinuation,
140
153
  resolveLeaderFormation: () => resolveLeaderFormation,
@@ -161,6 +174,7 @@ __export(index_exports, {
161
174
  updateClerkUserMetadata: () => updateClerkUserMetadata,
162
175
  updateClerkUserMetadataByEmail: () => updateClerkUserMetadataByEmail,
163
176
  validateAdmissionGrantDocument: () => validateAdmissionGrantDocument,
177
+ validateBuilderLibraryDocument: () => validateBuilderLibraryDocument,
164
178
  validateGiftOfferCatalog: () => validateGiftOfferCatalog,
165
179
  validateMemberContentDocument: () => validateMemberContentDocument,
166
180
  validateMembershipPageDocument: () => validateMembershipPageDocument,
@@ -644,6 +658,30 @@ var memberContentLocal = {
644
658
  updatedBy: attr4("string", { optional: true })
645
659
  }
646
660
  };
661
+ var builderLibraryControls = {
662
+ attrs: {
663
+ id: id3(),
664
+ sourceId: attr4("string", { indexed: true }),
665
+ targetId: attr4("string", { indexed: true }),
666
+ environment: attr4("string", { indexed: true }),
667
+ revision: attr4("number", { indexed: true }),
668
+ digest: attr4("string", { indexed: true }),
669
+ document: attr4("json"),
670
+ active: attr4("boolean", { indexed: true }),
671
+ receivedAt: attr4("number", { indexed: true })
672
+ }
673
+ };
674
+ var builderLibraryLocal = {
675
+ attrs: {
676
+ id: id3(),
677
+ appId: attr4("string", { indexed: true }),
678
+ environment: attr4("string", { indexed: true }),
679
+ revision: attr4("number", { indexed: true }),
680
+ document: attr4("json"),
681
+ updatedAt: attr4("number", { indexed: true }),
682
+ updatedBy: attr4("string", { optional: true })
683
+ }
684
+ };
647
685
  var groups = {
648
686
  attrs: {
649
687
  id: id3(),
@@ -715,7 +753,7 @@ var emailLog = {
715
753
  sentAt: attr4("number", { indexed: true })
716
754
  }
717
755
  };
718
- function chapterDb(mode, auth, includeNetworkNotes = false, application = resolveApplication(void 0), includeSignupControl = false, includeMemberContent = false) {
756
+ function chapterDb(mode, auth, includeNetworkNotes = false, application = resolveApplication(void 0), includeSignupControl = false, includeMemberContent = false, includeBuilderLibrary = false) {
719
757
  const entities = {};
720
758
  if (mode === "chapter") {
721
759
  entities.applications = applicationsFor(application);
@@ -739,6 +777,10 @@ function chapterDb(mode, auth, includeNetworkNotes = false, application = resolv
739
777
  if (auth.source === "table") entities.admins = admins;
740
778
  if (auth.superAdmins) entities.superAdmins = superAdmins;
741
779
  if (includeNetworkNotes) entities.networkNotes = networkNotes;
780
+ if (includeBuilderLibrary) {
781
+ entities.builderLibraryControls = builderLibraryControls;
782
+ entities.builderLibraryLocal = builderLibraryLocal;
783
+ }
742
784
  const schema = { entities, links: {} };
743
785
  const rules = {};
744
786
  for (const ns of Object.keys(entities)) {
@@ -1374,8 +1416,8 @@ var APPLICATION_LABELS = {
1374
1416
  state: "State"
1375
1417
  };
1376
1418
  function humanize(id4) {
1377
- const text3 = id4.replace(/([a-z0-9])([A-Z])/g, "$1 $2").replace(/[_-]+/g, " ").trim();
1378
- return text3 ? `${text3[0].toUpperCase()}${text3.slice(1)}` : id4;
1419
+ const text4 = id4.replace(/([a-z0-9])([A-Z])/g, "$1 $2").replace(/[_-]+/g, " ").trim();
1420
+ return text4 ? `${text4[0].toUpperCase()}${text4.slice(1)}` : id4;
1379
1421
  }
1380
1422
  function applicationDefs(chapter) {
1381
1423
  try {
@@ -1666,15 +1708,54 @@ function resolveMemberContent(input) {
1666
1708
  };
1667
1709
  }
1668
1710
 
1669
- // src/config.ts
1711
+ // src/config-builder-library.ts
1670
1712
  var SLUG4 = /^[a-z0-9][a-z0-9-]{1,62}$/;
1713
+ function resolveBuilderLibrary(mode, input, network = { targets: [], readers: [] }) {
1714
+ if (!input) return null;
1715
+ if (input.sources !== void 0 && !Array.isArray(input.sources)) {
1716
+ throw new Error("defineChapter.builderLibrary.sources: must be an array");
1717
+ }
1718
+ const seen = /* @__PURE__ */ new Set();
1719
+ const sources = (input.sources ?? []).map((source, index) => {
1720
+ if (!source || typeof source !== "object" || !SLUG4.test(source.sourceId)) {
1721
+ throw new Error(`defineChapter.builderLibrary.sources[${index}].sourceId: must be a lowercase app slug`);
1722
+ }
1723
+ if (source.sourceId === "built-not-found" && mode === "hub") {
1724
+ throw new Error(`defineChapter.builderLibrary.sources[${index}]: a hub accepts Chapter sources, not itself`);
1725
+ }
1726
+ if (mode === "chapter" && source.sourceId !== "built-not-found") {
1727
+ throw new Error(`defineChapter.builderLibrary.sources[${index}]: Chapters may accept only the built-not-found source`);
1728
+ }
1729
+ if (seen.has(source.sourceId)) {
1730
+ throw new Error(`defineChapter.builderLibrary.sources: duplicate source "${source.sourceId}"`);
1731
+ }
1732
+ seen.add(source.sourceId);
1733
+ if ("secretName" in source) {
1734
+ throw new Error(
1735
+ `defineChapter.builderLibrary.sources[${index}].secretName: remove this field; Builder Library reuses the Chapter network edge`
1736
+ );
1737
+ }
1738
+ const networkPeer = mode === "hub" ? network.targets.some((target) => target.id === source.sourceId) : network.readers.some((reader) => reader.id === source.sourceId);
1739
+ if (!networkPeer) {
1740
+ const side = mode === "hub" ? "targets" : "readers";
1741
+ throw new Error(
1742
+ `defineChapter.builderLibrary.sources[${index}]: "${source.sourceId}" must already be configured in network.${side}`
1743
+ );
1744
+ }
1745
+ return { sourceId: source.sourceId };
1746
+ });
1747
+ return { enabled: true, localAuthoring: input.localAuthoring !== false, sources };
1748
+ }
1749
+
1750
+ // src/config.ts
1751
+ var SLUG5 = /^[a-z0-9][a-z0-9-]{1,62}$/;
1671
1752
  function isResolvedCrm(x) {
1672
1753
  return !!x && typeof x === "object" && "prepare" in x && typeof x.prepare === "function";
1673
1754
  }
1674
1755
  function defineChapter(config) {
1675
1756
  if (!config || typeof config !== "object") throw new Error("defineChapter: a config object is required");
1676
1757
  const { id: id4, name } = config;
1677
- if (typeof id4 !== "string" || !SLUG4.test(id4)) {
1758
+ if (typeof id4 !== "string" || !SLUG5.test(id4)) {
1678
1759
  throw new Error(`defineChapter.id: must be a lowercase slug [a-z0-9-] (2-63 chars) \u2014 got ${JSON.stringify(id4)}`);
1679
1760
  }
1680
1761
  if (typeof name !== "string" || name.trim() === "") throw new Error("defineChapter.name: a non-empty name is required");
@@ -1700,6 +1781,7 @@ function defineChapter(config) {
1700
1781
  const network = resolveNetwork(config, crm);
1701
1782
  const signupControl = resolveSignupControl(config.signupControl);
1702
1783
  const memberContent = resolveMemberContent(config.memberContent);
1784
+ const builderLibrary = resolveBuilderLibrary(mode, config.builderLibrary, network);
1703
1785
  const formation = resolveLeaderFormation(config.formation, crm);
1704
1786
  if (signupControl && mode !== "chapter") {
1705
1787
  throw new Error("defineChapter.signupControl: only chapter-mode followers can receive signup control");
@@ -1713,7 +1795,8 @@ function defineChapter(config) {
1713
1795
  network.targets.length > 0,
1714
1796
  application,
1715
1797
  Boolean(signupControl),
1716
- Boolean(memberContent)
1798
+ Boolean(memberContent),
1799
+ Boolean(builderLibrary)
1717
1800
  );
1718
1801
  const services = config.services ?? ["db", "calendar", "o11y"];
1719
1802
  const account = config.account ?? "none";
@@ -1771,6 +1854,7 @@ function defineChapter(config) {
1771
1854
  network,
1772
1855
  signupControl,
1773
1856
  memberContent,
1857
+ builderLibrary,
1774
1858
  formation,
1775
1859
  crm,
1776
1860
  auth,
@@ -2053,6 +2137,7 @@ function createChapterIntegration(chapter, options = {}) {
2053
2137
  },
2054
2138
  rules: { ...crmDesc.rules, ...chapter.rules },
2055
2139
  seeds,
2140
+ secrets: [],
2056
2141
  probes: [
2057
2142
  ...crmDesc.probes ?? [],
2058
2143
  // The snapshot route is deliberately private. A credential-free smoke
@@ -2069,6 +2154,11 @@ function createChapterIntegration(chapter, options = {}) {
2069
2154
  ...chapter.memberContent ? [
2070
2155
  { path: "/api/member-content", expectedStatus: 401 },
2071
2156
  { path: "/api/admin/member-content", expectedStatus: 401 }
2157
+ ] : [],
2158
+ ...chapter.builderLibrary ? [
2159
+ { path: "/api/builder-library-control", expectedStatus: 401 },
2160
+ { path: "/api/admin/builder-library", expectedStatus: 401 },
2161
+ { path: "/api/admin/builder-library/manifest", expectedStatus: 401 }
2072
2162
  ] : []
2073
2163
  ],
2074
2164
  runbooks
@@ -2077,7 +2167,7 @@ function createChapterIntegration(chapter, options = {}) {
2077
2167
 
2078
2168
  // src/member-content-validation-helpers.ts
2079
2169
  var ID = /^[a-z][a-z0-9-]{0,79}$/;
2080
- var SLUG5 = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
2170
+ var SLUG6 = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
2081
2171
  var ROOT_KEYS = /* @__PURE__ */ new Set(["schemaVersion", "revision", "generatedAt", "source", "target", "pages", "upsellOverrides"]);
2082
2172
  var SOURCE_KEYS = /* @__PURE__ */ new Set(["type", "appId"]);
2083
2173
  var TARGET_KEYS = /* @__PURE__ */ new Set(["chapterId", "environment"]);
@@ -2187,7 +2277,7 @@ function validateMemberContentDocument(input, options = {}) {
2187
2277
  const slug = textValue(raw.slug, `${path}.slug`, errors, 120, true) ?? "";
2188
2278
  const title = textValue(raw.title, `${path}.title`, errors, MAX_TEXT, true) ?? "";
2189
2279
  if (id4 && !ID.test(id4)) errors.push(`${path}.id must be a lowercase kebab-case id`);
2190
- if (slug && !SLUG5.test(slug)) errors.push(`${path}.slug must be a lowercase URL slug`);
2280
+ if (slug && !SLUG6.test(slug)) errors.push(`${path}.slug must be a lowercase URL slug`);
2191
2281
  if (ids.has(id4) || knownParentIds.has(id4)) errors.push(`${path}.id duplicates page "${id4}"`);
2192
2282
  if (slugs.has(slug) || (options.parentPages ?? []).some((page) => page.slug === slug)) errors.push(`${path}.slug duplicates "${slug}"`);
2193
2283
  ids.add(id4);
@@ -2373,9 +2463,9 @@ function canonicalValue(value) {
2373
2463
  }
2374
2464
  if (Array.isArray(value)) return `[${value.map(canonicalValue).join(",")}]`;
2375
2465
  if (typeof value !== "object") throw new TypeError("member content contains an unsupported value");
2376
- const record3 = value;
2377
- const entries = Object.keys(record3).sort().flatMap((key) => {
2378
- const item = record3[key];
2466
+ const record4 = value;
2467
+ const entries = Object.keys(record4).sort().flatMap((key) => {
2468
+ const item = record4[key];
2379
2469
  return item === void 0 ? [] : [`${JSON.stringify(key)}:${canonicalValue(item)}`];
2380
2470
  });
2381
2471
  return `{${entries.join(",")}}`;
@@ -2391,6 +2481,243 @@ async function createMemberContentEnvelope(document) {
2391
2481
  return { document, digest: await memberContentDigest(document) };
2392
2482
  }
2393
2483
 
2484
+ // src/builder-library-validation.ts
2485
+ var ID2 = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
2486
+ var APP_ID = /^[a-z0-9][a-z0-9-]{1,62}$/;
2487
+ var MAX_RESOURCES = 200;
2488
+ var MAX_MARKDOWN2 = 65536;
2489
+ var MAX_TEXT2 = 500;
2490
+ var MAX_TAGS = 20;
2491
+ var KINDS = /* @__PURE__ */ new Set(["document", "note", "link", "inspiration", "image", "file"]);
2492
+ var STATUSES = /* @__PURE__ */ new Set(["draft", "published", "archived"]);
2493
+ var ROOT_KEYS2 = /* @__PURE__ */ new Set(["schemaVersion", "revision", "generatedAt", "source", "target", "resources"]);
2494
+ var SOURCE_KEYS2 = /* @__PURE__ */ new Set(["type", "appId"]);
2495
+ var TARGET_KEYS2 = /* @__PURE__ */ new Set(["appId", "environment"]);
2496
+ var RESOURCE_KEYS = /* @__PURE__ */ new Set(["id", "kind", "title", "summary", "tags", "scope", "status", "revision", "bodyMarkdown", "url", "brandAsset", "provenance"]);
2497
+ var SCOPE_KEYS = /* @__PURE__ */ new Set(["kind", "chapterId"]);
2498
+ var ASSET_KEYS = /* @__PURE__ */ new Set(["bookId", "assetId", "filename"]);
2499
+ var PROVENANCE_KEYS = /* @__PURE__ */ new Set(["createdBy", "createdAt", "updatedBy", "updatedAt", "promotedFrom"]);
2500
+ var PROMOTION_KEYS = /* @__PURE__ */ new Set(["chapterId", "resourceId", "resourceRevision", "documentRevision", "promotedBy", "promotedAt"]);
2501
+ var record2 = (value) => value !== null && typeof value === "object" && !Array.isArray(value) ? value : null;
2502
+ function unknownKeys2(value, allowed2, path, errors) {
2503
+ for (const key of Object.keys(value)) if (!allowed2.has(key)) errors.push(`${path}.${key} is not supported`);
2504
+ }
2505
+ function text(value, path, errors, max = MAX_TEXT2, required = true) {
2506
+ if (value === void 0 && !required) return void 0;
2507
+ if (typeof value !== "string" || required && !value.trim()) {
2508
+ errors.push(`${path} must be a non-empty string`);
2509
+ return void 0;
2510
+ }
2511
+ const result = value.trim();
2512
+ if (result.length > max) errors.push(`${path} must be at most ${max} characters`);
2513
+ return result;
2514
+ }
2515
+ function integer(value, path, errors, minimum = 1) {
2516
+ if (!Number.isSafeInteger(value) || Number(value) < minimum) errors.push(`${path} must be an integer >= ${minimum}`);
2517
+ return Number(value);
2518
+ }
2519
+ function scopeValue(value, path, errors) {
2520
+ const raw = record2(value);
2521
+ if (!raw) {
2522
+ errors.push(`${path} must be an object`);
2523
+ return null;
2524
+ }
2525
+ unknownKeys2(raw, SCOPE_KEYS, path, errors);
2526
+ if (raw.kind === "global" || raw.kind === "built_not_found") {
2527
+ if (raw.chapterId !== void 0) errors.push(`${path}.chapterId is allowed only for chapter scope`);
2528
+ return { kind: raw.kind };
2529
+ }
2530
+ if (raw.kind === "chapter") {
2531
+ const chapterId = text(raw.chapterId, `${path}.chapterId`, errors, 63) ?? "";
2532
+ if (chapterId && !APP_ID.test(chapterId)) errors.push(`${path}.chapterId must be a lowercase app slug`);
2533
+ return { kind: "chapter", chapterId };
2534
+ }
2535
+ errors.push(`${path}.kind must be chapter, built_not_found, or global`);
2536
+ return null;
2537
+ }
2538
+ function resourceValue(value, path, errors) {
2539
+ const raw = record2(value);
2540
+ if (!raw) {
2541
+ errors.push(`${path} must be an object`);
2542
+ return null;
2543
+ }
2544
+ unknownKeys2(raw, RESOURCE_KEYS, path, errors);
2545
+ const id4 = text(raw.id, `${path}.id`, errors, 80) ?? "";
2546
+ if (id4 && !ID2.test(id4)) errors.push(`${path}.id must be lowercase kebab-case`);
2547
+ const kind = text(raw.kind, `${path}.kind`, errors) ?? "";
2548
+ if (!KINDS.has(kind)) errors.push(`${path}.kind is unsupported`);
2549
+ const title = text(raw.title, `${path}.title`, errors, 240) ?? "";
2550
+ const summary = text(raw.summary, `${path}.summary`, errors, 1e3, false);
2551
+ const status = text(raw.status, `${path}.status`, errors) ?? "";
2552
+ if (!STATUSES.has(status)) errors.push(`${path}.status must be draft, published, or archived`);
2553
+ const revision = integer(raw.revision, `${path}.revision`, errors);
2554
+ const scope = scopeValue(raw.scope, `${path}.scope`, errors) ?? { kind: "global" };
2555
+ const tags = Array.isArray(raw.tags) ? raw.tags.map((tag, index) => text(tag, `${path}.tags[${index}]`, errors, 80) ?? "") : [];
2556
+ if (!Array.isArray(raw.tags)) errors.push(`${path}.tags must be an array`);
2557
+ if (tags.length > MAX_TAGS) errors.push(`${path}.tags may contain at most ${MAX_TAGS} items`);
2558
+ if (new Set(tags).size !== tags.length) errors.push(`${path}.tags must be unique`);
2559
+ const bodyMarkdown = text(raw.bodyMarkdown, `${path}.bodyMarkdown`, errors, MAX_MARKDOWN2, false);
2560
+ const url = text(raw.url, `${path}.url`, errors, 2048, false);
2561
+ if (url) {
2562
+ try {
2563
+ if (new URL(url).protocol !== "https:") errors.push(`${path}.url must use https`);
2564
+ } catch {
2565
+ errors.push(`${path}.url must be a valid https URL`);
2566
+ }
2567
+ }
2568
+ const asset = raw.brandAsset === void 0 ? null : record2(raw.brandAsset);
2569
+ if (raw.brandAsset !== void 0 && !asset) errors.push(`${path}.brandAsset must be an object`);
2570
+ if (asset) unknownKeys2(asset, ASSET_KEYS, `${path}.brandAsset`, errors);
2571
+ const brandAsset = asset ? {
2572
+ bookId: text(asset.bookId, `${path}.brandAsset.bookId`, errors, 160) ?? "",
2573
+ assetId: text(asset.assetId, `${path}.brandAsset.assetId`, errors, 160) ?? "",
2574
+ ...asset.filename === void 0 ? {} : { filename: text(asset.filename, `${path}.brandAsset.filename`, errors, 240) ?? "" }
2575
+ } : void 0;
2576
+ if ((kind === "document" || kind === "note") && !bodyMarkdown) errors.push(`${path}.bodyMarkdown is required for ${kind}`);
2577
+ if ((kind === "link" || kind === "inspiration") && !url) errors.push(`${path}.url is required for ${kind}`);
2578
+ if ((kind === "image" || kind === "file") && !brandAsset) errors.push(`${path}.brandAsset is required for ${kind}`);
2579
+ if (!["document", "note"].includes(kind) && bodyMarkdown) errors.push(`${path}.bodyMarkdown is not allowed for ${kind}`);
2580
+ if (!["link", "inspiration"].includes(kind) && url) errors.push(`${path}.url is not allowed for ${kind}`);
2581
+ if (!["image", "file"].includes(kind) && brandAsset) errors.push(`${path}.brandAsset is not allowed for ${kind}`);
2582
+ const provenanceRaw = record2(raw.provenance);
2583
+ if (!provenanceRaw) errors.push(`${path}.provenance must be an object`);
2584
+ if (provenanceRaw) unknownKeys2(provenanceRaw, PROVENANCE_KEYS, `${path}.provenance`, errors);
2585
+ const promotedRaw = provenanceRaw?.promotedFrom === void 0 ? null : record2(provenanceRaw.promotedFrom);
2586
+ if (provenanceRaw?.promotedFrom !== void 0 && !promotedRaw) errors.push(`${path}.provenance.promotedFrom must be an object`);
2587
+ if (promotedRaw) unknownKeys2(promotedRaw, PROMOTION_KEYS, `${path}.provenance.promotedFrom`, errors);
2588
+ const promotedFrom = promotedRaw ? {
2589
+ chapterId: text(promotedRaw.chapterId, `${path}.provenance.promotedFrom.chapterId`, errors, 63) ?? "",
2590
+ resourceId: text(promotedRaw.resourceId, `${path}.provenance.promotedFrom.resourceId`, errors, 80) ?? "",
2591
+ resourceRevision: integer(promotedRaw.resourceRevision, `${path}.provenance.promotedFrom.resourceRevision`, errors),
2592
+ documentRevision: integer(promotedRaw.documentRevision, `${path}.provenance.promotedFrom.documentRevision`, errors),
2593
+ promotedBy: text(promotedRaw.promotedBy, `${path}.provenance.promotedFrom.promotedBy`, errors, 320) ?? "",
2594
+ promotedAt: integer(promotedRaw.promotedAt, `${path}.provenance.promotedFrom.promotedAt`, errors, 0)
2595
+ } : void 0;
2596
+ const provenance = {
2597
+ createdBy: text(provenanceRaw?.createdBy, `${path}.provenance.createdBy`, errors, 320) ?? "",
2598
+ createdAt: integer(provenanceRaw?.createdAt, `${path}.provenance.createdAt`, errors, 0),
2599
+ updatedBy: text(provenanceRaw?.updatedBy, `${path}.provenance.updatedBy`, errors, 320) ?? "",
2600
+ updatedAt: integer(provenanceRaw?.updatedAt, `${path}.provenance.updatedAt`, errors, 0),
2601
+ ...promotedFrom ? { promotedFrom } : {}
2602
+ };
2603
+ return { id: id4, kind, title, ...summary ? { summary } : {}, tags, scope, status, revision, ...bodyMarkdown ? { bodyMarkdown } : {}, ...url ? { url } : {}, ...brandAsset ? { brandAsset } : {}, provenance };
2604
+ }
2605
+ function validateBuilderLibraryDocument(value, options = {}) {
2606
+ const errors = [];
2607
+ const raw = record2(value);
2608
+ if (!raw) return { ok: false, errors: ["document must be an object"] };
2609
+ unknownKeys2(raw, ROOT_KEYS2, "document", errors);
2610
+ if (raw.schemaVersion !== BUILDER_LIBRARY_SCHEMA_VERSION) errors.push("document.schemaVersion must be 1");
2611
+ const revision = integer(raw.revision, "document.revision", errors);
2612
+ const generatedAt = integer(raw.generatedAt, "document.generatedAt", errors, 0);
2613
+ const sourceRaw = record2(raw.source);
2614
+ if (!sourceRaw) errors.push("document.source must be an object");
2615
+ if (sourceRaw) unknownKeys2(sourceRaw, SOURCE_KEYS2, "document.source", errors);
2616
+ const sourceType = sourceRaw?.type;
2617
+ if (sourceType !== "chapter" && sourceType !== "built_not_found") errors.push("document.source.type must be chapter or built_not_found");
2618
+ const sourceAppId = text(sourceRaw?.appId, "document.source.appId", errors, 63) ?? "";
2619
+ if (sourceAppId && !APP_ID.test(sourceAppId)) errors.push("document.source.appId must be a lowercase app slug");
2620
+ if (options.sourceType && sourceType !== options.sourceType) errors.push(`document.source.type must be ${options.sourceType}`);
2621
+ if (options.sourceAppId && sourceAppId !== options.sourceAppId) errors.push(`document.source.appId must be ${options.sourceAppId}`);
2622
+ const targetRaw = raw.target === void 0 ? null : record2(raw.target);
2623
+ if (raw.target !== void 0 && !targetRaw) errors.push("document.target must be an object");
2624
+ if (targetRaw) unknownKeys2(targetRaw, TARGET_KEYS2, "document.target", errors);
2625
+ const target = targetRaw ? {
2626
+ appId: text(targetRaw.appId, "document.target.appId", errors, 63) ?? "",
2627
+ environment: text(targetRaw.environment, "document.target.environment", errors, 80) ?? ""
2628
+ } : void 0;
2629
+ if (options.targetAppId && target?.appId !== options.targetAppId) errors.push(`document.target.appId must be ${options.targetAppId}`);
2630
+ if (!Array.isArray(raw.resources)) errors.push("document.resources must be an array");
2631
+ const inputs = Array.isArray(raw.resources) ? raw.resources : [];
2632
+ if (inputs.length > MAX_RESOURCES) errors.push(`document.resources may contain at most ${MAX_RESOURCES} items`);
2633
+ const resources = inputs.map((item, index) => resourceValue(item, `document.resources[${index}]`, errors)).filter(Boolean);
2634
+ const ids = resources.map(({ id: id4 }) => id4);
2635
+ if (new Set(ids).size !== ids.length) errors.push("document.resources ids must be unique");
2636
+ for (const resource of resources) {
2637
+ if (sourceType === "chapter" && (resource.scope.kind !== "chapter" || resource.scope.chapterId !== sourceAppId)) {
2638
+ errors.push(`document.resources.${resource.id}: Chapter sources may author only their own chapter scope`);
2639
+ }
2640
+ if (sourceType === "built_not_found" && resource.scope.kind === "chapter") {
2641
+ errors.push(`document.resources.${resource.id}: Built Not Found sources may author only built_not_found or global scope`);
2642
+ }
2643
+ }
2644
+ if (errors.length) return { ok: false, errors };
2645
+ return { ok: true, value: { schemaVersion: 1, revision, generatedAt, source: { type: sourceType, appId: sourceAppId }, ...target ? { target } : {}, resources } };
2646
+ }
2647
+
2648
+ // src/builder-library.ts
2649
+ var BUILDER_LIBRARY_SCHEMA_VERSION = 1;
2650
+ var BUILDER_LIBRARY_ADMIN_PATH = "/api/admin/builder-library";
2651
+ var BUILDER_LIBRARY_MANIFEST_PATH = "/api/admin/builder-library/manifest";
2652
+ var BUILDER_LIBRARY_PROMOTE_PATH = "/api/admin/builder-library/promote";
2653
+ var BUILDER_LIBRARY_CONTROL_PATH = "/api/builder-library-control";
2654
+ function canViewBuilderLibraryResource(resource, viewer) {
2655
+ if (viewer.kind === "built_not_found") return true;
2656
+ return resource.scope.kind === "global" || resource.scope.kind === "chapter" && resource.scope.chapterId === viewer.chapterId;
2657
+ }
2658
+ function buildBuilderLibraryManifest(documents, viewer, generatedAt = Date.now(), limit = 100) {
2659
+ const ordered = documents.flatMap((document) => document.resources.map((resource) => ({ document, resource }))).filter(({ resource }) => resource.status === "published" && canViewBuilderLibraryResource(resource, viewer)).sort((a, b) => a.resource.title.localeCompare(b.resource.title) || a.document.source.appId.localeCompare(b.document.source.appId) || a.resource.id.localeCompare(b.resource.id));
2660
+ return {
2661
+ schemaVersion: BUILDER_LIBRARY_SCHEMA_VERSION,
2662
+ generatedAt,
2663
+ viewer,
2664
+ revisions: documents.map(({ source, revision }) => ({ source, revision })),
2665
+ resources: ordered.slice(0, limit).map(({ resource }) => resource),
2666
+ truncated: ordered.length > limit
2667
+ };
2668
+ }
2669
+ function projectBuilderLibraryDelivery(document, target) {
2670
+ return {
2671
+ ...document,
2672
+ target,
2673
+ resources: document.source.type === "built_not_found" ? document.resources.filter((resource) => resource.scope.kind === "global") : document.resources
2674
+ };
2675
+ }
2676
+ function promoteBuilderLibraryResource(input) {
2677
+ if (input.resource.scope.kind !== "chapter") throw new TypeError("only a Chapter-scoped resource can be promoted");
2678
+ const chapterId = input.resource.scope.chapterId;
2679
+ return {
2680
+ ...input.resource,
2681
+ id: input.globalId ?? `${chapterId}-${input.resource.id}`,
2682
+ scope: { kind: "global" },
2683
+ revision: 1,
2684
+ provenance: {
2685
+ createdBy: input.actor,
2686
+ createdAt: input.promotedAt,
2687
+ updatedBy: input.actor,
2688
+ updatedAt: input.promotedAt,
2689
+ promotedFrom: {
2690
+ chapterId,
2691
+ resourceId: input.resource.id,
2692
+ resourceRevision: input.resource.revision,
2693
+ documentRevision: input.chapterDocumentRevision,
2694
+ promotedBy: input.actor,
2695
+ promotedAt: input.promotedAt
2696
+ }
2697
+ }
2698
+ };
2699
+ }
2700
+
2701
+ // src/builder-library-canonical.ts
2702
+ var encoder2 = new TextEncoder();
2703
+ function canonicalValue2(value) {
2704
+ if (value === null || typeof value === "boolean" || typeof value === "number" || typeof value === "string") return JSON.stringify(value);
2705
+ if (Array.isArray(value)) return `[${value.map(canonicalValue2).join(",")}]`;
2706
+ if (typeof value !== "object") throw new TypeError("Builder Library contains an unsupported value");
2707
+ const record4 = value;
2708
+ return `{${Object.keys(record4).sort().flatMap((key) => record4[key] === void 0 ? [] : [`${JSON.stringify(key)}:${canonicalValue2(record4[key])}`]).join(",")}}`;
2709
+ }
2710
+ function canonicalBuilderLibrary(value) {
2711
+ return canonicalValue2(value);
2712
+ }
2713
+ async function builderLibraryDigest(document) {
2714
+ const digest = await crypto.subtle.digest("SHA-256", encoder2.encode(canonicalBuilderLibrary(document)));
2715
+ return `sha256:${Array.from(new Uint8Array(digest), (byte) => byte.toString(16).padStart(2, "0")).join("")}`;
2716
+ }
2717
+ async function createBuilderLibraryEnvelope(document) {
2718
+ return { document, digest: await builderLibraryDigest(document) };
2719
+ }
2720
+
2394
2721
  // src/email.ts
2395
2722
  function render(template, vars) {
2396
2723
  return template.replace(/\{\{(\w+)\}\}/g, (_, key) => vars[key] ?? "");
@@ -2421,10 +2748,10 @@ function planDelivery(input) {
2421
2748
  const transport = !isProd && !redirect ? "log-only" : input.cloudflareReady ? "cloudflare" : "log-only";
2422
2749
  const to = redirect ? input.group.debugEmail : input.to;
2423
2750
  const subject = (redirect ? "[dev] " : "") + render(tpl.subject, vars);
2424
- const text3 = redirect ? `(dev redirect; original recipient: ${input.to})
2751
+ const text4 = redirect ? `(dev redirect; original recipient: ${input.to})
2425
2752
 
2426
2753
  ` + render(tpl.text, vars) : render(tpl.text, vars);
2427
- return { deliver: true, transport, to, subject, text: text3, redirected: redirect };
2754
+ return { deliver: true, transport, to, subject, text: text4, redirected: redirect };
2428
2755
  }
2429
2756
 
2430
2757
  // src/notify.ts
@@ -2581,25 +2908,25 @@ var ADMISSION_GRANTS_SCHEMA_VERSION = 1;
2581
2908
  var ADMISSION_GRANTS_PATH = "/api/admission-grants";
2582
2909
 
2583
2910
  // src/signup-control-canonical.ts
2584
- var encoder2 = new TextEncoder();
2585
- function canonicalValue2(value) {
2911
+ var encoder3 = new TextEncoder();
2912
+ function canonicalValue3(value) {
2586
2913
  if (value === null || typeof value === "boolean" || typeof value === "number" || typeof value === "string") {
2587
2914
  return JSON.stringify(value);
2588
2915
  }
2589
- if (Array.isArray(value)) return `[${value.map(canonicalValue2).join(",")}]`;
2916
+ if (Array.isArray(value)) return `[${value.map(canonicalValue3).join(",")}]`;
2590
2917
  if (typeof value !== "object") throw new TypeError("signup control contains an unsupported value");
2591
- const record3 = value;
2592
- const entries = Object.keys(record3).sort().flatMap((key) => {
2593
- const item = record3[key];
2594
- return item === void 0 ? [] : [`${JSON.stringify(key)}:${canonicalValue2(item)}`];
2918
+ const record4 = value;
2919
+ const entries = Object.keys(record4).sort().flatMap((key) => {
2920
+ const item = record4[key];
2921
+ return item === void 0 ? [] : [`${JSON.stringify(key)}:${canonicalValue3(item)}`];
2595
2922
  });
2596
2923
  return `{${entries.join(",")}}`;
2597
2924
  }
2598
2925
  function canonicalSignupControl(value) {
2599
- return canonicalValue2(value);
2926
+ return canonicalValue3(value);
2600
2927
  }
2601
2928
  async function signupControlDigest(document) {
2602
- const digest = await crypto.subtle.digest("SHA-256", encoder2.encode(canonicalSignupControl(document)));
2929
+ const digest = await crypto.subtle.digest("SHA-256", encoder3.encode(canonicalSignupControl(document)));
2603
2930
  return `sha256:${Array.from(new Uint8Array(digest), (byte) => byte.toString(16).padStart(2, "0")).join("")}`;
2604
2931
  }
2605
2932
  async function createSignupControlEnvelope(document) {
@@ -2607,7 +2934,7 @@ async function createSignupControlEnvelope(document) {
2607
2934
  }
2608
2935
 
2609
2936
  // src/admission-grants.ts
2610
- var ID2 = /^[a-zA-Z0-9][a-zA-Z0-9_-]{0,127}$/;
2937
+ var ID3 = /^[a-zA-Z0-9][a-zA-Z0-9_-]{0,127}$/;
2611
2938
  var MAX_GRANTS = 2e3;
2612
2939
  var MAX_NOTE = 2e3;
2613
2940
  var EMAIL_RE2 = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
@@ -2629,10 +2956,10 @@ function validateAdmissionGrantDocument(input) {
2629
2956
  const sourceActor = typeof source.actor === "string" ? source.actor.trim() : "";
2630
2957
  const chapterId = typeof target.chapterId === "string" ? target.chapterId : "";
2631
2958
  const environment = typeof target.environment === "string" ? target.environment : "";
2632
- if (!ID2.test(sourceId)) errors.push("document.source.appId must be an identifier");
2959
+ if (!ID3.test(sourceId)) errors.push("document.source.appId must be an identifier");
2633
2960
  if (!sourceActor || sourceActor.length > 256) errors.push("document.source.actor must be 1-256 characters");
2634
- if (!ID2.test(chapterId)) errors.push("document.target.chapterId must be an identifier");
2635
- if (!ID2.test(environment)) errors.push("document.target.environment must be an identifier");
2961
+ if (!ID3.test(chapterId)) errors.push("document.target.chapterId must be an identifier");
2962
+ if (!ID3.test(environment)) errors.push("document.target.environment must be an identifier");
2636
2963
  if (!Array.isArray(root.grants)) errors.push("document.grants must be an array");
2637
2964
  const rawGrants = Array.isArray(root.grants) ? root.grants : [];
2638
2965
  if (rawGrants.length > MAX_GRANTS) errors.push(`document.grants supports at most ${MAX_GRANTS} entries`);
@@ -2647,7 +2974,7 @@ function validateAdmissionGrantDocument(input) {
2647
2974
  const decision = grant.decision;
2648
2975
  const note = typeof grant.note === "string" ? grant.note.trim() : void 0;
2649
2976
  const expiresAt = grant.expiresAt === void 0 ? void 0 : Number(grant.expiresAt);
2650
- if (!ID2.test(id4)) errors.push(`document.grants[${index}].id must be an identifier`);
2977
+ if (!ID3.test(id4)) errors.push(`document.grants[${index}].id must be an identifier`);
2651
2978
  if (seenIds.has(id4)) errors.push(`document.grants repeats id "${id4}"`);
2652
2979
  if (!isAdmissionEmail(normalized)) errors.push(`document.grants[${index}].email must be valid`);
2653
2980
  if (seenEmails.has(normalized)) errors.push(`document.grants repeats email "${normalized}"`);
@@ -2804,11 +3131,11 @@ function resolveJoinContinuation(input) {
2804
3131
  // src/gift-offers.ts
2805
3132
  var GIFT_OFFER_CATALOG_VERSION = 1;
2806
3133
  var MAX_GIFT_OFFERS = 20;
2807
- var ID3 = /^[a-z][a-z0-9-]{1,79}$/;
3134
+ var ID4 = /^[a-z][a-z0-9-]{1,79}$/;
2808
3135
  var PRICE = /^price_[A-Za-z0-9]{12,}$/;
2809
3136
  var CURRENCY = /^[a-z]{3}$/;
2810
- var record2 = (value) => value && typeof value === "object" && !Array.isArray(value) ? value : null;
2811
- var text = (value, path, errors, max) => {
3137
+ var record3 = (value) => value && typeof value === "object" && !Array.isArray(value) ? value : null;
3138
+ var text2 = (value, path, errors, max) => {
2812
3139
  if (typeof value !== "string" || !value.trim()) {
2813
3140
  errors.push(`${path} is required`);
2814
3141
  return "";
@@ -2818,7 +3145,7 @@ var text = (value, path, errors, max) => {
2818
3145
  };
2819
3146
  function validateGiftOfferCatalog(input, tierIds) {
2820
3147
  const errors = [];
2821
- const root = record2(input);
3148
+ const root = record3(input);
2822
3149
  if (!root) return { ok: false, errors: ["document.giftOffers must be an object"] };
2823
3150
  for (const key of Object.keys(root)) if (key !== "schemaVersion" && key !== "offers") errors.push(`document.giftOffers.${key} is not supported`);
2824
3151
  if (root.schemaVersion !== GIFT_OFFER_CATALOG_VERSION) errors.push("document.giftOffers.schemaVersion must be 1");
@@ -2829,30 +3156,30 @@ function validateGiftOfferCatalog(input, tierIds) {
2829
3156
  const offers = [];
2830
3157
  for (const [index, raw] of rawOffers.slice(0, MAX_GIFT_OFFERS).entries()) {
2831
3158
  const path = `document.giftOffers.offers[${index}]`;
2832
- const item = record2(raw);
3159
+ const item = record3(raw);
2833
3160
  if (!item) {
2834
3161
  errors.push(`${path} must be an object`);
2835
3162
  continue;
2836
3163
  }
2837
3164
  const allowed2 = /* @__PURE__ */ new Set(["id", "target", "billingMode", "stripePriceId", "priceCents", "currency", "redemptionLifetimeHours", "activeFrom", "activeUntil", "title", "description", "ctaLabel"]);
2838
3165
  for (const key of Object.keys(item)) if (!allowed2.has(key)) errors.push(`${path}.${key} is not supported`);
2839
- const id4 = text(item.id, `${path}.id`, errors, 80);
2840
- if (id4 && !ID3.test(id4)) errors.push(`${path}.id must be a lowercase kebab-case id`);
3166
+ const id4 = text2(item.id, `${path}.id`, errors, 80);
3167
+ if (id4 && !ID4.test(id4)) errors.push(`${path}.id must be a lowercase kebab-case id`);
2841
3168
  if (ids.has(id4)) errors.push(`${path}.id duplicates "${id4}"`);
2842
3169
  ids.add(id4);
2843
- const targetRaw = record2(item.target);
3170
+ const targetRaw = record3(item.target);
2844
3171
  let target = { type: "entitlement", entitlementKey: "invalid" };
2845
3172
  if (targetRaw?.type === "tier") {
2846
- const tierId = text(targetRaw.tierId, `${path}.target.tierId`, errors, 80);
3173
+ const tierId = text2(targetRaw.tierId, `${path}.target.tierId`, errors, 80);
2847
3174
  if (!tierIds.has(tierId)) errors.push(`${path}.target.tierId must reference a declared tier`);
2848
3175
  target = { type: "tier", tierId };
2849
3176
  } else if (targetRaw?.type === "entitlement") {
2850
- const entitlementKey = text(targetRaw.entitlementKey, `${path}.target.entitlementKey`, errors, 80);
2851
- if (entitlementKey && !ID3.test(entitlementKey)) errors.push(`${path}.target.entitlementKey must be a lowercase kebab-case id`);
3177
+ const entitlementKey = text2(targetRaw.entitlementKey, `${path}.target.entitlementKey`, errors, 80);
3178
+ if (entitlementKey && !ID4.test(entitlementKey)) errors.push(`${path}.target.entitlementKey must be a lowercase kebab-case id`);
2852
3179
  target = { type: "entitlement", entitlementKey };
2853
3180
  } else errors.push(`${path}.target.type must be tier or entitlement`);
2854
3181
  if (item.billingMode !== "one_time" && item.billingMode !== "subscription") errors.push(`${path}.billingMode must be one_time or subscription`);
2855
- const stripePriceId = text(item.stripePriceId, `${path}.stripePriceId`, errors, 128);
3182
+ const stripePriceId = text2(item.stripePriceId, `${path}.stripePriceId`, errors, 128);
2856
3183
  if (stripePriceId && !PRICE.test(stripePriceId)) errors.push(`${path}.stripePriceId must be a real Stripe Price id`);
2857
3184
  if (!Number.isSafeInteger(item.priceCents) || Number(item.priceCents) < 1 || Number(item.priceCents) > 1e8) errors.push(`${path}.priceCents must be an integer from 1 to 100,000,000`);
2858
3185
  const currency = typeof item.currency === "string" ? item.currency.trim().toLowerCase() : "";
@@ -2874,9 +3201,9 @@ function validateGiftOfferCatalog(input, tierIds) {
2874
3201
  redemptionLifetimeHours: lifetime,
2875
3202
  ...activeFrom !== void 0 ? { activeFrom } : {},
2876
3203
  ...activeUntil !== void 0 ? { activeUntil } : {},
2877
- title: text(item.title, `${path}.title`, errors, 160),
2878
- description: text(item.description, `${path}.description`, errors, 2e3),
2879
- ctaLabel: text(item.ctaLabel, `${path}.ctaLabel`, errors, 120)
3204
+ title: text2(item.title, `${path}.title`, errors, 160),
3205
+ description: text2(item.description, `${path}.description`, errors, 2e3),
3206
+ ctaLabel: text2(item.ctaLabel, `${path}.ctaLabel`, errors, 120)
2880
3207
  });
2881
3208
  }
2882
3209
  return errors.length ? { ok: false, errors } : { ok: true, value: { schemaVersion: 1, offers } };
@@ -2928,34 +3255,34 @@ function networkSourceTag(type, hubRecordId, sourceId) {
2928
3255
  if (readable && raw.length <= 64) return raw;
2929
3256
  return `network:${typeKey.slice(0, 16)}:${shortHash(`${sourceId ?? ""}\0${type}\0${hubRecordId}`)}`;
2930
3257
  }
2931
- function normalizeSharedRecord(record3, fallbackSourceId = "legacy-source") {
2932
- if ("input" in record3 && record3.version === 2) {
3258
+ function normalizeSharedRecord(record4, fallbackSourceId = "legacy-source") {
3259
+ if ("input" in record4 && record4.version === 2) {
2933
3260
  return {
2934
3261
  version: 2,
2935
- sourceId: record3.source.siteId,
2936
- sourceRecordId: record3.source.recordId,
2937
- type: record3.type,
2938
- input: record3.input
3262
+ sourceId: record4.source.siteId,
3263
+ sourceRecordId: record4.source.recordId,
3264
+ type: record4.type,
3265
+ input: record4.input
2939
3266
  };
2940
3267
  }
2941
- if ("input" in record3) {
3268
+ if ("input" in record4) {
2942
3269
  return {
2943
3270
  version: 1,
2944
3271
  sourceId: fallbackSourceId,
2945
- sourceRecordId: record3.hubRecordId,
2946
- type: record3.type,
2947
- input: record3.input
3272
+ sourceRecordId: record4.hubRecordId,
3273
+ type: record4.type,
3274
+ input: record4.input
2948
3275
  };
2949
3276
  }
2950
- if ("type" in record3 && record3.type === "company") {
2951
- const input = { name: record3.name };
3277
+ if ("type" in record4 && record4.type === "company") {
3278
+ const input = { name: record4.name };
2952
3279
  for (const key of ["domain", "industry", "location", "linkedin", "notes"]) {
2953
- if (record3[key]) input[key] = record3[key];
3280
+ if (record4[key]) input[key] = record4[key];
2954
3281
  }
2955
3282
  return {
2956
3283
  version: 1,
2957
3284
  sourceId: fallbackSourceId,
2958
- sourceRecordId: record3.hubRecordId,
3285
+ sourceRecordId: record4.hubRecordId,
2959
3286
  type: "company",
2960
3287
  input
2961
3288
  };
@@ -2963,28 +3290,28 @@ function normalizeSharedRecord(record3, fallbackSourceId = "legacy-source") {
2963
3290
  return {
2964
3291
  version: 1,
2965
3292
  sourceId: fallbackSourceId,
2966
- sourceRecordId: record3.hubRecordId,
3293
+ sourceRecordId: record4.hubRecordId,
2967
3294
  type: "person",
2968
- input: sharedPersonInput(record3)
3295
+ input: sharedPersonInput(record4)
2969
3296
  };
2970
3297
  }
2971
- function sharedRecordFromCrm(crm, record3, target, sourceId) {
2972
- if (target.fields && !target.fields[record3.type]) {
2973
- throw new Error(`${target.name} does not accept "${record3.type}" records`);
3298
+ function sharedRecordFromCrm(crm, record4, target, sourceId) {
3299
+ if (target.fields && !target.fields[record4.type]) {
3300
+ throw new Error(`${target.name} does not accept "${record4.type}" records`);
2974
3301
  }
2975
- const def = crm.type(record3.type);
2976
- const fields = target.fields?.[record3.type] ?? DEFAULT_SHARE_FIELDS[record3.type];
3302
+ const def = crm.type(record4.type);
3303
+ const fields = target.fields?.[record4.type] ?? DEFAULT_SHARE_FIELDS[record4.type];
2977
3304
  if (!fields) {
2978
- throw new Error(`${target.name} requires an explicit field allowlist for "${record3.type}" records`);
3305
+ throw new Error(`${target.name} requires an explicit field allowlist for "${record4.type}" records`);
2979
3306
  }
2980
3307
  const nameField = def.nameField ?? "name";
2981
3308
  const input = {};
2982
3309
  for (const field of /* @__PURE__ */ new Set([nameField, ...fields])) {
2983
- const value = record3.fields?.[field];
3310
+ const value = record4.fields?.[field];
2984
3311
  if (value !== void 0) input[field] = value;
2985
3312
  }
2986
- if (input[nameField] === void 0) input[nameField] = record3.name;
2987
- return sourceId ? { version: 2, source: { siteId: sourceId, recordId: record3.id }, type: record3.type, input } : { version: 1, type: record3.type, hubRecordId: record3.id, input };
3313
+ if (input[nameField] === void 0) input[nameField] = record4.name;
3314
+ return sourceId ? { version: 2, source: { siteId: sourceId, recordId: record4.id }, type: record4.type, input } : { version: 1, type: record4.type, hubRecordId: record4.id, input };
2988
3315
  }
2989
3316
  async function upsertPerson(deps, opts) {
2990
3317
  const email = opts.email.toLowerCase();
@@ -2998,11 +3325,11 @@ async function upsertPerson(deps, opts) {
2998
3325
  const created = await (0, import_crm5.createRecord)(crmDeps, { type: "person", input: opts.input, mutationId: opts.mutationId });
2999
3326
  return { recordId: created.id };
3000
3327
  }
3001
- async function findSharedRecord(deps, record3, tag) {
3328
+ async function findSharedRecord(deps, record4, tag) {
3002
3329
  const structured = await (0, import_crm5.getRecordByOrigin)(
3003
3330
  { crm: deps.crm, db: deps.db, now: deps.now, newId: deps.newId },
3004
- record3.sourceId,
3005
- record3.sourceRecordId
3331
+ record4.sourceId,
3332
+ record4.sourceRecordId
3006
3333
  );
3007
3334
  if (structured) return structured;
3008
3335
  const mapped = await deps.db.query({ crm_tag: { $: { where: { tag }, limit: 1 } } });
@@ -3011,53 +3338,53 @@ async function findSharedRecord(deps, record3, tag) {
3011
3338
  const found = await deps.db.query({ crm_record: { $: { where: { id: mappedId }, limit: 1 } } });
3012
3339
  if (found.crm_record?.[0]) return found.crm_record[0];
3013
3340
  }
3014
- const def = deps.crm.type(record3.type);
3341
+ const def = deps.crm.type(record4.type);
3015
3342
  const emailField = def.emailField;
3016
- if (emailField && typeof record3.input[emailField] === "string") {
3017
- const primaryEmail = record3.input[emailField].toLowerCase();
3343
+ if (emailField && typeof record4.input[emailField] === "string") {
3344
+ const primaryEmail = record4.input[emailField].toLowerCase();
3018
3345
  const found = await deps.db.query({
3019
- crm_record: { $: { where: { type: record3.type, primaryEmail }, limit: 1 } }
3346
+ crm_record: { $: { where: { type: record4.type, primaryEmail }, limit: 1 } }
3020
3347
  });
3021
3348
  if (found.crm_record?.[0]) return found.crm_record[0];
3022
3349
  }
3023
- const domain = record3.input.domain;
3350
+ const domain = record4.input.domain;
3024
3351
  const domainSlot = def.fields.domain?.slot;
3025
3352
  if (typeof domain === "string" && domainSlot) {
3026
3353
  const found = await deps.db.query({
3027
- crm_record: { $: { where: { type: record3.type, [domainSlot]: domain }, limit: 1 } }
3354
+ crm_record: { $: { where: { type: record4.type, [domainSlot]: domain }, limit: 1 } }
3028
3355
  });
3029
3356
  if (found.crm_record?.[0]) return found.crm_record[0];
3030
3357
  }
3031
3358
  const nameField = def.nameField ?? "name";
3032
- const name = record3.input[nameField];
3033
- if (record3.type === "company" && typeof name === "string" && name.trim()) {
3359
+ const name = record4.input[nameField];
3360
+ if (record4.type === "company" && typeof name === "string" && name.trim()) {
3034
3361
  const found = await deps.db.query({
3035
- crm_record: { $: { where: { type: record3.type, name: name.trim() }, limit: 1 } }
3362
+ crm_record: { $: { where: { type: record4.type, name: name.trim() }, limit: 1 } }
3036
3363
  });
3037
3364
  if (found.crm_record?.[0]) return found.crm_record[0];
3038
3365
  }
3039
3366
  return void 0;
3040
3367
  }
3041
3368
  async function projectSharedRecord(deps, shared, options = {}) {
3042
- const record3 = normalizeSharedRecord(shared, options.sourceId);
3043
- if (!record3.type.trim() || !record3.sourceId.trim() || !record3.sourceRecordId.trim()) {
3369
+ const record4 = normalizeSharedRecord(shared, options.sourceId);
3370
+ if (!record4.type.trim() || !record4.sourceId.trim() || !record4.sourceRecordId.trim()) {
3044
3371
  throw new Error("type, source site id, and source record id must be non-empty");
3045
3372
  }
3046
- if (options.sourceId && options.sourceId !== record3.sourceId) {
3373
+ if (options.sourceId && options.sourceId !== record4.sourceId) {
3047
3374
  throw new Error("signed sender does not match payload source");
3048
3375
  }
3049
- const tag = networkSourceTag(record3.type, record3.sourceRecordId, record3.version === 2 ? record3.sourceId : void 0);
3376
+ const tag = networkSourceTag(record4.type, record4.sourceRecordId, record4.version === 2 ? record4.sourceId : void 0);
3050
3377
  const crmDeps = { crm: deps.crm, db: deps.db, now: deps.now, newId: deps.newId };
3051
- const existing = await findSharedRecord(deps, record3, tag);
3378
+ const existing = await findSharedRecord(deps, record4, tag);
3052
3379
  let recordId;
3053
3380
  if (existing && typeof existing.id === "string") {
3054
- await (0, import_crm5.updateRecord)(crmDeps, { id: existing.id, input: record3.input });
3381
+ await (0, import_crm5.updateRecord)(crmDeps, { id: existing.id, input: record4.input });
3055
3382
  recordId = existing.id;
3056
3383
  } else {
3057
- recordId = `network_${shortHash(`${record3.sourceId}\0${record3.type}\0${record3.sourceRecordId}`)}`;
3384
+ recordId = `network_${shortHash(`${record4.sourceId}\0${record4.type}\0${record4.sourceRecordId}`)}`;
3058
3385
  await (0, import_crm5.createRecord)({ ...crmDeps, newId: () => recordId }, {
3059
- type: record3.type,
3060
- input: record3.input,
3386
+ type: record4.type,
3387
+ input: record4.input,
3061
3388
  mutationId: `share-create:${tag}`
3062
3389
  });
3063
3390
  }
@@ -3067,10 +3394,10 @@ async function projectSharedRecord(deps, shared, options = {}) {
3067
3394
  );
3068
3395
  await (0, import_crm5.upsertRecordOrigin)(crmDeps, {
3069
3396
  recordId,
3070
- sourceId: record3.sourceId,
3071
- sourceRecordId: record3.sourceRecordId,
3397
+ sourceId: record4.sourceId,
3398
+ sourceRecordId: record4.sourceRecordId,
3072
3399
  ...options.sourceUrl ? { sourceUrl: options.sourceUrl } : {},
3073
- payloadVersion: record3.version
3400
+ payloadVersion: record4.version
3074
3401
  });
3075
3402
  return { recordId };
3076
3403
  }
@@ -3354,8 +3681,8 @@ async function clerkListUsers(secretKey, fetchImpl = fetch) {
3354
3681
  const data = await clerkGet(`/v1/users?limit=${PAGE}&offset=${offset}`, secretKey, fetchImpl);
3355
3682
  const page = Array.isArray(data) ? data : [];
3356
3683
  for (const u of page) {
3357
- const record3 = toRecord(u);
3358
- if (record3) out.push(record3);
3684
+ const record4 = toRecord(u);
3685
+ if (record4) out.push(record4);
3359
3686
  }
3360
3687
  if (page.length < PAGE) break;
3361
3688
  }
@@ -3762,7 +4089,7 @@ var SIGNUP_CONTROL_PATH = "/api/signup-control";
3762
4089
  var MEMBERSHIP_PAGE_RENDERER = "membership-cards-v1";
3763
4090
  var TIER_ID = /^[a-z][a-z0-9-]{0,79}$/;
3764
4091
  var VARIANTS = /* @__PURE__ */ new Set(["standard", "featured", "supporting"]);
3765
- var ROOT_KEYS2 = /* @__PURE__ */ new Set(["schemaVersion", "renderer", "tiers", "footnoteMarkdown"]);
4092
+ var ROOT_KEYS3 = /* @__PURE__ */ new Set(["schemaVersion", "renderer", "tiers", "footnoteMarkdown"]);
3766
4093
  var TIER_KEYS = /* @__PURE__ */ new Set([
3767
4094
  "tierId",
3768
4095
  "variant",
@@ -3781,7 +4108,7 @@ var CALLOUT_KEYS = /* @__PURE__ */ new Set(["label", "bodyMarkdown"]);
3781
4108
  var CAPACITY_KEYS = /* @__PURE__ */ new Set(["total", "label"]);
3782
4109
  var MAX_TIERS = 20;
3783
4110
  var MAX_BENEFITS = 24;
3784
- var MAX_MARKDOWN2 = 65536;
4111
+ var MAX_MARKDOWN3 = 65536;
3785
4112
  var MAX_LABEL = 160;
3786
4113
  var objectRecord = (value) => value !== null && typeof value === "object" && !Array.isArray(value) ? value : null;
3787
4114
  function rejectUnknownKeys(value, allowed2, path, errors) {
@@ -3798,16 +4125,16 @@ function boundedString(value, path, errors, max, required = false) {
3798
4125
  errors.push(`${path} must be a string`);
3799
4126
  return void 0;
3800
4127
  }
3801
- const text3 = value.trim();
3802
- if (required && !text3) errors.push(`${path} is required`);
3803
- if (text3.length > max) errors.push(`${path} must be at most ${max.toLocaleString("en-US")} characters`);
3804
- return text3;
4128
+ const text4 = value.trim();
4129
+ if (required && !text4) errors.push(`${path} is required`);
4130
+ if (text4.length > max) errors.push(`${path} must be at most ${max.toLocaleString("en-US")} characters`);
4131
+ return text4;
3805
4132
  }
3806
4133
  function validateMembershipPageDocument(input, options = {}) {
3807
4134
  const errors = [];
3808
4135
  const root = objectRecord(input);
3809
4136
  if (!root) return { ok: false, errors: ["document must be an object"] };
3810
- rejectUnknownKeys(root, ROOT_KEYS2, "document", errors);
4137
+ rejectUnknownKeys(root, ROOT_KEYS3, "document", errors);
3811
4138
  if (root.schemaVersion !== 1) errors.push("document.schemaVersion must be 1");
3812
4139
  if (root.renderer !== MEMBERSHIP_PAGE_RENDERER) {
3813
4140
  errors.push(`document.renderer must be "${MEMBERSHIP_PAGE_RENDERER}"`);
@@ -3850,8 +4177,8 @@ function validateMembershipPageDocument(input, options = {}) {
3850
4177
  else {
3851
4178
  if (item.benefits.length > MAX_BENEFITS) errors.push(`${path}.benefits supports at most ${MAX_BENEFITS} entries`);
3852
4179
  benefits = item.benefits.slice(0, MAX_BENEFITS).flatMap((benefit, benefitIndex) => {
3853
- const text3 = boundedString(benefit, `${path}.benefits[${benefitIndex}]`, errors, 2e3, true);
3854
- return text3 ? [text3] : [];
4180
+ const text4 = boundedString(benefit, `${path}.benefits[${benefitIndex}]`, errors, 2e3, true);
4181
+ return text4 ? [text4] : [];
3855
4182
  });
3856
4183
  }
3857
4184
  }
@@ -3861,7 +4188,7 @@ function validateMembershipPageDocument(input, options = {}) {
3861
4188
  if (!rawCallout) errors.push(`${path}.callout must be an object`);
3862
4189
  else {
3863
4190
  rejectUnknownKeys(rawCallout, CALLOUT_KEYS, `${path}.callout`, errors);
3864
- const bodyMarkdown = boundedString(rawCallout.bodyMarkdown, `${path}.callout.bodyMarkdown`, errors, MAX_MARKDOWN2, true);
4191
+ const bodyMarkdown = boundedString(rawCallout.bodyMarkdown, `${path}.callout.bodyMarkdown`, errors, MAX_MARKDOWN3, true);
3865
4192
  const label = boundedString(rawCallout.label, `${path}.callout.label`, errors, MAX_LABEL);
3866
4193
  if (bodyMarkdown) callout = { ...label ? { label } : {}, bodyMarkdown };
3867
4194
  }
@@ -3883,10 +4210,10 @@ function validateMembershipPageDocument(input, options = {}) {
3883
4210
  }
3884
4211
  }
3885
4212
  const badge = boundedString(item.badge, `${path}.badge`, errors, MAX_LABEL);
3886
- const summaryMarkdown = boundedString(item.summaryMarkdown, `${path}.summaryMarkdown`, errors, MAX_MARKDOWN2);
4213
+ const summaryMarkdown = boundedString(item.summaryMarkdown, `${path}.summaryMarkdown`, errors, MAX_MARKDOWN3);
3887
4214
  const priceEyebrow = boundedString(item.priceEyebrow, `${path}.priceEyebrow`, errors, MAX_LABEL);
3888
4215
  const billingLabel = boundedString(item.billingLabel, `${path}.billingLabel`, errors, MAX_LABEL);
3889
- const priceNoteMarkdown = boundedString(item.priceNoteMarkdown, `${path}.priceNoteMarkdown`, errors, MAX_MARKDOWN2);
4216
+ const priceNoteMarkdown = boundedString(item.priceNoteMarkdown, `${path}.priceNoteMarkdown`, errors, MAX_MARKDOWN3);
3890
4217
  const ctaLabel = boundedString(item.ctaLabel, `${path}.ctaLabel`, errors, MAX_LABEL);
3891
4218
  tiers2.push({
3892
4219
  tierId,
@@ -3908,7 +4235,7 @@ function validateMembershipPageDocument(input, options = {}) {
3908
4235
  if (!seen.has(tierId)) errors.push(`document.tiers is missing offered tier "${tierId}"`);
3909
4236
  }
3910
4237
  }
3911
- const footnoteMarkdown = boundedString(root.footnoteMarkdown, "document.footnoteMarkdown", errors, MAX_MARKDOWN2);
4238
+ const footnoteMarkdown = boundedString(root.footnoteMarkdown, "document.footnoteMarkdown", errors, MAX_MARKDOWN3);
3912
4239
  if (errors.length) return { ok: false, errors };
3913
4240
  return {
3914
4241
  ok: true,
@@ -3927,11 +4254,11 @@ function membershipPageDocument(input, options = {}) {
3927
4254
  }
3928
4255
 
3929
4256
  // src/signup-control-validate.ts
3930
- var SLUG6 = /^[a-z0-9][a-z0-9-]{1,62}$/;
4257
+ var SLUG7 = /^[a-z0-9][a-z0-9-]{1,62}$/;
3931
4258
  var ENVIRONMENT = /^[a-z][a-z0-9_-]{0,62}$/;
3932
4259
  var TIER = /^[a-z][a-z0-9-]{0,79}$/;
3933
4260
  var PRICE2 = /^price_[A-Za-z0-9]{12,}$/;
3934
- var ROOT_KEYS3 = /* @__PURE__ */ new Set([
4261
+ var ROOT_KEYS4 = /* @__PURE__ */ new Set([
3935
4262
  "schemaVersion",
3936
4263
  "revision",
3937
4264
  "generatedAt",
@@ -3947,10 +4274,10 @@ var ROOT_KEYS3 = /* @__PURE__ */ new Set([
3947
4274
  ]);
3948
4275
  var TIER_KEYS2 = /* @__PURE__ */ new Set(["id", "name", "available", "sortOrder", "priceCents", "stripePriceId", "blurb"]);
3949
4276
  var recordOf = (value) => value !== null && typeof value === "object" && !Array.isArray(value) ? value : null;
3950
- function unknownKeys2(value, allowed2, path, errors) {
4277
+ function unknownKeys3(value, allowed2, path, errors) {
3951
4278
  for (const key of Object.keys(value)) if (!allowed2.has(key)) errors.push(`${path}.${key} is not supported`);
3952
4279
  }
3953
- function text2(value, path, errors, max = 65536) {
4280
+ function text3(value, path, errors, max = 65536) {
3954
4281
  if (typeof value !== "string" || !value.trim()) {
3955
4282
  errors.push(`${path} is required`);
3956
4283
  return "";
@@ -3972,12 +4299,12 @@ function tiersOf(value, errors) {
3972
4299
  errors.push(`${path} must be an object`);
3973
4300
  return [];
3974
4301
  }
3975
- unknownKeys2(item, TIER_KEYS2, path, errors);
3976
- const id4 = text2(item.id, `${path}.id`, errors, 80);
4302
+ unknownKeys3(item, TIER_KEYS2, path, errors);
4303
+ const id4 = text3(item.id, `${path}.id`, errors, 80);
3977
4304
  if (id4 && !TIER.test(id4)) errors.push(`${path}.id must be a lowercase kebab-case id`);
3978
4305
  if (seen.has(id4)) errors.push(`${path}.id duplicates "${id4}"`);
3979
4306
  seen.add(id4);
3980
- const name = text2(item.name, `${path}.name`, errors, 160);
4307
+ const name = text3(item.name, `${path}.name`, errors, 160);
3981
4308
  if (typeof item.available !== "boolean") errors.push(`${path}.available must be boolean`);
3982
4309
  if (!Number.isInteger(item.sortOrder) || Number(item.sortOrder) < 0) errors.push(`${path}.sortOrder must be a non-negative integer`);
3983
4310
  if (!Number.isInteger(item.priceCents) || Number(item.priceCents) < 0 || Number(item.priceCents) > 1e8) {
@@ -3989,7 +4316,7 @@ function tiersOf(value, errors) {
3989
4316
  if (item.available === true && Number(item.priceCents) > 0 && !item.stripePriceId) {
3990
4317
  errors.push(`${path}.stripePriceId is required for an available paid tier`);
3991
4318
  }
3992
- const blurb = item.blurb === void 0 ? void 0 : text2(item.blurb, `${path}.blurb`, errors, 4e3);
4319
+ const blurb = item.blurb === void 0 ? void 0 : text3(item.blurb, `${path}.blurb`, errors, 4e3);
3993
4320
  return [{
3994
4321
  id: id4,
3995
4322
  name,
@@ -4008,7 +4335,7 @@ function applicationOf(value, errors) {
4008
4335
  errors.push("document.application must be an object");
4009
4336
  return void 0;
4010
4337
  }
4011
- unknownKeys2(input, /* @__PURE__ */ new Set(["required", "optional", "conditions", "copy"]), "document.application", errors);
4338
+ unknownKeys3(input, /* @__PURE__ */ new Set(["required", "optional", "conditions", "copy"]), "document.application", errors);
4012
4339
  try {
4013
4340
  const resolved = resolveApplication(input);
4014
4341
  const copy = recordOf(input.copy);
@@ -4034,11 +4361,11 @@ function policyOf(value, errors) {
4034
4361
  return void 0;
4035
4362
  }
4036
4363
  const allowed2 = /* @__PURE__ */ new Set(["disclaimerText", "refundPolicyText", "trustCopy", "commitmentText", "normsText"]);
4037
- unknownKeys2(input, allowed2, "document.policy", errors);
4364
+ unknownKeys3(input, allowed2, "document.policy", errors);
4038
4365
  const result = {};
4039
4366
  for (const key of allowed2) {
4040
4367
  if (input[key] === void 0 && key !== "disclaimerText" && key !== "refundPolicyText") continue;
4041
- result[key] = text2(input[key], `document.policy.${key}`, errors);
4368
+ result[key] = text3(input[key], `document.policy.${key}`, errors);
4042
4369
  }
4043
4370
  return result;
4044
4371
  }
@@ -4050,7 +4377,7 @@ function themeOf(value, errors) {
4050
4377
  errors.push("document.theme.tokens must be an object");
4051
4378
  return void 0;
4052
4379
  }
4053
- unknownKeys2(input, /* @__PURE__ */ new Set(["tokens"]), "document.theme", errors);
4380
+ unknownKeys3(input, /* @__PURE__ */ new Set(["tokens"]), "document.theme", errors);
4054
4381
  if (Object.keys(tokens).length > 256) errors.push("document.theme.tokens supports at most 256 entries");
4055
4382
  let serializedLength = 0;
4056
4383
  for (const [key, token] of Object.entries(tokens)) {
@@ -4070,17 +4397,17 @@ function validateSignupControlDocument(input) {
4070
4397
  const errors = [];
4071
4398
  const root = recordOf(input);
4072
4399
  if (!root) return { ok: false, errors: ["document must be an object"] };
4073
- unknownKeys2(root, ROOT_KEYS3, "document", errors);
4400
+ unknownKeys3(root, ROOT_KEYS4, "document", errors);
4074
4401
  if (root.schemaVersion !== 1) errors.push("document.schemaVersion must be 1");
4075
4402
  if (!Number.isSafeInteger(root.revision) || Number(root.revision) < 1) errors.push("document.revision must be a positive integer");
4076
4403
  if (!Number.isSafeInteger(root.generatedAt) || Number(root.generatedAt) < 1) errors.push("document.generatedAt must be epoch milliseconds");
4077
4404
  const source = recordOf(root.source);
4078
4405
  const target = recordOf(root.target);
4079
- const appId = text2(source?.appId, "document.source.appId", errors, 63);
4080
- const chapterId = text2(target?.chapterId, "document.target.chapterId", errors, 63);
4081
- const environment = text2(target?.environment, "document.target.environment", errors, 63);
4082
- if (appId && !SLUG6.test(appId)) errors.push("document.source.appId must be a lowercase app slug");
4083
- if (chapterId && !SLUG6.test(chapterId)) errors.push("document.target.chapterId must be a lowercase app slug");
4406
+ const appId = text3(source?.appId, "document.source.appId", errors, 63);
4407
+ const chapterId = text3(target?.chapterId, "document.target.chapterId", errors, 63);
4408
+ const environment = text3(target?.environment, "document.target.environment", errors, 63);
4409
+ if (appId && !SLUG7.test(appId)) errors.push("document.source.appId must be a lowercase app slug");
4410
+ if (chapterId && !SLUG7.test(chapterId)) errors.push("document.target.chapterId must be a lowercase app slug");
4084
4411
  if (environment && !ENVIRONMENT.test(environment)) errors.push("document.target.environment is invalid");
4085
4412
  if (target?.stripeMode !== "test" && target?.stripeMode !== "live") errors.push("document.target.stripeMode must be test or live");
4086
4413
  const tiers2 = tiersOf(root.tiers, errors);
@@ -4230,7 +4557,7 @@ function hydrateSignupControlTheme(target, revision, options = {}) {
4230
4557
 
4231
4558
  // src/membership-markdown.ts
4232
4559
  var MAX_BENEFITS2 = 24;
4233
- var MAX_MARKDOWN3 = 65536;
4560
+ var MAX_MARKDOWN4 = 65536;
4234
4561
  var MAX_LABEL2 = 160;
4235
4562
  var MARKDOWN_SECTION = /^##\s+(.+?)\s*$/;
4236
4563
  var MARKDOWN_HEADING = /^#{1,6}\s+/;
@@ -4255,17 +4582,17 @@ function markdownBenefits(lines, path, errors) {
4255
4582
  errors.push(`${path} line ${index + 1} must be a Markdown bullet`);
4256
4583
  continue;
4257
4584
  }
4258
- const text3 = (match[1] ?? "").trim();
4259
- if (text3.length > 2e3) errors.push(`${path} line ${index + 1} must be at most 2,000 characters`);
4260
- if (text3) benefits.push(text3);
4585
+ const text4 = (match[1] ?? "").trim();
4586
+ if (text4.length > 2e3) errors.push(`${path} line ${index + 1} must be at most 2,000 characters`);
4587
+ if (text4) benefits.push(text4);
4261
4588
  }
4262
4589
  if (benefits.length > MAX_BENEFITS2) errors.push(`${path} supports at most ${MAX_BENEFITS2} entries`);
4263
4590
  return benefits.slice(0, MAX_BENEFITS2);
4264
4591
  }
4265
4592
  function applyMembershipTierMarkdown(presentation, source) {
4266
4593
  if (typeof source !== "string") return { ok: false, errors: ["membership Markdown must be a string"] };
4267
- if (source.length > MAX_MARKDOWN3) {
4268
- return { ok: false, errors: [`membership Markdown must be at most ${MAX_MARKDOWN3.toLocaleString("en-US")} characters`] };
4594
+ if (source.length > MAX_MARKDOWN4) {
4595
+ return { ok: false, errors: [`membership Markdown must be at most ${MAX_MARKDOWN4.toLocaleString("en-US")} characters`] };
4269
4596
  }
4270
4597
  if (!source.trim()) return { ok: true, value: { ...presentation } };
4271
4598
  const errors = [];