@odla-ai/chapter 0.39.1 → 0.40.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/README.md CHANGED
@@ -517,11 +517,20 @@ it on both the Built Not Found hub and each participating Chapter:
517
517
 
518
518
  ```ts
519
519
  // Built Not Found
520
+ network: {
521
+ targets: [
522
+ { id: "oakland", url: "https://oakland.example", binding: "OAKLAND" },
523
+ { id: "denver", url: "https://denver.example", binding: "DENVER" },
524
+ ],
525
+ },
520
526
  builderLibrary: {
521
527
  sources: [{ sourceId: "oakland" }, { sourceId: "denver" }],
522
528
  },
523
529
 
524
530
  // Oakland
531
+ network: {
532
+ readers: [{ id: "built-not-found", fields: { person: ["name"] } }],
533
+ },
525
534
  builderLibrary: {
526
535
  sources: [{ sourceId: "built-not-found" }],
527
536
  },
@@ -544,18 +553,22 @@ Saving a library item never creates or changes a member page.
544
553
  An authenticated admin or site-building agent reads the bounded, published-only
545
554
  manifest from `/api/admin/builder-library/manifest`. On Built Not Found, pass a
546
555
  configured `chapterId` query parameter; on a Chapter, the server fixes the
547
- viewer to that Chapter. Signed delivery uses the dedicated
548
- `builder_library_control_secret` vault key and never reuses CRM, member-content,
549
- or signup-control credentials.
556
+ viewer to that Chapter. Every Builder source must already be the corresponding
557
+ `network.target` (on Built Not Found) or `network.reader` (on a Chapter). Signed
558
+ delivery reuses that Chapter network edge: the Chapter's existing
559
+ `network_share_secret` is the same value Built Not Found already stores as
560
+ `network_share_<chapter_id>`. Builder Library declares and provisions no secret
561
+ of its own.
550
562
 
551
563
  ```bash
552
564
  odla-ai chapter builder-library validate --file oakland-library.json --source-type chapter --source oakland --json
553
565
  odla-ai chapter builder-library manifest --file oakland-library.json --chapter oakland --json
554
566
  odla-ai chapter builder-library deliver --file oakland-library.json \
