@odla-ai/chapter 0.44.0 → 0.46.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
@@ -811,6 +811,8 @@ export const chapter = defineChapter({
811
811
  name: "Example Chapter",
812
812
  url: "https://chapter.example.com",
813
813
  fields: { person: ["name", "email"] },
814
+ // Independent consent to compare the chapter's commercial projection.
815
+ commercialParity: true,
814
816
  }],
815
817
  },
816
818
  });
@@ -859,6 +861,61 @@ and relations by composing its own config before passing it to Chapter.
859
861
  Formation is off unless configured. Chapter intentionally does not ship a
860
862
  public form design: the site renders the fields in its own voice and brand.
861
863
 
864
+ ### Shared-Stripe commercial shadow
865
+
866
+ A hub may opt into a read-only network commercial projection when multiple
867
+ chapters intentionally bill through one Stripe account. Every accepted source
868
+ is an exact partition, including Stripe test/live mode:
869
+
870
+ ```ts
871
+ networkCommercialReadModel: {
872
+ mode: "shadow",
873
+ sources: [{
874
+ stripeMode: "test",
875
+ appId: "example-chapter",
876
+ chapterId: "example-chapter",
877
+ environment: "dev",
878
+ dataEnvironment: "dev",
879
+ runtime: "cory",
880
+ }],
881
+ },
882
+ ```
883
+
884
+ The dedicated `/api/network-commercial/stripe-webhook` verifies Stripe's
885
+ signature with `network_commercial_stripe_webhook_secret` before writing an
886
+ append-only, PII-free event fact. The projector is rebuildable and deterministic:
887
+ late metadata-less events may inherit a partition only through an unambiguous
888
+ provider customer/subscription/invoice/charge reference. Wrong, missing,
889
+ ambiguous, unsupported, and colliding events remain explained exceptions.
890
+
891
+ `GET /api/admin/network-commercial-read-model` is super-admin-only and exposes
892
+ current customer, subscription, invoice, refund, and currency-total facts for a
893
+ BNF operator surface. `POST` with `{ command: "rebuild", mutationId }` rebuilds
894
+ from the local event log without calling or mutating Stripe. `POST` with
895
+ `{ command: "reconcile-stripe" }` performs bounded, GET-only current-object
896
+ reads through the hub's vaulted `stripe_secret_key` and reports missing,
897
+ unexpected, mismatched, truncated, and totals exceptions without changing the
898
+ event log or projection. Objects outside the configured exact partitions are
899
+ counted as ignored rather than adopted. This shadow never
900
+ authorizes charges, refunds, subscription changes, or Chapter lifecycle writes;
901
+ removing it changes no customer-facing behavior. The BNF frontend may be down
902
+ while the webhook continues to ingest. Registering a Stripe endpoint and
903
+ enabling production remain separate reviewed operations.
904
+
905
+ `POST` with `{ command: "reconcile-chapters" }` is a separate, two-sided opt-in
906
+ comparison. A hub target and the matching follower reader must both set
907
+ `commercialParity: true`; neither record browsing nor Stripe shadow mode grants
908
+ it implicitly. The hub signs a `GET /api/network/commercial-parity` request with
909
+ the edge's `network_share_*` secret. The chapter returns only bounded status,
910
+ cancellation, counts, completeness, and domain-separated HMAC-SHA-256 digests of
911
+ application, Stripe customer, and Stripe subscription identifiers. Name, email,
912
+ raw identifiers, and arbitrary application fields never cross the edge. The hub
913
+ compares those digests with its exact Stripe partition and reports chapter-only,
914
+ shadow-only, cancellation, count, unavailable, invalid, and truncation
915
+ exceptions without persisting or changing either side. Chapter application
916
+ status and Stripe subscription status remain distinct vocabularies and are shown
917
+ side by side rather than falsely compared for equality.
918
+
862
919
  For a hub with network targets, the standard admin catalog is Dashboard,
863
920
  Network, People, Portfolio, and Settings. Dashboard Overview renders aggregate network
864
921
  health; People contains CRM types whose `workspace` is absent or `"people"`;
@@ -893,6 +950,8 @@ export const chapter = defineChapter({
893
950
  editableFields: { person: ["name", "email", "phone", "linkedin"] },
894
951
  stageTransitions: ["person"],
895
952
  admissionGrants: true,
953
+ // Independent disclosure consent. This may be the reader's only ability.
954
+ commercialParity: true,
896
955
  }],
897
956
  },
898
957
  });
@@ -147,6 +147,26 @@ interface ResolvedNetworkPersonRegistry {
147
147
  chapterIds: readonly string[];
148
148
  }
