@odla-ai/chapter 0.47.9 → 0.48.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 +257 -17
- package/dist/{chunk-VWDEHUNI.js → chunk-KZ2AXEZW.js} +497 -204
- package/dist/chunk-KZ2AXEZW.js.map +1 -0
- package/dist/{chunk-7MEVZEL2.js → chunk-MQ4OPZCS.js} +969 -516
- package/dist/chunk-MQ4OPZCS.js.map +1 -0
- package/dist/{copy-context-Dorxp-WK.d.ts → copy-context-BveBZglM.d.ts} +18 -7
- package/dist/index.cjs +3789 -761
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +753 -70
- package/dist/index.d.ts +753 -70
- package/dist/index.js +3782 -754
- package/dist/index.js.map +1 -1
- package/dist/testing.cjs +308 -88
- package/dist/testing.cjs.map +1 -1
- package/dist/testing.d.cts +102 -4
- package/dist/testing.d.ts +102 -4
- package/dist/testing.js +308 -88
- package/dist/testing.js.map +1 -1
- package/dist/ui/admin/index.d.ts +75 -5
- package/dist/ui/admin/index.js +3 -1
- package/dist/ui/index.d.ts +2 -2
- package/dist/ui/index.js +4 -2
- package/dist/ui/member/index.d.ts +9 -2
- package/dist/ui/member/index.js +1 -1
- package/dist/worker/index.cjs +9698 -4695
- package/dist/worker/index.cjs.map +1 -1
- package/dist/worker/index.d.cts +509 -9
- package/dist/worker/index.d.ts +509 -9
- package/dist/worker/index.js +8385 -3383
- package/dist/worker/index.js.map +1 -1
- package/package.json +5 -5
- package/runbooks/adopt-existing.md +44 -8
- package/dist/chunk-7MEVZEL2.js.map +0 -1
- package/dist/chunk-VWDEHUNI.js.map +0 -1
package/dist/worker/index.d.ts
CHANGED
|
@@ -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
|
-
* - `"
|
|
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
|
-
|
|
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
|
|
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
|
|
536
|
-
* for
|
|
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 };
|