@odla-ai/chapter 0.47.10 → 0.48.1

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.
@@ -84,6 +84,8 @@ interface ChapterSignupControl {
84
84
  sourceId: string;
85
85
  /** Follower-vault key holding the dedicated edge secret. */
86
86
  secretName?: string;
87
+ /** Optional per-runtime vault keys. When present, unlisted runtimes and legacy untargeted deliveries fail closed. */
88
+ runtimeSecrets?: Readonly<Record<string, string>>;
87
89
  /** Stripe mode this deployed environment accepts. */
88
90
  stripeMode: "test" | "live";
89
91
  }
@@ -91,6 +93,7 @@ interface ChapterSignupControl {
91
93
  interface ResolvedSignupControl {
92
94
  sourceId: string;
93
95
  secretName: string;
96
+ runtimeSecrets?: Readonly<Record<string, string>>;
94
97
  stripeMode: "test" | "live";
95
98
  }
96
99
 
@@ -266,6 +269,9 @@ interface ChapterNetworkReader {
266
269
  * receive pushed records. It must explicitly declare `readers` before any peer
267
270
  * can browse its CRM. */
268
271
  interface ChapterNetwork {
272
+ /** Require the protected super-admin tier for remote mutations and operational
273
+ * detail. Ordinary admins retain routine name/email directory reads. */
274
+ requireSuperAdminWrites?: boolean;
269
275
  targets?: readonly ChapterNetworkTarget[];
270
276
  readers?: readonly ChapterNetworkReader[];
271
277
  }
@@ -320,6 +326,7 @@ interface ResolvedNetworkReader {
320
326
  }
321
327
  /** Fully-resolved leader network configuration. */
322
328
  interface ResolvedNetwork {
329
+ requireSuperAdminWrites?: boolean;
323
330
  targets: readonly ResolvedNetworkTarget[];
324
331
  readers: readonly ResolvedNetworkReader[];
325
332
  }
@@ -514,7 +521,9 @@ interface ChapterScheduling {
514
521
  }
515
522
  /** How a signed-in user's role is determined.
516
523
  *
517
- * - `"claim"` reads a role ladder from a JWT claim (the member-site model:
524
+ * - `"clerk"` reads backend-only private Clerk roles for each authorization
525
+ * check, without trusting public metadata or stale session roles.
526
+ * - `"claim"` reads a role ladder from a JWT claim (the legacy member-site model:
518
527
  * `provisional → member → admin`). Admin is the last (highest) rung; the
519
528
  * Studio-managed super-admin tier sits above it.
520
529
  * - `"table"` gates on the deny-all `admins` allowlist (the hub model):
@@ -522,23 +531,25 @@ interface ChapterScheduling {
522
531
  *
523
532
  * Defaults: `"claim"` in `chapter` mode, `"table"` in `hub` mode. */
524
533
  interface ChapterAuth {
525
- source?: "claim" | "table";
534
+ /** `clerk` reads backend-only private_metadata.role on each request, ignoring
535
+ * JWT/public roles. Select explicitly after the coordinated role migration. */
536
+ source?: "claim" | "table" | "clerk";
526
537
  /** JWT claim holding the role, for `source: "claim"`. Default `"role"`. */
527
538
  claim?: string;
528
- /** Role ladder low→high, for `source: "claim"`. The last entry is admin.
539
+ /** Role ladder low→high, for claim or private Clerk roles. The last entry is admin.
529
540
  * Default `["provisional", "member", "admin"]`. */
530
541
  ladder?: readonly string[];
531
542
  /** Enable the Studio-managed super-admin tier — the only tier that may
532
- * create/modify admins. Default: `true` for `"claim"`, `false` for `"table"`. */
543
+ * create/modify admins. Default: `true` for `"claim"`/`"clerk"`, `false` for `"table"`. */
533
544
  superAdmins?: boolean;
534
545
  /** Authority behind the super-admin tier. `"table"` preserves the legacy
535
- * app allowlist; `"odla"` reads protected `$users.role`. Default `"table"`
536
- * for backward compatibility. */
546
+ * app allowlist; `"odla"` reads protected `$users.role` by exact subject ID.
547
+ * Default `"odla"` for private Clerk mode, `"table"` for legacy modes. */
537
548
  superAdminSource?: "table" | "odla";
538
549
  }
539
550
  /** The fully-resolved auth policy (defaults applied) carried on the {@link Chapter}. */
540
551
  interface ResolvedAuth {
541
- source: "claim" | "table";
552
+ source: "claim" | "table" | "clerk";
542
553
  claim: string;
543
554
  ladder: readonly string[];
544
555
  /** The highest ladder rung (last entry) — the "admin" gate. */
@@ -775,6 +786,292 @@ interface Chapter {
775
786
  groupSeed(): Record<string, unknown> | null;
776
787
  }
777
788
 
789
+ /** Frozen commercial terms supplied by the issuing membership authority, never the browser. */
790
+ interface SubscriptionBillingTerms {
791
+ /** Immutable offering version whose base Price is subscribed. */
792
+ offeringVersionId: string;
793
+ /** Immutable discount policy, independent of the base SKU and permanent cohort. */
794
+ discount?: {
795
+ policyVersionId: string;
796
+ couponId: string;
797
+ percentOffBps: number;
798
+ duration: "once" | "forever";
799
+ };
800
+ /** Extended initial paid service end, epoch milliseconds. Renewal starts here. */
801
+ initialTermEndsAt?: number;
802
+ }
803
+
804
+ /** Per-tier policy overrides prevent a chapter-wide price claim leaking into another SKU. */
805
+ interface TierPaymentPolicy {
806
+ refundPolicyText: string;
807
+ trustCopy?: string;
808
+ }
809
+
810
+ /** Immutable commercial policy, separate from the recurring base SKU. */
811
+ interface MembershipOfferingVersion {
812
+ id: string;
813
+ chapterId: string;
814
+ baseSkuId: string;
815
+ version: number;
816
+ status: "draft" | "published" | "retired";
817
+ effectiveFrom: number;
818
+ effectiveUntil?: number;
819
+ basePriceCents: number;
820
+ currency: string;
821
+ interval: "month" | "year";
822
+ term: {
823
+ kind: "fixed_end";
824
+ endsAt: number;
825
+ } | {
826
+ kind: "rolling_months";
827
+ months: number;
828
+ };
829
+ autoRenew: true;
830
+ paymentGraceDays: number;
831
+ seatPriceCents: number;
832
+ seatPurchaseWindowCalendarMonths: number;
833
+ requiredDisclosures: string[];
834
+ }
835
+ /** Cohort qualification survives the expiration of its monetary benefit. */
836
+ interface MembershipDiscountPolicyVersion {
837
+ id: string;
838
+ chapterId: string;
839
+ version: number;
840
+ status: "draft" | "published" | "retired";
841
+ cohortLabel: string;
842
+ cohortEligibleSkuIds: string[];
843
+ discountEligibleSkuIds: string[];
844
+ softTarget: number;
845
+ percentOffBps: number;
846
+ qualifyingStartsAt: number;
847
+ qualifyingEndsAt: number;
848
+ benefitDuration: {
849
+ kind: "first_term" | "while_active";
850
+ } | {
851
+ kind: "fixed_end";
852
+ endsAt: number;
853
+ };
854
+ }
855
+
856
+ /** Money and service promise frozen at offer issuance, not recomputed from later policy edits. */
857
+ interface MembershipCommercialOffer {
858
+ offering: MembershipOfferingVersion;
859
+ discountPolicy: MembershipDiscountPolicyVersion | null;
860
+ issuedAt: number;
861
+ founding: boolean;
862
+ dueTodayCents: number;
863
+ /** Fixed service end only; rolling service starts at the provider-confirmed payment. */
864
+ initialTermEndsAt?: number;
865
+ renewalCents: number;
866
+ billingTerms: SubscriptionBillingTerms;
867
+ }
868
+
869
+ /** Durable pre-payment promise. The opaque digest is what the member acknowledges. */
870
+ interface IssuedMembershipOffer {
871
+ id: string;
872
+ applicationId: string;
873
+ appId: string;
874
+ chapterId: string;
875
+ environment: string;
876
+ runtime: string;
877
+ skuId: string;
878
+ issuedAt: number;
879
+ expiresAt: number;
880
+ digest: string;
881
+ contract: MembershipCommercialOffer;
882
+ }
883
+
884
+ /** Server-owned source identity; the browser never supplies an offer or its price. */
885
+ interface MembershipQuoteTarget {
886
+ appId: string;
887
+ chapterId: string;
888
+ environment: string;
889
+ runtime: string;
890
+ applicationId: string;
891
+ skuId: string;
892
+ }
893
+ /** Host transport adapter; may call the signed edge or an in-process network command. */
894
+ type MembershipOfferIssuer = (input: MembershipQuoteTarget & {
895
+ requestId: string;
896
+ }) => Promise<IssuedMembershipOffer>;
897
+
898
+ /** A consented offer, bound once to the follower's immutable provider Price and quote. */
899
+ interface MembershipCheckoutBindingInput extends MembershipQuoteTarget {
900
+ offerId: string;
901
+ offerDigest: string;
902
+ quoteDigest: string;
903
+ priceId: string;
904
+ }
905
+ /** Content-free authority receipt. It does not mean paid, approved, or entitled. */
906
+ interface MembershipCheckoutBinding extends MembershipCheckoutBindingInput {
907
+ id: string;
908
+ boundAt: number;
909
+ }
910
+
911
+ /** References only. Money facts must already exist in the verified network Stripe event log. */
912
+ interface ConfirmMembershipPaymentInput extends MembershipQuoteTarget {
913
+ eventId: string;
914
+ providerSubscriptionId: string;
915
+ providerCustomerId: string;
916
+ }
917
+ /** The network identity remains source-linked until an explicit canonical Person link exists. */
918
+ interface MembershipPaymentReceipt {
919
+ membershipId: string;
920
+ status: string;
921
+ initialTermStartedAt: number;
922
+ initialTermEndsAt: number;
923
+ cohort: string;
924
+ memberNumber?: number;
925
+ }
926
+
927
+ /** Session identity attested by the signed chapter adapter, never a browser's email_verified flag. */
928
+ interface NamedSeatRecipientInput extends MembershipQuoteTarget {
929
+ seatId: string;
930
+ recipientApplicationId: string;
931
+ recipientUserId: string;
932
+ verifiedRecipientEmail: string;
933
+ }
934
+
935
+ /** Verified full initial refund and subscription cancellation, not a browser assertion. */
936
+ interface MembershipRefundReceipt {
937
+ membershipId: string;
938
+ status: "refunded";
939
+ refundedCents: number;
940
+ subscriptionCanceled: true;
941
+ }
942
+
943
+ /** Current authoritative billing state; a chapter projection must not derive it from arrival order. */
944
+ interface MembershipRenewalReceipt {
945
+ membershipId: string;
946
+ status: string;
947
+ termEndsAt: number;
948
+ graceEndsAt: number;
949
+ autoRenew: boolean;
950
+ }
951
+
952
+ /** Short-lived decision from current network state; never a durable bearer capability. */
953
+ interface MembershipAccessReceipt {
954
+ membershipId: string;
955
+ allowed: boolean;
956
+ status: string;
957
+ accessEndsAt: number;
958
+ revision: number;
959
+ }
960
+
961
+ /** Named-seat offer for the authenticated purchaser, not a transferable gift or arbitrary quantity. */
962
+ interface NamedSeatOffer {
963
+ membershipId: string;
964
+ amountCents: number;
965
+ currency: string;
966
+ interval: "year" | "month";
967
+ termEndsAt: number;
968
+ purchaseEndsAt: number;
969
+ autoRenew: boolean;
970
+ eligible: boolean;
971
+ digest: string;
972
+ seat?: {
973
+ seatId: string;
974
+ status: string;
975
+ recipientName: string;
976
+ recipientEmail: string;
977
+ };
978
+ }
979
+
980
+ /** Identity attested by the recipient's chapter after checking Clerk's verified primary email. */
981
+ interface NamedSeatInvitationRequest {
982
+ appId: string;
983
+ chapterId: string;
984
+ environment: string;
985
+ runtime: string;
986
+ seatId: string;
987
+ recipientUserId: string;
988
+ verifiedRecipientEmail: string;
989
+ }
990
+ /** Recipient-visible invitation; no purchaser contact information or provider credentials. */
991
+ interface NamedSeatInvitation {
992
+ seatId: string;
993
+ primaryApplicationId: string;
994
+ skuId: string;
995
+ recipientName: string;
996
+ status: string;
997
+ acceptanceEndsAt: number;
998
+ termEndsAt: number;
999
+ }
1000
+
1001
+ /** Exact configured chapter/runtime serviced by one scheduled network worker. */
1002
+ type MembershipEffectScope = Pick<MembershipQuoteTarget, "appId" | "chapterId" | "environment" | "runtime">;
1003
+ /** Downstream requests carry only identifiers; the signed chapter receiver reads current state and deduplicates by effectId. */
1004
+ interface MembershipChapterEffect extends MembershipQuoteTarget {
1005
+ effectId: string;
1006
+ seatId: string;
1007
+ kind: "invite_named_recipient" | "grant_named_recipient_access" | "revoke_named_recipient_access";
1008
+ }
1009
+ /** Recover provider effects and expired invitations from durable state, independently of browser/webhook retries.
1010
+ * The optional chapter adapter must return a durable, idempotent receipt. An absent adapter leaves work visibly pending.
1011
+ * Provider HTTP timeouts, ambiguous receipts and scope conflicts never become success or an unbounded automatic retry loop.
1012
+ */
1013
+ declare function drainMembershipEffects(db: ChapterDb, options: {
1014
+ scope: MembershipEffectScope;
1015
+ stripeSecretKey: string;
1016
+ limit?: number;
1017
+ deliverChapterEffect?: (input: MembershipChapterEffect) => Promise<{
1018
+ effectId: string;
1019
+ complete: true;
1020
+ }>;
1021
+ }, now: number): Promise<{
1022
+ complete: number;
1023
+ pending: number;
1024
+ expired: number;
1025
+ more: boolean;
1026
+ }>;
1027
+
1028
+ /** Minimal chapter-owned delivery data, fetched over the authenticated runtime edge, never from a browser payload. */
1029
+ interface MembershipChapterEffectData extends MembershipChapterEffect {
1030
+ recipientName: string;
1031
+ recipientEmail: string;
1032
+ recipientUserId: string | null;
1033
+ recipientApplicationId: string | null;
1034
+ acceptanceEndsAt: number;
1035
+ termEndsAt: number;
1036
+ applicable: boolean;
1037
+ }
1038
+
1039
+ /** Only the named buyer's prepared payment details; no network credentials or canonical PII. */
1040
+ interface NamedSeatCheckoutReceipt {
1041
+ seatId: string;
1042
+ paymentIntentId: string;
1043
+ clientSecret: string;
1044
+ amountCents: number;
1045
+ currency: string;
1046
+ termEndsAt: number;
1047
+ status: string;
1048
+ }
1049
+ /** Result of attesting one verified recipient's acceptance or completed Journey. */
1050
+ interface NamedSeatRecipientReceipt {
1051
+ seatId: string;
1052
+ status: string;
1053
+ recipientApplicationId: string;
1054
+ }
1055
+ /** Full seat-only refund. The purchaser's primary subscription is not canceled. */
1056
+ interface NamedSeatRefundReceipt {
1057
+ seatId: string;
1058
+ status: "refunded";
1059
+ refundedCents: number;
1060
+ subscriptionCanceled: false;
1061
+ }
1062
+ /** Verified purchaser identity and their named recipient, with no caller-selected pricing. */
1063
+ type NamedSeatCheckoutRequest = MembershipQuoteTarget & {
1064
+ recipientName: string;
1065
+ recipientEmail: string;
1066
+ purchaserEmail: string;
1067
+ purchaserUserId: string;
1068
+ };
1069
+ /** Reference to an independent network Stripe fact, never caller-supplied payment amounts. */
1070
+ type NamedSeatEvidenceRequest = MembershipQuoteTarget & {
1071
+ seatId: string;
1072
+ eventId: string;
1073
+ };
1074
+
778
1075
  /** The Cloudflare SEND_EMAIL binding payload. */
779
1076
  interface EmailPayload {
780
1077
  from: string;
@@ -809,10 +1106,54 @@ interface ChapterEnv {
809
1106
  }>;
810
1107
  };
811
1108
  EMAIL_FROM?: string;
1109
+ /** Optional runtime-local inbox for non-production lifecycle mail. Ignored in prod. */
1110
+ ODLA_DEBUG_EMAIL?: string;
1111
+ /** Optional, allowlisted dev-only named-seat acceptance test transport. */
1112
+ SEND_INVITATION_TEST?: ChapterEnv["SEND_EMAIL"];
1113
+ ODLA_INVITATION_TEST_RECIPIENTS?: string;
1114
+ ODLA_INVITATION_TEST_TO?: string;
1115
+ ODLA_INVITATION_TEST_FROM?: string;
812
1116
  }
813
1117
  /** Options for `chapterWorker`. */
814
1118
  interface ChapterWorkerOptions {
815
1119
  chapter: Chapter;
1120
+ /** Require the pre-consent quote digest on membership checkout. Enable with the current JoinIsland after upgrading older clients. */
1121
+ requirePaymentQuote?: boolean;
1122
+ /** Opt-in network authority adapter. All operations must use the same network
1123
+ * scope. Never implement these by promoting chapter-local projections to authority. */
1124
+ membershipAuthority?: {
1125
+ issueOffer: (input: Parameters<MembershipOfferIssuer>[0], env: ChapterEnv) => ReturnType<MembershipOfferIssuer>;
1126
+ bindCheckout: (input: MembershipCheckoutBindingInput, env: ChapterEnv) => Promise<MembershipCheckoutBinding>;
1127
+ confirmPayment: (input: ConfirmMembershipPaymentInput, env: ChapterEnv) => Promise<MembershipPaymentReceipt>;
1128
+ approveMembership: (input: MembershipQuoteTarget, env: ChapterEnv) => Promise<MembershipPaymentReceipt>;
1129
+ refundMembership: (input: MembershipQuoteTarget, env: ChapterEnv) => Promise<MembershipRefundReceipt>;
1130
+ reconcileRenewal: (input: MembershipQuoteTarget & {
1131
+ eventId: string;
1132
+ }, env: ChapterEnv) => Promise<MembershipRenewalReceipt>;
1133
+ readAccess: (input: MembershipQuoteTarget, env: ChapterEnv) => Promise<MembershipAccessReceipt>;
1134
+ namedSeats?: {
1135
+ offer: (input: MembershipQuoteTarget, env: ChapterEnv) => Promise<NamedSeatOffer>;
1136
+ invitation: (input: NamedSeatInvitationRequest, env: ChapterEnv) => Promise<NamedSeatInvitation>;
1137
+ readAccess: (input: MembershipQuoteTarget & {
1138
+ seatId: string;
1139
+ recipientUserId: string;
1140
+ recipientApplicationId: string;
1141
+ }, env: ChapterEnv) => Promise<MembershipAccessReceipt>;
1142
+ checkout: (input: NamedSeatCheckoutRequest, env: ChapterEnv) => Promise<NamedSeatCheckoutReceipt>;
1143
+ reserve: (input: NamedSeatCheckoutRequest, env: ChapterEnv) => Promise<Pick<NamedSeatCheckoutReceipt, "seatId" | "amountCents" | "currency" | "termEndsAt">>;
1144
+ confirmPayment: (input: NamedSeatEvidenceRequest, env: ChapterEnv) => Promise<{
1145
+ seatId: string;
1146
+ reconciled: true;
1147
+ }>;
1148
+ confirmRefund: (input: NamedSeatEvidenceRequest, env: ChapterEnv) => Promise<{
1149
+ seatId: string;
1150
+ reconciled: true;
1151
+ }>;
1152
+ accept: (input: NamedSeatRecipientInput, env: ChapterEnv) => Promise<NamedSeatRecipientReceipt>;
1153
+ approve: (input: NamedSeatRecipientInput, env: ChapterEnv) => Promise<NamedSeatRecipientReceipt>;
1154
+ decline: (input: NamedSeatRecipientInput, env: ChapterEnv) => Promise<NamedSeatRefundReceipt>;
1155
+ };
1156
+ };
816
1157
  /** CRM mount point. Default "/api/crm". */
817
1158
  crmBasePath?: string;
818
1159
  /** Host routes, tried BEFORE the built-ins — so a wrapping site can add its own
@@ -845,11 +1186,45 @@ declare function createWorkerContext(options: ChapterWorkerOptions): {
845
1186
  chapter: Chapter;
846
1187
  auth: ResolvedAuth;
847
1188
  crmBase: string;
1189
+ requirePaymentQuote: boolean;
1190
+ membershipAuthority: {
1191
+ issueOffer: (input: Parameters<MembershipOfferIssuer>[0], env: ChapterEnv) => ReturnType<MembershipOfferIssuer>;
1192
+ bindCheckout: (input: MembershipCheckoutBindingInput, env: ChapterEnv) => Promise<MembershipCheckoutBinding>;
1193
+ confirmPayment: (input: ConfirmMembershipPaymentInput, env: ChapterEnv) => Promise<MembershipPaymentReceipt>;
1194
+ approveMembership: (input: MembershipQuoteTarget, env: ChapterEnv) => Promise<MembershipPaymentReceipt>;
1195
+ refundMembership: (input: MembershipQuoteTarget, env: ChapterEnv) => Promise<MembershipRefundReceipt>;
1196
+ reconcileRenewal: (input: MembershipQuoteTarget & {
1197
+ eventId: string;
1198
+ }, env: ChapterEnv) => Promise<MembershipRenewalReceipt>;
1199
+ readAccess: (input: MembershipQuoteTarget, env: ChapterEnv) => Promise<MembershipAccessReceipt>;
1200
+ namedSeats?: {
1201
+ offer: (input: MembershipQuoteTarget, env: ChapterEnv) => Promise<NamedSeatOffer>;
1202
+ invitation: (input: NamedSeatInvitationRequest, env: ChapterEnv) => Promise<NamedSeatInvitation>;
1203
+ readAccess: (input: MembershipQuoteTarget & {
1204
+ seatId: string;
1205
+ recipientUserId: string;
1206
+ recipientApplicationId: string;
1207
+ }, env: ChapterEnv) => Promise<MembershipAccessReceipt>;
1208
+ checkout: (input: NamedSeatCheckoutRequest, env: ChapterEnv) => Promise<NamedSeatCheckoutReceipt>;
1209
+ reserve: (input: NamedSeatCheckoutRequest, env: ChapterEnv) => Promise<Pick<NamedSeatCheckoutReceipt, "seatId" | "amountCents" | "currency" | "termEndsAt">>;
1210
+ confirmPayment: (input: NamedSeatEvidenceRequest, env: ChapterEnv) => Promise<{
1211
+ seatId: string;
1212
+ reconciled: true;
1213
+ }>;
1214
+ confirmRefund: (input: NamedSeatEvidenceRequest, env: ChapterEnv) => Promise<{
1215
+ seatId: string;
1216
+ reconciled: true;
1217
+ }>;
1218
+ accept: (input: NamedSeatRecipientInput, env: ChapterEnv) => Promise<NamedSeatRecipientReceipt>;
1219
+ approve: (input: NamedSeatRecipientInput, env: ChapterEnv) => Promise<NamedSeatRecipientReceipt>;
1220
+ decline: (input: NamedSeatRecipientInput, env: ChapterEnv) => Promise<NamedSeatRefundReceipt>;
1221
+ };
1222
+ } | undefined;
848
1223
  getPublicConfig: (env: ChapterEnv) => Promise<PublicConfig>;
849
1224
  verifyUser: (req: Request, env: ChapterEnv) => Promise<Verified | null>;
850
1225
  makeDb: (env: ChapterEnv) => Db;
851
1226
  isAdminEmail: (db: Db, email: string | undefined) => Promise<boolean>;
852
- isSuperAdminEmail: (db: Db, email: string | undefined) => Promise<boolean>;
1227
+ isSuperAdminEmail: (db: Db, email: string | undefined, userId?: string) => Promise<boolean>;
853
1228
  roleFor: (db: Db, u: Verified) => Promise<string>;
854
1229
  isAdmin: (db: Db, u: Verified) => Promise<boolean>;
855
1230
  crmSender: (env: ChapterEnv) => {
@@ -865,6 +1240,119 @@ type WorkerContext = ReturnType<typeof createWorkerContext>;
865
1240
  * {@link WorkerContext} the built-ins receive. */
866
1241
  type Route = (req: Request, url: URL, env: ChapterEnv, ctx: WorkerContext) => Promise<Response | null>;
867
1242
 
1243
+ /** A dedicated two-sided edge: no chapter receives the network database credential. */
1244
+ interface MembershipAuthorityPeer {
1245
+ appId: string;
1246
+ chapterId: string;
1247
+ environment: string;
1248
+ runtime: string;
1249
+ secretName: string;
1250
+ /** Authority-configured, product-separated recurring named-seat Price. Omit to disable seat commands. */
1251
+ seatPriceId?: string;
1252
+ }
1253
+ /** Business API configuration, independent of the browser UI. May be mounted in
1254
+ * the existing BNF backend using its existing odla database; no extra app required.
1255
+ */
1256
+ interface MembershipAuthorityHandlerOptions {
1257
+ peers: readonly MembershipAuthorityPeer[];
1258
+ /** Connect to the network owner's database (BNF), never a chapter projection. */
1259
+ makeDb: () => ChapterDb;
1260
+ now?: () => number;
1261
+ offerLifetimeMs: number;
1262
+ /** Network vault slot; never supplied by a caller. Defaults to stripe_secret_key. */
1263
+ stripeSecretName?: string;
1264
+ }
1265
+ /** Signed server-to-server route; the runtime is included in the signed request target. */
1266
+ declare const MEMBERSHIP_AUTHORITY_PATH = "/api/membership-authority";
1267
+ /** Create the narrow signed offer issuer. Authentication maps each dedicated edge
1268
+ * to an exact app/chapter/environment/runtime; callers cannot select another scope,
1269
+ * provide prices, grant roles, or run arbitrary network database operations.
1270
+ * Replayed signed requests are safe because issuance is durably idempotent.
1271
+ */
1272
+ declare function createMembershipAuthorityHandler(options: MembershipAuthorityHandlerOptions): (req: Request) => Promise<Response>;
1273
+
1274
+ /** Bind reusable authority commands to their isolated collections in BNF's
1275
+ * existing database. Remaps operations AND revision guards in one transaction;
1276
+ * allows policy/provider evidence reads but no writes to those or role tables.
1277
+ * Only named vault slots are exposed. This is defense against accidental command
1278
+ * misuse, NOT isolation from a compromised host holding the underlying app key.
1279
+ */
1280
+ declare function createMembershipAuthorityStorage(db: ChapterDb, secretNames: readonly string[]): ChapterDb;
1281
+
1282
+ type Row = Record<string, unknown>;
1283
+ /** Host routes may reuse this gate after resolving a verified chapter/runtime account. */
1284
+ declare function chapterMembershipAccess(db: ChapterDb, env: ChapterEnv, ctx: WorkerContext, app: Row): Promise<boolean>;
1285
+
1286
+ /** Retry durable unfinished approval effects in one explicitly configured runtime.
1287
+ * Hosts call this from their scheduled handler; sibling runtime rows are never selected.
1288
+ * The configured origin is used for links, not a stored browser-controlled value.
1289
+ */
1290
+ declare function retryChapterApprovalEffects(ctx: WorkerContext, env: ChapterEnv, options: {
1291
+ origin: string;
1292
+ limit?: number;
1293
+ }): Promise<{
1294
+ repaired: number;
1295
+ pending: number;
1296
+ more: boolean;
1297
+ }>;
1298
+
1299
+ /** Deliver one effect identifier. The receiver independently reloads its current authority record. */
1300
+ declare function deliverMembershipChapterEffect(options: {
1301
+ secret: string;
1302
+ sender: string;
1303
+ targetUrl: string;
1304
+ effect: MembershipChapterEffect;
1305
+ fetch?: typeof fetch;
1306
+ }): Promise<{
1307
+ effectId: string;
1308
+ complete: true;
1309
+ }>;
1310
+ /** Explicitly configured signed receiver; the host provides the existing authority edge, never its database key.
1311
+ * Stable effect IDs deduplicate email; grant/revoke retries converge on the exact bound recipient.
1312
+ */
1313
+ declare function createMembershipChapterEffectRoute(options: {
1314
+ sender: string;
1315
+ secretName: string;
1316
+ origin: string;
1317
+ /** Existing application route on this chapter, not its marketing interest page. */
1318
+ applicationPath?: string;
1319
+ readEffect: (input: MembershipChapterEffect, env: ChapterEnv) => Promise<MembershipChapterEffectData>;
1320
+ }): Route;
1321
+
1322
+ /** Existing chapter-to-network edge configuration. This never provisions an app, database, credential, or Worker. */
1323
+ interface MembershipWorkerAdapterOptions {
1324
+ chapterId: string;
1325
+ secretName: string;
1326
+ makeDb: (env: ChapterEnv) => ChapterDb;
1327
+ targetUrl: (env: ChapterEnv) => string;
1328
+ /** Explicit transport resolver, e.g. a configured service binding. Absence/failure never falls back to a public request. */
1329
+ fetch: (env: ChapterEnv) => typeof globalThis.fetch;
1330
+ namedSeats: boolean;
1331
+ }
1332
+ /** One complete adapter for every chapter host, plus the reverse receiver's current-state reader. */
1333
+ interface MembershipWorkerAdapter {
1334
+ authority: NonNullable<ChapterWorkerOptions["membershipAuthority"]>;
1335
+ readEffect: (input: MembershipChapterEffect, env: ChapterEnv) => Promise<MembershipChapterEffectData>;
1336
+ }
1337
+ /** Wire all membership commands to one exact configured runtime edge; hosts cannot accidentally omit refunds or use a second scope. */
1338
+ declare function createMembershipWorkerAdapter(options: MembershipWorkerAdapterOptions): MembershipWorkerAdapter;
1339
+
1340
+ /** The server transport selected for one configured follower edge. */
1341
+ interface NetworkTargetTransport {
1342
+ kind: "binding" | "url";
1343
+ binding?: string;
1344
+ fetch(destination: URL, init: RequestInit): Promise<Response>;
1345
+ }
1346
+ /** A configured binding is authoritative: an invalid binding never falls
1347
+ * through to the public origin and accidentally crosses an account boundary. */
1348
+ declare class NetworkTargetTransportError extends Error {
1349
+ readonly code = "binding_unavailable";
1350
+ readonly binding: string;
1351
+ constructor(binding: string);
1352
+ }
1353
+ /** Resolve the same binding-first transport used by health, reads, and writes. */
1354
+ declare function resolveNetworkTargetTransport(env: ChapterEnv, target: Pick<ResolvedNetworkTarget, "binding">): NetworkTargetTransport;
1355
+
868
1356
  /** The first stable, shared Chapter membership-page renderer contract. */
869
1357
  declare const MEMBERSHIP_PAGE_RENDERER: "membership-cards-v1";
870
1358
  /** Fixed visual emphasis choices supported by the versioned card renderer. */
@@ -971,6 +1459,8 @@ interface SignupControlPolicy {
971
1459
  trustCopy?: string;
972
1460
  commitmentText?: string;
973
1461
  normsText?: string;
1462
+ /** Price-specific refund/trust copy, keyed by an explicitly declared tier. */
1463
+ tierPolicies?: Readonly<Record<string, TierPaymentPolicy>>;
974
1464
  }
975
1465
  /** Bounded semantic design tokens delegated to the chapter signup surface. */
976
1466
  interface SignupControlTheme {
@@ -1002,6 +1492,14 @@ interface SignupControlDocument {
1002
1492
  interface SignupControlEnvelope {
1003
1493
  digest: string;
1004
1494
  document: SignupControlDocument;
1495
+ /** Signed transport identity, deliberately excluded from the shared document digest. */
1496
+ deliveryTarget?: SignupControlRuntimeTarget;
1497
+ }
1498
+ /** Exact deployment receiving a shared data-environment revision. */
1499
+ interface SignupControlRuntimeTarget {
1500
+ appId: string;
1501
+ environment: string;
1502
+ runtime: string;
1005
1503
  }
1006
1504
  /** Exact follower acknowledgement for one accepted or replayed revision. */
1007
1505
  interface SignupControlReceipt {
@@ -1009,6 +1507,8 @@ interface SignupControlReceipt {
1009
1507
  target: {
1010
1508
  chapterId: string;
1011
1509
  environment: string;
1510
+ appId?: string;
1511
+ runtime?: string;
1012
1512
  };
1013
1513
  revision: number;
1014
1514
  digest: string;
@@ -1397,4 +1897,4 @@ declare function chapterWorker(options: ChapterWorkerOptions): {
1397
1897
  fetch(req: Request, env: ChapterEnv): Promise<Response>;
1398
1898
  };
1399
1899
 
1400
- export { type ActiveSignupControlHtml, type ActiveSignupControlThemeOptions, AdmissionGrantDeliveryError, type AdmissionGrantDeliveryOptions, type BuilderLibraryDeliveryOptions, type ChapterEnv, type ChapterWorkerOptions, type LeaderAdmissionAssignment, type LeaderAdmissionDelivery, type LeaderAdmissionDeliveryStatus, type MemberContentDeliveryOptions, type Route, type SignupControlDeliveryOptions, type WorkerContext, chapterWorker, createWorkerContext, deliverAdmissionGrants, deliverBuilderLibrary, deliverMemberContent, deliverSignupControl, renderActiveSignupControlHtml, retryLeaderAdmissionDeliveries, withActiveSignupControlTheme };
1900
+ export { type ActiveSignupControlHtml, type ActiveSignupControlThemeOptions, AdmissionGrantDeliveryError, type AdmissionGrantDeliveryOptions, type BuilderLibraryDeliveryOptions, type ChapterEnv, type ChapterWorkerOptions, type LeaderAdmissionAssignment, type LeaderAdmissionDelivery, type LeaderAdmissionDeliveryStatus, MEMBERSHIP_AUTHORITY_PATH, type MemberContentDeliveryOptions, type MembershipAuthorityHandlerOptions, type MembershipAuthorityPeer, type MembershipChapterEffect, type MembershipEffectScope, type MembershipWorkerAdapter, type MembershipWorkerAdapterOptions, type NetworkTargetTransport, NetworkTargetTransportError, type Route, type SignupControlDeliveryOptions, type WorkerContext, chapterMembershipAccess, chapterWorker, createMembershipAuthorityHandler, createMembershipAuthorityStorage, createMembershipChapterEffectRoute, createMembershipWorkerAdapter, createWorkerContext, deliverAdmissionGrants, deliverBuilderLibrary, deliverMemberContent, deliverMembershipChapterEffect, deliverSignupControl, drainMembershipEffects, renderActiveSignupControlHtml, resolveNetworkTargetTransport, retryChapterApprovalEffects, retryLeaderAdmissionDeliveries, withActiveSignupControlTheme };