149
149
 
150
+ /** One exact Stripe event partition accepted by the network commercial shadow. */
151
+ interface NetworkCommercialSource {
152
+ appId: string;
153
+ chapterId: string;
154
+ environment: string;
155
+ dataEnvironment: string;
156
+ runtime: string;
157
+ stripeMode: "test" | "live";
158
+ }
159
+ /** Opt-in, read-only projection of a shared Stripe account. */
160
+ interface ChapterNetworkCommercialReadModel {
161
+ mode: "shadow";
162
+ sources: readonly NetworkCommercialSource[];
163
+ }
164
+ /** Validated settings carried by a resolved hub. */
165
+ interface ResolvedNetworkCommercialReadModel {
166
+ mode: "shadow";
167
+ sources: readonly NetworkCommercialSource[];
168
+ }
169
+
150
170
  /** Scalar kinds accepted by the Chapter schema adapter. */
151
171
  type AttrType = "string" | "number" | "boolean" | "json";
152
172
  /** One odla-db attribute: its type and index/uniqueness/optionality flags. */
@@ -209,6 +229,10 @@ interface ChapterNetworkTarget {
209
229
  /** Allow package-owned desired admission decisions for existing leader CRM
210
230
  * people to be delivered to this target. Omitted means default deny. */
211
231
  admissionGrants?: boolean;
232
+ /** Allow this leader to request a PII-free commercial parity snapshot from
233
+ * this follower. The follower must independently enable the same reader;
234
+ * omitted means no application or Stripe identifier digest crosses. */
235
+ commercialParity?: boolean;
212
236
  }
213
237
  /** One signed peer allowed to browse a privacy-bounded projection of this
214
238
  * site's CRM. The follower owns this allowlist; a leader target declaration
@@ -233,6 +257,9 @@ interface ChapterNetworkReader {
233
257
  /** Accept signed desired admission decisions from this reader. Independent
234
258
  * of record browsing; an admission-only reader may omit `fields`. */
235
259
  admissionGrants?: boolean;
260
+ /** Disclose a bounded, PII-free commercial parity snapshot to this signed
261
+ * reader. Identifiers leave only as edge-keyed, domain-separated HMACs. */
262
+ commercialParity?: boolean;
236
263
  }
237
264
  /** Leader/follower network configuration. A follower needs no code config to
238
265
  * receive pushed records. It must explicitly declare `readers` before any peer
@@ -278,6 +305,7 @@ interface ResolvedNetworkTarget {
278
305
  editableFields: Record<string, readonly string[]>;
279
306
  stageTransitions: readonly string[];
280
307
  admissionGrants?: boolean;
308
+ commercialParity?: boolean;
281
309
  }
282
310
  /** A validated follower-owned record projection for one signed reader. */
283
311
  interface ResolvedNetworkReader {
@@ -287,6 +315,7 @@ interface ResolvedNetworkReader {
287
315
  editableFields: Record<string, readonly string[]>;
288
316
  stageTransitions: readonly string[];
289
317
  admissionGrants?: boolean;
318
+ commercialParity?: boolean;
290
319
  }
291
320
  /** Fully-resolved leader network configuration. */
292
321
  interface ResolvedNetwork {
@@ -554,6 +583,8 @@ interface ChapterConfig {
554
583
  /** Network-scoped canonical Person registry. Shadow mode is additive only:
555
584
  * Chapter workflows and access continue to read their existing authority. */
556
585
  networkPersonRegistry?: ChapterNetworkPersonRegistry;
586
+ /** Read-only, rebuildable commercial projection from a shared Stripe account. */
587
+ networkCommercialReadModel?: ChapterNetworkCommercialReadModel;
557
588
  /** Enable a public, host-rendered formation application workflow. */
558
589
  formation?: LeaderFormationConfig;
559
590
  thesis?: unknown;
@@ -656,6 +687,8 @@ interface Chapter {
656
687
  builderLibrary: ResolvedBuilderLibrary | null;
657
688
  /** Null unless this hub explicitly enables the shadow Person registry. */
658
689
  networkPersonRegistry: ResolvedNetworkPersonRegistry | null;
690
+ /** Null unless this hub explicitly enables the commercial shadow. */
691
+ networkCommercialReadModel: ResolvedNetworkCommercialReadModel | null;
659
692
  /** Resolved public formation intake contract (disabled when omitted). */
660
693
  formation: ResolvedLeaderFormation;
661
694
  /** Resolved CRM engine (from `defineCrm`). */