555
- --url https://builtnotfoundcapital.com --from-env BUILDER_LIBRARY_SECRET --json
567
+ --url https://builtnotfoundcapital.com --from-env NETWORK_SHARE_SECRET --json
556
568
  ```
557
569
 
558
- The signing secret is accepted only from a named environment variable or stdin.
570
+ The existing network share secret is accepted only from a named environment
571
+ variable or stdin; it is never copied into Builder Library config or argv.
559
572
  For a Built Not Found local document, add `--target oakland --environment dev`;
560
573
  the CLI signs only its global resources for that Chapter, never BNF-only items.
561
574
 
@@ -118,16 +118,14 @@ interface ResolvedMemberContent {
118
118
  interface ChapterBuilderLibrary {
119
119
  /** Keep local admin authoring enabled. Defaults to true. */
120
120
  localAuthoring?: boolean;
121
- /** Signed sources this app accepts. Hub sources are Chapters; Chapter sources are Built Not Found. */
121
+ /** Existing Chapter-network peers this app accepts. Hub sources must be
122
+ * configured network targets; a Chapter source must be a configured reader. */
122
123
  sources?: readonly {
123
124
  sourceId: string;
124
- /** Vault key holding the dedicated Builder Library edge secret. */
125
- secretName?: string;
126
125
  }[];
127
126
  }
128
127
  interface ResolvedBuilderLibrarySource {
129
128
  sourceId: string;
130
- secretName: string;
131
129
  }
132
130
  /** Validated Builder Library settings carried on a resolved Chapter. */
133
131
  interface ResolvedBuilderLibrary {
package/dist/index.cjs CHANGED
@@ -402,6 +402,7 @@ var applicationAttrs = {
402
402
  tierId: attr("string", { indexed: true, optional: true }),
403
403
  stripeCustomerId: attr("string", { indexed: true, optional: true }),
404
404
  stripeSubscriptionId: attr("string", { indexed: true, optional: true }),
405
+ stripeRuntime: attr("string", { indexed: true, optional: true }),
405
406
  renewalAt: attr("number", { optional: true }),
406
407
  disclaimerAckAt: attr("number", { optional: true }),
407
408
  refundPolicyAckAt: attr("number", { optional: true }),
@@ -497,6 +498,7 @@ var giftPurchaseIntentsEntity = { attrs: {
497
498
  id: id2(),
498
499
  chapterId: attr3("string", { indexed: true }),
499
500
  environment: attr3("string", { indexed: true }),
501
+ runtime: attr3("string", { optional: true, indexed: true }),
500
502
  purchaserType: attr3("string", { indexed: true }),
501
503
  purchaserApplicationId: attr3("string", { optional: true, indexed: true }),
502
504
  purchaserUserId: attr3("string", { optional: true, indexed: true }),
@@ -1710,8 +1712,7 @@ function resolveMemberContent(input) {
1710
1712
 
1711
1713
  // src/config-builder-library.ts
1712
1714
  var SLUG4 = /^[a-z0-9][a-z0-9-]{1,62}$/;
1713
- var SECRET_NAME3 = /^[a-z][a-z0-9_]{1,127}$/;
1714
- function resolveBuilderLibrary(mode, input) {
1715
+ function resolveBuilderLibrary(mode, input, network = { targets: [], readers: [] }) {
1715
1716
  if (!input) return null;
1716
1717
  if (input.sources !== void 0 && !Array.isArray(input.sources)) {
1717
1718
  throw new Error("defineChapter.builderLibrary.sources: must be an array");
@@ -1731,11 +1732,19 @@ function resolveBuilderLibrary(mode, input) {
1731
1732
  throw new Error(`defineChapter.builderLibrary.sources: duplicate source "${source.sourceId}"`);
1732
1733
  }
1733
1734
  seen.add(source.sourceId);
1734
- const secretName = source.secretName?.trim() || "builder_library_control_secret";
1735
- if (!SECRET_NAME3.test(secretName)) {
1736
- throw new Error(`defineChapter.builderLibrary.sources[${index}].secretName: must be a vault-key identifier`);
1735
+ if ("secretName" in source) {
1736
+ throw new Error(
1737
+ `defineChapter.builderLibrary.sources[${index}].secretName: remove this field; Builder Library reuses the Chapter network edge`
1738
+ );
1739
+ }
1740
+ const networkPeer = mode === "hub" ? network.targets.some((target) => target.id === source.sourceId) : network.readers.some((reader) => reader.id === source.sourceId);
1741
+ if (!networkPeer) {
1742
+ const side = mode === "hub" ? "targets" : "readers";
1743
+ throw new Error(
1744
+ `defineChapter.builderLibrary.sources[${index}]: "${source.sourceId}" must already be configured in network.${side}`
1745
+ );
1737
1746
  }
1738
- return { sourceId: source.sourceId, secretName };
1747
+ return { sourceId: source.sourceId };
1739
1748
  });
1740
1749
  return { enabled: true, localAuthoring: input.localAuthoring !== false, sources };
1741
1750
  }
@@ -1774,7 +1783,7 @@ function defineChapter(config) {
1774
1783
  const network = resolveNetwork(config, crm);
1775
1784
  const signupControl = resolveSignupControl(config.signupControl);
1776
1785
  const memberContent = resolveMemberContent(config.memberContent);
1777
- const builderLibrary = resolveBuilderLibrary(mode, config.builderLibrary);
1786
+ const builderLibrary = resolveBuilderLibrary(mode, config.builderLibrary, network);
1778
1787
  const formation = resolveLeaderFormation(config.formation, crm);
1779
1788
  if (signupControl && mode !== "chapter") {
1780
1789
  throw new Error("defineChapter.signupControl: only chapter-mode followers can receive signup control");
@@ -2098,13 +2107,6 @@ function createChapterIntegration(chapter, options = {}) {
2098
2107
  ...emails?.debugEmail ? { debugEmail: emails.debugEmail } : {}
2099
2108
  });
2100
2109
  const seeds = [...crmDesc.seeds ?? []];
2101
- const builderLibrarySecrets = [...new Set(
2102
- chapter.builderLibrary?.sources.map((source) => source.secretName) ?? []
2103
- )].map((name) => ({
2104
- name,
2105
- description: "Signing secret for private Builder Library delivery from a configured source.",
2106
- required: true
2107
- }));
2108
2110
  const group = chapter.groupSeed();
2109
2111
  if (group) {
2110
2112
  seeds.push({ id: "group", ns: "groups", key: { attr: "id", value: chapter.id }, attrs: { ...group, createdAt: now } });
@@ -2137,7 +2139,7 @@ function createChapterIntegration(chapter, options = {}) {
2137
2139
  },
2138
2140
  rules: { ...crmDesc.rules, ...chapter.rules },
2139
2141
  seeds,
2140
- secrets: builderLibrarySecrets,
2142
+ secrets: [],
2141
2143
  probes: [
2142
2144
  ...crmDesc.probes ?? [],
2143
2145
  // The snapshot route is deliberately private. A credential-free smoke
@@ -2842,12 +2844,16 @@ function findApplicationRef(obj) {
2842
2844
  const applicationId = candidates.map(pick).find(Boolean);
2843
2845
  const appId = candidates.map((metadata) => typeof metadata.odlaAppId === "string" ? metadata.odlaAppId : void 0).find(Boolean);
2844
2846
  const environment = candidates.map((metadata) => typeof metadata.odlaEnvironment === "string" ? metadata.odlaEnvironment : void 0).find(Boolean);
2847
+ const dataEnvironment = candidates.map((metadata) => typeof metadata.odlaDataEnvironment === "string" ? metadata.odlaDataEnvironment : void 0).find(Boolean);
2848
+ const runtime = candidates.map((metadata) => typeof metadata.odlaRuntime === "string" ? metadata.odlaRuntime : void 0).find(Boolean);
2845
2849
  const customerId = typeof obj.customer === "string" ? obj.customer : void 0;
2846
2850
  return {
2847
2851
  ...applicationId ? { applicationId } : {},
2848
2852
  ...customerId ? { customerId } : {},
2849
2853
  ...appId ? { appId } : {},
2850
- ...environment ? { environment } : {}
2854
+ ...environment ? { environment } : {},
2855
+ ...dataEnvironment ? { dataEnvironment } : {},
2856
+ ...runtime ? { runtime } : {}
2851
2857
  };
2852
2858
  }
2853
2859
  function findGiftRef(obj) {
@@ -2857,17 +2863,31 @@ function findGiftRef(obj) {
2857
2863
  const subscription = obj.subscription_details ?? parent?.subscription_details;
2858
2864
  const giftIntentId = pick(metaOf(obj)) ?? pick(metaOf(subscription));
2859
2865
  const subscriptionId = typeof obj.subscription === "string" ? obj.subscription : typeof subscription?.subscription === "string" ? String(subscription.subscription) : void 0;
2860
- return { ...giftIntentId ? { giftIntentId } : {}, providerObjectId: subscriptionId ?? String(obj.id ?? "") };
2866
+ const candidates = [metaOf(obj), metaOf(subscription)];
2867
+ const pickIdentity = (name) => candidates.map((metadata) => typeof metadata[name] === "string" ? String(metadata[name]) : void 0).find(Boolean);
2868
+ const appId = pickIdentity("odlaAppId");
2869
+ const environment = pickIdentity("odlaEnvironment");
2870
+ const dataEnvironment = pickIdentity("odlaDataEnvironment");
2871
+ const runtime = pickIdentity("odlaRuntime");
2872
+ return {
2873
+ ...giftIntentId ? { giftIntentId } : {},
2874
+ providerObjectId: subscriptionId ?? String(obj.id ?? ""),
2875
+ ...appId ? { appId } : {},
2876
+ ...environment ? { environment } : {},
2877
+ ...dataEnvironment ? { dataEnvironment } : {},
2878
+ ...runtime ? { runtime } : {}
2879
+ };
2861
2880
  }
2862
2881
  function normalizeWebhookEvent(event) {
2863
2882
  const obj = event.data?.object ?? {};
2864
2883
  const gift = findGiftRef(obj);
2865
2884
  if (gift.giftIntentId) {
2885
+ const { giftIntentId, providerObjectId, ...identity } = gift;
2866
2886
  if (event.type === "payment_intent.succeeded" || event.type === "invoice.paid" && obj.billing_reason === "subscription_create") {
2867
- return { kind: "gift_paid", giftIntentId: gift.giftIntentId, providerObjectId: gift.providerObjectId };
2887
+ return { kind: "gift_paid", giftIntentId, providerObjectId, ...identity };
2868
2888
  }
2869
- if (event.type === "charge.refunded") return { kind: "gift_revoked", giftIntentId: gift.giftIntentId, reason: "refunded" };
2870
- if (event.type === "customer.subscription.deleted") return { kind: "gift_revoked", giftIntentId: gift.giftIntentId, reason: "canceled" };
2889
+ if (event.type === "charge.refunded") return { kind: "gift_revoked", giftIntentId, ...identity, reason: "refunded" };
2890
+ if (event.type === "customer.subscription.deleted") return { kind: "gift_revoked", giftIntentId, ...identity, reason: "canceled" };
2871
2891
  return { kind: "ignored", type: event.type };
2872
2892
  }
2873
2893
  const ref = findApplicationRef(obj);