@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.js CHANGED
@@ -471,6 +471,30 @@ var memberContentLocal = {
471
471
  updatedBy: attr4("string", { optional: true })
472
472
  }
473
473
  };
474
+ var builderLibraryControls = {
475
+ attrs: {
476
+ id: id3(),
477
+ sourceId: attr4("string", { indexed: true }),
478
+ targetId: attr4("string", { indexed: true }),
479
+ environment: attr4("string", { indexed: true }),
480
+ revision: attr4("number", { indexed: true }),
481
+ digest: attr4("string", { indexed: true }),
482
+ document: attr4("json"),
483
+ active: attr4("boolean", { indexed: true }),
484
+ receivedAt: attr4("number", { indexed: true })
485
+ }
486
+ };
487
+ var builderLibraryLocal = {
488
+ attrs: {
489
+ id: id3(),
490
+ appId: attr4("string", { indexed: true }),
491
+ environment: attr4("string", { indexed: true }),
492
+ revision: attr4("number", { indexed: true }),
493
+ document: attr4("json"),
494
+ updatedAt: attr4("number", { indexed: true }),
495
+ updatedBy: attr4("string", { optional: true })
496
+ }
497
+ };
474
498
  var groups = {
475
499
  attrs: {
476
500
  id: id3(),
@@ -542,7 +566,7 @@ var emailLog = {
542
566
  sentAt: attr4("number", { indexed: true })
543
567
  }
544
568
  };
545
- function chapterDb(mode, auth, includeNetworkNotes = false, application = resolveApplication(void 0), includeSignupControl = false, includeMemberContent = false) {
569
+ function chapterDb(mode, auth, includeNetworkNotes = false, application = resolveApplication(void 0), includeSignupControl = false, includeMemberContent = false, includeBuilderLibrary = false) {
546
570
  const entities = {};
547
571
  if (mode === "chapter") {
548
572
  entities.applications = applicationsFor(application);
@@ -566,6 +590,10 @@ function chapterDb(mode, auth, includeNetworkNotes = false, application = resolv
566
590
  if (auth.source === "table") entities.admins = admins;
567
591
  if (auth.superAdmins) entities.superAdmins = superAdmins;
568
592
  if (includeNetworkNotes) entities.networkNotes = networkNotes;
593
+ if (includeBuilderLibrary) {
594
+ entities.builderLibraryControls = builderLibraryControls;
595
+ entities.builderLibraryLocal = builderLibraryLocal;
596
+ }
569
597
  const schema = { entities, links: {} };
570
598
  const rules = {};
571
599
  for (const ns of Object.keys(entities)) {
@@ -1201,8 +1229,8 @@ var APPLICATION_LABELS = {
1201
1229
  state: "State"
1202
1230
  };
1203
1231
  function humanize(id4) {
1204
- const text3 = id4.replace(/([a-z0-9])([A-Z])/g, "$1 $2").replace(/[_-]+/g, " ").trim();
1205
- return text3 ? `${text3[0].toUpperCase()}${text3.slice(1)}` : id4;
1232
+ const text4 = id4.replace(/([a-z0-9])([A-Z])/g, "$1 $2").replace(/[_-]+/g, " ").trim();
1233
+ return text4 ? `${text4[0].toUpperCase()}${text4.slice(1)}` : id4;
1206
1234
  }
1207
1235
  function applicationDefs(chapter) {
1208
1236
  try {
@@ -1493,15 +1521,54 @@ function resolveMemberContent(input) {
1493
1521
  };
1494
1522
  }
1495
1523
 
1496
- // src/config.ts
1524
+ // src/config-builder-library.ts
1497
1525
  var SLUG4 = /^[a-z0-9][a-z0-9-]{1,62}$/;
1526
+ function resolveBuilderLibrary(mode, input, network = { targets: [], readers: [] }) {
1527
+ if (!input) return null;
1528
+ if (input.sources !== void 0 && !Array.isArray(input.sources)) {
1529
+ throw new Error("defineChapter.builderLibrary.sources: must be an array");
1530
+ }
1531
+ const seen = /* @__PURE__ */ new Set();
1532
+ const sources = (input.sources ?? []).map((source, index) => {
1533
+ if (!source || typeof source !== "object" || !SLUG4.test(source.sourceId)) {
1534
+ throw new Error(`defineChapter.builderLibrary.sources[${index}].sourceId: must be a lowercase app slug`);
1535
+ }
1536
+ if (source.sourceId === "built-not-found" && mode === "hub") {
1537
+ throw new Error(`defineChapter.builderLibrary.sources[${index}]: a hub accepts Chapter sources, not itself`);
1538
+ }
1539
+ if (mode === "chapter" && source.sourceId !== "built-not-found") {
1540
+ throw new Error(`defineChapter.builderLibrary.sources[${index}]: Chapters may accept only the built-not-found source`);
1541
+ }
1542
+ if (seen.has(source.sourceId)) {
1543
+ throw new Error(`defineChapter.builderLibrary.sources: duplicate source "${source.sourceId}"`);
1544
+ }
1545
+ seen.add(source.sourceId);
1546
+ if ("secretName" in source) {
1547
+ throw new Error(
1548
+ `defineChapter.builderLibrary.sources[${index}].secretName: remove this field; Builder Library reuses the Chapter network edge`
1549
+ );
1550
+ }
1551
+ const networkPeer = mode === "hub" ? network.targets.some((target) => target.id === source.sourceId) : network.readers.some((reader) => reader.id === source.sourceId);
1552
+ if (!networkPeer) {
1553
+ const side = mode === "hub" ? "targets" : "readers";
1554
+ throw new Error(
1555
+ `defineChapter.builderLibrary.sources[${index}]: "${source.sourceId}" must already be configured in network.${side}`
1556
+ );
1557
+ }
1558
+ return { sourceId: source.sourceId };
1559
+ });
1560
+ return { enabled: true, localAuthoring: input.localAuthoring !== false, sources };
1561
+ }
1562
+
1563
+ // src/config.ts
1564
+ var SLUG5 = /^[a-z0-9][a-z0-9-]{1,62}$/;
1498
1565
  function isResolvedCrm(x) {
1499
1566
  return !!x && typeof x === "object" && "prepare" in x && typeof x.prepare === "function";
1500
1567
  }
1501
1568
  function defineChapter(config) {
1502
1569
  if (!config || typeof config !== "object") throw new Error("defineChapter: a config object is required");
1503
1570
  const { id: id4, name } = config;
1504
- if (typeof id4 !== "string" || !SLUG4.test(id4)) {
1571
+ if (typeof id4 !== "string" || !SLUG5.test(id4)) {
1505
1572
  throw new Error(`defineChapter.id: must be a lowercase slug [a-z0-9-] (2-63 chars) \u2014 got ${JSON.stringify(id4)}`);
1506
1573
  }
1507
1574
  if (typeof name !== "string" || name.trim() === "") throw new Error("defineChapter.name: a non-empty name is required");
@@ -1527,6 +1594,7 @@ function defineChapter(config) {
1527
1594
  const network = resolveNetwork(config, crm);
1528
1595
  const signupControl = resolveSignupControl(config.signupControl);
1529
1596
  const memberContent = resolveMemberContent(config.memberContent);
1597
+ const builderLibrary = resolveBuilderLibrary(mode, config.builderLibrary, network);
1530
1598
  const formation = resolveLeaderFormation(config.formation, crm);
1531
1599
  if (signupControl && mode !== "chapter") {
1532
1600
  throw new Error("defineChapter.signupControl: only chapter-mode followers can receive signup control");
@@ -1540,7 +1608,8 @@ function defineChapter(config) {
1540
1608
  network.targets.length > 0,
1541
1609
  application,
1542
1610
  Boolean(signupControl),
1543
- Boolean(memberContent)
1611
+ Boolean(memberContent),
1612
+ Boolean(builderLibrary)
1544
1613
  );
1545
1614
  const services = config.services ?? ["db", "calendar", "o11y"];
1546
1615
  const account = config.account ?? "none";
@@ -1598,6 +1667,7 @@ function defineChapter(config) {
1598
1667
  network,
1599
1668
  signupControl,
1600
1669
  memberContent,
1670
+ builderLibrary,
1601
1671
  formation,
1602
1672
  crm,
1603
1673
  auth,
@@ -1880,6 +1950,7 @@ function createChapterIntegration(chapter, options = {}) {
1880
1950
  },
1881
1951
  rules: { ...crmDesc.rules, ...chapter.rules },
1882
1952
  seeds,
1953
+ secrets: [],
1883
1954
  probes: [
1884
1955
  ...crmDesc.probes ?? [],
1885
1956
  // The snapshot route is deliberately private. A credential-free smoke
@@ -1896,6 +1967,11 @@ function createChapterIntegration(chapter, options = {}) {
1896
1967
  ...chapter.memberContent ? [
1897
1968
  { path: "/api/member-content", expectedStatus: 401 },
1898
1969
  { path: "/api/admin/member-content", expectedStatus: 401 }
1970
+ ] : [],
1971
+ ...chapter.builderLibrary ? [
1972
+ { path: "/api/builder-library-control", expectedStatus: 401 },
1973
+ { path: "/api/admin/builder-library", expectedStatus: 401 },
1974
+ { path: "/api/admin/builder-library/manifest", expectedStatus: 401 }
1899
1975
  ] : []
1900
1976
  ],
1901
1977
  runbooks
@@ -1904,7 +1980,7 @@ function createChapterIntegration(chapter, options = {}) {
1904
1980
 
1905
1981
  // src/member-content-validation-helpers.ts
1906
1982
  var ID = /^[a-z][a-z0-9-]{0,79}$/;
1907
- var SLUG5 = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
1983
+ var SLUG6 = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
1908
1984
  var ROOT_KEYS = /* @__PURE__ */ new Set(["schemaVersion", "revision", "generatedAt", "source", "target", "pages", "upsellOverrides"]);
1909
1985
  var SOURCE_KEYS = /* @__PURE__ */ new Set(["type", "appId"]);
1910
1986
  var TARGET_KEYS = /* @__PURE__ */ new Set(["chapterId", "environment"]);
@@ -2014,7 +2090,7 @@ function validateMemberContentDocument(input, options = {}) {
2014
2090
  const slug = textValue(raw.slug, `${path}.slug`, errors, 120, true) ?? "";
2015
2091
  const title = textValue(raw.title, `${path}.title`, errors, MAX_TEXT, true) ?? "";
2016
2092
  if (id4 && !ID.test(id4)) errors.push(`${path}.id must be a lowercase kebab-case id`);
2017
- if (slug && !SLUG5.test(slug)) errors.push(`${path}.slug must be a lowercase URL slug`);
2093
+ if (slug && !SLUG6.test(slug)) errors.push(`${path}.slug must be a lowercase URL slug`);
2018
2094
  if (ids.has(id4) || knownParentIds.has(id4)) errors.push(`${path}.id duplicates page "${id4}"`);
2019
2095
  if (slugs.has(slug) || (options.parentPages ?? []).some((page) => page.slug === slug)) errors.push(`${path}.slug duplicates "${slug}"`);
2020
2096
  ids.add(id4);
@@ -2200,9 +2276,9 @@ function canonicalValue(value) {
2200
2276
  }
2201
2277
  if (Array.isArray(value)) return `[${value.map(canonicalValue).join(",")}]`;
2202
2278
  if (typeof value !== "object") throw new TypeError("member content contains an unsupported value");
2203
- const record3 = value;
2204
- const entries = Object.keys(record3).sort().flatMap((key) => {
2205
- const item = record3[key];
2279
+ const record4 = value;
2280
+ const entries = Object.keys(record4).sort().flatMap((key) => {
2281
+ const item = record4[key];
2206
2282
  return item === void 0 ? [] : [`${JSON.stringify(key)}:${canonicalValue(item)}`];
2207
2283
  });
2208
2284
  return `{${entries.join(",")}}`;
@@ -2218,6 +2294,243 @@ async function createMemberContentEnvelope(document) {
2218
2294
  return { document, digest: await memberContentDigest(document) };
2219
2295
  }
2220
2296
 
2297
+ // src/builder-library-validation.ts
2298
+ var ID2 = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
2299
+ var APP_ID = /^[a-z0-9][a-z0-9-]{1,62}$/;
2300
+ var MAX_RESOURCES = 200;
2301
+ var MAX_MARKDOWN2 = 65536;
2302
+ var MAX_TEXT2 = 500;
2303
+ var MAX_TAGS = 20;
2304
+ var KINDS = /* @__PURE__ */ new Set(["document", "note", "link", "inspiration", "image", "file"]);
2305
+ var STATUSES = /* @__PURE__ */ new Set(["draft", "published", "archived"]);
2306
+ var ROOT_KEYS2 = /* @__PURE__ */ new Set(["schemaVersion", "revision", "generatedAt", "source", "target", "resources"]);
2307
+ var SOURCE_KEYS2 = /* @__PURE__ */ new Set(["type", "appId"]);
2308
+ var TARGET_KEYS2 = /* @__PURE__ */ new Set(["appId", "environment"]);
2309
+ var RESOURCE_KEYS = /* @__PURE__ */ new Set(["id", "kind", "title", "summary", "tags", "scope", "status", "revision", "bodyMarkdown", "url", "brandAsset", "provenance"]);
2310
+ var SCOPE_KEYS = /* @__PURE__ */ new Set(["kind", "chapterId"]);
2311
+ var ASSET_KEYS = /* @__PURE__ */ new Set(["bookId", "assetId", "filename"]);
2312
+ var PROVENANCE_KEYS = /* @__PURE__ */ new Set(["createdBy", "createdAt", "updatedBy", "updatedAt", "promotedFrom"]);
2313
+ var PROMOTION_KEYS = /* @__PURE__ */ new Set(["chapterId", "resourceId", "resourceRevision", "documentRevision", "promotedBy", "promotedAt"]);
2314
+ var record2 = (value) => value !== null && typeof value === "object" && !Array.isArray(value) ? value : null;
2315
+ function unknownKeys2(value, allowed2, path, errors) {
2316
+ for (const key of Object.keys(value)) if (!allowed2.has(key)) errors.push(`${path}.${key} is not supported`);
2317
+ }
2318
+ function text(value, path, errors, max = MAX_TEXT2, required = true) {
2319
+ if (value === void 0 && !required) return void 0;
2320
+ if (typeof value !== "string" || required && !value.trim()) {
2321
+ errors.push(`${path} must be a non-empty string`);
2322
+ return void 0;
2323
+ }
2324
+ const result = value.trim();
2325
+ if (result.length > max) errors.push(`${path} must be at most ${max} characters`);
2326
+ return result;
2327
+ }
2328
+ function integer(value, path, errors, minimum = 1) {
2329
+ if (!Number.isSafeInteger(value) || Number(value) < minimum) errors.push(`${path} must be an integer >= ${minimum}`);
2330
+ return Number(value);
2331
+ }
2332
+ function scopeValue(value, path, errors) {
2333
+ const raw = record2(value);
2334
+ if (!raw) {
2335
+ errors.push(`${path} must be an object`);
2336
+ return null;
2337
+ }
2338
+ unknownKeys2(raw, SCOPE_KEYS, path, errors);
2339
+ if (raw.kind === "global" || raw.kind === "built_not_found") {
2340
+ if (raw.chapterId !== void 0) errors.push(`${path}.chapterId is allowed only for chapter scope`);
2341
+ return { kind: raw.kind };
2342
+ }
2343
+ if (raw.kind === "chapter") {
2344
+ const chapterId = text(raw.chapterId, `${path}.chapterId`, errors, 63) ?? "";
2345
+ if (chapterId && !APP_ID.test(chapterId)) errors.push(`${path}.chapterId must be a lowercase app slug`);
2346
+ return { kind: "chapter", chapterId };
2347
+ }
2348
+ errors.push(`${path}.kind must be chapter, built_not_found, or global`);
2349
+ return null;
2350
+ }
2351
+ function resourceValue(value, path, errors) {
2352
+ const raw = record2(value);
2353
+ if (!raw) {
2354
+ errors.push(`${path} must be an object`);
2355
+ return null;
2356
+ }
2357
+ unknownKeys2(raw, RESOURCE_KEYS, path, errors);
2358
+ const id4 = text(raw.id, `${path}.id`, errors, 80) ?? "";
2359
+ if (id4 && !ID2.test(id4)) errors.push(`${path}.id must be lowercase kebab-case`);
2360
+ const kind = text(raw.kind, `${path}.kind`, errors) ?? "";
2361
+ if (!KINDS.has(kind)) errors.push(`${path}.kind is unsupported`);
2362
+ const title = text(raw.title, `${path}.title`, errors, 240) ?? "";
2363
+ const summary = text(raw.summary, `${path}.summary`, errors, 1e3, false);
2364
+ const status = text(raw.status, `${path}.status`, errors) ?? "";
2365
+ if (!STATUSES.has(status)) errors.push(`${path}.status must be draft, published, or archived`);
2366
+ const revision = integer(raw.revision, `${path}.revision`, errors);
2367
+ const scope = scopeValue(raw.scope, `${path}.scope`, errors) ?? { kind: "global" };
2368
+ const tags = Array.isArray(raw.tags) ? raw.tags.map((tag, index) => text(tag, `${path}.tags[${index}]`, errors, 80) ?? "") : [];
2369
+ if (!Array.isArray(raw.tags)) errors.push(`${path}.tags must be an array`);
2370
+ if (tags.length > MAX_TAGS) errors.push(`${path}.tags may contain at most ${MAX_TAGS} items`);
2371
+ if (new Set(tags).size !== tags.length) errors.push(`${path}.tags must be unique`);
2372
+ const bodyMarkdown = text(raw.bodyMarkdown, `${path}.bodyMarkdown`, errors, MAX_MARKDOWN2, false);
2373
+ const url = text(raw.url, `${path}.url`, errors, 2048, false);
2374
+ if (url) {
2375
+ try {
2376
+ if (new URL(url).protocol !== "https:") errors.push(`${path}.url must use https`);
2377
+ } catch {
2378
+ errors.push(`${path}.url must be a valid https URL`);
2379
+ }
2380
+ }
2381
+ const asset = raw.brandAsset === void 0 ? null : record2(raw.brandAsset);
2382
+ if (raw.brandAsset !== void 0 && !asset) errors.push(`${path}.brandAsset must be an object`);
2383
+ if (asset) unknownKeys2(asset, ASSET_KEYS, `${path}.brandAsset`, errors);
2384
+ const brandAsset = asset ? {
2385
+ bookId: text(asset.bookId, `${path}.brandAsset.bookId`, errors, 160) ?? "",
2386
+ assetId: text(asset.assetId, `${path}.brandAsset.assetId`, errors, 160) ?? "",
2387
+ ...asset.filename === void 0 ? {} : { filename: text(asset.filename, `${path}.brandAsset.filename`, errors, 240) ?? "" }
2388
+ } : void 0;
2389
+ if ((kind === "document" || kind === "note") && !bodyMarkdown) errors.push(`${path}.bodyMarkdown is required for ${kind}`);
2390
+ if ((kind === "link" || kind === "inspiration") && !url) errors.push(`${path}.url is required for ${kind}`);
2391
+ if ((kind === "image" || kind === "file") && !brandAsset) errors.push(`${path}.brandAsset is required for ${kind}`);
2392
+ if (!["document", "note"].includes(kind) && bodyMarkdown) errors.push(`${path}.bodyMarkdown is not allowed for ${kind}`);
2393
+ if (!["link", "inspiration"].includes(kind) && url) errors.push(`${path}.url is not allowed for ${kind}`);
2394
+ if (!["image", "file"].includes(kind) && brandAsset) errors.push(`${path}.brandAsset is not allowed for ${kind}`);
2395
+ const provenanceRaw = record2(raw.provenance);
2396
+ if (!provenanceRaw) errors.push(`${path}.provenance must be an object`);
2397
+ if (provenanceRaw) unknownKeys2(provenanceRaw, PROVENANCE_KEYS, `${path}.provenance`, errors);
2398
+ const promotedRaw = provenanceRaw?.promotedFrom === void 0 ? null : record2(provenanceRaw.promotedFrom);
2399
+ if (provenanceRaw?.promotedFrom !== void 0 && !promotedRaw) errors.push(`${path}.provenance.promotedFrom must be an object`);
2400
+ if (promotedRaw) unknownKeys2(promotedRaw, PROMOTION_KEYS, `${path}.provenance.promotedFrom`, errors);
2401
+ const promotedFrom = promotedRaw ? {
2402
+ chapterId: text(promotedRaw.chapterId, `${path}.provenance.promotedFrom.chapterId`, errors, 63) ?? "",
2403
+ resourceId: text(promotedRaw.resourceId, `${path}.provenance.promotedFrom.resourceId`, errors, 80) ?? "",
2404
+ resourceRevision: integer(promotedRaw.resourceRevision, `${path}.provenance.promotedFrom.resourceRevision`, errors),
2405
+ documentRevision: integer(promotedRaw.documentRevision, `${path}.provenance.promotedFrom.documentRevision`, errors),
2406
+ promotedBy: text(promotedRaw.promotedBy, `${path}.provenance.promotedFrom.promotedBy`, errors, 320) ?? "",
2407
+ promotedAt: integer(promotedRaw.promotedAt, `${path}.provenance.promotedFrom.promotedAt`, errors, 0)
2408
+ } : void 0;
2409
+ const provenance = {
2410
+ createdBy: text(provenanceRaw?.createdBy, `${path}.provenance.createdBy`, errors, 320) ?? "",
2411
+ createdAt: integer(provenanceRaw?.createdAt, `${path}.provenance.createdAt`, errors, 0),
2412
+ updatedBy: text(provenanceRaw?.updatedBy, `${path}.provenance.updatedBy`, errors, 320) ?? "",
2413
+ updatedAt: integer(provenanceRaw?.updatedAt, `${path}.provenance.updatedAt`, errors, 0),
2414
+ ...promotedFrom ? { promotedFrom } : {}
2415
+ };
2416
+ return { id: id4, kind, title, ...summary ? { summary } : {}, tags, scope, status, revision, ...bodyMarkdown ? { bodyMarkdown } : {}, ...url ? { url } : {}, ...brandAsset ? { brandAsset } : {}, provenance };
2417
+ }
2418
+ function validateBuilderLibraryDocument(value, options = {}) {
2419
+ const errors = [];
2420
+ const raw = record2(value);
2421
+ if (!raw) return { ok: false, errors: ["document must be an object"] };
2422
+ unknownKeys2(raw, ROOT_KEYS2, "document", errors);
2423
+ if (raw.schemaVersion !== BUILDER_LIBRARY_SCHEMA_VERSION) errors.push("document.schemaVersion must be 1");
2424
+ const revision = integer(raw.revision, "document.revision", errors);
2425
+ const generatedAt = integer(raw.generatedAt, "document.generatedAt", errors, 0);
2426
+ const sourceRaw = record2(raw.source);
2427
+ if (!sourceRaw) errors.push("document.source must be an object");
2428
+ if (sourceRaw) unknownKeys2(sourceRaw, SOURCE_KEYS2, "document.source", errors);
2429
+ const sourceType = sourceRaw?.type;
2430
+ if (sourceType !== "chapter" && sourceType !== "built_not_found") errors.push("document.source.type must be chapter or built_not_found");
2431
+ const sourceAppId = text(sourceRaw?.appId, "document.source.appId", errors, 63) ?? "";
2432
+ if (sourceAppId && !APP_ID.test(sourceAppId)) errors.push("document.source.appId must be a lowercase app slug");
2433
+ if (options.sourceType && sourceType !== options.sourceType) errors.push(`document.source.type must be ${options.sourceType}`);
2434
+ if (options.sourceAppId && sourceAppId !== options.sourceAppId) errors.push(`document.source.appId must be ${options.sourceAppId}`);
2435
+ const targetRaw = raw.target === void 0 ? null : record2(raw.target);
2436
+ if (raw.target !== void 0 && !targetRaw) errors.push("document.target must be an object");
2437
+ if (targetRaw) unknownKeys2(targetRaw, TARGET_KEYS2, "document.target", errors);
2438
+ const target = targetRaw ? {
2439
+ appId: text(targetRaw.appId, "document.target.appId", errors, 63) ?? "",
2440
+ environment: text(targetRaw.environment, "document.target.environment", errors, 80) ?? ""
2441
+ } : void 0;
2442
+ if (options.targetAppId && target?.appId !== options.targetAppId) errors.push(`document.target.appId must be ${options.targetAppId}`);
2443
+ if (!Array.isArray(raw.resources)) errors.push("document.resources must be an array");
2444
+ const inputs = Array.isArray(raw.resources) ? raw.resources : [];
2445
+ if (inputs.length > MAX_RESOURCES) errors.push(`document.resources may contain at most ${MAX_RESOURCES} items`);
2446
+ const resources = inputs.map((item, index) => resourceValue(item, `document.resources[${index}]`, errors)).filter(Boolean);
2447
+ const ids = resources.map(({ id: id4 }) => id4);
2448
+ if (new Set(ids).size !== ids.length) errors.push("document.resources ids must be unique");
2449
+ for (const resource of resources) {
2450
+ if (sourceType === "chapter" && (resource.scope.kind !== "chapter" || resource.scope.chapterId !== sourceAppId)) {
2451
+ errors.push(`document.resources.${resource.id}: Chapter sources may author only their own chapter scope`);
2452
+ }
2453
+ if (sourceType === "built_not_found" && resource.scope.kind === "chapter") {
2454
+ errors.push(`document.resources.${resource.id}: Built Not Found sources may author only built_not_found or global scope`);
2455
+ }
2456
+ }
2457
+ if (errors.length) return { ok: false, errors };
2458
+ return { ok: true, value: { schemaVersion: 1, revision, generatedAt, source: { type: sourceType, appId: sourceAppId }, ...target ? { target } : {}, resources } };
2459
+ }
2460
+
2461
+ // src/builder-library.ts
2462
+ var BUILDER_LIBRARY_SCHEMA_VERSION = 1;
2463
+ var BUILDER_LIBRARY_ADMIN_PATH = "/api/admin/builder-library";
2464
+ var BUILDER_LIBRARY_MANIFEST_PATH = "/api/admin/builder-library/manifest";
2465
+ var BUILDER_LIBRARY_PROMOTE_PATH = "/api/admin/builder-library/promote";
2466
+ var BUILDER_LIBRARY_CONTROL_PATH = "/api/builder-library-control";
2467
+ function canViewBuilderLibraryResource(resource, viewer) {
2468
+ if (viewer.kind === "built_not_found") return true;
2469
+ return resource.scope.kind === "global" || resource.scope.kind === "chapter" && resource.scope.chapterId === viewer.chapterId;
2470
+ }
2471
+ function buildBuilderLibraryManifest(documents, viewer, generatedAt = Date.now(), limit = 100) {
2472
+ 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));
2473
+ return {
2474
+ schemaVersion: BUILDER_LIBRARY_SCHEMA_VERSION,
2475
+ generatedAt,
2476
+ viewer,
2477
+ revisions: documents.map(({ source, revision }) => ({ source, revision })),
2478
+ resources: ordered.slice(0, limit).map(({ resource }) => resource),
2479
+ truncated: ordered.length > limit
2480
+ };
2481
+ }
2482
+ function projectBuilderLibraryDelivery(document, target) {
2483
+ return {
2484
+ ...document,
2485
+ target,
2486
+ resources: document.source.type === "built_not_found" ? document.resources.filter((resource) => resource.scope.kind === "global") : document.resources
2487
+ };
2488
+ }
2489
+ function promoteBuilderLibraryResource(input) {
2490
+ if (input.resource.scope.kind !== "chapter") throw new TypeError("only a Chapter-scoped resource can be promoted");
2491
+ const chapterId = input.resource.scope.chapterId;
2492
+ return {
2493
+ ...input.resource,
2494
+ id: input.globalId ?? `${chapterId}-${input.resource.id}`,
2495
+ scope: { kind: "global" },
2496
+ revision: 1,
2497
+ provenance: {
2498
+ createdBy: input.actor,
2499
+ createdAt: input.promotedAt,
2500
+ updatedBy: input.actor,
2501
+ updatedAt: input.promotedAt,
2502
+ promotedFrom: {
2503
+ chapterId,
2504
+ resourceId: input.resource.id,
2505
+ resourceRevision: input.resource.revision,
2506
+ documentRevision: input.chapterDocumentRevision,
2507
+ promotedBy: input.actor,
2508
+ promotedAt: input.promotedAt
2509
+ }
2510
+ }
2511
+ };
2512
+ }
2513
+
2514
+ // src/builder-library-canonical.ts
2515
+ var encoder2 = new TextEncoder();
2516
+ function canonicalValue2(value) {
2517
+ if (value === null || typeof value === "boolean" || typeof value === "number" || typeof value === "string") return JSON.stringify(value);
2518
+ if (Array.isArray(value)) return `[${value.map(canonicalValue2).join(",")}]`;
2519
+ if (typeof value !== "object") throw new TypeError("Builder Library contains an unsupported value");
2520
+ const record4 = value;
2521
+ return `{${Object.keys(record4).sort().flatMap((key) => record4[key] === void 0 ? [] : [`${JSON.stringify(key)}:${canonicalValue2(record4[key])}`]).join(",")}}`;
2522
+ }
2523
+ function canonicalBuilderLibrary(value) {
2524
+ return canonicalValue2(value);
2525
+ }
2526
+ async function builderLibraryDigest(document) {
2527
+ const digest = await crypto.subtle.digest("SHA-256", encoder2.encode(canonicalBuilderLibrary(document)));
2528
+ return `sha256:${Array.from(new Uint8Array(digest), (byte) => byte.toString(16).padStart(2, "0")).join("")}`;
2529
+ }
2530
+ async function createBuilderLibraryEnvelope(document) {
2531
+ return { document, digest: await builderLibraryDigest(document) };
2532
+ }
2533
+
2221
2534
  // src/email.ts
2222
2535
  function render(template, vars) {
2223
2536
  return template.replace(/\{\{(\w+)\}\}/g, (_, key) => vars[key] ?? "");
@@ -2248,10 +2561,10 @@ function planDelivery(input) {
2248
2561
  const transport = !isProd && !redirect ? "log-only" : input.cloudflareReady ? "cloudflare" : "log-only";
2249
2562
  const to = redirect ? input.group.debugEmail : input.to;
2250
2563
  const subject = (redirect ? "[dev] " : "") + render(tpl.subject, vars);
2251
- const text3 = redirect ? `(dev redirect; original recipient: ${input.to})
2564
+ const text4 = redirect ? `(dev redirect; original recipient: ${input.to})
2252
2565
 
2253
2566
  ` + render(tpl.text, vars) : render(tpl.text, vars);
2254
- return { deliver: true, transport, to, subject, text: text3, redirected: redirect };
2567
+ return { deliver: true, transport, to, subject, text: text4, redirected: redirect };
2255
2568
  }
2256
2569
 
2257
2570
  // src/notify.ts
@@ -2408,25 +2721,25 @@ var ADMISSION_GRANTS_SCHEMA_VERSION = 1;
2408
2721
  var ADMISSION_GRANTS_PATH = "/api/admission-grants";
2409
2722
 
2410
2723
  // src/signup-control-canonical.ts
2411
- var encoder2 = new TextEncoder();
2412
- function canonicalValue2(value) {
2724
+ var encoder3 = new TextEncoder();
2725
+ function canonicalValue3(value) {
2413
2726
  if (value === null || typeof value === "boolean" || typeof value === "number" || typeof value === "string") {
2414
2727
  return JSON.stringify(value);
2415
2728
  }
2416
- if (Array.isArray(value)) return `[${value.map(canonicalValue2).join(",")}]`;
2729
+ if (Array.isArray(value)) return `[${value.map(canonicalValue3).join(",")}]`;
2417
2730
  if (typeof value !== "object") throw new TypeError("signup control contains an unsupported value");
2418
- const record3 = value;
2419
- const entries = Object.keys(record3).sort().flatMap((key) => {
2420
- const item = record3[key];
2421
- return item === void 0 ? [] : [`${JSON.stringify(key)}:${canonicalValue2(item)}`];
2731
+ const record4 = value;
2732
+ const entries = Object.keys(record4).sort().flatMap((key) => {
2733
+ const item = record4[key];
2734
+ return item === void 0 ? [] : [`${JSON.stringify(key)}:${canonicalValue3(item)}`];
2422
2735
  });
2423
2736
  return `{${entries.join(",")}}`;
2424
2737
  }
2425
2738
  function canonicalSignupControl(value) {
2426
- return canonicalValue2(value);
2739
+ return canonicalValue3(value);
2427
2740
  }
2428
2741
  async function signupControlDigest(document) {
2429
- const digest = await crypto.subtle.digest("SHA-256", encoder2.encode(canonicalSignupControl(document)));
2742
+ const digest = await crypto.subtle.digest("SHA-256", encoder3.encode(canonicalSignupControl(document)));
2430
2743
  return `sha256:${Array.from(new Uint8Array(digest), (byte) => byte.toString(16).padStart(2, "0")).join("")}`;
2431
2744
  }
2432
2745
  async function createSignupControlEnvelope(document) {
@@ -2434,7 +2747,7 @@ async function createSignupControlEnvelope(document) {
2434
2747
  }
2435
2748
 
2436
2749
  // src/admission-grants.ts
2437
- var ID2 = /^[a-zA-Z0-9][a-zA-Z0-9_-]{0,127}$/;
2750
+ var ID3 = /^[a-zA-Z0-9][a-zA-Z0-9_-]{0,127}$/;
2438
2751
  var MAX_GRANTS = 2e3;
2439
2752
  var MAX_NOTE = 2e3;
2440
2753
  var EMAIL_RE2 = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
@@ -2456,10 +2769,10 @@ function validateAdmissionGrantDocument(input) {
2456
2769
  const sourceActor = typeof source.actor === "string" ? source.actor.trim() : "";
2457
2770
  const chapterId = typeof target.chapterId === "string" ? target.chapterId : "";
2458
2771
  const environment = typeof target.environment === "string" ? target.environment : "";
2459
- if (!ID2.test(sourceId)) errors.push("document.source.appId must be an identifier");
2772
+ if (!ID3.test(sourceId)) errors.push("document.source.appId must be an identifier");
2460
2773
  if (!sourceActor || sourceActor.length > 256) errors.push("document.source.actor must be 1-256 characters");
2461
- if (!ID2.test(chapterId)) errors.push("document.target.chapterId must be an identifier");
2462
- if (!ID2.test(environment)) errors.push("document.target.environment must be an identifier");
2774
+ if (!ID3.test(chapterId)) errors.push("document.target.chapterId must be an identifier");
2775
+ if (!ID3.test(environment)) errors.push("document.target.environment must be an identifier");
2463
2776
  if (!Array.isArray(root.grants)) errors.push("document.grants must be an array");
2464
2777
  const rawGrants = Array.isArray(root.grants) ? root.grants : [];
2465
2778
  if (rawGrants.length > MAX_GRANTS) errors.push(`document.grants supports at most ${MAX_GRANTS} entries`);
@@ -2474,7 +2787,7 @@ function validateAdmissionGrantDocument(input) {
2474
2787
  const decision = grant.decision;
2475
2788
  const note = typeof grant.note === "string" ? grant.note.trim() : void 0;
2476
2789
  const expiresAt = grant.expiresAt === void 0 ? void 0 : Number(grant.expiresAt);
2477
- if (!ID2.test(id4)) errors.push(`document.grants[${index}].id must be an identifier`);
2790
+ if (!ID3.test(id4)) errors.push(`document.grants[${index}].id must be an identifier`);
2478
2791
  if (seenIds.has(id4)) errors.push(`document.grants repeats id "${id4}"`);
2479
2792
  if (!isAdmissionEmail(normalized)) errors.push(`document.grants[${index}].email must be valid`);
2480
2793
  if (seenEmails.has(normalized)) errors.push(`document.grants repeats email "${normalized}"`);
@@ -2631,11 +2944,11 @@ function resolveJoinContinuation(input) {
2631
2944
  // src/gift-offers.ts
2632
2945
  var GIFT_OFFER_CATALOG_VERSION = 1;
2633
2946
  var MAX_GIFT_OFFERS = 20;
2634
- var ID3 = /^[a-z][a-z0-9-]{1,79}$/;
2947
+ var ID4 = /^[a-z][a-z0-9-]{1,79}$/;
2635
2948
  var PRICE = /^price_[A-Za-z0-9]{12,}$/;
2636
2949
  var CURRENCY = /^[a-z]{3}$/;
2637
- var record2 = (value) => value && typeof value === "object" && !Array.isArray(value) ? value : null;
2638
- var text = (value, path, errors, max) => {
2950
+ var record3 = (value) => value && typeof value === "object" && !Array.isArray(value) ? value : null;
2951
+ var text2 = (value, path, errors, max) => {
2639
2952
  if (typeof value !== "string" || !value.trim()) {
2640
2953
  errors.push(`${path} is required`);
2641
2954
  return "";
@@ -2645,7 +2958,7 @@ var text = (value, path, errors, max) => {
2645
2958
  };
2646
2959
  function validateGiftOfferCatalog(input, tierIds) {
2647
2960
  const errors = [];
2648
- const root = record2(input);
2961
+ const root = record3(input);
2649
2962
  if (!root) return { ok: false, errors: ["document.giftOffers must be an object"] };
2650
2963
  for (const key of Object.keys(root)) if (key !== "schemaVersion" && key !== "offers") errors.push(`document.giftOffers.${key} is not supported`);
2651
2964
  if (root.schemaVersion !== GIFT_OFFER_CATALOG_VERSION) errors.push("document.giftOffers.schemaVersion must be 1");
@@ -2656,30 +2969,30 @@ function validateGiftOfferCatalog(input, tierIds) {
2656
2969
  const offers = [];
2657
2970
  for (const [index, raw] of rawOffers.slice(0, MAX_GIFT_OFFERS).entries()) {
2658
2971
  const path = `document.giftOffers.offers[${index}]`;
2659
- const item = record2(raw);
2972
+ const item = record3(raw);
2660
2973
  if (!item) {
2661
2974
  errors.push(`${path} must be an object`);
2662
2975
  continue;
2663
2976
  }
2664
2977
  const allowed2 = /* @__PURE__ */ new Set(["id", "target", "billingMode", "stripePriceId", "priceCents", "currency", "redemptionLifetimeHours", "activeFrom", "activeUntil", "title", "description", "ctaLabel"]);
2665
2978
  for (const key of Object.keys(item)) if (!allowed2.has(key)) errors.push(`${path}.${key} is not supported`);
2666
- const id4 = text(item.id, `${path}.id`, errors, 80);
2667
- if (id4 && !ID3.test(id4)) errors.push(`${path}.id must be a lowercase kebab-case id`);
2979
+ const id4 = text2(item.id, `${path}.id`, errors, 80);
2980
+ if (id4 && !ID4.test(id4)) errors.push(`${path}.id must be a lowercase kebab-case id`);
2668
2981
  if (ids.has(id4)) errors.push(`${path}.id duplicates "${id4}"`);
2669
2982
  ids.add(id4);
2670
- const targetRaw = record2(item.target);
2983
+ const targetRaw = record3(item.target);
2671
2984
  let target = { type: "entitlement", entitlementKey: "invalid" };
2672
2985
  if (targetRaw?.type === "tier") {
2673
- const tierId = text(targetRaw.tierId, `${path}.target.tierId`, errors, 80);
2986
+ const tierId = text2(targetRaw.tierId, `${path}.target.tierId`, errors, 80);
2674
2987
  if (!tierIds.has(tierId)) errors.push(`${path}.target.tierId must reference a declared tier`);
2675
2988
  target = { type: "tier", tierId };
2676
2989
  } else if (targetRaw?.type === "entitlement") {
2677
- const entitlementKey = text(targetRaw.entitlementKey, `${path}.target.entitlementKey`, errors, 80);
2678
- if (entitlementKey && !ID3.test(entitlementKey)) errors.push(`${path}.target.entitlementKey must be a lowercase kebab-case id`);
2990
+ const entitlementKey = text2(targetRaw.entitlementKey, `${path}.target.entitlementKey`, errors, 80);
2991
+ if (entitlementKey && !ID4.test(entitlementKey)) errors.push(`${path}.target.entitlementKey must be a lowercase kebab-case id`);
2679
2992
  target = { type: "entitlement", entitlementKey };
2680
2993
  } else errors.push(`${path}.target.type must be tier or entitlement`);
2681
2994
  if (item.billingMode !== "one_time" && item.billingMode !== "subscription") errors.push(`${path}.billingMode must be one_time or subscription`);
2682
- const stripePriceId = text(item.stripePriceId, `${path}.stripePriceId`, errors, 128);
2995
+ const stripePriceId = text2(item.stripePriceId, `${path}.stripePriceId`, errors, 128);
2683
2996
  if (stripePriceId && !PRICE.test(stripePriceId)) errors.push(`${path}.stripePriceId must be a real Stripe Price id`);
2684
2997
  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`);
2685
2998
  const currency = typeof item.currency === "string" ? item.currency.trim().toLowerCase() : "";
@@ -2701,9 +3014,9 @@ function validateGiftOfferCatalog(input, tierIds) {
2701
3014
  redemptionLifetimeHours: lifetime,
2702
3015
  ...activeFrom !== void 0 ? { activeFrom } : {},
2703
3016
  ...activeUntil !== void 0 ? { activeUntil } : {},
2704
- title: text(item.title, `${path}.title`, errors, 160),
2705
- description: text(item.description, `${path}.description`, errors, 2e3),
2706
- ctaLabel: text(item.ctaLabel, `${path}.ctaLabel`, errors, 120)
3017
+ title: text2(item.title, `${path}.title`, errors, 160),
3018
+ description: text2(item.description, `${path}.description`, errors, 2e3),
3019
+ ctaLabel: text2(item.ctaLabel, `${path}.ctaLabel`, errors, 120)
2707
3020
  });
2708
3021
  }
2709
3022
  return errors.length ? { ok: false, errors } : { ok: true, value: { schemaVersion: 1, offers } };
@@ -2760,34 +3073,34 @@ function networkSourceTag(type, hubRecordId, sourceId) {
2760
3073
  if (readable && raw.length <= 64) return raw;
2761
3074
  return `network:${typeKey.slice(0, 16)}:${shortHash(`${sourceId ?? ""}\0${type}\0${hubRecordId}`)}`;
2762
3075
  }
2763
- function normalizeSharedRecord(record3, fallbackSourceId = "legacy-source") {
2764
- if ("input" in record3 && record3.version === 2) {
3076
+ function normalizeSharedRecord(record4, fallbackSourceId = "legacy-source") {
3077
+ if ("input" in record4 && record4.version === 2) {
2765
3078
  return {
2766
3079
  version: 2,
2767
- sourceId: record3.source.siteId,
2768
- sourceRecordId: record3.source.recordId,
2769
- type: record3.type,
2770
- input: record3.input
3080
+ sourceId: record4.source.siteId,
3081
+ sourceRecordId: record4.source.recordId,
3082
+ type: record4.type,
3083
+ input: record4.input
2771
3084
  };
2772
3085
  }
2773
- if ("input" in record3) {
3086
+ if ("input" in record4) {
2774
3087
  return {
2775
3088
  version: 1,
2776
3089
  sourceId: fallbackSourceId,
2777
- sourceRecordId: record3.hubRecordId,
2778
- type: record3.type,
2779
- input: record3.input
3090
+ sourceRecordId: record4.hubRecordId,
3091
+ type: record4.type,
3092
+ input: record4.input
2780
3093
  };
2781
3094
  }
2782
- if ("type" in record3 && record3.type === "company") {
2783
- const input = { name: record3.name };
3095
+ if ("type" in record4 && record4.type === "company") {
3096
+ const input = { name: record4.name };
2784
3097
  for (const key of ["domain", "industry", "location", "linkedin", "notes"]) {
2785
- if (record3[key]) input[key] = record3[key];
3098
+ if (record4[key]) input[key] = record4[key];
2786
3099
  }
2787
3100
  return {
2788
3101
  version: 1,
2789
3102
  sourceId: fallbackSourceId,
2790
- sourceRecordId: record3.hubRecordId,
3103
+ sourceRecordId: record4.hubRecordId,
2791
3104
  type: "company",
2792
3105
  input
2793
3106
  };
@@ -2795,28 +3108,28 @@ function normalizeSharedRecord(record3, fallbackSourceId = "legacy-source") {
2795
3108
  return {
2796
3109
  version: 1,
2797
3110
  sourceId: fallbackSourceId,
2798
- sourceRecordId: record3.hubRecordId,
3111
+ sourceRecordId: record4.hubRecordId,
2799
3112
  type: "person",
2800
- input: sharedPersonInput(record3)
3113
+ input: sharedPersonInput(record4)
2801
3114
  };
2802
3115
  }
2803
- function sharedRecordFromCrm(crm, record3, target, sourceId) {
2804
- if (target.fields && !target.fields[record3.type]) {
2805
- throw new Error(`${target.name} does not accept "${record3.type}" records`);
3116
+ function sharedRecordFromCrm(crm, record4, target, sourceId) {
3117
+ if (target.fields && !target.fields[record4.type]) {
3118
+ throw new Error(`${target.name} does not accept "${record4.type}" records`);
2806
3119
  }
2807
- const def = crm.type(record3.type);
2808
- const fields = target.fields?.[record3.type] ?? DEFAULT_SHARE_FIELDS[record3.type];
3120
+ const def = crm.type(record4.type);
3121
+ const fields = target.fields?.[record4.type] ?? DEFAULT_SHARE_FIELDS[record4.type];
2809
3122
  if (!fields) {
2810
- throw new Error(`${target.name} requires an explicit field allowlist for "${record3.type}" records`);
3123
+ throw new Error(`${target.name} requires an explicit field allowlist for "${record4.type}" records`);
2811
3124
  }
2812
3125
  const nameField = def.nameField ?? "name";
2813
3126
  const input = {};
2814
3127
  for (const field of /* @__PURE__ */ new Set([nameField, ...fields])) {
2815
- const value = record3.fields?.[field];
3128
+ const value = record4.fields?.[field];
2816
3129
  if (value !== void 0) input[field] = value;
2817
3130
  }
2818
- if (input[nameField] === void 0) input[nameField] = record3.name;
2819
- return sourceId ? { version: 2, source: { siteId: sourceId, recordId: record3.id }, type: record3.type, input } : { version: 1, type: record3.type, hubRecordId: record3.id, input };
3131
+ if (input[nameField] === void 0) input[nameField] = record4.name;
3132
+ return sourceId ? { version: 2, source: { siteId: sourceId, recordId: record4.id }, type: record4.type, input } : { version: 1, type: record4.type, hubRecordId: record4.id, input };
2820
3133
  }
2821
3134
  async function upsertPerson(deps, opts) {
2822
3135
  const email = opts.email.toLowerCase();
@@ -2830,11 +3143,11 @@ async function upsertPerson(deps, opts) {
2830
3143
  const created = await createRecord(crmDeps, { type: "person", input: opts.input, mutationId: opts.mutationId });
2831
3144
  return { recordId: created.id };
2832
3145
  }
2833
- async function findSharedRecord(deps, record3, tag) {
3146
+ async function findSharedRecord(deps, record4, tag) {
2834
3147
  const structured = await getRecordByOrigin(
2835
3148
  { crm: deps.crm, db: deps.db, now: deps.now, newId: deps.newId },
2836
- record3.sourceId,
2837
- record3.sourceRecordId
3149
+ record4.sourceId,
3150
+ record4.sourceRecordId
2838
3151
  );
2839
3152
  if (structured) return structured;
2840
3153
  const mapped = await deps.db.query({ crm_tag: { $: { where: { tag }, limit: 1 } } });
@@ -2843,53 +3156,53 @@ async function findSharedRecord(deps, record3, tag) {
2843
3156
  const found = await deps.db.query({ crm_record: { $: { where: { id: mappedId }, limit: 1 } } });
2844
3157
  if (found.crm_record?.[0]) return found.crm_record[0];
2845
3158
  }
2846
- const def = deps.crm.type(record3.type);
3159
+ const def = deps.crm.type(record4.type);
2847
3160
  const emailField = def.emailField;
2848
- if (emailField && typeof record3.input[emailField] === "string") {
2849
- const primaryEmail = record3.input[emailField].toLowerCase();
3161
+ if (emailField && typeof record4.input[emailField] === "string") {
3162
+ const primaryEmail = record4.input[emailField].toLowerCase();
2850
3163
  const found = await deps.db.query({
2851
- crm_record: { $: { where: { type: record3.type, primaryEmail }, limit: 1 } }
3164
+ crm_record: { $: { where: { type: record4.type, primaryEmail }, limit: 1 } }
2852
3165
  });
2853
3166
  if (found.crm_record?.[0]) return found.crm_record[0];
2854
3167
  }
2855
- const domain = record3.input.domain;
3168
+ const domain = record4.input.domain;
2856
3169
  const domainSlot = def.fields.domain?.slot;
2857
3170
  if (typeof domain === "string" && domainSlot) {
2858
3171
  const found = await deps.db.query({
2859
- crm_record: { $: { where: { type: record3.type, [domainSlot]: domain }, limit: 1 } }
3172
+ crm_record: { $: { where: { type: record4.type, [domainSlot]: domain }, limit: 1 } }
2860
3173
  });
2861
3174
  if (found.crm_record?.[0]) return found.crm_record[0];
2862
3175
  }
2863
3176
  const nameField = def.nameField ?? "name";
2864
- const name = record3.input[nameField];
2865
- if (record3.type === "company" && typeof name === "string" && name.trim()) {
3177
+ const name = record4.input[nameField];
3178
+ if (record4.type === "company" && typeof name === "string" && name.trim()) {
2866
3179
  const found = await deps.db.query({
2867
- crm_record: { $: { where: { type: record3.type, name: name.trim() }, limit: 1 } }
3180
+ crm_record: { $: { where: { type: record4.type, name: name.trim() }, limit: 1 } }
2868
3181
  });
2869
3182
  if (found.crm_record?.[0]) return found.crm_record[0];
2870
3183
  }
2871
3184
  return void 0;
2872
3185
  }
2873
3186
  async function projectSharedRecord(deps, shared, options = {}) {
2874
- const record3 = normalizeSharedRecord(shared, options.sourceId);
2875
- if (!record3.type.trim() || !record3.sourceId.trim() || !record3.sourceRecordId.trim()) {
3187
+ const record4 = normalizeSharedRecord(shared, options.sourceId);
3188
+ if (!record4.type.trim() || !record4.sourceId.trim() || !record4.sourceRecordId.trim()) {
2876
3189
  throw new Error("type, source site id, and source record id must be non-empty");
2877
3190
  }
2878
- if (options.sourceId && options.sourceId !== record3.sourceId) {
3191
+ if (options.sourceId && options.sourceId !== record4.sourceId) {
2879
3192
  throw new Error("signed sender does not match payload source");
2880
3193
  }
2881
- const tag = networkSourceTag(record3.type, record3.sourceRecordId, record3.version === 2 ? record3.sourceId : void 0);
3194
+ const tag = networkSourceTag(record4.type, record4.sourceRecordId, record4.version === 2 ? record4.sourceId : void 0);
2882
3195
  const crmDeps = { crm: deps.crm, db: deps.db, now: deps.now, newId: deps.newId };
2883
- const existing = await findSharedRecord(deps, record3, tag);
3196
+ const existing = await findSharedRecord(deps, record4, tag);
2884
3197
  let recordId;
2885
3198
  if (existing && typeof existing.id === "string") {
2886
- await updateRecord(crmDeps, { id: existing.id, input: record3.input });
3199
+ await updateRecord(crmDeps, { id: existing.id, input: record4.input });
2887
3200
  recordId = existing.id;
2888
3201
  } else {
2889
- recordId = `network_${shortHash(`${record3.sourceId}\0${record3.type}\0${record3.sourceRecordId}`)}`;
3202
+ recordId = `network_${shortHash(`${record4.sourceId}\0${record4.type}\0${record4.sourceRecordId}`)}`;
2890
3203
  await createRecord({ ...crmDeps, newId: () => recordId }, {
2891
- type: record3.type,
2892
- input: record3.input,
3204
+ type: record4.type,
3205
+ input: record4.input,
2893
3206
  mutationId: `share-create:${tag}`
2894
3207
  });
2895
3208
  }
@@ -2899,10 +3212,10 @@ async function projectSharedRecord(deps, shared, options = {}) {
2899
3212
  );
2900
3213
  await upsertRecordOrigin(crmDeps, {
2901
3214
  recordId,
2902
- sourceId: record3.sourceId,
2903
- sourceRecordId: record3.sourceRecordId,
3215
+ sourceId: record4.sourceId,
3216
+ sourceRecordId: record4.sourceRecordId,
2904
3217
  ...options.sourceUrl ? { sourceUrl: options.sourceUrl } : {},
2905
- payloadVersion: record3.version
3218
+ payloadVersion: record4.version
2906
3219
  });
2907
3220
  return { recordId };
2908
3221
  }
@@ -3186,8 +3499,8 @@ async function clerkListUsers(secretKey, fetchImpl = fetch) {
3186
3499
  const data = await clerkGet(`/v1/users?limit=${PAGE}&offset=${offset}`, secretKey, fetchImpl);
3187
3500
  const page = Array.isArray(data) ? data : [];
3188
3501
  for (const u of page) {
3189
- const record3 = toRecord(u);
3190
- if (record3) out.push(record3);
3502
+ const record4 = toRecord(u);
3503
+ if (record4) out.push(record4);
3191
3504
  }
3192
3505
  if (page.length < PAGE) break;
3193
3506
  }
@@ -3594,7 +3907,7 @@ var SIGNUP_CONTROL_PATH = "/api/signup-control";
3594
3907
  var MEMBERSHIP_PAGE_RENDERER = "membership-cards-v1";
3595
3908
  var TIER_ID = /^[a-z][a-z0-9-]{0,79}$/;
3596
3909
  var VARIANTS = /* @__PURE__ */ new Set(["standard", "featured", "supporting"]);
3597
- var ROOT_KEYS2 = /* @__PURE__ */ new Set(["schemaVersion", "renderer", "tiers", "footnoteMarkdown"]);
3910
+ var ROOT_KEYS3 = /* @__PURE__ */ new Set(["schemaVersion", "renderer", "tiers", "footnoteMarkdown"]);
3598
3911
  var TIER_KEYS = /* @__PURE__ */ new Set([
3599
3912
  "tierId",
3600
3913
  "variant",
@@ -3613,7 +3926,7 @@ var CALLOUT_KEYS = /* @__PURE__ */ new Set(["label", "bodyMarkdown"]);
3613
3926
  var CAPACITY_KEYS = /* @__PURE__ */ new Set(["total", "label"]);
3614
3927
  var MAX_TIERS = 20;
3615
3928
  var MAX_BENEFITS = 24;
3616
- var MAX_MARKDOWN2 = 65536;
3929
+ var MAX_MARKDOWN3 = 65536;
3617
3930
  var MAX_LABEL = 160;
3618
3931
  var objectRecord = (value) => value !== null && typeof value === "object" && !Array.isArray(value) ? value : null;
3619
3932
  function rejectUnknownKeys(value, allowed2, path, errors) {
@@ -3630,16 +3943,16 @@ function boundedString(value, path, errors, max, required = false) {
3630
3943
  errors.push(`${path} must be a string`);
3631
3944
  return void 0;
3632
3945
  }
3633
- const text3 = value.trim();
3634
- if (required && !text3) errors.push(`${path} is required`);
3635
- if (text3.length > max) errors.push(`${path} must be at most ${max.toLocaleString("en-US")} characters`);
3636
- return text3;
3946
+ const text4 = value.trim();
3947
+ if (required && !text4) errors.push(`${path} is required`);
3948
+ if (text4.length > max) errors.push(`${path} must be at most ${max.toLocaleString("en-US")} characters`);
3949
+ return text4;
3637
3950
  }
3638
3951
  function validateMembershipPageDocument(input, options = {}) {
3639
3952
  const errors = [];
3640
3953
  const root = objectRecord(input);
3641
3954
  if (!root) return { ok: false, errors: ["document must be an object"] };
3642
- rejectUnknownKeys(root, ROOT_KEYS2, "document", errors);
3955
+ rejectUnknownKeys(root, ROOT_KEYS3, "document", errors);
3643
3956
  if (root.schemaVersion !== 1) errors.push("document.schemaVersion must be 1");
3644
3957
  if (root.renderer !== MEMBERSHIP_PAGE_RENDERER) {
3645
3958
  errors.push(`document.renderer must be "${MEMBERSHIP_PAGE_RENDERER}"`);
@@ -3682,8 +3995,8 @@ function validateMembershipPageDocument(input, options = {}) {
3682
3995
  else {
3683
3996
  if (item.benefits.length > MAX_BENEFITS) errors.push(`${path}.benefits supports at most ${MAX_BENEFITS} entries`);
3684
3997
  benefits = item.benefits.slice(0, MAX_BENEFITS).flatMap((benefit, benefitIndex) => {
3685
- const text3 = boundedString(benefit, `${path}.benefits[${benefitIndex}]`, errors, 2e3, true);
3686
- return text3 ? [text3] : [];
3998
+ const text4 = boundedString(benefit, `${path}.benefits[${benefitIndex}]`, errors, 2e3, true);
3999
+ return text4 ? [text4] : [];
3687
4000
  });
3688
4001
  }
3689
4002
  }
@@ -3693,7 +4006,7 @@ function validateMembershipPageDocument(input, options = {}) {
3693
4006
  if (!rawCallout) errors.push(`${path}.callout must be an object`);
3694
4007
  else {
3695
4008
  rejectUnknownKeys(rawCallout, CALLOUT_KEYS, `${path}.callout`, errors);
3696
- const bodyMarkdown = boundedString(rawCallout.bodyMarkdown, `${path}.callout.bodyMarkdown`, errors, MAX_MARKDOWN2, true);
4009
+ const bodyMarkdown = boundedString(rawCallout.bodyMarkdown, `${path}.callout.bodyMarkdown`, errors, MAX_MARKDOWN3, true);
3697
4010
  const label = boundedString(rawCallout.label, `${path}.callout.label`, errors, MAX_LABEL);
3698
4011
  if (bodyMarkdown) callout = { ...label ? { label } : {}, bodyMarkdown };
3699
4012
  }
@@ -3715,10 +4028,10 @@ function validateMembershipPageDocument(input, options = {}) {
3715
4028
  }
3716
4029
  }
3717
4030
  const badge = boundedString(item.badge, `${path}.badge`, errors, MAX_LABEL);
3718
- const summaryMarkdown = boundedString(item.summaryMarkdown, `${path}.summaryMarkdown`, errors, MAX_MARKDOWN2);
4031
+ const summaryMarkdown = boundedString(item.summaryMarkdown, `${path}.summaryMarkdown`, errors, MAX_MARKDOWN3);
3719
4032
  const priceEyebrow = boundedString(item.priceEyebrow, `${path}.priceEyebrow`, errors, MAX_LABEL);
3720
4033
  const billingLabel = boundedString(item.billingLabel, `${path}.billingLabel`, errors, MAX_LABEL);
3721
- const priceNoteMarkdown = boundedString(item.priceNoteMarkdown, `${path}.priceNoteMarkdown`, errors, MAX_MARKDOWN2);
4034
+ const priceNoteMarkdown = boundedString(item.priceNoteMarkdown, `${path}.priceNoteMarkdown`, errors, MAX_MARKDOWN3);
3722
4035
  const ctaLabel = boundedString(item.ctaLabel, `${path}.ctaLabel`, errors, MAX_LABEL);
3723
4036
  tiers2.push({
3724
4037
  tierId,
@@ -3740,7 +4053,7 @@ function validateMembershipPageDocument(input, options = {}) {
3740
4053
  if (!seen.has(tierId)) errors.push(`document.tiers is missing offered tier "${tierId}"`);
3741
4054
  }
3742
4055
  }
3743
- const footnoteMarkdown = boundedString(root.footnoteMarkdown, "document.footnoteMarkdown", errors, MAX_MARKDOWN2);
4056
+ const footnoteMarkdown = boundedString(root.footnoteMarkdown, "document.footnoteMarkdown", errors, MAX_MARKDOWN3);
3744
4057
  if (errors.length) return { ok: false, errors };
3745
4058
  return {
3746
4059
  ok: true,
@@ -3759,11 +4072,11 @@ function membershipPageDocument(input, options = {}) {
3759
4072
  }
3760
4073
 
3761
4074
  // src/signup-control-validate.ts
3762
- var SLUG6 = /^[a-z0-9][a-z0-9-]{1,62}$/;
4075
+ var SLUG7 = /^[a-z0-9][a-z0-9-]{1,62}$/;
3763
4076
  var ENVIRONMENT = /^[a-z][a-z0-9_-]{0,62}$/;
3764
4077
  var TIER = /^[a-z][a-z0-9-]{0,79}$/;
3765
4078
  var PRICE2 = /^price_[A-Za-z0-9]{12,}$/;
3766
- var ROOT_KEYS3 = /* @__PURE__ */ new Set([
4079
+ var ROOT_KEYS4 = /* @__PURE__ */ new Set([
3767
4080
  "schemaVersion",
3768
4081
  "revision",
3769
4082
  "generatedAt",
@@ -3779,10 +4092,10 @@ var ROOT_KEYS3 = /* @__PURE__ */ new Set([
3779
4092
  ]);
3780
4093
  var TIER_KEYS2 = /* @__PURE__ */ new Set(["id", "name", "available", "sortOrder", "priceCents", "stripePriceId", "blurb"]);
3781
4094
  var recordOf = (value) => value !== null && typeof value === "object" && !Array.isArray(value) ? value : null;
3782
- function unknownKeys2(value, allowed2, path, errors) {
4095
+ function unknownKeys3(value, allowed2, path, errors) {
3783
4096
  for (const key of Object.keys(value)) if (!allowed2.has(key)) errors.push(`${path}.${key} is not supported`);
3784
4097
  }
3785
- function text2(value, path, errors, max = 65536) {
4098
+ function text3(value, path, errors, max = 65536) {
3786
4099
  if (typeof value !== "string" || !value.trim()) {
3787
4100
  errors.push(`${path} is required`);
3788
4101
  return "";
@@ -3804,12 +4117,12 @@ function tiersOf(value, errors) {
3804
4117
  errors.push(`${path} must be an object`);
3805
4118
  return [];
3806
4119
  }
3807
- unknownKeys2(item, TIER_KEYS2, path, errors);
3808
- const id4 = text2(item.id, `${path}.id`, errors, 80);
4120
+ unknownKeys3(item, TIER_KEYS2, path, errors);
4121
+ const id4 = text3(item.id, `${path}.id`, errors, 80);
3809
4122
  if (id4 && !TIER.test(id4)) errors.push(`${path}.id must be a lowercase kebab-case id`);
3810
4123
  if (seen.has(id4)) errors.push(`${path}.id duplicates "${id4}"`);
3811
4124
  seen.add(id4);
3812
- const name = text2(item.name, `${path}.name`, errors, 160);
4125
+ const name = text3(item.name, `${path}.name`, errors, 160);
3813
4126
  if (typeof item.available !== "boolean") errors.push(`${path}.available must be boolean`);
3814
4127
  if (!Number.isInteger(item.sortOrder) || Number(item.sortOrder) < 0) errors.push(`${path}.sortOrder must be a non-negative integer`);
3815
4128
  if (!Number.isInteger(item.priceCents) || Number(item.priceCents) < 0 || Number(item.priceCents) > 1e8) {
@@ -3821,7 +4134,7 @@ function tiersOf(value, errors) {
3821
4134
  if (item.available === true && Number(item.priceCents) > 0 && !item.stripePriceId) {
3822
4135
  errors.push(`${path}.stripePriceId is required for an available paid tier`);
3823
4136
  }
3824
- const blurb = item.blurb === void 0 ? void 0 : text2(item.blurb, `${path}.blurb`, errors, 4e3);
4137
+ const blurb = item.blurb === void 0 ? void 0 : text3(item.blurb, `${path}.blurb`, errors, 4e3);
3825
4138
  return [{
3826
4139
  id: id4,
3827
4140
  name,
@@ -3840,7 +4153,7 @@ function applicationOf(value, errors) {
3840
4153
  errors.push("document.application must be an object");
3841
4154
  return void 0;
3842
4155
  }
3843
- unknownKeys2(input, /* @__PURE__ */ new Set(["required", "optional", "conditions", "copy"]), "document.application", errors);
4156
+ unknownKeys3(input, /* @__PURE__ */ new Set(["required", "optional", "conditions", "copy"]), "document.application", errors);
3844
4157
  try {
3845
4158
  const resolved = resolveApplication(input);
3846
4159
  const copy = recordOf(input.copy);
@@ -3866,11 +4179,11 @@ function policyOf(value, errors) {
3866
4179
  return void 0;
3867
4180
  }
3868
4181
  const allowed2 = /* @__PURE__ */ new Set(["disclaimerText", "refundPolicyText", "trustCopy", "commitmentText", "normsText"]);
3869
- unknownKeys2(input, allowed2, "document.policy", errors);
4182
+ unknownKeys3(input, allowed2, "document.policy", errors);
3870
4183
  const result = {};
3871
4184
  for (const key of allowed2) {
3872
4185
  if (input[key] === void 0 && key !== "disclaimerText" && key !== "refundPolicyText") continue;
3873
- result[key] = text2(input[key], `document.policy.${key}`, errors);
4186
+ result[key] = text3(input[key], `document.policy.${key}`, errors);
3874
4187
  }
3875
4188
  return result;
3876
4189
  }
@@ -3882,7 +4195,7 @@ function themeOf(value, errors) {
3882
4195
  errors.push("document.theme.tokens must be an object");
3883
4196
  return void 0;
3884
4197
  }
3885
- unknownKeys2(input, /* @__PURE__ */ new Set(["tokens"]), "document.theme", errors);
4198
+ unknownKeys3(input, /* @__PURE__ */ new Set(["tokens"]), "document.theme", errors);
3886
4199
  if (Object.keys(tokens).length > 256) errors.push("document.theme.tokens supports at most 256 entries");
3887
4200
  let serializedLength = 0;
3888
4201
  for (const [key, token] of Object.entries(tokens)) {
@@ -3902,17 +4215,17 @@ function validateSignupControlDocument(input) {
3902
4215
  const errors = [];
3903
4216
  const root = recordOf(input);
3904
4217
  if (!root) return { ok: false, errors: ["document must be an object"] };
3905
- unknownKeys2(root, ROOT_KEYS3, "document", errors);
4218
+ unknownKeys3(root, ROOT_KEYS4, "document", errors);
3906
4219
  if (root.schemaVersion !== 1) errors.push("document.schemaVersion must be 1");
3907
4220
  if (!Number.isSafeInteger(root.revision) || Number(root.revision) < 1) errors.push("document.revision must be a positive integer");
3908
4221
  if (!Number.isSafeInteger(root.generatedAt) || Number(root.generatedAt) < 1) errors.push("document.generatedAt must be epoch milliseconds");
3909
4222
  const source = recordOf(root.source);
3910
4223
  const target = recordOf(root.target);
3911
- const appId = text2(source?.appId, "document.source.appId", errors, 63);
3912
- const chapterId = text2(target?.chapterId, "document.target.chapterId", errors, 63);
3913
- const environment = text2(target?.environment, "document.target.environment", errors, 63);
3914
- if (appId && !SLUG6.test(appId)) errors.push("document.source.appId must be a lowercase app slug");
3915
- if (chapterId && !SLUG6.test(chapterId)) errors.push("document.target.chapterId must be a lowercase app slug");
4224
+ const appId = text3(source?.appId, "document.source.appId", errors, 63);
4225
+ const chapterId = text3(target?.chapterId, "document.target.chapterId", errors, 63);
4226
+ const environment = text3(target?.environment, "document.target.environment", errors, 63);
4227
+ if (appId && !SLUG7.test(appId)) errors.push("document.source.appId must be a lowercase app slug");
4228
+ if (chapterId && !SLUG7.test(chapterId)) errors.push("document.target.chapterId must be a lowercase app slug");
3916
4229
  if (environment && !ENVIRONMENT.test(environment)) errors.push("document.target.environment is invalid");
3917
4230
  if (target?.stripeMode !== "test" && target?.stripeMode !== "live") errors.push("document.target.stripeMode must be test or live");
3918
4231
  const tiers2 = tiersOf(root.tiers, errors);
@@ -4062,7 +4375,7 @@ function hydrateSignupControlTheme(target, revision, options = {}) {
4062
4375
 
4063
4376
  // src/membership-markdown.ts
4064
4377
  var MAX_BENEFITS2 = 24;
4065
- var MAX_MARKDOWN3 = 65536;
4378
+ var MAX_MARKDOWN4 = 65536;
4066
4379
  var MAX_LABEL2 = 160;
4067
4380
  var MARKDOWN_SECTION = /^##\s+(.+?)\s*$/;
4068
4381
  var MARKDOWN_HEADING = /^#{1,6}\s+/;
@@ -4087,17 +4400,17 @@ function markdownBenefits(lines, path, errors) {
4087
4400
  errors.push(`${path} line ${index + 1} must be a Markdown bullet`);
4088
4401
  continue;
4089
4402
  }
4090
- const text3 = (match[1] ?? "").trim();
4091
- if (text3.length > 2e3) errors.push(`${path} line ${index + 1} must be at most 2,000 characters`);
4092
- if (text3) benefits.push(text3);
4403
+ const text4 = (match[1] ?? "").trim();
4404
+ if (text4.length > 2e3) errors.push(`${path} line ${index + 1} must be at most 2,000 characters`);
4405
+ if (text4) benefits.push(text4);
4093
4406
  }
4094
4407
  if (benefits.length > MAX_BENEFITS2) errors.push(`${path} supports at most ${MAX_BENEFITS2} entries`);
4095
4408
  return benefits.slice(0, MAX_BENEFITS2);
4096
4409
  }
4097
4410
  function applyMembershipTierMarkdown(presentation, source) {
4098
4411
  if (typeof source !== "string") return { ok: false, errors: ["membership Markdown must be a string"] };
4099
- if (source.length > MAX_MARKDOWN3) {
4100
- return { ok: false, errors: [`membership Markdown must be at most ${MAX_MARKDOWN3.toLocaleString("en-US")} characters`] };
4412
+ if (source.length > MAX_MARKDOWN4) {
4413
+ return { ok: false, errors: [`membership Markdown must be at most ${MAX_MARKDOWN4.toLocaleString("en-US")} characters`] };
4101
4414
  }
4102
4415
  if (!source.trim()) return { ok: true, value: { ...presentation } };
4103
4416
  const errors = [];
@@ -4275,6 +4588,11 @@ async function exportChapterPromotion(input) {
4275
4588
  export {
4276
4589
  ADMISSION_GRANTS_PATH,
4277
4590
  ADMISSION_GRANTS_SCHEMA_VERSION,
4591
+ BUILDER_LIBRARY_ADMIN_PATH,
4592
+ BUILDER_LIBRARY_CONTROL_PATH,
4593
+ BUILDER_LIBRARY_MANIFEST_PATH,
4594
+ BUILDER_LIBRARY_PROMOTE_PATH,
4595
+ BUILDER_LIBRARY_SCHEMA_VERSION,
4278
4596
  CHAPTER_FLOW_FIELDS_SCHEMA_VERSION,
4279
4597
  CHAPTER_PROMOTION_ADAPTER,
4280
4598
  DEFAULT_CHAPTER_COPY,
@@ -4299,13 +4617,17 @@ export {
4299
4617
  bookingDecision,
4300
4618
  brandTokens,
4301
4619
  bucketSeries,
4620
+ buildBuilderLibraryManifest,
4302
4621
  buildGroupSeed,
4303
4622
  buildTierSeeds,
4623
+ builderLibraryDigest,
4304
4624
  canApprove,
4305
4625
  canBook,
4306
4626
  canChangeRole,
4307
4627
  canTransition,
4628
+ canViewBuilderLibraryResource,
4308
4629
  canceledPatch,
4630
+ canonicalBuilderLibrary,
4309
4631
  canonicalMemberContent,
4310
4632
  canonicalSignupControl,
4311
4633
  chapterBrandFromTokens,
@@ -4321,6 +4643,7 @@ export {
4321
4643
  clerkUserRequest,
4322
4644
  composeMemberContent,
4323
4645
  createAdmissionGrantEnvelope,
4646
+ createBuilderLibraryEnvelope,
4324
4647
  createChapterIntegration,
4325
4648
  createClerkInvitation,
4326
4649
  createClerkUser,
@@ -4376,8 +4699,10 @@ export {
4376
4699
  personInputFromApp,
4377
4700
  planDelivery,
4378
4701
  projectApplicant,
4702
+ projectBuilderLibraryDelivery,
4379
4703
  projectMemberContent,
4380
4704
  projectSharedRecord,
4705
+ promoteBuilderLibraryResource,
4381
4706
  publicGiftOffers,
4382
4707
  reconcileMeetings,
4383
4708
  refundedPatch,
@@ -4388,6 +4713,7 @@ export {
4388
4713
  renewalPatch,
4389
4714
  resolveApplication,
4390
4715
  resolveAuth,
4716
+ resolveBuilderLibrary,
4391
4717
  resolveChapterCopy,
4392
4718
  resolveJoinContinuation,
4393
4719
  resolveLeaderFormation,
@@ -4414,6 +4740,7 @@ export {
4414
4740
  updateClerkUserMetadata,
4415
4741
  updateClerkUserMetadataByEmail,
4416
4742
  validateAdmissionGrantDocument,
4743
+ validateBuilderLibraryDocument,
4417
4744
  validateGiftOfferCatalog,
4418
4745
  validateMemberContentDocument,
4419
4746
  validateMembershipPageDocument,