@odla-ai/chapter 0.43.2 → 0.45.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -36,7 +36,7 @@ function resolveApplication(a) {
36
36
  throw new Error(`defineChapter.application.${name}: field "${unsafe}" must match ${SCHEMA_FIELD.source}`);
37
37
  }
38
38
  }
39
- const duplicate = [...required, ...optional].find((field, index, fields) => fields.indexOf(field) !== index);
39
+ const duplicate = [...required, ...optional].find((field, index2, fields) => fields.indexOf(field) !== index2);
40
40
  if (duplicate) {
41
41
  throw new Error(`defineChapter.application: duplicate field "${duplicate}" across required/optional lists`);
42
42
  }
@@ -115,8 +115,8 @@ async function submitApplication(db, chapter, fields, opts) {
115
115
  if (app.requireDisclaimerAck && !acked) {
116
116
  return { ok: false, error: "disclaimerAck is required" };
117
117
  }
118
- const id4 = opts.submissionId ? await applicationIdForSubmission(opts.submissionId) : opts.newId();
119
- const row = { id: id4, status: chapter.pipeline.initial, createdAt: opts.now };
118
+ const id6 = opts.submissionId ? await applicationIdForSubmission(opts.submissionId) : opts.newId();
119
+ const row = { id: id6, status: chapter.pipeline.initial, createdAt: opts.now };
120
120
  for (const f of [...app.required, ...app.optional]) {
121
121
  if (fieldStates[f]?.visible === false) continue;
122
122
  if (typeof fields[f] === "string") row[f] = fields[f].trim();
@@ -140,19 +140,19 @@ async function submitApplication(db, chapter, fields, opts) {
140
140
  }
141
141
  if (acked) row.disclaimerAckAt = opts.now;
142
142
  const ops = [
143
- { t: "update", ns: "applications", id: id4, attrs: row }
143
+ { t: "update", ns: "applications", id: id6, attrs: row }
144
144
  ];
145
145
  if (opts.admissionGrant?.decision === "waive_interview") {
146
146
  ops.push({ t: "update", ns: "admissionGrants", id: opts.admissionGrant.id, attrs: {
147
147
  consumedAt: opts.now,
148
- consumedApplicationId: id4
148
+ consumedApplicationId: id6
149
149
  } });
150
150
  }
151
151
  const { duplicate } = await db.transact(
152
152
  ops,
153
153
  opts.submissionId ? { mutationId: `join:${opts.submissionId}` } : void 0
154
154
  );
155
- return { ok: true, id: id4, duplicate, status: chapter.pipeline.initial, disclaimerAckAt: acked ? opts.now : null };
155
+ return { ok: true, id: id6, duplicate, status: chapter.pipeline.initial, disclaimerAckAt: acked ? opts.now : null };
156
156
  }
157
157
  function joinConfig(group, paymentsReady2, tiers2 = [], conditions = {}) {
158
158
  return {
@@ -466,6 +466,146 @@ var stripeEventReceiptsEntity = {
466
466
  }
467
467
  };
468
468
 
469
+ // src/schema-network-person.ts
470
+ var string = (flags = {}) => ({
471
+ type: "string",
472
+ unique: flags.unique ?? false,
473
+ indexed: flags.indexed ?? false,
474
+ optional: flags.optional ?? false
475
+ });
476
+ var number = (flags = {}) => ({
477
+ type: "number",
478
+ unique: false,
479
+ indexed: flags.indexed ?? false,
480
+ optional: flags.optional ?? false
481
+ });
482
+ var json = (flags = {}) => ({
483
+ type: "json",
484
+ unique: false,
485
+ indexed: false,
486
+ optional: flags.optional ?? false
487
+ });
488
+ var id3 = () => string({ unique: true, indexed: true });
489
+ var networkPersonsEntity = { attrs: {
490
+ id: id3(),
491
+ displayName: string({ indexed: true }),
492
+ state: string({ indexed: true }),
493
+ mergedIntoId: string({ indexed: true, optional: true }),
494
+ contactPolicy: json(),
495
+ revision: number({ indexed: true }),
496
+ createdAt: number({ indexed: true }),
497
+ updatedAt: number({ indexed: true })
498
+ } };
499
+ var networkIdentityClaimsEntity = { attrs: {
500
+ id: id3(),
501
+ personId: string({ indexed: true }),
502
+ kind: string({ indexed: true }),
503
+ value: string(),
504
+ hint: string({ optional: true }),
505
+ verifiedAt: number({ indexed: true }),
506
+ createdAt: number({ indexed: true })
507
+ } };
508
+ var networkPersonAliasesEntity = { attrs: {
509
+ id: id3(),
510
+ sourceSystem: string({ indexed: true }),
511
+ sourceRecordId: string({ indexed: true }),
512
+ sourceKey: string({ unique: true, indexed: true }),
513
+ displayName: string({ indexed: true }),
514
+ provenance: string(),
515
+ consent: json(),
516
+ status: string({ indexed: true }),
517
+ personId: string({ indexed: true, optional: true }),
518
+ candidatePersonIds: json({ optional: true }),
519
+ identityClaims: json(),
520
+ chapterLinks: json(),
521
+ revision: number({ indexed: true }),
522
+ createdAt: number({ indexed: true }),
523
+ updatedAt: number({ indexed: true })
524
+ } };
525
+ var networkChapterPersonLinksEntity = { attrs: {
526
+ id: id3(),
527
+ personId: string({ indexed: true }),
528
+ chapterId: string({ indexed: true }),
529
+ chapterPersonId: string({ indexed: true }),
530
+ state: string({ indexed: true }),
531
+ createdAt: number({ indexed: true })
532
+ } };
533
+ var networkPersonEventsEntity = { attrs: {
534
+ id: id3(),
535
+ kind: string({ indexed: true }),
536
+ personId: string({ indexed: true, optional: true }),
537
+ aliasId: string({ indexed: true, optional: true }),
538
+ actorId: string({ indexed: true }),
539
+ mutationId: string({ unique: true, indexed: true }),
540
+ detail: json(),
541
+ createdAt: number({ indexed: true })
542
+ } };
543
+ var networkPersonEntities = {
544
+ networkPersons: networkPersonsEntity,
545
+ networkIdentityClaims: networkIdentityClaimsEntity,
546
+ networkPersonAliases: networkPersonAliasesEntity,
547
+ networkChapterPersonLinks: networkChapterPersonLinksEntity,
548
+ networkPersonEvents: networkPersonEventsEntity
549
+ };
550
+
551
+ // src/schema-network-commercial.ts
552
+ var string2 = (flags = {}) => ({
553
+ type: "string",
554
+ unique: flags.unique ?? false,
555
+ indexed: flags.indexed ?? false,
556
+ optional: flags.optional ?? false
557
+ });
558
+ var number2 = (flags = {}) => ({
559
+ type: "number",
560
+ unique: false,
561
+ indexed: flags.indexed ?? false,
562
+ optional: flags.optional ?? false
563
+ });
564
+ var boolean = (flags = {}) => ({
565
+ type: "boolean",
566
+ unique: false,
567
+ indexed: flags.indexed ?? false,
568
+ optional: false
569
+ });
570
+ var json2 = () => ({ type: "json", unique: false, indexed: false, optional: false });
571
+ var id4 = () => string2({ unique: true, indexed: true });
572
+ var events = { attrs: {
573
+ id: id4(),
574
+ eventId: string2({ unique: true, indexed: true }),
575
+ digest: string2({ indexed: true }),
576
+ eventType: string2({ indexed: true }),
577
+ eventCreated: number2({ indexed: true }),
578
+ livemode: boolean({ indexed: true }),
579
+ fact: json2(),
580
+ receivedAt: number2({ indexed: true })
581
+ } };
582
+ var projection = () => ({ attrs: {
583
+ id: id4(),
584
+ providerId: string2({ indexed: true }),
585
+ sourceKey: string2({ indexed: true }),
586
+ status: string2({ indexed: true }),
587
+ snapshot: json2(),
588
+ eventCreated: number2({ indexed: true }),
589
+ updatedAt: number2({ indexed: true })
590
+ } });
591
+ var exceptions = { attrs: {
592
+ id: id4(),
593
+ eventId: string2({ indexed: true }),
594
+ kind: string2({ indexed: true }),
595
+ sourceKey: string2({ indexed: true, optional: true }),
596
+ status: string2({ indexed: true }),
597
+ detail: json2(),
598
+ updatedAt: number2({ indexed: true })
599
+ } };
600
+ var networkCommercialEntities = {
601
+ networkCommercialEvents: events,
602
+ networkCommercialCustomers: projection(),
603
+ networkCommercialSubscriptions: projection(),
604
+ networkCommercialInvoices: projection(),
605
+ networkCommercialRefunds: projection(),
606
+ networkCommercialExceptions: exceptions
607
+ };
608
+
469
609
  // src/schema.ts
470
610
  function attr5(type, flags = {}) {
471
611
  return {
@@ -475,10 +615,10 @@ function attr5(type, flags = {}) {
475
615
  optional: flags.optional ?? false
476
616
  };
477
617
  }
478
- var id3 = () => attr5("string", { unique: true, indexed: true });
618
+ var id5 = () => attr5("string", { unique: true, indexed: true });
479
619
  var admins = {
480
620
  attrs: {
481
- id: id3(),
621
+ id: id5(),
482
622
  email: attr5("string", { unique: true, indexed: true }),
483
623
  name: attr5("string", { optional: true }),
484
624
  note: attr5("string", { optional: true })
@@ -486,7 +626,7 @@ var admins = {
486
626
  };
487
627
  var superAdmins = {
488
628
  attrs: {
489
- id: id3(),
629
+ id: id5(),
490
630
  email: attr5("string", { unique: true, indexed: true }),
491
631
  note: attr5("string", { optional: true }),
492
632
  createdAt: attr5("number", { indexed: true })
@@ -494,7 +634,7 @@ var superAdmins = {
494
634
  };
495
635
  var networkNotes = {
496
636
  attrs: {
497
- id: id3(),
637
+ id: id5(),
498
638
  recordKey: attr5("string", { indexed: true }),
499
639
  targetId: attr5("string", { indexed: true }),
500
640
  recordType: attr5("string", { indexed: true }),
@@ -507,7 +647,7 @@ var networkNotes = {
507
647
  };
508
648
  var signupControls = {
509
649
  attrs: {
510
- id: id3(),
650
+ id: id5(),
511
651
  sourceId: attr5("string", { indexed: true }),
512
652
  targetId: attr5("string", { indexed: true }),
513
653
  environment: attr5("string", { indexed: true }),
@@ -520,7 +660,7 @@ var signupControls = {
520
660
  };
521
661
  var memberContentControls = {
522
662
  attrs: {
523
- id: id3(),
663
+ id: id5(),
524
664
  sourceId: attr5("string", { indexed: true }),
525
665
  targetId: attr5("string", { indexed: true }),
526
666
  environment: attr5("string", { indexed: true }),
@@ -533,7 +673,7 @@ var memberContentControls = {
533
673
  };
534
674
  var memberContentLocal = {
535
675
  attrs: {
536
- id: id3(),
676
+ id: id5(),
537
677
  chapterId: attr5("string", { indexed: true }),
538
678
  environment: attr5("string", { indexed: true }),
539
679
  revision: attr5("number", { indexed: true }),
@@ -544,7 +684,7 @@ var memberContentLocal = {
544
684
  };
545
685
  var builderLibraryControls = {
546
686
  attrs: {
547
- id: id3(),
687
+ id: id5(),
548
688
  sourceId: attr5("string", { indexed: true }),
549
689
  targetId: attr5("string", { indexed: true }),
550
690
  environment: attr5("string", { indexed: true }),
@@ -557,7 +697,7 @@ var builderLibraryControls = {
557
697
  };
558
698
  var builderLibraryLocal = {
559
699
  attrs: {
560
- id: id3(),
700
+ id: id5(),
561
701
  appId: attr5("string", { indexed: true }),
562
702
  environment: attr5("string", { indexed: true }),
563
703
  revision: attr5("number", { indexed: true }),
@@ -568,7 +708,7 @@ var builderLibraryLocal = {
568
708
  };
569
709
  var groups = {
570
710
  attrs: {
571
- id: id3(),
711
+ id: id5(),
572
712
  name: attr5("string"),
573
713
  standardPriceCents: attr5("number"),
574
714
  foundingDiscountCents: attr5("number"),
@@ -591,7 +731,7 @@ var groups = {
591
731
  };
592
732
  var tiers = {
593
733
  attrs: {
594
- id: id3(),
734
+ id: id5(),
595
735
  groupId: attr5("string", { indexed: true }),
596
736
  name: attr5("string"),
597
737
  priceCents: attr5("number"),
@@ -604,7 +744,7 @@ var tiers = {
604
744
  };
605
745
  var meetings = {
606
746
  attrs: {
607
- id: id3(),
747
+ id: id5(),
608
748
  applicationId: attr5("string", { indexed: true }),
609
749
  groupId: attr5("string", { indexed: true }),
610
750
  startAt: attr5("number", { indexed: true }),
@@ -623,7 +763,7 @@ var meetings = {
623
763
  };
624
764
  var emailLog = {
625
765
  attrs: {
626
- id: id3(),
766
+ id: id5(),
627
767
  groupId: attr5("string", { indexed: true }),
628
768
  applicationId: attr5("string", { indexed: true, optional: true }),
629
769
  to: attr5("string", { indexed: true }),
@@ -638,7 +778,7 @@ var emailLog = {
638
778
  sentAt: attr5("number", { indexed: true })
639
779
  }
640
780
  };
641
- function chapterDb(mode, auth, includeNetworkNotes = false, application = resolveApplication(void 0), includeSignupControl = false, includeMemberContent = false, includeBuilderLibrary = false, includeLeaderAdmissions = false) {
781
+ function chapterDb(mode, auth, includeNetworkNotes = false, application = resolveApplication(void 0), includeSignupControl = false, includeMemberContent = false, includeBuilderLibrary = false, includeLeaderAdmissions = false, includeNetworkPersonRegistry = false, includeNetworkCommercialReadModel = false) {
642
782
  const entities = {};
643
783
  if (mode === "chapter") {
644
784
  entities.applications = applicationsFor(application);
@@ -672,6 +812,10 @@ function chapterDb(mode, auth, includeNetworkNotes = false, application = resolv
672
812
  entities.leaderAdmissionDeliveries = leaderAdmissionDeliveriesEntity;
673
813
  entities.leaderAdmissionMutations = leaderAdmissionMutationsEntity;
674
814
  }
815
+ if (includeNetworkPersonRegistry) {
816
+ Object.assign(entities, networkPersonEntities);
817
+ }
818
+ if (includeNetworkCommercialReadModel) Object.assign(entities, networkCommercialEntities);
675
819
  const schema = { entities, links: {} };
676
820
  const rules = {};
677
821
  for (const ns of Object.keys(entities)) {
@@ -843,8 +987,8 @@ function resolveAuth(mode, auth) {
843
987
  return { source, claim, ladder, adminRole, superAdmins: superAdmins2 };
844
988
  }
845
989
  function roleFromClaim(payload, auth) {
846
- const raw = payload[auth.claim];
847
- return typeof raw === "string" && auth.ladder.includes(raw) ? raw : auth.ladder[0];
990
+ const raw2 = payload[auth.claim];
991
+ return typeof raw2 === "string" && auth.ladder.includes(raw2) ? raw2 : auth.ladder[0];
848
992
  }
849
993
  function isAdminRole(role, auth) {
850
994
  return role === auth.adminRole;
@@ -1323,9 +1467,9 @@ var APPLICATION_LABELS = {
1323
1467
  phone: "Phone",
1324
1468
  state: "State"
1325
1469
  };
1326
- function humanize(id4) {
1327
- const text4 = id4.replace(/([a-z0-9])([A-Z])/g, "$1 $2").replace(/[_-]+/g, " ").trim();
1328
- return text4 ? `${text4[0].toUpperCase()}${text4.slice(1)}` : id4;
1470
+ function humanize(id6) {
1471
+ const text5 = id6.replace(/([a-z0-9])([A-Z])/g, "$1 $2").replace(/[_-]+/g, " ").trim();
1472
+ return text5 ? `${text5[0].toUpperCase()}${text5.slice(1)}` : id6;
1329
1473
  }
1330
1474
  function applicationDefs(chapter) {
1331
1475
  try {
@@ -1334,23 +1478,23 @@ function applicationDefs(chapter) {
1334
1478
  return {};
1335
1479
  }
1336
1480
  }
1337
- function applicationType(id4, def) {
1481
+ function applicationType(id6, def) {
1338
1482
  if (def && ["string", "email", "url", "enum"].includes(def.type)) return def.type;
1339
- return id4 === "email" ? "email" : id4 === "linkedin" ? "url" : "string";
1483
+ return id6 === "email" ? "email" : id6 === "linkedin" ? "url" : "string";
1340
1484
  }
1341
1485
  function applicationFlowFields(chapter, application = chapter.application) {
1342
1486
  const defs = applicationDefs(chapter);
1343
- return [...application.required, ...application.optional].map((id4, order) => {
1344
- const def = defs[id4];
1345
- const conditions = application.conditions[id4];
1346
- const type = applicationType(id4, def);
1487
+ return [...application.required, ...application.optional].map((id6, order2) => {
1488
+ const def = defs[id6];
1489
+ const conditions = application.conditions[id6];
1490
+ const type = applicationType(id6, def);
1347
1491
  return {
1348
- id: id4,
1349
- label: def?.label ?? APPLICATION_LABELS[id4] ?? humanize(id4),
1492
+ id: id6,
1493
+ label: def?.label ?? APPLICATION_LABELS[id6] ?? humanize(id6),
1350
1494
  type,
1351
- required: application.required.includes(id4),
1352
- order,
1353
- maxLength: application.maxLen[id4] ?? application.defaultMaxLen,
1495
+ required: application.required.includes(id6),
1496
+ order: order2,
1497
+ maxLength: application.maxLen[id6] ?? application.defaultMaxLen,
1354
1498
  ...type === "enum" && def?.options ? { options: [...def.options] } : {},
1355
1499
  ...conditions ? { conditions: { ...conditions } } : {},
1356
1500
  ...type === "email" ? { emailIdentity: "literal" } : {}
@@ -1360,17 +1504,17 @@ function applicationFlowFields(chapter, application = chapter.application) {
1360
1504
  function resolveFormationFlowFields(crm, formation) {
1361
1505
  if (!formation.enabled) return [];
1362
1506
  const def = crm.type(formation.type);
1363
- return [...formation.required, ...formation.optional].map((id4, order) => {
1364
- const field = def.fields[id4];
1507
+ return [...formation.required, ...formation.optional].map((id6, order2) => {
1508
+ const field = def.fields[id6];
1365
1509
  return {
1366
- id: id4,
1367
- label: field.label ?? humanize(id4),
1510
+ id: id6,
1511
+ label: field.label ?? humanize(id6),
1368
1512
  type: field.type,
1369
- required: formation.required.includes(id4),
1370
- order,
1371
- maxLength: formation.maxLen[id4] ?? formation.defaultMaxLen,
1513
+ required: formation.required.includes(id6),
1514
+ order: order2,
1515
+ maxLength: formation.maxLen[id6] ?? formation.defaultMaxLen,
1372
1516
  ...field.options ? { options: [...field.options] } : {},
1373
- ...id4 === def.emailField || field.type === "email" ? { emailIdentity: "literal" } : {}
1517
+ ...id6 === def.emailField || field.type === "email" ? { emailIdentity: "literal" } : {}
1374
1518
  };
1375
1519
  });
1376
1520
  }
@@ -1410,7 +1554,7 @@ function resolveLeaderFormation(input, crm) {
1410
1554
  throw new Error(`defineChapter.formation.${name}: must be an array of field-name strings`);
1411
1555
  }
1412
1556
  }
1413
- const duplicates = [...required, ...optional].filter((field, index, fields) => fields.indexOf(field) !== index);
1557
+ const duplicates = [...required, ...optional].filter((field, index2, fields) => fields.indexOf(field) !== index2);
1414
1558
  if (duplicates.length) throw new Error(`defineChapter.formation: duplicate public field "${duplicates[0]}"`);
1415
1559
  const unknown = [...required, ...optional].filter((field) => !def.fields[field]);
1416
1560
  if (unknown.length) throw new Error(`defineChapter.formation: field "${unknown[0]}" is not declared on CRM type "${type}"`);
@@ -1461,7 +1605,7 @@ var SLUG = /^[a-z0-9][a-z0-9-]{1,62}$/;
1461
1605
  var FIELD = /^[a-z][a-zA-Z0-9_]*$/;
1462
1606
  var BINDING = /^[A-Z][A-Z0-9_]{0,127}$/;
1463
1607
  function validateSharedNotes(opts) {
1464
- const duplicate = opts.values.some((type, index) => opts.values.indexOf(type) !== index);
1608
+ const duplicate = opts.values.some((type, index2) => opts.values.indexOf(type) !== index2);
1465
1609
  const invalid = opts.values.some((type) => !FIELD.test(type) || !opts.crm.config.types[type] || (opts.fields ? !opts.fields[type] : false));
1466
1610
  if (duplicate || invalid) {
1467
1611
  throw new Error(`${opts.path}: must contain unique CRM types allowed by fields`);
@@ -1471,7 +1615,7 @@ function validateEditableFields(opts) {
1471
1615
  for (const [type, names] of Object.entries(opts.values)) {
1472
1616
  const readable = opts.readable?.[type];
1473
1617
  const def = opts.crm.config.types[type];
1474
- const duplicate = names.some((name, index) => names.indexOf(name) !== index);
1618
+ const duplicate = names.some((name, index2) => names.indexOf(name) !== index2);
1475
1619
  const unknown = names.some((name) => !FIELD.test(name) || !def?.fields[name]);
1476
1620
  const hidden = opts.readable ? !readable || names.some((name) => !readable.includes(name)) : false;
1477
1621
  if (!FIELD.test(type) || names.length === 0 || duplicate || !def || unknown || hidden) {
@@ -1482,7 +1626,7 @@ function validateEditableFields(opts) {
1482
1626
  }
1483
1627
  }
1484
1628
  function validateStageTransitions(opts) {
1485
- const duplicate = opts.values.some((type, index) => opts.values.indexOf(type) !== index);
1629
+ const duplicate = opts.values.some((type, index2) => opts.values.indexOf(type) !== index2);
1486
1630
  const invalid = opts.values.some((type) => !FIELD.test(type) || !opts.crm.config.types[type]?.pipeline || (opts.readable ? !opts.readable[type] : false));
1487
1631
  if (duplicate || invalid) {
1488
1632
  throw new Error(`${opts.path}: must contain unique, readable CRM types with pipelines`);
@@ -1664,12 +1808,58 @@ function resolveNetwork(config, crm) {
1664
1808
  return { targets, readers };
1665
1809
  }
1666
1810
 
1811
+ // src/config-network-person.ts
1812
+ var SLUG2 = /^[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?$/;
1813
+ function resolveNetworkPersonRegistry(mode, input, targetIds) {
1814
+ if (!input) return null;
1815
+ if (mode !== "hub") throw new Error("defineChapter.networkPersonRegistry: only hub mode may own network Person authority");
1816
+ if (input.mode !== "shadow") throw new Error('defineChapter.networkPersonRegistry.mode: must be "shadow"');
1817
+ const chapterIds = input.chapterIds ? [...input.chapterIds] : [...targetIds];
1818
+ if (chapterIds.length === 0) throw new Error("defineChapter.networkPersonRegistry.chapterIds: at least one chapter is required");
1819
+ if (new Set(chapterIds).size !== chapterIds.length || chapterIds.some((id6) => !SLUG2.test(id6))) {
1820
+ throw new Error("defineChapter.networkPersonRegistry.chapterIds: expected distinct lowercase slugs");
1821
+ }
1822
+ const unknown = chapterIds.filter((id6) => !targetIds.includes(id6));
1823
+ if (unknown.length) {
1824
+ throw new Error(`defineChapter.networkPersonRegistry.chapterIds: ${unknown.map((id6) => `"${id6}"`).join(", ")} are not configured network targets`);
1825
+ }
1826
+ return { mode: "shadow", chapterIds };
1827
+ }
1828
+
1829
+ // src/config-network-commercial.ts
1830
+ var SLUG3 = /^[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?$/;
1831
+ var TOKEN = /^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/;
1832
+ function networkCommercialSourceKey(source) {
1833
+ return [source.stripeMode, source.appId, source.environment, source.dataEnvironment, source.runtime, source.chapterId].join(":");
1834
+ }
1835
+ function resolveNetworkCommercialReadModel(mode, input, targetIds) {
1836
+ if (!input) return null;
1837
+ if (mode !== "hub") throw new Error("defineChapter.networkCommercialReadModel: only hub mode may own the network commercial projection");
1838
+ if (input.mode !== "shadow") throw new Error('defineChapter.networkCommercialReadModel.mode: must be "shadow"');
1839
+ if (!Array.isArray(input.sources) || input.sources.length === 0) {
1840
+ throw new Error("defineChapter.networkCommercialReadModel.sources: at least one exact source is required");
1841
+ }
1842
+ const sources = input.sources.map((source) => ({ ...source }));
1843
+ for (const source of sources) {
1844
+ if (!SLUG3.test(source.appId) || !SLUG3.test(source.chapterId) || !TOKEN.test(source.environment) || !TOKEN.test(source.dataEnvironment) || !TOKEN.test(source.runtime) || source.stripeMode !== "test" && source.stripeMode !== "live") {
1845
+ throw new Error("defineChapter.networkCommercialReadModel.sources: every source requires valid app, chapter, environment, data-environment, runtime, and Stripe mode values");
1846
+ }
1847
+ if (!targetIds.includes(source.chapterId)) {
1848
+ throw new Error(`defineChapter.networkCommercialReadModel.sources: chapter "${source.chapterId}" is not a configured network target`);
1849
+ }
1850
+ }
1851
+ if (new Set(sources.map(networkCommercialSourceKey)).size !== sources.length) {
1852
+ throw new Error("defineChapter.networkCommercialReadModel.sources: exact source partitions must be distinct");
1853
+ }
1854
+ return { mode: "shadow", sources };
1855
+ }
1856
+
1667
1857
  // src/config-signup-control.ts
1668
- var SLUG2 = /^[a-z0-9][a-z0-9-]{1,62}$/;
1858
+ var SLUG4 = /^[a-z0-9][a-z0-9-]{1,62}$/;
1669
1859
  var SECRET_NAME = /^[a-z][a-z0-9_]{1,127}$/;
1670
1860
  function resolveSignupControl(input) {
1671
1861
  if (!input) return null;
1672
- if (!SLUG2.test(input.sourceId)) {
1862
+ if (!SLUG4.test(input.sourceId)) {
1673
1863
  throw new Error("defineChapter.signupControl.sourceId: must be a lowercase app slug");
1674
1864
  }
1675
1865
  if (input.stripeMode !== "test" && input.stripeMode !== "live") {
@@ -1683,12 +1873,12 @@ function resolveSignupControl(input) {
1683
1873
  }
1684
1874
 
1685
1875
  // src/config-member-content.ts
1686
- var SLUG3 = /^[a-z0-9][a-z0-9-]{1,62}$/;
1876
+ var SLUG5 = /^[a-z0-9][a-z0-9-]{1,62}$/;
1687
1877
  var SECRET_NAME2 = /^[a-z][a-z0-9_]{1,127}$/;
1688
1878
  function resolveMemberContent(input) {
1689
1879
  if (!input) return null;
1690
1880
  const parent = input.parent;
1691
- if (parent && !SLUG3.test(parent.sourceId)) {
1881
+ if (parent && !SLUG5.test(parent.sourceId)) {
1692
1882
  throw new Error("defineChapter.memberContent.parent.sourceId: must be a lowercase app slug");
1693
1883
  }
1694
1884
  const secretName = parent?.secretName?.trim() || "member_content_control_secret";
@@ -1703,22 +1893,22 @@ function resolveMemberContent(input) {
1703
1893
  }
1704
1894
 
1705
1895
  // src/config-builder-library.ts
1706
- var SLUG4 = /^[a-z0-9][a-z0-9-]{1,62}$/;
1896
+ var SLUG6 = /^[a-z0-9][a-z0-9-]{1,62}$/;
1707
1897
  function resolveBuilderLibrary(mode, input, network = { targets: [], readers: [] }) {
1708
1898
  if (!input) return null;
1709
1899
  if (input.sources !== void 0 && !Array.isArray(input.sources)) {
1710
1900
  throw new Error("defineChapter.builderLibrary.sources: must be an array");
1711
1901
  }
1712
1902
  const seen = /* @__PURE__ */ new Set();
1713
- const sources = (input.sources ?? []).map((source, index) => {
1714
- if (!source || typeof source !== "object" || !SLUG4.test(source.sourceId)) {
1715
- throw new Error(`defineChapter.builderLibrary.sources[${index}].sourceId: must be a lowercase app slug`);
1903
+ const sources = (input.sources ?? []).map((source, index2) => {
1904
+ if (!source || typeof source !== "object" || !SLUG6.test(source.sourceId)) {
1905
+ throw new Error(`defineChapter.builderLibrary.sources[${index2}].sourceId: must be a lowercase app slug`);
1716
1906
  }
1717
1907
  if (source.sourceId === "built-not-found" && mode === "hub") {
1718
- throw new Error(`defineChapter.builderLibrary.sources[${index}]: a hub accepts Chapter sources, not itself`);
1908
+ throw new Error(`defineChapter.builderLibrary.sources[${index2}]: a hub accepts Chapter sources, not itself`);
1719
1909
  }
1720
1910
  if (mode === "chapter" && source.sourceId !== "built-not-found") {
1721
- throw new Error(`defineChapter.builderLibrary.sources[${index}]: Chapters may accept only the built-not-found source`);
1911
+ throw new Error(`defineChapter.builderLibrary.sources[${index2}]: Chapters may accept only the built-not-found source`);
1722
1912
  }
1723
1913
  if (seen.has(source.sourceId)) {
1724
1914
  throw new Error(`defineChapter.builderLibrary.sources: duplicate source "${source.sourceId}"`);
@@ -1726,14 +1916,14 @@ function resolveBuilderLibrary(mode, input, network = { targets: [], readers: []
1726
1916
  seen.add(source.sourceId);
1727
1917
  if ("secretName" in source) {
1728
1918
  throw new Error(
1729
- `defineChapter.builderLibrary.sources[${index}].secretName: remove this field; Builder Library reuses the Chapter network edge`
1919
+ `defineChapter.builderLibrary.sources[${index2}].secretName: remove this field; Builder Library reuses the Chapter network edge`
1730
1920
  );
1731
1921
  }
1732
1922
  const networkPeer = mode === "hub" ? network.targets.some((target) => target.id === source.sourceId) : network.readers.some((reader) => reader.id === source.sourceId);
1733
1923
  if (!networkPeer) {
1734
1924
  const side = mode === "hub" ? "targets" : "readers";
1735
1925
  throw new Error(
1736
- `defineChapter.builderLibrary.sources[${index}]: "${source.sourceId}" must already be configured in network.${side}`
1926
+ `defineChapter.builderLibrary.sources[${index2}]: "${source.sourceId}" must already be configured in network.${side}`
1737
1927
  );
1738
1928
  }
1739
1929
  return { sourceId: source.sourceId };
@@ -1742,15 +1932,15 @@ function resolveBuilderLibrary(mode, input, network = { targets: [], readers: []
1742
1932
  }
1743
1933
 
1744
1934
  // src/config.ts
1745
- var SLUG5 = /^[a-z0-9][a-z0-9-]{1,62}$/;
1935
+ var SLUG7 = /^[a-z0-9][a-z0-9-]{1,62}$/;
1746
1936
  function isResolvedCrm(x) {
1747
1937
  return !!x && typeof x === "object" && "prepare" in x && typeof x.prepare === "function";
1748
1938
  }
1749
1939
  function defineChapter(config) {
1750
1940
  if (!config || typeof config !== "object") throw new Error("defineChapter: a config object is required");
1751
- const { id: id4, name } = config;
1752
- if (typeof id4 !== "string" || !SLUG5.test(id4)) {
1753
- throw new Error(`defineChapter.id: must be a lowercase slug [a-z0-9-] (2-63 chars) \u2014 got ${JSON.stringify(id4)}`);
1941
+ const { id: id6, name } = config;
1942
+ if (typeof id6 !== "string" || !SLUG7.test(id6)) {
1943
+ throw new Error(`defineChapter.id: must be a lowercase slug [a-z0-9-] (2-63 chars) \u2014 got ${JSON.stringify(id6)}`);
1754
1944
  }
1755
1945
  if (typeof name !== "string" || name.trim() === "") throw new Error("defineChapter.name: a non-empty name is required");
1756
1946
  const mode = config.mode ?? "chapter";
@@ -1773,6 +1963,16 @@ function defineChapter(config) {
1773
1963
  const brand = { ...config.brand, wordmark: config.brand?.wordmark ?? name };
1774
1964
  const copy = resolveChapterCopy(config.copy);
1775
1965
  const network = resolveNetwork(config, crm);
1966
+ const networkPersonRegistry = resolveNetworkPersonRegistry(
1967
+ mode,
1968
+ config.networkPersonRegistry,
1969
+ network.targets.map((target) => target.id)
1970
+ );
1971
+ const networkCommercialReadModel = resolveNetworkCommercialReadModel(
1972
+ mode,
1973
+ config.networkCommercialReadModel,
1974
+ network.targets.map((target) => target.id)
1975
+ );
1776
1976
  const signupControl = resolveSignupControl(config.signupControl);
1777
1977
  const memberContent = resolveMemberContent(config.memberContent);
1778
1978
  const builderLibrary = resolveBuilderLibrary(mode, config.builderLibrary, network);
@@ -1791,7 +1991,9 @@ function defineChapter(config) {
1791
1991
  Boolean(signupControl),
1792
1992
  Boolean(memberContent),
1793
1993
  Boolean(builderLibrary),
1794
- network.targets.some((target) => target.admissionGrants)
1994
+ network.targets.some((target) => target.admissionGrants),
1995
+ Boolean(networkPersonRegistry),
1996
+ Boolean(networkCommercialReadModel)
1795
1997
  );
1796
1998
  const services = config.services ?? ["db", "calendar", "o11y"];
1797
1999
  const account = config.account ?? "none";
@@ -1841,7 +2043,7 @@ function defineChapter(config) {
1841
2043
  };
1842
2044
  const chapter = {
1843
2045
  config,
1844
- id: id4,
2046
+ id: id6,
1845
2047
  name,
1846
2048
  mode,
1847
2049
  brand,
@@ -1850,6 +2052,8 @@ function defineChapter(config) {
1850
2052
  signupControl,
1851
2053
  memberContent,
1852
2054
  builderLibrary,
2055
+ networkPersonRegistry,
2056
+ networkCommercialReadModel,
1853
2057
  formation,
1854
2058
  crm,
1855
2059
  auth,
@@ -1867,6 +2071,304 @@ function defineChapter(config) {
1867
2071
  return chapter;
1868
2072
  }
1869
2073
 
2074
+ // src/network-commercial.ts
2075
+ var raw = (value) => value && typeof value === "object" && !Array.isArray(value) ? value : {};
2076
+ var text = (value) => typeof value === "string" && value.length > 0 && value.length <= 255 ? value : void 0;
2077
+ var integer = (value) => typeof value === "number" && Number.isSafeInteger(value) ? value : void 0;
2078
+ var bool = (value) => typeof value === "boolean" ? value : void 0;
2079
+ var providerId = (value, prefix) => {
2080
+ const valueText = text(value);
2081
+ return valueText && (!prefix || valueText.startsWith(prefix)) ? valueText : void 0;
2082
+ };
2083
+ function metadataCandidates(object) {
2084
+ const parent = raw(object.parent);
2085
+ const subscriptionDetails = raw(object.subscription_details);
2086
+ const parentSubscription = raw(parent.subscription_details);
2087
+ return [raw(object.metadata), raw(subscriptionDetails.metadata), raw(parentSubscription.metadata)];
2088
+ }
2089
+ function metadataValue(candidates, key) {
2090
+ return candidates.map((candidate) => text(candidate[key])).find(Boolean);
2091
+ }
2092
+ function refId(value, prefix) {
2093
+ if (typeof value === "string") return providerId(value, prefix);
2094
+ return providerId(raw(value).id, prefix);
2095
+ }
2096
+ function subscriptionRef(object) {
2097
+ return refId(object.subscription, "sub_") ?? refId(raw(object.subscription_details).subscription, "sub_") ?? refId(raw(raw(object.parent).subscription_details).subscription, "sub_");
2098
+ }
2099
+ function firstPrice(object) {
2100
+ const data = raw(object.items).data;
2101
+ const item = Array.isArray(data) ? raw(data[0]) : {};
2102
+ const priceId = refId(item.price, "price_");
2103
+ const quantity = integer(item.quantity);
2104
+ return { ...priceId ? { priceId } : {}, ...quantity !== void 0 ? { quantity } : {} };
2105
+ }
2106
+ function normalizeNetworkCommercialStripeEvent(input, digest) {
2107
+ const event = raw(input);
2108
+ const eventId = providerId(event.id, "evt_") ?? "";
2109
+ const eventType = text(event.type) ?? "";
2110
+ const object = raw(raw(event.data).object);
2111
+ const objectId = providerId(object.id) ?? "";
2112
+ const candidates = metadataCandidates(object);
2113
+ const identity = {};
2114
+ const identityKeys = {
2115
+ appId: "odlaAppId",
2116
+ chapterId: "chapterId",
2117
+ environment: "odlaEnvironment",
2118
+ dataEnvironment: "odlaDataEnvironment",
2119
+ runtime: "odlaRuntime"
2120
+ };
2121
+ for (const [target, source] of Object.entries(identityKeys)) {
2122
+ const value = metadataValue(candidates, source);
2123
+ if (value) identity[target] = value;
2124
+ }
2125
+ let objectKind = "ignored";
2126
+ if (eventType.startsWith("customer.subscription.")) objectKind = "subscription";
2127
+ else if (eventType.startsWith("customer.")) objectKind = "customer";
2128
+ else if (eventType.startsWith("invoice.")) objectKind = "invoice";
2129
+ else if (eventType.startsWith("refund.")) objectKind = "refund";
2130
+ else if (eventType === "charge.refunded") objectKind = "refund";
2131
+ const customerId = refId(object.customer, "cus_");
2132
+ const subscriptionId = objectKind === "subscription" ? providerId(objectId, "sub_") : subscriptionRef(object);
2133
+ const invoiceId = objectKind === "invoice" ? providerId(objectId, "in_") : refId(object.invoice, "in_");
2134
+ const chargeId = eventType === "charge.refunded" ? providerId(objectId, "ch_") : refId(object.charge, "ch_");
2135
+ const paymentIntentId = refId(object.payment_intent, "pi_");
2136
+ const price = firstPrice(object);
2137
+ const amountCents = integer(eventType === "charge.refunded" ? object.amount_refunded : object.amount);
2138
+ const currency = text(object.currency)?.toLowerCase();
2139
+ const status = text(object.status);
2140
+ return {
2141
+ eventId,
2142
+ digest,
2143
+ eventType,
2144
+ eventCreated: integer(event.created) ?? 0,
2145
+ livemode: event.livemode === true,
2146
+ objectKind,
2147
+ objectId,
2148
+ identity,
2149
+ ...metadataValue(candidates, "applicationId") ? { applicationId: metadataValue(candidates, "applicationId") } : {},
2150
+ ...metadataValue(candidates, "personId") ? { personId: metadataValue(candidates, "personId") } : {},
2151
+ ...metadataValue(candidates, "membershipId") ? { membershipId: metadataValue(candidates, "membershipId") } : {},
2152
+ ...customerId ? { customerId } : {},
2153
+ ...subscriptionId ? { subscriptionId } : {},
2154
+ ...invoiceId ? { invoiceId } : {},
2155
+ ...chargeId ? { chargeId } : {},
2156
+ ...paymentIntentId ? { paymentIntentId } : {},
2157
+ ...price,
2158
+ ...status ? { status } : {},
2159
+ ...currency ? { currency } : {},
2160
+ ...amountCents !== void 0 ? { amountCents } : {},
2161
+ ...integer(object.amount_paid) !== void 0 ? { amountPaidCents: integer(object.amount_paid) } : {},
2162
+ ...integer(object.amount_remaining) !== void 0 ? { amountRemainingCents: integer(object.amount_remaining) } : {},
2163
+ ...integer(object.current_period_start) !== void 0 ? { currentPeriodStart: integer(object.current_period_start) } : {},
2164
+ ...integer(object.current_period_end) !== void 0 ? { currentPeriodEnd: integer(object.current_period_end) } : {},
2165
+ ...bool(object.cancel_at_period_end) !== void 0 ? { cancelAtPeriodEnd: bool(object.cancel_at_period_end) } : {}
2166
+ };
2167
+ }
2168
+ var identityFields = ["appId", "chapterId", "environment", "dataEnvironment", "runtime"];
2169
+ function directSource(fact, sources) {
2170
+ const mode = fact.livemode ? "live" : "test";
2171
+ const supplied = identityFields.filter((field) => fact.identity[field] !== void 0);
2172
+ const candidates = sources.filter((source) => source.stripeMode === mode && supplied.every((field) => source[field] === fact.identity[field]));
2173
+ if (supplied.length === identityFields.length) return candidates.length === 1 ? candidates[0] : "wrong";
2174
+ if (supplied.length > 0 && candidates.length === 0) return "wrong";
2175
+ return null;
2176
+ }
2177
+ var objectRefs = (fact) => [
2178
+ fact.customerId && `customer:${fact.customerId}`,
2179
+ fact.subscriptionId && `subscription:${fact.subscriptionId}`,
2180
+ fact.invoiceId && `invoice:${fact.invoiceId}`,
2181
+ fact.chargeId && `charge:${fact.chargeId}`,
2182
+ fact.paymentIntentId && `payment_intent:${fact.paymentIntentId}`
2183
+ ].filter((value) => Boolean(value));
2184
+ function ownRef(fact) {
2185
+ if (!fact.objectId) return null;
2186
+ if (fact.objectKind === "customer") return `customer:${fact.objectId}`;
2187
+ if (fact.objectKind === "subscription") return `subscription:${fact.objectId}`;
2188
+ if (fact.objectKind === "invoice") return `invoice:${fact.objectId}`;
2189
+ if (fact.eventType.startsWith("refund.")) return `refund:${fact.objectId}`;
2190
+ return fact.eventType === "charge.refunded" ? `charge:${fact.objectId}` : null;
2191
+ }
2192
+ var order = (left, right) => left.eventCreated - right.eventCreated || left.eventId.localeCompare(right.eventId);
2193
+ function projectNetworkCommercialEvents(input, sources) {
2194
+ const facts2 = [...input].sort(order);
2195
+ const exceptions2 = [];
2196
+ const unique = [];
2197
+ const digests = /* @__PURE__ */ new Map();
2198
+ for (const fact of facts2) {
2199
+ const prior = digests.get(fact.eventId);
2200
+ if (prior === void 0) {
2201
+ digests.set(fact.eventId, fact.digest);
2202
+ unique.push(fact);
2203
+ } else if (prior !== fact.digest) exceptions2.push({ eventId: fact.eventId, kind: "collision", detail: { eventType: fact.eventType, objectId: fact.objectId } });
2204
+ }
2205
+ const assignments = /* @__PURE__ */ new Map();
2206
+ const refs = /* @__PURE__ */ new Map();
2207
+ for (const fact of unique) {
2208
+ const direct = directSource(fact, sources);
2209
+ if (direct && direct !== "wrong") assignments.set(fact.eventId, direct);
2210
+ }
2211
+ let changed = true;
2212
+ while (changed) {
2213
+ changed = false;
2214
+ for (const fact of unique) {
2215
+ const assigned = assignments.get(fact.eventId);
2216
+ if (assigned) {
2217
+ const key = networkCommercialSourceKey(assigned);
2218
+ for (const ref of [...objectRefs(fact), ownRef(fact)].filter((value) => Boolean(value))) {
2219
+ const keys2 = refs.get(ref) ?? /* @__PURE__ */ new Set();
2220
+ const before = keys2.size;
2221
+ keys2.add(key);
2222
+ refs.set(ref, keys2);
2223
+ if (keys2.size !== before) changed = true;
2224
+ }
2225
+ continue;
2226
+ }
2227
+ if (directSource(fact, sources) === "wrong") continue;
2228
+ const keys = new Set(objectRefs(fact).flatMap((ref) => [...refs.get(ref) ?? []]));
2229
+ if (keys.size !== 1) continue;
2230
+ const candidate = sources.find((source) => networkCommercialSourceKey(source) === [...keys][0]);
2231
+ if (candidate && directSource({ ...fact, identity: fact.identity }, [candidate]) !== "wrong") {
2232
+ assignments.set(fact.eventId, candidate);
2233
+ changed = true;
2234
+ }
2235
+ }
2236
+ }
2237
+ const latest = /* @__PURE__ */ new Map();
2238
+ for (const fact of unique) {
2239
+ if (fact.objectKind === "ignored" || !fact.eventId || !fact.objectId) {
2240
+ exceptions2.push({ eventId: fact.eventId, kind: "unsupported", detail: { eventType: fact.eventType, objectId: fact.objectId } });
2241
+ continue;
2242
+ }
2243
+ const source = assignments.get(fact.eventId);
2244
+ if (!source) {
2245
+ const direct = directSource(fact, sources);
2246
+ const keys = new Set(objectRefs(fact).flatMap((ref) => [...refs.get(ref) ?? []]));
2247
+ exceptions2.push({ eventId: fact.eventId, kind: direct === "wrong" ? "wrong_partition" : keys.size > 1 ? "ambiguous_partition" : "unmatched_partition", detail: { eventType: fact.eventType, objectId: fact.objectId } });
2248
+ continue;
2249
+ }
2250
+ const row = { ...fact, sourceKey: networkCommercialSourceKey(source) };
2251
+ const key = `${fact.objectKind}:${fact.objectId}`;
2252
+ const prior = latest.get(key);
2253
+ if (!prior || order(prior, fact) <= 0) latest.set(key, row);
2254
+ }
2255
+ const rows3 = [...latest.values()];
2256
+ const customers = rows3.filter((row) => row.objectKind === "customer");
2257
+ const subscriptions = rows3.filter((row) => row.objectKind === "subscription");
2258
+ const invoices = rows3.filter((row) => row.objectKind === "invoice");
2259
+ const refunds = rows3.filter((row) => row.objectKind === "refund" && row.eventType.startsWith("refund."));
2260
+ for (const charge of rows3.filter((row) => row.eventType === "charge.refunded")) {
2261
+ if (!refunds.some((refund) => refund.chargeId === charge.objectId)) {
2262
+ exceptions2.push({ eventId: charge.eventId, kind: "charge_refund_without_refund_object", sourceKey: charge.sourceKey, detail: { eventType: charge.eventType, objectId: charge.objectId } });
2263
+ }
2264
+ }
2265
+ const totalsMap = /* @__PURE__ */ new Map();
2266
+ const add = (row, field, amount) => {
2267
+ if (!row.currency) return;
2268
+ const key = `${row.sourceKey}:${row.currency}`;
2269
+ const total = totalsMap.get(key) ?? { sourceKey: row.sourceKey, currency: row.currency, paidCents: 0, refundedCents: 0, netCents: 0 };
2270
+ total[field] += amount;
2271
+ total.netCents = total.paidCents - total.refundedCents;
2272
+ totalsMap.set(key, total);
2273
+ };
2274
+ for (const row of invoices) add(row, "paidCents", row.amountPaidCents ?? 0);
2275
+ for (const row of refunds) add(row, "refundedCents", row.amountCents ?? 0);
2276
+ return { customers, subscriptions, invoices, refunds, exceptions: exceptions2, totals: [...totalsMap.values()].sort((a, b) => `${a.sourceKey}:${a.currency}`.localeCompare(`${b.sourceKey}:${b.currency}`)) };
2277
+ }
2278
+
2279
+ // src/network-commercial-reconcile.ts
2280
+ var asRaw = (value) => value && typeof value === "object" && !Array.isArray(value) ? value : null;
2281
+ async function listStripeObjects(client, path, params = {}, maxPages = 20) {
2282
+ const objects = [];
2283
+ let startingAfter;
2284
+ for (let pageNumber = 0; pageNumber < maxPages; pageNumber += 1) {
2285
+ const page = await client.request("GET", path, {
2286
+ ...params,
2287
+ limit: 100,
2288
+ ...startingAfter ? { starting_after: startingAfter } : {}
2289
+ });
2290
+ if (!Array.isArray(page.data) || typeof page.has_more !== "boolean") {
2291
+ throw new Error("Stripe returned an invalid commercial list response");
2292
+ }
2293
+ const rows3 = page.data.map(asRaw).filter((row) => row !== null);
2294
+ if (rows3.length !== page.data.length) throw new Error("Stripe returned a malformed commercial object");
2295
+ objects.push(...rows3);
2296
+ if (!page.has_more) return { objects, complete: true };
2297
+ const cursor = rows3.at(-1)?.id;
2298
+ if (typeof cursor !== "string" || !cursor) throw new Error("Stripe pagination did not provide a cursor");
2299
+ startingAfter = cursor;
2300
+ }
2301
+ return { objects, complete: false };
2302
+ }
2303
+ function facts(snapshots) {
2304
+ const groups2 = [
2305
+ ["customer.updated", snapshots.customers.objects],
2306
+ ["customer.subscription.updated", snapshots.subscriptions.objects],
2307
+ ["invoice.updated", snapshots.invoices.objects],
2308
+ ["refund.updated", snapshots.refunds.objects]
2309
+ ];
2310
+ return groups2.flatMap(([eventType, objects]) => objects.map((object) => {
2311
+ const id6 = typeof object.id === "string" ? object.id : "invalid";
2312
+ return normalizeNetworkCommercialStripeEvent({
2313
+ id: `evt_snapshot_${eventType.replaceAll(".", "_")}_${id6}`,
2314
+ type: eventType,
2315
+ created: typeof object.created === "number" ? object.created : 0,
2316
+ livemode: object.livemode === true,
2317
+ data: { object }
2318
+ }, `stripe-snapshot:${eventType}:${id6}`);
2319
+ }));
2320
+ }
2321
+ var selected = (row) => ({
2322
+ status: row.status ?? null,
2323
+ currency: row.currency ?? null,
2324
+ amountCents: row.amountCents ?? null,
2325
+ amountPaidCents: row.amountPaidCents ?? null,
2326
+ amountRemainingCents: row.amountRemainingCents ?? null,
2327
+ customerId: row.customerId ?? null,
2328
+ subscriptionId: row.subscriptionId ?? null,
2329
+ invoiceId: row.invoiceId ?? null,
2330
+ priceId: row.priceId ?? null,
2331
+ quantity: row.quantity ?? null,
2332
+ applicationId: row.applicationId ?? null,
2333
+ personId: row.personId ?? null,
2334
+ membershipId: row.membershipId ?? null,
2335
+ currentPeriodStart: row.currentPeriodStart ?? null,
2336
+ currentPeriodEnd: row.currentPeriodEnd ?? null,
2337
+ cancelAtPeriodEnd: row.cancelAtPeriodEnd ?? null
2338
+ });
2339
+ var index = (projection2) => new Map(
2340
+ [...projection2.customers, ...projection2.subscriptions, ...projection2.invoices, ...projection2.refunds].map((row) => [`${row.sourceKey}:${row.objectKind}:${row.objectId}`, row])
2341
+ );
2342
+ function reconcileStripeSnapshot(shadow, snapshots, sources) {
2343
+ const stripe = projectNetworkCommercialEvents(facts(snapshots), sources);
2344
+ const shadowRows = index(shadow);
2345
+ const stripeRows = index(stripe);
2346
+ const exceptions2 = [];
2347
+ const complete = Object.values(snapshots).every((snapshot) => snapshot.complete);
2348
+ if (!complete) exceptions2.push({ kind: "stripe_snapshot_truncated", detail: {} });
2349
+ for (const [key, row] of shadowRows) {
2350
+ const other = stripeRows.get(key);
2351
+ if (!other) {
2352
+ exceptions2.push({ kind: "missing_from_stripe", sourceKey: row.sourceKey, objectKind: row.objectKind, providerId: row.objectId, detail: {} });
2353
+ } else if (JSON.stringify(selected(row)) !== JSON.stringify(selected(other))) {
2354
+ exceptions2.push({ kind: "object_mismatch", sourceKey: row.sourceKey, objectKind: row.objectKind, providerId: row.objectId, detail: { shadow: selected(row), stripe: selected(other) } });
2355
+ }
2356
+ }
2357
+ for (const [key, row] of stripeRows) {
2358
+ if (!shadowRows.has(key)) exceptions2.push({ kind: "missing_from_shadow", sourceKey: row.sourceKey, objectKind: row.objectKind, providerId: row.objectId, detail: {} });
2359
+ }
2360
+ if (JSON.stringify(shadow.totals) !== JSON.stringify(stripe.totals)) {
2361
+ exceptions2.push({ kind: "totals_mismatch", detail: { shadow: shadow.totals, stripe: stripe.totals } });
2362
+ }
2363
+ return {
2364
+ ok: exceptions2.length === 0,
2365
+ complete,
2366
+ ignoredObjects: stripe.exceptions.filter((item) => item.kind === "wrong_partition" || item.kind === "unmatched_partition").length,
2367
+ exceptions: exceptions2,
2368
+ stripeTotals: stripe.totals
2369
+ };
2370
+ }
2371
+
1870
2372
  // src/leader.ts
1871
2373
  import { composeCrmConfigs } from "@odla-ai/crm";
1872
2374
  function leaderCrmConfig(options = {}) {
@@ -2035,14 +2537,14 @@ import { createCrmIntegration } from "@odla-ai/crm";
2035
2537
 
2036
2538
  // src/tiers.ts
2037
2539
  function buildTierSeeds(groupId, configured = [], now = 0) {
2038
- return configured.map((tier, index) => ({
2540
+ return configured.map((tier, index2) => ({
2039
2541
  id: tier.id,
2040
2542
  groupId,
2041
2543
  name: tier.name,
2042
2544
  priceCents: tier.priceCents,
2043
2545
  ...tier.stripePriceId ? { stripePriceId: tier.stripePriceId } : {},
2044
2546
  ...tier.blurb ? { blurb: tier.blurb } : {},
2045
- sortOrder: tier.sortOrder ?? index,
2547
+ sortOrder: tier.sortOrder ?? index2,
2046
2548
  active: tier.active !== false,
2047
2549
  createdAt: now
2048
2550
  }));
@@ -2115,13 +2617,26 @@ function createChapterIntegration(chapter, options = {}) {
2115
2617
  const isLeader = (chapter.config.network?.targets?.length ?? 0) > 0;
2116
2618
  const runbooks = isLeader ? {
2117
2619
  role: "leader",
2118
- slugs: ["chapter-leader", "chapter-network", "crm"],
2620
+ slugs: ["chapter-leader", "chapter-network", "crm", ...chapter.networkCommercialReadModel ? ["stripe-network-shadow"] : []],
2119
2621
  guidance: `${chapter.name} is a LEADER hub: it holds the CRM and shares records with its chapters. Read it with \`odla-ai runbook get chapter-leader\`.`
2120
2622
  } : {
2121
2623
  role: "follower",
2122
2624
  slugs: ["chapter-follower", "chapter-network"],
2123
2625
  guidance: `${chapter.name} is a FOLLOWER site: it receives people and records from a leader hub. Read it with \`odla-ai runbook get chapter-follower\`.`
2124
2626
  };
2627
+ const secrets = [];
2628
+ if (chapter.account !== "none") secrets.push({
2629
+ // Released Chapter workers request this effective name. odla-db maps it to
2630
+ // the canonical reserved `$clerk_secret` value written by the CLI.
2631
+ name: "clerk_secret_key",
2632
+ description: "Clerk backend key (set with odla-ai secrets set-clerk-key) required by the configured Chapter account model.",
2633
+ required: true
2634
+ });
2635
+ if (chapter.networkCommercialReadModel) secrets.push({
2636
+ name: "network_commercial_stripe_webhook_secret",
2637
+ description: "Signing secret for the read-only network commercial Stripe webhook endpoint.",
2638
+ required: true
2639
+ });
2125
2640
  return {
2126
2641
  id: "chapter",
2127
2642
  title: `Chapter: ${chapter.name}`,
@@ -2132,14 +2647,7 @@ function createChapterIntegration(chapter, options = {}) {
2132
2647
  },
2133
2648
  rules: { ...crmDesc.rules, ...chapter.rules },
2134
2649
  seeds,
2135
- secrets: chapter.account === "none" ? [] : [{
2136
- // Released Chapter workers request this effective name. odla-db maps
2137
- // it to the canonical reserved `$clerk_secret` value written by
2138
- // `odla-ai secrets set-clerk-key`.
2139
- name: "clerk_secret_key",
2140
- description: "Clerk backend key (set with odla-ai secrets set-clerk-key) required by the configured Chapter account model.",
2141
- required: true
2142
- }],
2650
+ secrets,
2143
2651
  probes: [
2144
2652
  ...crmDesc.probes ?? [],
2145
2653
  // The snapshot route is deliberately private. A credential-free smoke
@@ -2161,6 +2669,11 @@ function createChapterIntegration(chapter, options = {}) {
2161
2669
  { path: "/api/builder-library-control", expectedStatus: 401 },
2162
2670
  { path: "/api/admin/builder-library", expectedStatus: 401 },
2163
2671
  { path: "/api/admin/builder-library/manifest", expectedStatus: 401 }
2672
+ ] : [],
2673
+ ...chapter.networkPersonRegistry ? [{ path: "/api/admin/network-person-registry", expectedStatus: 401 }] : [],
2674
+ ...chapter.networkCommercialReadModel ? [
2675
+ { path: "/api/network-commercial/stripe-webhook", expectedStatus: 405 },
2676
+ { path: "/api/admin/network-commercial-read-model", expectedStatus: 401 }
2164
2677
  ] : []
2165
2678
  ],
2166
2679
  runbooks
@@ -2169,7 +2682,7 @@ function createChapterIntegration(chapter, options = {}) {
2169
2682
 
2170
2683
  // src/member-content-validation-helpers.ts
2171
2684
  var ID = /^[a-z][a-z0-9-]{0,79}$/;
2172
- var SLUG6 = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
2685
+ var SLUG8 = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
2173
2686
  var ROOT_KEYS = /* @__PURE__ */ new Set(["schemaVersion", "revision", "generatedAt", "source", "target", "pages", "upsellOverrides"]);
2174
2687
  var SOURCE_KEYS = /* @__PURE__ */ new Set(["type", "appId"]);
2175
2688
  var TARGET_KEYS = /* @__PURE__ */ new Set(["chapterId", "environment"]);
@@ -2201,21 +2714,21 @@ function textValue(value, path, errors, max = MAX_TEXT, required = false) {
2201
2714
  return valueText;
2202
2715
  }
2203
2716
  function offerValue(value, path, errors, tiers2, audience) {
2204
- const raw = record(value);
2205
- if (!raw) {
2717
+ const raw2 = record(value);
2718
+ if (!raw2) {
2206
2719
  errors.push(`${path} must be an object`);
2207
2720
  return void 0;
2208
2721
  }
2209
- unknownKeys(raw, OFFER_KEYS, path, errors);
2210
- const tierId = textValue(raw.tierId, `${path}.tierId`, errors, 80, true) ?? "";
2722
+ unknownKeys(raw2, OFFER_KEYS, path, errors);
2723
+ const tierId = textValue(raw2.tierId, `${path}.tierId`, errors, 80, true) ?? "";
2211
2724
  if (tierId && !ID.test(tierId)) errors.push(`${path}.tierId must be a lowercase kebab-case id`);
2212
2725
  if (tiers2.size && !tiers2.has(tierId)) errors.push(`${path}.tierId must reference a declared tier`);
2213
2726
  if (audience?.kind === "tiers" && tierId && !audience.tierIds.includes(tierId)) {
2214
2727
  errors.push(`${path}.tierId must grant access to this page`);
2215
2728
  }
2216
- const label = textValue(raw.label, `${path}.label`, errors);
2217
- const bodyMarkdown = textValue(raw.bodyMarkdown, `${path}.bodyMarkdown`, errors, MAX_MARKDOWN);
2218
- const ctaLabel = textValue(raw.ctaLabel, `${path}.ctaLabel`, errors);
2729
+ const label = textValue(raw2.label, `${path}.label`, errors);
2730
+ const bodyMarkdown = textValue(raw2.bodyMarkdown, `${path}.bodyMarkdown`, errors, MAX_MARKDOWN);
2731
+ const ctaLabel = textValue(raw2.ctaLabel, `${path}.ctaLabel`, errors);
2219
2732
  return {
2220
2733
  tierId,
2221
2734
  ...label ? { label } : {},
@@ -2267,37 +2780,37 @@ function validateMemberContentDocument(input, options = {}) {
2267
2780
  const ids = /* @__PURE__ */ new Set();
2268
2781
  const slugs = /* @__PURE__ */ new Set();
2269
2782
  const pages = [];
2270
- for (const [index, value] of rawPages.slice(0, MAX_PAGES).entries()) {
2271
- const path = `document.pages[${index}]`;
2272
- const raw = record(value);
2273
- if (!raw) {
2783
+ for (const [index2, value] of rawPages.slice(0, MAX_PAGES).entries()) {
2784
+ const path = `document.pages[${index2}]`;
2785
+ const raw2 = record(value);
2786
+ if (!raw2) {
2274
2787
  errors.push(`${path} must be an object`);
2275
2788
  continue;
2276
2789
  }
2277
- unknownKeys(raw, PAGE_KEYS, path, errors);
2278
- const id4 = textValue(raw.id, `${path}.id`, errors, 80, true) ?? "";
2279
- const slug = textValue(raw.slug, `${path}.slug`, errors, 120, true) ?? "";
2280
- const title = textValue(raw.title, `${path}.title`, errors, MAX_TEXT, true) ?? "";
2281
- if (id4 && !ID.test(id4)) errors.push(`${path}.id must be a lowercase kebab-case id`);
2282
- if (slug && !SLUG6.test(slug)) errors.push(`${path}.slug must be a lowercase URL slug`);
2283
- if (ids.has(id4) || knownParentIds.has(id4)) errors.push(`${path}.id duplicates page "${id4}"`);
2790
+ unknownKeys(raw2, PAGE_KEYS, path, errors);
2791
+ const id6 = textValue(raw2.id, `${path}.id`, errors, 80, true) ?? "";
2792
+ const slug = textValue(raw2.slug, `${path}.slug`, errors, 120, true) ?? "";
2793
+ const title = textValue(raw2.title, `${path}.title`, errors, MAX_TEXT, true) ?? "";
2794
+ if (id6 && !ID.test(id6)) errors.push(`${path}.id must be a lowercase kebab-case id`);
2795
+ if (slug && !SLUG8.test(slug)) errors.push(`${path}.slug must be a lowercase URL slug`);
2796
+ if (ids.has(id6) || knownParentIds.has(id6)) errors.push(`${path}.id duplicates page "${id6}"`);
2284
2797
  if (slugs.has(slug) || (options.parentPages ?? []).some((page) => page.slug === slug)) errors.push(`${path}.slug duplicates "${slug}"`);
2285
- ids.add(id4);
2798
+ ids.add(id6);
2286
2799
  slugs.add(slug);
2287
- const navRaw = record(raw.navigation);
2288
- let navigation = { section: "General", order: index };
2800
+ const navRaw = record(raw2.navigation);
2801
+ let navigation = { section: "General", order: index2 };
2289
2802
  if (!navRaw) errors.push(`${path}.navigation must be an object`);
2290
2803
  else {
2291
2804
  unknownKeys(navRaw, NAV_KEYS, `${path}.navigation`, errors);
2292
2805
  const section = textValue(navRaw.section, `${path}.navigation.section`, errors, 120, true) ?? "";
2293
- const order = navRaw.order;
2294
- if (!Number.isInteger(order) || Number(order) < 0 || Number(order) > 1e6) {
2806
+ const order2 = navRaw.order;
2807
+ if (!Number.isInteger(order2) || Number(order2) < 0 || Number(order2) > 1e6) {
2295
2808
  errors.push(`${path}.navigation.order must be an integer from 0 to 1,000,000`);
2296
2809
  }
2297
2810
  const parentId = textValue(navRaw.parentId, `${path}.navigation.parentId`, errors, 80);
2298
- navigation = { section, order: Number.isInteger(order) ? Number(order) : index, ...parentId ? { parentId } : {} };
2811
+ navigation = { section, order: Number.isInteger(order2) ? Number(order2) : index2, ...parentId ? { parentId } : {} };
2299
2812
  }
2300
- const audienceRaw = record(raw.audience);
2813
+ const audienceRaw = record(raw2.audience);
2301
2814
  let audience = { kind: "all-members" };
2302
2815
  if (!audienceRaw) errors.push(`${path}.audience must be an object`);
2303
2816
  else {
@@ -2319,12 +2832,12 @@ function validateMemberContentDocument(input, options = {}) {
2319
2832
  audience = { kind: "tiers", tierIds: normalized };
2320
2833
  } else errors.push(`${path}.audience.kind must be all-members or tiers`);
2321
2834
  }
2322
- const summary = textValue(raw.summary, `${path}.summary`, errors, 2e3);
2323
- const teaserMarkdown = textValue(raw.teaserMarkdown, `${path}.teaserMarkdown`, errors, MAX_MARKDOWN);
2324
- const bodyMarkdown = textValue(raw.bodyMarkdown, `${path}.bodyMarkdown`, errors, MAX_MARKDOWN, true) ?? "";
2835
+ const summary = textValue(raw2.summary, `${path}.summary`, errors, 2e3);
2836
+ const teaserMarkdown = textValue(raw2.teaserMarkdown, `${path}.teaserMarkdown`, errors, MAX_MARKDOWN);
2837
+ const bodyMarkdown = textValue(raw2.bodyMarkdown, `${path}.bodyMarkdown`, errors, MAX_MARKDOWN, true) ?? "";
2325
2838
  let upsell;
2326
- if (raw.upsell !== void 0) {
2327
- const upsellRaw = record(raw.upsell);
2839
+ if (raw2.upsell !== void 0) {
2840
+ const upsellRaw = record(raw2.upsell);
2328
2841
  if (!upsellRaw) errors.push(`${path}.upsell must be an object`);
2329
2842
  else {
2330
2843
  unknownKeys(upsellRaw, UPSELL_KEYS, `${path}.upsell`, errors);
@@ -2338,13 +2851,13 @@ function validateMemberContentDocument(input, options = {}) {
2338
2851
  else if (owner === "chapter") upsell = { owner, ...offer ? { offer } : {} };
2339
2852
  }
2340
2853
  }
2341
- pages.push({ id: id4, slug, title, ...summary ? { summary } : {}, navigation, audience, ...teaserMarkdown ? { teaserMarkdown } : {}, bodyMarkdown, ...upsell ? { upsell } : {} });
2854
+ pages.push({ id: id6, slug, title, ...summary ? { summary } : {}, navigation, audience, ...teaserMarkdown ? { teaserMarkdown } : {}, bodyMarkdown, ...upsell ? { upsell } : {} });
2342
2855
  }
2343
2856
  const allIds = /* @__PURE__ */ new Set([...knownParentIds, ...ids]);
2344
- for (const [index, page] of pages.entries()) {
2857
+ for (const [index2, page] of pages.entries()) {
2345
2858
  const parentId = page.navigation.parentId;
2346
- if (parentId && !allIds.has(parentId)) errors.push(`document.pages[${index}].navigation.parentId references unknown page "${parentId}"`);
2347
- if (parentId === page.id) errors.push(`document.pages[${index}].navigation.parentId cannot reference itself`);
2859
+ if (parentId && !allIds.has(parentId)) errors.push(`document.pages[${index2}].navigation.parentId references unknown page "${parentId}"`);
2860
+ if (parentId === page.id) errors.push(`document.pages[${index2}].navigation.parentId cannot reference itself`);
2348
2861
  }
2349
2862
  for (const page of pages) {
2350
2863
  const seen = /* @__PURE__ */ new Set([page.id]);
@@ -2365,21 +2878,21 @@ function validateMemberContentDocument(input, options = {}) {
2365
2878
  const values = Array.isArray(root.upsellOverrides) ? root.upsellOverrides : [];
2366
2879
  const seen = /* @__PURE__ */ new Set();
2367
2880
  upsellOverrides = [];
2368
- for (const [index, value] of values.entries()) {
2369
- const path = `document.upsellOverrides[${index}]`;
2370
- const raw = record(value);
2371
- if (!raw) {
2881
+ for (const [index2, value] of values.entries()) {
2882
+ const path = `document.upsellOverrides[${index2}]`;
2883
+ const raw2 = record(value);
2884
+ if (!raw2) {
2372
2885
  errors.push(`${path} must be an object`);
2373
2886
  continue;
2374
2887
  }
2375
- unknownKeys(raw, OVERRIDE_KEYS, path, errors);
2376
- const pageId = textValue(raw.pageId, `${path}.pageId`, errors, 80, true) ?? "";
2888
+ unknownKeys(raw2, OVERRIDE_KEYS, path, errors);
2889
+ const pageId = textValue(raw2.pageId, `${path}.pageId`, errors, 80, true) ?? "";
2377
2890
  if (seen.has(pageId)) errors.push(`${path}.pageId duplicates "${pageId}"`);
2378
2891
  seen.add(pageId);
2379
2892
  const parentPage = (options.parentPages ?? []).find((page) => page.id === pageId);
2380
2893
  if (!parentPage) errors.push(`${path}.pageId must reference a parent page`);
2381
2894
  else if (parentPage.upsell?.owner !== "chapter") errors.push(`${path}.pageId does not delegate upsell authority to the chapter`);
2382
- const offer = offerValue(raw.offer, `${path}.offer`, errors, tierIds, parentPage?.audience);
2895
+ const offer = offerValue(raw2.offer, `${path}.offer`, errors, tierIds, parentPage?.audience);
2383
2896
  if (offer) upsellOverrides.push({ pageId, offer });
2384
2897
  }
2385
2898
  }
@@ -2504,7 +3017,7 @@ var record2 = (value) => value !== null && typeof value === "object" && !Array.i
2504
3017
  function unknownKeys2(value, allowed2, path, errors) {
2505
3018
  for (const key of Object.keys(value)) if (!allowed2.has(key)) errors.push(`${path}.${key} is not supported`);
2506
3019
  }
2507
- function text(value, path, errors, max = MAX_TEXT2, required = true) {
3020
+ function text2(value, path, errors, max = MAX_TEXT2, required = true) {
2508
3021
  if (value === void 0 && !required) return void 0;
2509
3022
  if (typeof value !== "string" || required && !value.trim()) {
2510
3023
  errors.push(`${path} must be a non-empty string`);
@@ -2514,23 +3027,23 @@ function text(value, path, errors, max = MAX_TEXT2, required = true) {
2514
3027
  if (result.length > max) errors.push(`${path} must be at most ${max} characters`);
2515
3028
  return result;
2516
3029
  }
2517
- function integer(value, path, errors, minimum = 1) {
3030
+ function integer2(value, path, errors, minimum = 1) {
2518
3031
  if (!Number.isSafeInteger(value) || Number(value) < minimum) errors.push(`${path} must be an integer >= ${minimum}`);
2519
3032
  return Number(value);
2520
3033
  }
2521
3034
  function scopeValue(value, path, errors) {
2522
- const raw = record2(value);
2523
- if (!raw) {
3035
+ const raw2 = record2(value);
3036
+ if (!raw2) {
2524
3037
  errors.push(`${path} must be an object`);
2525
3038
  return null;
2526
3039
  }
2527
- unknownKeys2(raw, SCOPE_KEYS, path, errors);
2528
- if (raw.kind === "global" || raw.kind === "built_not_found") {
2529
- if (raw.chapterId !== void 0) errors.push(`${path}.chapterId is allowed only for chapter scope`);
2530
- return { kind: raw.kind };
3040
+ unknownKeys2(raw2, SCOPE_KEYS, path, errors);
3041
+ if (raw2.kind === "global" || raw2.kind === "built_not_found") {
3042
+ if (raw2.chapterId !== void 0) errors.push(`${path}.chapterId is allowed only for chapter scope`);
3043
+ return { kind: raw2.kind };
2531
3044
  }
2532
- if (raw.kind === "chapter") {
2533
- const chapterId = text(raw.chapterId, `${path}.chapterId`, errors, 63) ?? "";
3045
+ if (raw2.kind === "chapter") {
3046
+ const chapterId = text2(raw2.chapterId, `${path}.chapterId`, errors, 63) ?? "";
2534
3047
  if (chapterId && !APP_ID.test(chapterId)) errors.push(`${path}.chapterId must be a lowercase app slug`);
2535
3048
  return { kind: "chapter", chapterId };
2536
3049
  }
@@ -2538,28 +3051,28 @@ function scopeValue(value, path, errors) {
2538
3051
  return null;
2539
3052
  }
2540
3053
  function resourceValue(value, path, errors) {
2541
- const raw = record2(value);
2542
- if (!raw) {
3054
+ const raw2 = record2(value);
3055
+ if (!raw2) {
2543
3056
  errors.push(`${path} must be an object`);
2544
3057
  return null;
2545
3058
  }
2546
- unknownKeys2(raw, RESOURCE_KEYS, path, errors);
2547
- const id4 = text(raw.id, `${path}.id`, errors, 80) ?? "";
2548
- if (id4 && !ID2.test(id4)) errors.push(`${path}.id must be lowercase kebab-case`);
2549
- const kind = text(raw.kind, `${path}.kind`, errors) ?? "";
3059
+ unknownKeys2(raw2, RESOURCE_KEYS, path, errors);
3060
+ const id6 = text2(raw2.id, `${path}.id`, errors, 80) ?? "";
3061
+ if (id6 && !ID2.test(id6)) errors.push(`${path}.id must be lowercase kebab-case`);
3062
+ const kind = text2(raw2.kind, `${path}.kind`, errors) ?? "";
2550
3063
  if (!KINDS.has(kind)) errors.push(`${path}.kind is unsupported`);
2551
- const title = text(raw.title, `${path}.title`, errors, 240) ?? "";
2552
- const summary = text(raw.summary, `${path}.summary`, errors, 1e3, false);
2553
- const status = text(raw.status, `${path}.status`, errors) ?? "";
3064
+ const title = text2(raw2.title, `${path}.title`, errors, 240) ?? "";
3065
+ const summary = text2(raw2.summary, `${path}.summary`, errors, 1e3, false);
3066
+ const status = text2(raw2.status, `${path}.status`, errors) ?? "";
2554
3067
  if (!STATUSES.has(status)) errors.push(`${path}.status must be draft, published, or archived`);
2555
- const revision = integer(raw.revision, `${path}.revision`, errors);
2556
- const scope = scopeValue(raw.scope, `${path}.scope`, errors) ?? { kind: "global" };
2557
- const tags = Array.isArray(raw.tags) ? raw.tags.map((tag, index) => text(tag, `${path}.tags[${index}]`, errors, 80) ?? "") : [];
2558
- if (!Array.isArray(raw.tags)) errors.push(`${path}.tags must be an array`);
3068
+ const revision = integer2(raw2.revision, `${path}.revision`, errors);
3069
+ const scope = scopeValue(raw2.scope, `${path}.scope`, errors) ?? { kind: "global" };
3070
+ const tags = Array.isArray(raw2.tags) ? raw2.tags.map((tag, index2) => text2(tag, `${path}.tags[${index2}]`, errors, 80) ?? "") : [];
3071
+ if (!Array.isArray(raw2.tags)) errors.push(`${path}.tags must be an array`);
2559
3072
  if (tags.length > MAX_TAGS) errors.push(`${path}.tags may contain at most ${MAX_TAGS} items`);
2560
3073
  if (new Set(tags).size !== tags.length) errors.push(`${path}.tags must be unique`);
2561
- const bodyMarkdown = text(raw.bodyMarkdown, `${path}.bodyMarkdown`, errors, MAX_MARKDOWN2, false);
2562
- const url = text(raw.url, `${path}.url`, errors, 2048, false);
3074
+ const bodyMarkdown = text2(raw2.bodyMarkdown, `${path}.bodyMarkdown`, errors, MAX_MARKDOWN2, false);
3075
+ const url = text2(raw2.url, `${path}.url`, errors, 2048, false);
2563
3076
  if (url) {
2564
3077
  try {
2565
3078
  if (new URL(url).protocol !== "https:") errors.push(`${path}.url must use https`);
@@ -2567,13 +3080,13 @@ function resourceValue(value, path, errors) {
2567
3080
  errors.push(`${path}.url must be a valid https URL`);
2568
3081
  }
2569
3082
  }
2570
- const asset = raw.brandAsset === void 0 ? null : record2(raw.brandAsset);
2571
- if (raw.brandAsset !== void 0 && !asset) errors.push(`${path}.brandAsset must be an object`);
3083
+ const asset = raw2.brandAsset === void 0 ? null : record2(raw2.brandAsset);
3084
+ if (raw2.brandAsset !== void 0 && !asset) errors.push(`${path}.brandAsset must be an object`);
2572
3085
  if (asset) unknownKeys2(asset, ASSET_KEYS, `${path}.brandAsset`, errors);
2573
3086
  const brandAsset = asset ? {
2574
- bookId: text(asset.bookId, `${path}.brandAsset.bookId`, errors, 160) ?? "",
2575
- assetId: text(asset.assetId, `${path}.brandAsset.assetId`, errors, 160) ?? "",
2576
- ...asset.filename === void 0 ? {} : { filename: text(asset.filename, `${path}.brandAsset.filename`, errors, 240) ?? "" }
3087
+ bookId: text2(asset.bookId, `${path}.brandAsset.bookId`, errors, 160) ?? "",
3088
+ assetId: text2(asset.assetId, `${path}.brandAsset.assetId`, errors, 160) ?? "",
3089
+ ...asset.filename === void 0 ? {} : { filename: text2(asset.filename, `${path}.brandAsset.filename`, errors, 240) ?? "" }
2577
3090
  } : void 0;
2578
3091
  if ((kind === "document" || kind === "note") && !bodyMarkdown) errors.push(`${path}.bodyMarkdown is required for ${kind}`);
2579
3092
  if ((kind === "link" || kind === "inspiration") && !url) errors.push(`${path}.url is required for ${kind}`);
@@ -2581,59 +3094,59 @@ function resourceValue(value, path, errors) {
2581
3094
  if (!["document", "note"].includes(kind) && bodyMarkdown) errors.push(`${path}.bodyMarkdown is not allowed for ${kind}`);
2582
3095
  if (!["link", "inspiration"].includes(kind) && url) errors.push(`${path}.url is not allowed for ${kind}`);
2583
3096
  if (!["image", "file"].includes(kind) && brandAsset) errors.push(`${path}.brandAsset is not allowed for ${kind}`);
2584
- const provenanceRaw = record2(raw.provenance);
3097
+ const provenanceRaw = record2(raw2.provenance);
2585
3098
  if (!provenanceRaw) errors.push(`${path}.provenance must be an object`);
2586
3099
  if (provenanceRaw) unknownKeys2(provenanceRaw, PROVENANCE_KEYS, `${path}.provenance`, errors);
2587
3100
  const promotedRaw = provenanceRaw?.promotedFrom === void 0 ? null : record2(provenanceRaw.promotedFrom);
2588
3101
  if (provenanceRaw?.promotedFrom !== void 0 && !promotedRaw) errors.push(`${path}.provenance.promotedFrom must be an object`);
2589
3102
  if (promotedRaw) unknownKeys2(promotedRaw, PROMOTION_KEYS, `${path}.provenance.promotedFrom`, errors);
2590
3103
  const promotedFrom = promotedRaw ? {
2591
- chapterId: text(promotedRaw.chapterId, `${path}.provenance.promotedFrom.chapterId`, errors, 63) ?? "",
2592
- resourceId: text(promotedRaw.resourceId, `${path}.provenance.promotedFrom.resourceId`, errors, 80) ?? "",
2593
- resourceRevision: integer(promotedRaw.resourceRevision, `${path}.provenance.promotedFrom.resourceRevision`, errors),
2594
- documentRevision: integer(promotedRaw.documentRevision, `${path}.provenance.promotedFrom.documentRevision`, errors),
2595
- promotedBy: text(promotedRaw.promotedBy, `${path}.provenance.promotedFrom.promotedBy`, errors, 320) ?? "",
2596
- promotedAt: integer(promotedRaw.promotedAt, `${path}.provenance.promotedFrom.promotedAt`, errors, 0)
3104
+ chapterId: text2(promotedRaw.chapterId, `${path}.provenance.promotedFrom.chapterId`, errors, 63) ?? "",
3105
+ resourceId: text2(promotedRaw.resourceId, `${path}.provenance.promotedFrom.resourceId`, errors, 80) ?? "",
3106
+ resourceRevision: integer2(promotedRaw.resourceRevision, `${path}.provenance.promotedFrom.resourceRevision`, errors),
3107
+ documentRevision: integer2(promotedRaw.documentRevision, `${path}.provenance.promotedFrom.documentRevision`, errors),
3108
+ promotedBy: text2(promotedRaw.promotedBy, `${path}.provenance.promotedFrom.promotedBy`, errors, 320) ?? "",
3109
+ promotedAt: integer2(promotedRaw.promotedAt, `${path}.provenance.promotedFrom.promotedAt`, errors, 0)
2597
3110
  } : void 0;
2598
3111
  const provenance = {
2599
- createdBy: text(provenanceRaw?.createdBy, `${path}.provenance.createdBy`, errors, 320) ?? "",
2600
- createdAt: integer(provenanceRaw?.createdAt, `${path}.provenance.createdAt`, errors, 0),
2601
- updatedBy: text(provenanceRaw?.updatedBy, `${path}.provenance.updatedBy`, errors, 320) ?? "",
2602
- updatedAt: integer(provenanceRaw?.updatedAt, `${path}.provenance.updatedAt`, errors, 0),
3112
+ createdBy: text2(provenanceRaw?.createdBy, `${path}.provenance.createdBy`, errors, 320) ?? "",
3113
+ createdAt: integer2(provenanceRaw?.createdAt, `${path}.provenance.createdAt`, errors, 0),
3114
+ updatedBy: text2(provenanceRaw?.updatedBy, `${path}.provenance.updatedBy`, errors, 320) ?? "",
3115
+ updatedAt: integer2(provenanceRaw?.updatedAt, `${path}.provenance.updatedAt`, errors, 0),
2603
3116
  ...promotedFrom ? { promotedFrom } : {}
2604
3117
  };
2605
- return { id: id4, kind, title, ...summary ? { summary } : {}, tags, scope, status, revision, ...bodyMarkdown ? { bodyMarkdown } : {}, ...url ? { url } : {}, ...brandAsset ? { brandAsset } : {}, provenance };
3118
+ return { id: id6, kind, title, ...summary ? { summary } : {}, tags, scope, status, revision, ...bodyMarkdown ? { bodyMarkdown } : {}, ...url ? { url } : {}, ...brandAsset ? { brandAsset } : {}, provenance };
2606
3119
  }
2607
3120
  function validateBuilderLibraryDocument(value, options = {}) {
2608
3121
  const errors = [];
2609
- const raw = record2(value);
2610
- if (!raw) return { ok: false, errors: ["document must be an object"] };
2611
- unknownKeys2(raw, ROOT_KEYS2, "document", errors);
2612
- if (raw.schemaVersion !== BUILDER_LIBRARY_SCHEMA_VERSION) errors.push("document.schemaVersion must be 1");
2613
- const revision = integer(raw.revision, "document.revision", errors);
2614
- const generatedAt = integer(raw.generatedAt, "document.generatedAt", errors, 0);
2615
- const sourceRaw = record2(raw.source);
3122
+ const raw2 = record2(value);
3123
+ if (!raw2) return { ok: false, errors: ["document must be an object"] };
3124
+ unknownKeys2(raw2, ROOT_KEYS2, "document", errors);
3125
+ if (raw2.schemaVersion !== BUILDER_LIBRARY_SCHEMA_VERSION) errors.push("document.schemaVersion must be 1");
3126
+ const revision = integer2(raw2.revision, "document.revision", errors);
3127
+ const generatedAt = integer2(raw2.generatedAt, "document.generatedAt", errors, 0);
3128
+ const sourceRaw = record2(raw2.source);
2616
3129
  if (!sourceRaw) errors.push("document.source must be an object");
2617
3130
  if (sourceRaw) unknownKeys2(sourceRaw, SOURCE_KEYS2, "document.source", errors);
2618
3131
  const sourceType = sourceRaw?.type;
2619
3132
  if (sourceType !== "chapter" && sourceType !== "built_not_found") errors.push("document.source.type must be chapter or built_not_found");
2620
- const sourceAppId = text(sourceRaw?.appId, "document.source.appId", errors, 63) ?? "";
3133
+ const sourceAppId = text2(sourceRaw?.appId, "document.source.appId", errors, 63) ?? "";
2621
3134
  if (sourceAppId && !APP_ID.test(sourceAppId)) errors.push("document.source.appId must be a lowercase app slug");
2622
3135
  if (options.sourceType && sourceType !== options.sourceType) errors.push(`document.source.type must be ${options.sourceType}`);
2623
3136
  if (options.sourceAppId && sourceAppId !== options.sourceAppId) errors.push(`document.source.appId must be ${options.sourceAppId}`);
2624
- const targetRaw = raw.target === void 0 ? null : record2(raw.target);
2625
- if (raw.target !== void 0 && !targetRaw) errors.push("document.target must be an object");
3137
+ const targetRaw = raw2.target === void 0 ? null : record2(raw2.target);
3138
+ if (raw2.target !== void 0 && !targetRaw) errors.push("document.target must be an object");
2626
3139
  if (targetRaw) unknownKeys2(targetRaw, TARGET_KEYS2, "document.target", errors);
2627
3140
  const target = targetRaw ? {
2628
- appId: text(targetRaw.appId, "document.target.appId", errors, 63) ?? "",
2629
- environment: text(targetRaw.environment, "document.target.environment", errors, 80) ?? ""
3141
+ appId: text2(targetRaw.appId, "document.target.appId", errors, 63) ?? "",
3142
+ environment: text2(targetRaw.environment, "document.target.environment", errors, 80) ?? ""
2630
3143
  } : void 0;
2631
3144
  if (options.targetAppId && target?.appId !== options.targetAppId) errors.push(`document.target.appId must be ${options.targetAppId}`);
2632
- if (!Array.isArray(raw.resources)) errors.push("document.resources must be an array");
2633
- const inputs = Array.isArray(raw.resources) ? raw.resources : [];
3145
+ if (!Array.isArray(raw2.resources)) errors.push("document.resources must be an array");
3146
+ const inputs = Array.isArray(raw2.resources) ? raw2.resources : [];
2634
3147
  if (inputs.length > MAX_RESOURCES) errors.push(`document.resources may contain at most ${MAX_RESOURCES} items`);
2635
- const resources = inputs.map((item, index) => resourceValue(item, `document.resources[${index}]`, errors)).filter(Boolean);
2636
- const ids = resources.map(({ id: id4 }) => id4);
3148
+ const resources = inputs.map((item, index2) => resourceValue(item, `document.resources[${index2}]`, errors)).filter(Boolean);
3149
+ const ids = resources.map(({ id: id6 }) => id6);
2637
3150
  if (new Set(ids).size !== ids.length) errors.push("document.resources ids must be unique");
2638
3151
  for (const resource of resources) {
2639
3152
  if (sourceType === "chapter" && (resource.scope.kind !== "chapter" || resource.scope.chapterId !== sourceAppId)) {
@@ -2750,10 +3263,10 @@ function planDelivery(input) {
2750
3263
  const transport = !isProd && !redirect ? "log-only" : input.cloudflareReady ? "cloudflare" : "log-only";
2751
3264
  const to = redirect ? input.group.debugEmail : input.to;
2752
3265
  const subject = (redirect ? "[dev] " : "") + render(tpl.subject, vars);
2753
- const text4 = redirect ? `(dev redirect; original recipient: ${input.to})
3266
+ const text5 = redirect ? `(dev redirect; original recipient: ${input.to})
2754
3267
 
2755
3268
  ` + render(tpl.text, vars) : render(tpl.text, vars);
2756
- return { deliver: true, transport, to, subject, text: text4, redirected: redirect };
3269
+ return { deliver: true, transport, to, subject, text: text5, redirected: redirect };
2757
3270
  }
2758
3271
 
2759
3272
  // src/notify.ts
@@ -2788,9 +3301,9 @@ async function sendTemplated(deps, input) {
2788
3301
  error = e instanceof Error ? e.message : String(e);
2789
3302
  }
2790
3303
  }
2791
- const id4 = deps.newId();
3304
+ const id6 = deps.newId();
2792
3305
  const row = {
2793
- id: id4,
3306
+ id: id6,
2794
3307
  groupId: input.group.id,
2795
3308
  to: decision.to,
2796
3309
  template: input.template,
@@ -2804,7 +3317,7 @@ async function sendTemplated(deps, input) {
2804
3317
  ...messageId ? { messageId } : {},
2805
3318
  ...error ? { error } : {}
2806
3319
  };
2807
- await deps.db.transact([{ t: "update", ns: "emailLog", id: id4, attrs: row }], error ? void 0 : { mutationId: `email:${input.dedupeKey}` });
3320
+ await deps.db.transact([{ t: "update", ns: "emailLog", id: id6, attrs: row }], error ? void 0 : { mutationId: `email:${input.dedupeKey}` });
2808
3321
  return error ? { sent: false, reason: error } : { sent: true };
2809
3322
  }
2810
3323
  function emailGroupFrom(row) {
@@ -2990,29 +3503,29 @@ function validateAdmissionGrantDocument(input) {
2990
3503
  const grants = [];
2991
3504
  const seenIds = /* @__PURE__ */ new Set();
2992
3505
  const seenEmails = /* @__PURE__ */ new Set();
2993
- for (const [index, raw] of rawGrants.slice(0, MAX_GRANTS).entries()) {
2994
- const grant = raw && typeof raw === "object" ? raw : {};
2995
- const id4 = typeof grant.id === "string" ? grant.id : "";
3506
+ for (const [index2, raw2] of rawGrants.slice(0, MAX_GRANTS).entries()) {
3507
+ const grant = raw2 && typeof raw2 === "object" ? raw2 : {};
3508
+ const id6 = typeof grant.id === "string" ? grant.id : "";
2996
3509
  const email = typeof grant.email === "string" ? grant.email.trim() : "";
2997
3510
  const normalized = normalizeAdmissionEmail(email);
2998
3511
  const decision = grant.decision;
2999
3512
  const note = typeof grant.note === "string" ? grant.note.trim() : void 0;
3000
3513
  const expiresAt = grant.expiresAt === void 0 ? void 0 : Number(grant.expiresAt);
3001
- if (!ID3.test(id4)) errors.push(`document.grants[${index}].id must be an identifier`);
3002
- if (seenIds.has(id4)) errors.push(`document.grants repeats id "${id4}"`);
3003
- if (!isAdmissionEmail(normalized)) errors.push(`document.grants[${index}].email must be valid`);
3514
+ if (!ID3.test(id6)) errors.push(`document.grants[${index2}].id must be an identifier`);
3515
+ if (seenIds.has(id6)) errors.push(`document.grants repeats id "${id6}"`);
3516
+ if (!isAdmissionEmail(normalized)) errors.push(`document.grants[${index2}].email must be valid`);
3004
3517
  if (seenEmails.has(normalized)) errors.push(`document.grants repeats email "${normalized}"`);
3005
3518
  if (decision !== "waive_interview" && decision !== "require_interview") {
3006
- errors.push(`document.grants[${index}].decision is invalid`);
3519
+ errors.push(`document.grants[${index2}].decision is invalid`);
3007
3520
  }
3008
- if (note && note.length > MAX_NOTE) errors.push(`document.grants[${index}].note is too long`);
3521
+ if (note && note.length > MAX_NOTE) errors.push(`document.grants[${index2}].note is too long`);
3009
3522
  if (expiresAt !== void 0 && (!Number.isFinite(expiresAt) || expiresAt <= 0)) {
3010
- errors.push(`document.grants[${index}].expiresAt must be epoch milliseconds`);
3523
+ errors.push(`document.grants[${index2}].expiresAt must be epoch milliseconds`);
3011
3524
  }
3012
- seenIds.add(id4);
3525
+ seenIds.add(id6);
3013
3526
  seenEmails.add(normalized);
3014
3527
  grants.push({
3015
- id: id4,
3528
+ id: id6,
3016
3529
  email,
3017
3530
  decision,
3018
3531
  ...note ? { note } : {},
@@ -3083,8 +3596,8 @@ async function materializeAdmissionGrants(db, envelope, previous, receivedAt) {
3083
3596
  const incomingIds = new Set(document.grants.map((grant) => `admission:parent:${document.source.appId}:${grant.id}`));
3084
3597
  const ops = [];
3085
3598
  for (const row of current) {
3086
- const id4 = String(row.id ?? "");
3087
- if (id4 && !incomingIds.has(id4)) ops.push({ t: "update", ns: "admissionGrants", id: id4, attrs: {
3599
+ const id6 = String(row.id ?? "");
3600
+ if (id6 && !incomingIds.has(id6)) ops.push({ t: "update", ns: "admissionGrants", id: id6, attrs: {
3088
3601
  active: false,
3089
3602
  revokedAt: receivedAt,
3090
3603
  revokedBy: document.source.actor,
@@ -3093,10 +3606,10 @@ async function materializeAdmissionGrants(db, envelope, previous, receivedAt) {
3093
3606
  } });
3094
3607
  }
3095
3608
  for (const grant of document.grants) {
3096
- const id4 = `admission:parent:${document.source.appId}:${grant.id}`;
3097
- const existing = current.find((row) => row.id === id4);
3098
- ops.push({ t: "update", ns: "admissionGrants", id: id4, attrs: {
3099
- id: id4,
3609
+ const id6 = `admission:parent:${document.source.appId}:${grant.id}`;
3610
+ const existing = current.find((row) => row.id === id6);
3611
+ ops.push({ t: "update", ns: "admissionGrants", id: id6, attrs: {
3612
+ id: id6,
3100
3613
  sourceGrantId: grant.id,
3101
3614
  sourceScope: "parent",
3102
3615
  sourceId: document.source.appId,
@@ -3117,7 +3630,7 @@ async function materializeAdmissionGrants(db, envelope, previous, receivedAt) {
3117
3630
  ...!grant.note && existing?.note !== void 0 ? ["note"] : [],
3118
3631
  ...grant.expiresAt === void 0 && existing?.expiresAt !== void 0 ? ["expiresAt"] : []
3119
3632
  ];
3120
- if (retract.length) ops.push({ t: "retract", ns: "admissionGrants", id: id4, attrs: retract });
3633
+ if (retract.length) ops.push({ t: "retract", ns: "admissionGrants", id: id6, attrs: retract });
3121
3634
  }
3122
3635
  if (previous?.active) ops.push({ t: "update", ns: "admissionGrantRevisions", id: previous.id, attrs: { active: false } });
3123
3636
  const revisionId = `admission-revision:${document.source.appId}:${document.target.environment}:${document.revision}`;
@@ -3159,7 +3672,7 @@ var ID4 = /^[a-z][a-z0-9-]{1,79}$/;
3159
3672
  var PRICE = /^price_[A-Za-z0-9]{12,}$/;
3160
3673
  var CURRENCY = /^[a-z]{3}$/;
3161
3674
  var record3 = (value) => value && typeof value === "object" && !Array.isArray(value) ? value : null;
3162
- var text2 = (value, path, errors, max) => {
3675
+ var text3 = (value, path, errors, max) => {
3163
3676
  if (typeof value !== "string" || !value.trim()) {
3164
3677
  errors.push(`${path} is required`);
3165
3678
  return "";
@@ -3178,32 +3691,32 @@ function validateGiftOfferCatalog(input, tierIds) {
3178
3691
  if (rawOffers.length > MAX_GIFT_OFFERS) errors.push(`document.giftOffers.offers supports at most ${MAX_GIFT_OFFERS} entries`);
3179
3692
  const ids = /* @__PURE__ */ new Set();
3180
3693
  const offers = [];
3181
- for (const [index, raw] of rawOffers.slice(0, MAX_GIFT_OFFERS).entries()) {
3182
- const path = `document.giftOffers.offers[${index}]`;
3183
- const item = record3(raw);
3694
+ for (const [index2, raw2] of rawOffers.slice(0, MAX_GIFT_OFFERS).entries()) {
3695
+ const path = `document.giftOffers.offers[${index2}]`;
3696
+ const item = record3(raw2);
3184
3697
  if (!item) {
3185
3698
  errors.push(`${path} must be an object`);
3186
3699
  continue;
3187
3700
  }
3188
3701
  const allowed2 = /* @__PURE__ */ new Set(["id", "target", "billingMode", "stripePriceId", "priceCents", "currency", "redemptionLifetimeHours", "activeFrom", "activeUntil", "title", "description", "ctaLabel"]);
3189
3702
  for (const key of Object.keys(item)) if (!allowed2.has(key)) errors.push(`${path}.${key} is not supported`);
3190
- const id4 = text2(item.id, `${path}.id`, errors, 80);
3191
- if (id4 && !ID4.test(id4)) errors.push(`${path}.id must be a lowercase kebab-case id`);
3192
- if (ids.has(id4)) errors.push(`${path}.id duplicates "${id4}"`);
3193
- ids.add(id4);
3703
+ const id6 = text3(item.id, `${path}.id`, errors, 80);
3704
+ if (id6 && !ID4.test(id6)) errors.push(`${path}.id must be a lowercase kebab-case id`);
3705
+ if (ids.has(id6)) errors.push(`${path}.id duplicates "${id6}"`);
3706
+ ids.add(id6);
3194
3707
  const targetRaw = record3(item.target);
3195
3708
  let target = { type: "entitlement", entitlementKey: "invalid" };
3196
3709
  if (targetRaw?.type === "tier") {
3197
- const tierId = text2(targetRaw.tierId, `${path}.target.tierId`, errors, 80);
3710
+ const tierId = text3(targetRaw.tierId, `${path}.target.tierId`, errors, 80);
3198
3711
  if (!tierIds.has(tierId)) errors.push(`${path}.target.tierId must reference a declared tier`);
3199
3712
  target = { type: "tier", tierId };
3200
3713
  } else if (targetRaw?.type === "entitlement") {
3201
- const entitlementKey = text2(targetRaw.entitlementKey, `${path}.target.entitlementKey`, errors, 80);
3714
+ const entitlementKey = text3(targetRaw.entitlementKey, `${path}.target.entitlementKey`, errors, 80);
3202
3715
  if (entitlementKey && !ID4.test(entitlementKey)) errors.push(`${path}.target.entitlementKey must be a lowercase kebab-case id`);
3203
3716
  target = { type: "entitlement", entitlementKey };
3204
3717
  } else errors.push(`${path}.target.type must be tier or entitlement`);
3205
3718
  if (item.billingMode !== "one_time" && item.billingMode !== "subscription") errors.push(`${path}.billingMode must be one_time or subscription`);
3206
- const stripePriceId = text2(item.stripePriceId, `${path}.stripePriceId`, errors, 128);
3719
+ const stripePriceId = text3(item.stripePriceId, `${path}.stripePriceId`, errors, 128);
3207
3720
  if (stripePriceId && !PRICE.test(stripePriceId)) errors.push(`${path}.stripePriceId must be a real Stripe Price id`);
3208
3721
  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`);
3209
3722
  const currency = typeof item.currency === "string" ? item.currency.trim().toLowerCase() : "";
@@ -3216,7 +3729,7 @@ function validateGiftOfferCatalog(input, tierIds) {
3216
3729
  if (activeUntil !== void 0 && (!Number.isSafeInteger(activeUntil) || activeUntil < 1)) errors.push(`${path}.activeUntil must be epoch milliseconds`);
3217
3730
  if (activeFrom !== void 0 && activeUntil !== void 0 && activeUntil <= activeFrom) errors.push(`${path}.activeUntil must follow activeFrom`);
3218
3731
  offers.push({
3219
- id: id4,
3732
+ id: id6,
3220
3733
  target,
3221
3734
  billingMode: item.billingMode,
3222
3735
  stripePriceId,
@@ -3225,9 +3738,9 @@ function validateGiftOfferCatalog(input, tierIds) {
3225
3738
  redemptionLifetimeHours: lifetime,
3226
3739
  ...activeFrom !== void 0 ? { activeFrom } : {},
3227
3740
  ...activeUntil !== void 0 ? { activeUntil } : {},
3228
- title: text2(item.title, `${path}.title`, errors, 160),
3229
- description: text2(item.description, `${path}.description`, errors, 2e3),
3230
- ctaLabel: text2(item.ctaLabel, `${path}.ctaLabel`, errors, 120)
3741
+ title: text3(item.title, `${path}.title`, errors, 160),
3742
+ description: text3(item.description, `${path}.description`, errors, 2e3),
3743
+ ctaLabel: text3(item.ctaLabel, `${path}.ctaLabel`, errors, 120)
3231
3744
  });
3232
3745
  }
3233
3746
  return errors.length ? { ok: false, errors } : { ok: true, value: { schemaVersion: 1, offers } };
@@ -3272,8 +3785,8 @@ function networkSourceTag(type, hubRecordId, sourceId) {
3272
3785
  const typeKey = type.toLowerCase();
3273
3786
  const readable = /^[a-z0-9_-]+$/.test(hubRecordId);
3274
3787
  const prefix = sourceId ? `network:${sourceId.toLowerCase()}:${typeKey}` : `network:${typeKey}`;
3275
- const raw = `${prefix}:${hubRecordId}`;
3276
- if (readable && raw.length <= 64) return raw;
3788
+ const raw2 = `${prefix}:${hubRecordId}`;
3789
+ if (readable && raw2.length <= 64) return raw2;
3277
3790
  return `network:${typeKey.slice(0, 16)}:${shortHash(`${sourceId ?? ""}\0${type}\0${hubRecordId}`)}`;
3278
3791
  }
3279
3792
  function normalizeSharedRecord(record4, fallbackSourceId = "legacy-source") {
@@ -3543,14 +4056,14 @@ function periodSeries(points, now, count, periodMs) {
3543
4056
  for (const point of points) {
3544
4057
  if (!Number.isFinite(point.t) || !Number.isFinite(point.v)) continue;
3545
4058
  if (point.t >= start && point.t <= now) {
3546
- const index = Math.min(count - 1, Math.floor((point.t - start) / periodMs));
3547
- current[index] = (current[index] ?? 0) + point.v;
4059
+ const index2 = Math.min(count - 1, Math.floor((point.t - start) / periodMs));
4060
+ current[index2] = (current[index2] ?? 0) + point.v;
3548
4061
  } else if (point.t >= previousStart && point.t < start) {
3549
- const index = Math.min(count - 1, Math.floor((point.t - previousStart) / periodMs));
3550
- previous[index] = (previous[index] ?? 0) + point.v;
4062
+ const index2 = Math.min(count - 1, Math.floor((point.t - previousStart) / periodMs));
4063
+ previous[index2] = (previous[index2] ?? 0) + point.v;
3551
4064
  }
3552
4065
  }
3553
- const labels = current.map((_, index) => new Date(start + index * periodMs).toISOString().slice(5, 10));
4066
+ const labels = current.map((_, index2) => new Date(start + index2 * periodMs).toISOString().slice(5, 10));
3554
4067
  return { labels, current, previous };
3555
4068
  }
3556
4069
  function dashboardMetricData(points, now) {
@@ -3634,9 +4147,9 @@ async function updateClerkUserMetadataByEmail(secretKey, email, input, fetchImpl
3634
4147
  const found = await fetchImpl(`https://api.clerk.com/v1/users?email_address=${encodeURIComponent(email)}&limit=1`, { headers: auth });
3635
4148
  if (!found.ok) return false;
3636
4149
  const users = await found.json().catch(() => null);
3637
- const id4 = Array.isArray(users) && typeof users[0]?.id === "string" ? users[0].id : void 0;
3638
- if (!id4) return false;
3639
- return updateClerkUserMetadata(secretKey, id4, input, fetchImpl);
4150
+ const id6 = Array.isArray(users) && typeof users[0]?.id === "string" ? users[0].id : void 0;
4151
+ if (!id6) return false;
4152
+ return updateClerkUserMetadata(secretKey, id6, input, fetchImpl);
3640
4153
  }
3641
4154
  async function createClerkUser(secretKey, input, fetchImpl = fetch) {
3642
4155
  const { path, body } = clerkUserRequest(input);
@@ -3699,8 +4212,8 @@ async function clerkGetUserByEmail(secretKey, email, fetchImpl = fetch) {
3699
4212
  const user = Array.isArray(data) ? data[0] : void 0;
3700
4213
  return user ? toRecord(user) : null;
3701
4214
  }
3702
- async function clerkGetUser(secretKey, id4, fetchImpl = fetch) {
3703
- const data = await clerkGet(`/v1/users/${encodeURIComponent(id4)}`, secretKey, fetchImpl).catch(() => null);
4215
+ async function clerkGetUser(secretKey, id6, fetchImpl = fetch) {
4216
+ const data = await clerkGet(`/v1/users/${encodeURIComponent(id6)}`, secretKey, fetchImpl).catch(() => null);
3704
4217
  return data ? toRecord(data) : null;
3705
4218
  }
3706
4219
  async function clerkListUsers(secretKey, fetchImpl = fetch) {
@@ -3716,8 +4229,8 @@ async function clerkListUsers(secretKey, fetchImpl = fetch) {
3716
4229
  }
3717
4230
  return out;
3718
4231
  }
3719
- async function clerkSetRole(secretKey, id4, role, fetchImpl = fetch) {
3720
- const res = await fetchImpl(`${CLERK_API}/v1/users/${encodeURIComponent(id4)}/metadata`, {
4232
+ async function clerkSetRole(secretKey, id6, role, fetchImpl = fetch) {
4233
+ const res = await fetchImpl(`${CLERK_API}/v1/users/${encodeURIComponent(id6)}/metadata`, {
3721
4234
  method: "PATCH",
3722
4235
  headers: { authorization: `Bearer ${secretKey}`, "content-type": "application/json" },
3723
4236
  body: JSON.stringify({ public_metadata: { role } })
@@ -3943,8 +4456,8 @@ function chapterBrandFromTokens(compiled, base = {}) {
3943
4456
  function isReconcilable(meeting, now) {
3944
4457
  return meeting.status === "scheduled" && Boolean(meeting.googleEventId) && (meeting.startAt ?? 0) > now - 36e5;
3945
4458
  }
3946
- function reconcileMeetings(meetings2, events, now) {
3947
- const byEvent = new Map(events.map((e) => [e.eventId, e]));
4459
+ function reconcileMeetings(meetings2, events2, now) {
4460
+ const byEvent = new Map(events2.map((e) => [e.eventId, e]));
3948
4461
  const decisions = [];
3949
4462
  for (const m of meetings2) {
3950
4463
  if (!isReconcilable(m, now) || !m.googleEventId) continue;
@@ -4154,10 +4667,10 @@ function boundedString(value, path, errors, max, required = false) {
4154
4667
  errors.push(`${path} must be a string`);
4155
4668
  return void 0;
4156
4669
  }
4157
- const text4 = value.trim();
4158
- if (required && !text4) errors.push(`${path} is required`);
4159
- if (text4.length > max) errors.push(`${path} must be at most ${max.toLocaleString("en-US")} characters`);
4160
- return text4;
4670
+ const text5 = value.trim();
4671
+ if (required && !text5) errors.push(`${path} is required`);
4672
+ if (text5.length > max) errors.push(`${path} must be at most ${max.toLocaleString("en-US")} characters`);
4673
+ return text5;
4161
4674
  }
4162
4675
  function validateMembershipPageDocument(input, options = {}) {
4163
4676
  const errors = [];
@@ -4175,9 +4688,9 @@ function validateMembershipPageDocument(input, options = {}) {
4175
4688
  const authority = new Map((options.tiers ?? []).map((tier) => [tier.id, tier]));
4176
4689
  const seen = /* @__PURE__ */ new Set();
4177
4690
  const tiers2 = [];
4178
- for (const [index, raw] of rawTiers.slice(0, MAX_TIERS).entries()) {
4179
- const path = `document.tiers[${index}]`;
4180
- const item = objectRecord(raw);
4691
+ for (const [index2, raw2] of rawTiers.slice(0, MAX_TIERS).entries()) {
4692
+ const path = `document.tiers[${index2}]`;
4693
+ const item = objectRecord(raw2);
4181
4694
  if (!item) {
4182
4695
  errors.push(`${path} must be an object`);
4183
4696
  continue;
@@ -4206,8 +4719,8 @@ function validateMembershipPageDocument(input, options = {}) {
4206
4719
  else {
4207
4720
  if (item.benefits.length > MAX_BENEFITS) errors.push(`${path}.benefits supports at most ${MAX_BENEFITS} entries`);
4208
4721
  benefits = item.benefits.slice(0, MAX_BENEFITS).flatMap((benefit, benefitIndex) => {
4209
- const text4 = boundedString(benefit, `${path}.benefits[${benefitIndex}]`, errors, 2e3, true);
4210
- return text4 ? [text4] : [];
4722
+ const text5 = boundedString(benefit, `${path}.benefits[${benefitIndex}]`, errors, 2e3, true);
4723
+ return text5 ? [text5] : [];
4211
4724
  });
4212
4725
  }
4213
4726
  }
@@ -4283,7 +4796,7 @@ function membershipPageDocument(input, options = {}) {
4283
4796
  }
4284
4797
 
4285
4798
  // src/signup-control-validate.ts
4286
- var SLUG7 = /^[a-z0-9][a-z0-9-]{1,62}$/;
4799
+ var SLUG9 = /^[a-z0-9][a-z0-9-]{1,62}$/;
4287
4800
  var ENVIRONMENT = /^[a-z][a-z0-9_-]{0,62}$/;
4288
4801
  var TIER = /^[a-z][a-z0-9-]{0,79}$/;
4289
4802
  var PRICE2 = /^price_[A-Za-z0-9]{12,}$/;
@@ -4306,7 +4819,7 @@ var recordOf = (value) => value !== null && typeof value === "object" && !Array.
4306
4819
  function unknownKeys3(value, allowed2, path, errors) {
4307
4820
  for (const key of Object.keys(value)) if (!allowed2.has(key)) errors.push(`${path}.${key} is not supported`);
4308
4821
  }
4309
- function text3(value, path, errors, max = 65536) {
4822
+ function text4(value, path, errors, max = 65536) {
4310
4823
  if (typeof value !== "string" || !value.trim()) {
4311
4824
  errors.push(`${path} is required`);
4312
4825
  return "";
@@ -4321,19 +4834,19 @@ function tiersOf(value, errors) {
4321
4834
  }
4322
4835
  if (value.length > 20) errors.push("document.tiers supports at most 20 entries");
4323
4836
  const seen = /* @__PURE__ */ new Set();
4324
- return value.slice(0, 20).flatMap((raw, index) => {
4325
- const path = `document.tiers[${index}]`;
4326
- const item = recordOf(raw);
4837
+ return value.slice(0, 20).flatMap((raw2, index2) => {
4838
+ const path = `document.tiers[${index2}]`;
4839
+ const item = recordOf(raw2);
4327
4840
  if (!item) {
4328
4841
  errors.push(`${path} must be an object`);
4329
4842
  return [];
4330
4843
  }
4331
4844
  unknownKeys3(item, TIER_KEYS2, path, errors);
4332
- const id4 = text3(item.id, `${path}.id`, errors, 80);
4333
- if (id4 && !TIER.test(id4)) errors.push(`${path}.id must be a lowercase kebab-case id`);
4334
- if (seen.has(id4)) errors.push(`${path}.id duplicates "${id4}"`);
4335
- seen.add(id4);
4336
- const name = text3(item.name, `${path}.name`, errors, 160);
4845
+ const id6 = text4(item.id, `${path}.id`, errors, 80);
4846
+ if (id6 && !TIER.test(id6)) errors.push(`${path}.id must be a lowercase kebab-case id`);
4847
+ if (seen.has(id6)) errors.push(`${path}.id duplicates "${id6}"`);
4848
+ seen.add(id6);
4849
+ const name = text4(item.name, `${path}.name`, errors, 160);
4337
4850
  if (typeof item.available !== "boolean") errors.push(`${path}.available must be boolean`);
4338
4851
  if (!Number.isInteger(item.sortOrder) || Number(item.sortOrder) < 0) errors.push(`${path}.sortOrder must be a non-negative integer`);
4339
4852
  if (!Number.isInteger(item.priceCents) || Number(item.priceCents) < 0 || Number(item.priceCents) > 1e8) {
@@ -4345,9 +4858,9 @@ function tiersOf(value, errors) {
4345
4858
  if (item.available === true && Number(item.priceCents) > 0 && !item.stripePriceId) {
4346
4859
  errors.push(`${path}.stripePriceId is required for an available paid tier`);
4347
4860
  }
4348
- const blurb = item.blurb === void 0 ? void 0 : text3(item.blurb, `${path}.blurb`, errors, 4e3);
4861
+ const blurb = item.blurb === void 0 ? void 0 : text4(item.blurb, `${path}.blurb`, errors, 4e3);
4349
4862
  return [{
4350
- id: id4,
4863
+ id: id6,
4351
4864
  name,
4352
4865
  available: item.available === true,
4353
4866
  sortOrder: Number(item.sortOrder) || 0,
@@ -4401,7 +4914,7 @@ function policyOf(value, errors) {
4401
4914
  const result = {};
4402
4915
  for (const key of allowed2) {
4403
4916
  if (input[key] === void 0 && key !== "disclaimerText" && key !== "refundPolicyText") continue;
4404
- result[key] = text3(input[key], `document.policy.${key}`, errors);
4917
+ result[key] = text4(input[key], `document.policy.${key}`, errors);
4405
4918
  }
4406
4919
  return result;
4407
4920
  }
@@ -4439,18 +4952,18 @@ function validateSignupControlDocument(input) {
4439
4952
  if (!Number.isSafeInteger(root.generatedAt) || Number(root.generatedAt) < 1) errors.push("document.generatedAt must be epoch milliseconds");
4440
4953
  const source = recordOf(root.source);
4441
4954
  const target = recordOf(root.target);
4442
- const appId = text3(source?.appId, "document.source.appId", errors, 63);
4443
- const chapterId = text3(target?.chapterId, "document.target.chapterId", errors, 63);
4444
- const environment = text3(target?.environment, "document.target.environment", errors, 63);
4445
- if (appId && !SLUG7.test(appId)) errors.push("document.source.appId must be a lowercase app slug");
4446
- if (chapterId && !SLUG7.test(chapterId)) errors.push("document.target.chapterId must be a lowercase app slug");
4955
+ const appId = text4(source?.appId, "document.source.appId", errors, 63);
4956
+ const chapterId = text4(target?.chapterId, "document.target.chapterId", errors, 63);
4957
+ const environment = text4(target?.environment, "document.target.environment", errors, 63);
4958
+ if (appId && !SLUG9.test(appId)) errors.push("document.source.appId must be a lowercase app slug");
4959
+ if (chapterId && !SLUG9.test(chapterId)) errors.push("document.target.chapterId must be a lowercase app slug");
4447
4960
  if (environment && !ENVIRONMENT.test(environment)) errors.push("document.target.environment is invalid");
4448
4961
  if (target?.stripeMode !== "test" && target?.stripeMode !== "live") errors.push("document.target.stripeMode must be test or live");
4449
4962
  const tiers2 = tiersOf(root.tiers, errors);
4450
4963
  const available = tiers2.filter((tier) => tier.available).sort((a, b) => a.sortOrder - b.sortOrder);
4451
4964
  const page = validateMembershipPageDocument(root.membershipPage, { tiers: available });
4452
4965
  if (!page.ok) errors.push(...page.errors);
4453
- else if (page.value.tiers.some((tier, index) => tier.tierId !== available[index]?.id)) {
4966
+ else if (page.value.tiers.some((tier, index2) => tier.tierId !== available[index2]?.id)) {
4454
4967
  errors.push("document.membershipPage.tiers must follow the available tier display order");
4455
4968
  }
4456
4969
  const application = applicationOf(root.application, errors);
@@ -4497,14 +5010,14 @@ function validatedTokens(input) {
4497
5010
  }
4498
5011
  const tokens = {};
4499
5012
  let serializedLength = 0;
4500
- for (const [name, raw] of entries.sort(([left], [right]) => left.localeCompare(right))) {
5013
+ for (const [name, raw2] of entries.sort(([left], [right]) => left.localeCompare(right))) {
4501
5014
  if (!TOKEN_NAME.test(name)) {
4502
5015
  throw new TypeError(`signup-control theme token "${name}" must be an allowlisted --ui-* property`);
4503
5016
  }
4504
- if (typeof raw !== "string" && typeof raw !== "number" || typeof raw === "number" && !Number.isFinite(raw)) {
5017
+ if (typeof raw2 !== "string" && typeof raw2 !== "number" || typeof raw2 === "number" && !Number.isFinite(raw2)) {
4505
5018
  throw new TypeError(`signup-control theme token "${name}" must be a finite string or number`);
4506
5019
  }
4507
- const value = String(raw).trim();
5020
+ const value = String(raw2).trim();
4508
5021
  if (!value || value.length > MAX_VALUE_LENGTH || UNSAFE_VALUE.test(value)) {
4509
5022
  throw new TypeError(`signup-control theme token "${name}" has an unsafe or oversized value`);
4510
5023
  }
@@ -4611,16 +5124,16 @@ function markdownSection(rawHeading) {
4611
5124
  }
4612
5125
  function markdownBenefits(lines, path, errors) {
4613
5126
  const benefits = [];
4614
- for (const [index, line] of lines.entries()) {
5127
+ for (const [index2, line] of lines.entries()) {
4615
5128
  if (!line.trim()) continue;
4616
5129
  const match = line.match(MARKDOWN_BENEFIT);
4617
5130
  if (!match) {
4618
- errors.push(`${path} line ${index + 1} must be a Markdown bullet`);
5131
+ errors.push(`${path} line ${index2 + 1} must be a Markdown bullet`);
4619
5132
  continue;
4620
5133
  }
4621
- const text4 = (match[1] ?? "").trim();
4622
- if (text4.length > 2e3) errors.push(`${path} line ${index + 1} must be at most 2,000 characters`);
4623
- if (text4) benefits.push(text4);
5134
+ const text5 = (match[1] ?? "").trim();
5135
+ if (text5.length > 2e3) errors.push(`${path} line ${index2 + 1} must be at most 2,000 characters`);
5136
+ if (text5) benefits.push(text5);
4624
5137
  }
4625
5138
  if (benefits.length > MAX_BENEFITS2) errors.push(`${path} supports at most ${MAX_BENEFITS2} entries`);
4626
5139
  return benefits.slice(0, MAX_BENEFITS2);
@@ -4635,7 +5148,7 @@ function applyMembershipTierMarkdown(presentation, source) {
4635
5148
  const sections = /* @__PURE__ */ new Map();
4636
5149
  const prelude = [];
4637
5150
  let current = null;
4638
- for (const [index, line] of source.replace(/\r\n?/g, "\n").split("\n").entries()) {
5151
+ for (const [index2, line] of source.replace(/\r\n?/g, "\n").split("\n").entries()) {
4639
5152
  const heading = line.match(MARKDOWN_SECTION);
4640
5153
  if (heading) {
4641
5154
  const headingText = heading[1] ?? "";
@@ -4655,7 +5168,7 @@ function applyMembershipTierMarkdown(presentation, source) {
4655
5168
  continue;
4656
5169
  }
4657
5170
  if (MARKDOWN_HEADING.test(line)) {
4658
- errors.push(`membership Markdown line ${index + 1} uses an unsupported heading`);
5171
+ errors.push(`membership Markdown line ${index2 + 1} uses an unsupported heading`);
4659
5172
  continue;
4660
5173
  }
4661
5174
  if (current) sections.get(current)?.lines.push(line);
@@ -4722,7 +5235,7 @@ var exclusions = (chapterId, paidTierIds) => [
4722
5235
  id: chapterId,
4723
5236
  reason: "provider_identity",
4724
5237
  paths: [
4725
- ...paidTierIds.map((id4) => `tiers[${id4}].stripePriceId`),
5238
+ ...paidTierIds.map((id6) => `tiers[${id6}].stripePriceId`),
4726
5239
  "groups.stripePublishableKey",
4727
5240
  "applications.stripeCustomerId",
4728
5241
  "applications.stripeSubscriptionId",
@@ -4764,9 +5277,9 @@ async function exportChapterPromotion(input) {
4764
5277
  sourceIds: [`signup-control:${document.revision}`, document.source.appId, document.target.chapterId].sort(),
4765
5278
  receiptDigests: [input.digest]
4766
5279
  };
4767
- const artifact = (kind, id4, content) => ({
5280
+ const artifact = (kind, id6, content) => ({
4768
5281
  kind,
4769
- id: id4,
5282
+ id: id6,
4770
5283
  sourceRevision: revision,
4771
5284
  provenance: promotionProvenance,
4772
5285
  content: canonicalPromotionValue(content)
@@ -4805,7 +5318,7 @@ async function exportChapterPromotion(input) {
4805
5318
  }
4806
5319
 
4807
5320
  // src/member-system-authority.ts
4808
- var rows2 = (group) => group.fields.map((id4) => ({ ...group, id: id4 }));
5321
+ var rows2 = (group) => group.fields.map((id6) => ({ ...group, id: id6 }));
4809
5322
  var MEMBER_SYSTEM_AUTHORITY_MATRIX = [
4810
5323
  ...rows2({
4811
5324
  scope: "network",
@@ -5190,10 +5703,10 @@ function classifyMemberStripeEvent(identity, target, hasProvenLegacyLocalLink) {
5190
5703
  if (supplied.length === keys.length) return "apply";
5191
5704
  return hasProvenLegacyLocalLink ? "apply_legacy" : "quarantine_unmatched";
5192
5705
  }
5193
- function planMemberSystemEventReplay(events) {
5706
+ function planMemberSystemEventReplay(events2) {
5194
5707
  const seen = /* @__PURE__ */ new Map();
5195
5708
  const plan = { accepted: [], replayed: [], collisions: [] };
5196
- for (const event of events) {
5709
+ for (const event of events2) {
5197
5710
  const digest = seen.get(event.id);
5198
5711
  if (digest === void 0) {
5199
5712
  seen.set(event.id, event.digest);
@@ -5348,6 +5861,7 @@ export {
5348
5861
  joinConfig,
5349
5862
  joinConfigTiers,
5350
5863
  leaderCrmConfig,
5864
+ listStripeObjects,
5351
5865
  loadLatestAdmissionRevision,
5352
5866
  loadOfferableTiers,
5353
5867
  loadTiers,
@@ -5362,9 +5876,11 @@ export {
5362
5876
  memberSystemRollbackPlan,
5363
5877
  membershipPageDocument,
5364
5878
  membershipTierPresentationFromMarkdown,
5879
+ networkCommercialSourceKey,
5365
5880
  networkSourceTag,
5366
5881
  normalizeAdmissionEmail,
5367
5882
  normalizeChapterEmail,
5883
+ normalizeNetworkCommercialStripeEvent,
5368
5884
  normalizeSharedRecord,
5369
5885
  normalizeWebhookEvent,
5370
5886
  offerableTiers,
@@ -5377,10 +5893,12 @@ export {
5377
5893
  projectBuilderLibraryDelivery,
5378
5894
  projectMemberContent,
5379
5895
  projectMemberSelf,
5896
+ projectNetworkCommercialEvents,
5380
5897
  projectSharedRecord,
5381
5898
  promoteBuilderLibraryResource,
5382
5899
  publicGiftOffers,
5383
5900
  reconcileMeetings,
5901
+ reconcileStripeSnapshot,
5384
5902
  refundedPatch,
5385
5903
  render,
5386
5904
  renderSignupControlThemeHead,
@@ -5394,6 +5912,8 @@ export {
5394
5912
  resolveJoinContinuation,
5395
5913
  resolveLeaderFormation,
5396
5914
  resolveMemberContent,
5915
+ resolveNetworkCommercialReadModel,
5916
+ resolveNetworkPersonRegistry,
5397
5917
  resolvePipeline,
5398
5918
  resolveScheduling,
5399
5919
  resolveSignupControl,