@odla-ai/chapter 0.60.0 → 0.62.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 +138 -0
- package/dist/{chunk-QNP3UEIL.js → chunk-7CVP6VSO.js} +61 -1
- package/dist/chunk-7CVP6VSO.js.map +1 -0
- package/dist/{chunk-J7ES2OWN.js → chunk-MUUBATZJ.js} +899 -707
- package/dist/chunk-MUUBATZJ.js.map +1 -0
- package/dist/{chunk-D5UR5H6R.js → chunk-ZIYK3OV2.js} +869 -239
- package/dist/chunk-ZIYK3OV2.js.map +1 -0
- package/dist/copy-context-BJUkVUjE.d.ts +364 -0
- package/dist/index.cjs +3847 -3186
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +435 -89
- package/dist/index.d.ts +435 -89
- package/dist/index.js +3851 -3190
- package/dist/index.js.map +1 -1
- package/dist/ui/admin/index.d.ts +672 -3
- package/dist/ui/admin/index.js +2 -2
- package/dist/ui/index.d.ts +3 -2
- package/dist/ui/index.js +49 -3
- package/dist/ui/member/index.d.ts +254 -3
- package/dist/ui/member/index.js +48 -2
- package/dist/worker/index.cjs +1641 -477
- package/dist/worker/index.cjs.map +1 -1
- package/dist/worker/index.d.cts +222 -82
- package/dist/worker/index.d.ts +222 -82
- package/dist/worker/index.js +1572 -408
- package/dist/worker/index.js.map +1 -1
- package/package.json +2 -2
- package/dist/chunk-D5UR5H6R.js.map +0 -1
- package/dist/chunk-J7ES2OWN.js.map +0 -1
- package/dist/chunk-QNP3UEIL.js.map +0 -1
- package/dist/copy-context-BP3G1iBd.d.ts +0 -899
package/dist/ui/admin/index.d.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import * as preact from 'preact';
|
|
2
2
|
import { ComponentChildren, JSX } from 'preact';
|
|
3
|
-
import { CrmClient, Crm, CrmRecord, FieldType } from '@odla-ai/crm';
|
|
4
|
-
import { a as
|
|
5
|
-
export { C as ChapterCopyProvider, u as useChapterCopy } from '../../copy-context-
|
|
3
|
+
import { CrmClient, CrmConfig, Crm, FieldConditions, CrmRecord, FieldType } from '@odla-ai/crm';
|
|
4
|
+
import { a as ChapterBrand, b as ChapterCopyInput, c as ChapterCopy, M as MemberContentDocument, E as EffectiveMemberContent, d as MemberContentTierAuthority } from '../../copy-context-BJUkVUjE.js';
|
|
5
|
+
export { C as ChapterCopyProvider, u as useChapterCopy } from '../../copy-context-BJUkVUjE.js';
|
|
6
6
|
import { CrmLifecycleAdapter, CrmWorkspaceMasterContext, CrmWorkspaceRecordContext, RecordPanelTab } from '@odla-ai/crm/ui';
|
|
7
7
|
|
|
8
8
|
/** Normalized public auth configuration consumed by the Clerk gate. */
|
|
@@ -130,6 +130,675 @@ interface AdminShellProps {
|
|
|
130
130
|
renderWorkspaceFrame?: (props: AdminWorkspaceFrameProps) => ComponentChildren;
|
|
131
131
|
}
|
|
132
132
|
|
|
133
|
+
/**
|
|
134
|
+
* A tier as declared in `defineChapter`, before it becomes a row.
|
|
135
|
+
*
|
|
136
|
+
* `groupId` is absent here because the chapter owns exactly one group at seed
|
|
137
|
+
* time; the seeder fills it in.
|
|
138
|
+
*/
|
|
139
|
+
interface ChapterTierConfig {
|
|
140
|
+
id: string;
|
|
141
|
+
name: string;
|
|
142
|
+
priceCents: number;
|
|
143
|
+
currency?: string;
|
|
144
|
+
interval?: "year" | "month";
|
|
145
|
+
stripePriceId?: string;
|
|
146
|
+
blurb?: string;
|
|
147
|
+
/** Defaults to declaration order when omitted. */
|
|
148
|
+
sortOrder?: number;
|
|
149
|
+
/** Defaults to true; set false to retire a tier without deleting it. */
|
|
150
|
+
active?: boolean;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/** A code-declared mail event. Copy stays in settings; this describes who receives it and the values the event supplies. */
|
|
154
|
+
interface ChapterEmailEvent {
|
|
155
|
+
label: string;
|
|
156
|
+
trigger: string;
|
|
157
|
+
audience: string;
|
|
158
|
+
variables: readonly string[];
|
|
159
|
+
/** Values deliberately allowed to render empty, such as a free tier's amount suffix. */
|
|
160
|
+
optionalVariables?: readonly string[];
|
|
161
|
+
/** Require a resolved tier display name even when the copy does not mention it. */
|
|
162
|
+
requiresTier?: boolean;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/** Explicit authority a chapter grants to one Built Not Found-style hub. */
|
|
166
|
+
interface ChapterSignupControl {
|
|
167
|
+
/** Stable app id expected in the signed delivery envelope. */
|
|
168
|
+
sourceId: string;
|
|
169
|
+
/** Follower-vault key holding the dedicated edge secret. */
|
|
170
|
+
secretName?: string;
|
|
171
|
+
/** Optional per-runtime vault keys. When present, unlisted runtimes and legacy untargeted deliveries fail closed. */
|
|
172
|
+
runtimeSecrets?: Readonly<Record<string, string>>;
|
|
173
|
+
/** Stripe mode this deployed environment accepts. */
|
|
174
|
+
stripeMode: "test" | "live";
|
|
175
|
+
}
|
|
176
|
+
/** Validated signup-control authority carried on a resolved Chapter. */
|
|
177
|
+
interface ResolvedSignupControl {
|
|
178
|
+
sourceId: string;
|
|
179
|
+
secretName: string;
|
|
180
|
+
runtimeSecrets?: Readonly<Record<string, string>>;
|
|
181
|
+
stripeMode: "test" | "live";
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/** Optional authenticated member-content surface for a chapter site. */
|
|
185
|
+
interface ChapterMemberContent {
|
|
186
|
+
/** Keep Chapter-admin authoring enabled. Defaults to true. */
|
|
187
|
+
localAuthoring?: boolean;
|
|
188
|
+
/** Optional Parent/Built Not Found authority for signed inherited revisions. */
|
|
189
|
+
parent?: {
|
|
190
|
+
sourceId: string;
|
|
191
|
+
/** Follower-vault key holding the dedicated content edge secret. */
|
|
192
|
+
secretName?: string;
|
|
193
|
+
};
|
|
194
|
+
}
|
|
195
|
+
/** Validated member-content settings carried on a resolved Chapter. */
|
|
196
|
+
interface ResolvedMemberContent {
|
|
197
|
+
enabled: true;
|
|
198
|
+
localAuthoring: boolean;
|
|
199
|
+
parent: {
|
|
200
|
+
sourceId: string;
|
|
201
|
+
secretName: string;
|
|
202
|
+
} | null;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
/** Invite links and referral credit for a chapter-mode site. */
|
|
206
|
+
interface ChapterMemberReferrals {
|
|
207
|
+
/** Public path that invite links live under; `<path>/<code>`. Defaults to `/join`. */
|
|
208
|
+
path?: string;
|
|
209
|
+
/** Where an invite link sends a visitor (absolute https URL or site path). `?ref=<code>` is appended. */
|
|
210
|
+
membershipUrl: string;
|
|
211
|
+
/** Lifetime of the first-party referral cookie, in days. Defaults to 90. */
|
|
212
|
+
cookieDays?: number;
|
|
213
|
+
/**
|
|
214
|
+
* Credit a dues-paying referrer earns when a friend they referred is approved
|
|
215
|
+
* on a dues-paying tier. Omit or `false` for invite links without credit.
|
|
216
|
+
*/
|
|
217
|
+
credit?: {
|
|
218
|
+
valueCents: number;
|
|
219
|
+
} | false;
|
|
220
|
+
}
|
|
221
|
+
/** The signed-in member portal. Its navigation and access come from the member-content catalog. */
|
|
222
|
+
interface ChapterMemberPortal {
|
|
223
|
+
/** Prompts a member may answer on their profile, in display order. */
|
|
224
|
+
profilePrompts?: readonly string[];
|
|
225
|
+
/** How many prompts a member may answer. Defaults to 3. */
|
|
226
|
+
maxPrompts?: number;
|
|
227
|
+
/** Member profile photos in odla-db file storage. Defaults to true. */
|
|
228
|
+
photos?: boolean;
|
|
229
|
+
/** Cohort courses on one recurring Google Calendar event. Chapter mode only. */
|
|
230
|
+
courses?: boolean;
|
|
231
|
+
/** Personal invite links, Referred by, and optional credit. Chapter mode only. */
|
|
232
|
+
referrals?: ChapterMemberReferrals;
|
|
233
|
+
}
|
|
234
|
+
/** Resolved invite-link settings. */
|
|
235
|
+
interface ResolvedMemberReferrals {
|
|
236
|
+
path: string;
|
|
237
|
+
membershipUrl: string;
|
|
238
|
+
cookieDays: number;
|
|
239
|
+
creditCents: number | null;
|
|
240
|
+
}
|
|
241
|
+
/** Resolved member portal settings. */
|
|
242
|
+
interface ResolvedMemberPortal {
|
|
243
|
+
profilePrompts: readonly string[];
|
|
244
|
+
maxPrompts: number;
|
|
245
|
+
photos: boolean;
|
|
246
|
+
courses: boolean;
|
|
247
|
+
referrals: ResolvedMemberReferrals | null;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
/** Optional private Builder Library shared across Built Not Found and Chapters. */
|
|
251
|
+
interface ChapterBuilderLibrary {
|
|
252
|
+
/** Keep local admin authoring enabled. Defaults to true. */
|
|
253
|
+
localAuthoring?: boolean;
|
|
254
|
+
/** Existing Chapter-network peers this app accepts. Hub sources must be
|
|
255
|
+
* configured network targets; a Chapter source must be a configured reader. */
|
|
256
|
+
sources?: readonly {
|
|
257
|
+
sourceId: string;
|
|
258
|
+
}[];
|
|
259
|
+
}
|
|
260
|
+
interface ResolvedBuilderLibrarySource {
|
|
261
|
+
sourceId: string;
|
|
262
|
+
}
|
|
263
|
+
/** Validated Builder Library settings carried on a resolved Chapter. */
|
|
264
|
+
interface ResolvedBuilderLibrary {
|
|
265
|
+
enabled: true;
|
|
266
|
+
localAuthoring: boolean;
|
|
267
|
+
sources: readonly ResolvedBuilderLibrarySource[];
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
/** Opt-in network-scoped Person registry. Shadow mode never gates Chapter work. */
|
|
271
|
+
interface ChapterNetworkPersonRegistry {
|
|
272
|
+
mode: "shadow";
|
|
273
|
+
/** Chapter ids that may be attached to imported source rows. Defaults to the
|
|
274
|
+
* hub's configured network target ids. */
|
|
275
|
+
chapterIds?: readonly string[];
|
|
276
|
+
}
|
|
277
|
+
/** Validated network Person registry settings carried by a resolved Chapter. */
|
|
278
|
+
interface ResolvedNetworkPersonRegistry {
|
|
279
|
+
mode: "shadow";
|
|
280
|
+
chapterIds: readonly string[];
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
/** One exact Stripe event partition accepted by the network commercial shadow. */
|
|
284
|
+
interface NetworkCommercialSource {
|
|
285
|
+
appId: string;
|
|
286
|
+
chapterId: string;
|
|
287
|
+
environment: string;
|
|
288
|
+
dataEnvironment: string;
|
|
289
|
+
runtime: string;
|
|
290
|
+
stripeMode: "test" | "live";
|
|
291
|
+
}
|
|
292
|
+
/** Opt-in, read-only projection of a shared Stripe account. */
|
|
293
|
+
interface ChapterNetworkCommercialReadModel {
|
|
294
|
+
mode: "shadow";
|
|
295
|
+
sources: readonly NetworkCommercialSource[];
|
|
296
|
+
}
|
|
297
|
+
/** Validated settings carried by a resolved hub. */
|
|
298
|
+
interface ResolvedNetworkCommercialReadModel {
|
|
299
|
+
mode: "shadow";
|
|
300
|
+
sources: readonly NetworkCommercialSource[];
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
/** Scalar kinds accepted by the Chapter schema adapter. */
|
|
304
|
+
type AttrType = "string" | "number" | "boolean" | "json";
|
|
305
|
+
/** One odla-db attribute: its type and index/uniqueness/optionality flags. */
|
|
306
|
+
interface Attr {
|
|
307
|
+
type: AttrType;
|
|
308
|
+
unique: boolean;
|
|
309
|
+
indexed: boolean;
|
|
310
|
+
optional: boolean;
|
|
311
|
+
}
|
|
312
|
+
/** One odla-db namespace: its attribute map. */
|
|
313
|
+
interface Entity {
|
|
314
|
+
attrs: Record<string, Attr>;
|
|
315
|
+
}
|
|
316
|
+
/** A serialized odla-db schema fragment (namespaces + links). */
|
|
317
|
+
interface DbSchema {
|
|
318
|
+
entities: Record<string, Entity>;
|
|
319
|
+
links: Record<string, unknown>;
|
|
320
|
+
}
|
|
321
|
+
/** Per-namespace CEL rule strings (deny-all is `"false"` for each action). */
|
|
322
|
+
interface Rule {
|
|
323
|
+
view: string;
|
|
324
|
+
create: string;
|
|
325
|
+
update: string;
|
|
326
|
+
delete: string;
|
|
327
|
+
}
|
|
328
|
+
/** Namespace → rule set. */
|
|
329
|
+
type DbRules = Record<string, Rule>;
|
|
330
|
+
|
|
331
|
+
/** One follower site a leader can push CRM records to. The secret VALUE never
|
|
332
|
+
* lives in config: `secretName` names the leader tenant's vaulted copy of the
|
|
333
|
+
* follower's `network_share_secret`. Both slots are declared by
|
|
334
|
+
* `createChapterIntegration` and filled together by
|
|
335
|
+
* `odla-ai chapter edge pair --leader <leader> --follower <id>`; until then
|
|
336
|
+
* every leader-side surface reports the edge as `edge_unpaired`. */
|
|
337
|
+
interface ChapterNetworkTarget {
|
|
338
|
+
/** Stable lowercase slug used by the UI, audit tags, and default secret name. */
|
|
339
|
+
id: string;
|
|
340
|
+
/** Human-facing site name. Defaults to `id`. */
|
|
341
|
+
name?: string;
|
|
342
|
+
/** Absolute follower origin, e.g. `https://chapter.example.com`. */
|
|
343
|
+
url: string;
|
|
344
|
+
/** Optional Cloudflare service-binding name for Worker-to-Worker delivery.
|
|
345
|
+
* The URL still defines the signed request origin; the binding only supplies
|
|
346
|
+
* the transport when a `workers.dev` subrequest cannot enter another Worker. */
|
|
347
|
+
binding?: string;
|
|
348
|
+
/** Leader-vault key holding this follower's share secret. Defaults to
|
|
349
|
+
* `network_share_<id-with-underscores>`. */
|
|
350
|
+
secretName?: string;
|
|
351
|
+
/** Per-record-type field allowlist. If present, omitted types cannot be sent
|
|
352
|
+
* to this target. Without it, package-safe person/company defaults apply. */
|
|
353
|
+
fields?: Record<string, readonly string[]>;
|
|
354
|
+
/** Record types on which leader admins may deliberately share notes with
|
|
355
|
+
* this follower. Omitted by default: a target never gains write permission
|
|
356
|
+
* merely because its records are browsable. */
|
|
357
|
+
sharedNotes?: readonly string[];
|
|
358
|
+
/** Per-record-type upper bound on profile fields this leader permits its
|
|
359
|
+
* admins to edit remotely. The follower must independently delegate the
|
|
360
|
+
* same field; readable fields never become writable implicitly. */
|
|
361
|
+
editableFields?: Record<string, readonly string[]>;
|
|
362
|
+
/** Record types on which this leader permits remote lifecycle transitions.
|
|
363
|
+
* The follower must independently opt in and remains pipeline authority. */
|
|
364
|
+
stageTransitions?: readonly string[];
|
|
365
|
+
/** Allow package-owned desired admission decisions for existing leader CRM
|
|
366
|
+
* people to be delivered to this target. Omitted means default deny. */
|
|
367
|
+
admissionGrants?: boolean;
|
|
368
|
+
/** Allow this leader to request a PII-free commercial parity snapshot from
|
|
369
|
+
* this follower. The follower must independently enable the same reader;
|
|
370
|
+
* omitted means no application or Stripe identifier digest crosses. */
|
|
371
|
+
commercialParity?: boolean;
|
|
372
|
+
}
|
|
373
|
+
/** One signed peer allowed to browse a privacy-bounded projection of this
|
|
374
|
+
* site's CRM. The follower owns this allowlist; a leader target declaration
|
|
375
|
+
* cannot widen it. */
|
|
376
|
+
interface ChapterNetworkReader {
|
|
377
|
+
/** Stable app/site slug expected in the signed federation envelope. */
|
|
378
|
+
id: string;
|
|
379
|
+
/** Per-record-type field allowlist. Omitted types and fields never leave
|
|
380
|
+
* this site. Record id, name, stage, and timestamps are bounded metadata. */
|
|
381
|
+
fields?: Record<string, readonly string[]>;
|
|
382
|
+
/** Record types on which this reader may append an explicitly shared note.
|
|
383
|
+
* Omitted by default: browse permission never silently grants write access.
|
|
384
|
+
* Shared notes land in the follower CRM activity feed. */
|
|
385
|
+
sharedNotes?: readonly string[];
|
|
386
|
+
/** Exact local CRM fields this reader may edit remotely, by record type.
|
|
387
|
+
* Every field must also be readable so the peer can perform an optimistic
|
|
388
|
+
* edit against the value and revision it actually reviewed. */
|
|
389
|
+
editableFields?: Record<string, readonly string[]>;
|
|
390
|
+
/** Local CRM record types on which this reader may request a lifecycle
|
|
391
|
+
* transition. Chapter still enforces the local pipeline and gates. */
|
|
392
|
+
stageTransitions?: readonly string[];
|
|
393
|
+
/** Accept signed desired admission decisions from this reader. Independent
|
|
394
|
+
* of record browsing; an admission-only reader may omit `fields`. */
|
|
395
|
+
admissionGrants?: boolean;
|
|
396
|
+
/** Disclose a bounded, PII-free commercial parity snapshot to this signed
|
|
397
|
+
* reader. Identifiers leave only as edge-keyed, domain-separated HMACs. */
|
|
398
|
+
commercialParity?: boolean;
|
|
399
|
+
}
|
|
400
|
+
/** Leader/follower network configuration. A follower needs no code config to
|
|
401
|
+
* receive pushed records. It must explicitly declare `readers` before any peer
|
|
402
|
+
* can browse its CRM. */
|
|
403
|
+
interface ChapterNetwork {
|
|
404
|
+
/** Require the protected super-admin tier for remote mutations and operational
|
|
405
|
+
* detail. Ordinary admins retain routine name/email directory reads. */
|
|
406
|
+
requireSuperAdminWrites?: boolean;
|
|
407
|
+
targets?: readonly ChapterNetworkTarget[];
|
|
408
|
+
readers?: readonly ChapterNetworkReader[];
|
|
409
|
+
}
|
|
410
|
+
/** Public chapter-formation intake backed by one CRM record type. The host owns
|
|
411
|
+
* the branded form; Chapter owns the bounded, idempotent write contract. */
|
|
412
|
+
interface LeaderFormationConfig {
|
|
413
|
+
/** CRM type receiving submissions. Default `"chapter_application"`. */
|
|
414
|
+
type?: string;
|
|
415
|
+
/** Public fields that must be non-empty strings. */
|
|
416
|
+
required?: readonly string[];
|
|
417
|
+
/** Additional public fields accepted by the route. */
|
|
418
|
+
optional?: readonly string[];
|
|
419
|
+
maxLen?: Record<string, number>;
|
|
420
|
+
/** Per-string cap when `maxLen` has no field override. Default 4000. */
|
|
421
|
+
defaultMaxLen?: number;
|
|
422
|
+
/** Maximum JSON request size in UTF-8 bytes, enforced while streaming. Default 32768. */
|
|
423
|
+
bodyCap?: number;
|
|
424
|
+
}
|
|
425
|
+
/** Fully resolved formation intake policy. */
|
|
426
|
+
interface ResolvedLeaderFormation {
|
|
427
|
+
enabled: boolean;
|
|
428
|
+
type: string;
|
|
429
|
+
required: readonly string[];
|
|
430
|
+
optional: readonly string[];
|
|
431
|
+
maxLen: Record<string, number>;
|
|
432
|
+
defaultMaxLen: number;
|
|
433
|
+
bodyCap: number;
|
|
434
|
+
}
|
|
435
|
+
/** A validated network target carried on the resolved chapter engine. */
|
|
436
|
+
interface ResolvedNetworkTarget {
|
|
437
|
+
id: string;
|
|
438
|
+
name: string;
|
|
439
|
+
url: string;
|
|
440
|
+
binding?: string;
|
|
441
|
+
secretName: string;
|
|
442
|
+
fields?: Record<string, readonly string[]>;
|
|
443
|
+
sharedNotes: readonly string[];
|
|
444
|
+
editableFields: Record<string, readonly string[]>;
|
|
445
|
+
stageTransitions: readonly string[];
|
|
446
|
+
admissionGrants?: boolean;
|
|
447
|
+
commercialParity?: boolean;
|
|
448
|
+
}
|
|
449
|
+
/** A validated follower-owned record projection for one signed reader. */
|
|
450
|
+
interface ResolvedNetworkReader {
|
|
451
|
+
id: string;
|
|
452
|
+
fields: Record<string, readonly string[]>;
|
|
453
|
+
sharedNotes: readonly string[];
|
|
454
|
+
editableFields: Record<string, readonly string[]>;
|
|
455
|
+
stageTransitions: readonly string[];
|
|
456
|
+
admissionGrants?: boolean;
|
|
457
|
+
commercialParity?: boolean;
|
|
458
|
+
}
|
|
459
|
+
/** Fully-resolved leader network configuration. */
|
|
460
|
+
interface ResolvedNetwork {
|
|
461
|
+
requireSuperAdminWrites?: boolean;
|
|
462
|
+
targets: readonly ResolvedNetworkTarget[];
|
|
463
|
+
readers: readonly ResolvedNetworkReader[];
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
/**
|
|
467
|
+
* Membership pricing for the group row (chapter mode).
|
|
468
|
+
*
|
|
469
|
+
* This is the single-price model that predates tiers. It still seeds the group
|
|
470
|
+
* row and remains the fallback a chapter reads when it declares no tiers, so
|
|
471
|
+
* existing chapters keep working; new work should prefer `tiers`.
|
|
472
|
+
*/
|
|
473
|
+
interface ChapterPrices {
|
|
474
|
+
standardCents: number;
|
|
475
|
+
foundingDiscountCents?: number;
|
|
476
|
+
/** ISO currency, default "usd". */
|
|
477
|
+
currency?: string;
|
|
478
|
+
/** Billing interval, default "year". */
|
|
479
|
+
interval?: "year" | "month";
|
|
480
|
+
}
|
|
481
|
+
/** Membership policy + compliance copy stored on the group row. */
|
|
482
|
+
interface ChapterPolicy {
|
|
483
|
+
disclaimerText?: string;
|
|
484
|
+
refundPolicyText?: string;
|
|
485
|
+
/** Plain-language merchant identity shown immediately before payment consent. */
|
|
486
|
+
merchantDisclosureText?: string;
|
|
487
|
+
trustCopy?: string;
|
|
488
|
+
commitmentText?: string;
|
|
489
|
+
normsText?: string;
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
/** Which feature profile a site runs. `chapter` is the full public member site
|
|
493
|
+
* (join, Stripe membership, booking, member area, admin, CRM); `hub` is
|
|
494
|
+
* admin-only and CRM-focused (a directory/registry over the same CRM). */
|
|
495
|
+
type ChapterMode = "chapter" | "hub";
|
|
496
|
+
/** One owner-editable transactional email template. Automatic sends require explicit `enabled: true`. */
|
|
497
|
+
interface EmailTemplate {
|
|
498
|
+
subject: string;
|
|
499
|
+
text: string;
|
|
500
|
+
enabled?: boolean;
|
|
501
|
+
}
|
|
502
|
+
/** Notification/reply/debug addresses + operational email templates. */
|
|
503
|
+
interface ChapterEmails {
|
|
504
|
+
notificationEmail: string;
|
|
505
|
+
replyTo?: string;
|
|
506
|
+
/** Display name on outbound lifecycle mail (`Tori Horton <from@…>`); the
|
|
507
|
+
* address itself stays the Worker's `EMAIL_FROM`. Absent ⇒ bare address. */
|
|
508
|
+
fromName?: string;
|
|
509
|
+
debugEmail?: string;
|
|
510
|
+
/** Operational lifecycle templates: adminNotification / submitConfirmation /
|
|
511
|
+
* paymentConfirmation / prepEmail / onboardingInvite / namedSeatInvitation / seatAccepted,
|
|
512
|
+
* plus declared host events. Missing enabled flags block sending. */
|
|
513
|
+
templates?: Record<string, EmailTemplate>;
|
|
514
|
+
/** Additional host-owned email events shown and validated in the email editor. Declaring an event never sends it. */
|
|
515
|
+
events?: Record<string, ChapterEmailEvent>;
|
|
516
|
+
}
|
|
517
|
+
/** Booking rules for the intro-call scheduler (stored as schedulingJson). */
|
|
518
|
+
interface ChapterScheduling {
|
|
519
|
+
slotMinutes?: number;
|
|
520
|
+
days?: readonly number[];
|
|
521
|
+
startHour?: number;
|
|
522
|
+
endHour?: number;
|
|
523
|
+
timezone?: string;
|
|
524
|
+
minNoticeHours?: number;
|
|
525
|
+
windowDays?: number;
|
|
526
|
+
summaryTemplate?: string;
|
|
527
|
+
}
|
|
528
|
+
/** How a signed-in user's role is determined.
|
|
529
|
+
*
|
|
530
|
+
* - `"clerk"` reads backend-only private Clerk roles for each authorization
|
|
531
|
+
* check, without trusting public metadata or stale session roles.
|
|
532
|
+
* - `"claim"` reads a role ladder from a JWT claim (the legacy member-site model:
|
|
533
|
+
* `provisional → member → admin`). Admin is the last (highest) rung; the
|
|
534
|
+
* Studio-managed super-admin tier sits above it.
|
|
535
|
+
* - `"table"` gates on the deny-all `admins` allowlist (the hub model):
|
|
536
|
+
* a binary "email is an admin or isn't", set only in odla Studio.
|
|
537
|
+
*
|
|
538
|
+
* Defaults: `"claim"` in `chapter` mode, `"table"` in `hub` mode. */
|
|
539
|
+
interface ChapterAuth {
|
|
540
|
+
/** `clerk` reads backend-only private_metadata.role on each request, ignoring
|
|
541
|
+
* JWT/public roles. Select explicitly after the coordinated role migration. */
|
|
542
|
+
source?: "claim" | "table" | "clerk";
|
|
543
|
+
/** JWT claim holding the role, for `source: "claim"`. Default `"role"`. */
|
|
544
|
+
claim?: string;
|
|
545
|
+
/** Role ladder low→high, for claim or private Clerk roles. The last entry is admin.
|
|
546
|
+
* Default `["provisional", "member", "admin"]`. */
|
|
547
|
+
ladder?: readonly string[];
|
|
548
|
+
/** Enable the Studio-managed super-admin tier — the only tier that may
|
|
549
|
+
* create/modify admins. Default: `true` for `"claim"`/`"clerk"`, `false` for `"table"`. */
|
|
550
|
+
superAdmins?: boolean;
|
|
551
|
+
/** Authority behind the super-admin tier. `"table"` preserves the legacy
|
|
552
|
+
* app allowlist; `"odla"` reads protected `$users.role` by exact subject ID.
|
|
553
|
+
* Default `"odla"` for private Clerk mode, `"table"` for legacy modes. */
|
|
554
|
+
superAdminSource?: "table" | "odla";
|
|
555
|
+
/** Require a second sign-in factor on admin routes: `off` (default), `report` (allow and log), `enforce`. */
|
|
556
|
+
adminSecondFactor?: "off" | "report" | "enforce";
|
|
557
|
+
}
|
|
558
|
+
/** The fully-resolved auth policy (defaults applied) carried on the {@link Chapter}. */
|
|
559
|
+
interface ResolvedAuth {
|
|
560
|
+
source: "claim" | "table" | "clerk";
|
|
561
|
+
claim: string;
|
|
562
|
+
ladder: readonly string[];
|
|
563
|
+
/** The highest ladder rung (last entry) — the "admin" gate. */
|
|
564
|
+
adminRole: string;
|
|
565
|
+
superAdmins: boolean;
|
|
566
|
+
superAdminSource: "none" | "table" | "odla";
|
|
567
|
+
adminSecondFactor: "off" | "report" | "enforce";
|
|
568
|
+
}
|
|
569
|
+
/** The application status pipeline. Which statuses exist, and the subsets a site
|
|
570
|
+
* allows a call to be booked from / an application approved from. Defaults to
|
|
571
|
+
* the reference pipeline. Status never moves backwards (package-enforced). */
|
|
572
|
+
interface ChapterPipeline {
|
|
573
|
+
stages?: readonly string[];
|
|
574
|
+
bookableFrom?: readonly string[];
|
|
575
|
+
approvableFrom?: readonly string[];
|
|
576
|
+
/** The status a new application starts at. Default: the first stage. */
|
|
577
|
+
initial?: string;
|
|
578
|
+
}
|
|
579
|
+
/** The fully-resolved pipeline (defaults applied) carried on the {@link Chapter}. */
|
|
580
|
+
interface ResolvedPipeline {
|
|
581
|
+
stages: readonly string[];
|
|
582
|
+
bookableFrom: readonly string[];
|
|
583
|
+
approvableFrom: readonly string[];
|
|
584
|
+
initial: string;
|
|
585
|
+
}
|
|
586
|
+
/** The application (join form) validation surface — which string fields are
|
|
587
|
+
* required vs accepted, their max lengths, and the request body cap. Drives
|
|
588
|
+
* submit validation, the generated `applications` schema, and the CRM slot
|
|
589
|
+
* projection; defaults to the reference form. Built-in fields retain their
|
|
590
|
+
* declared schema types/indexes, and site-defined fields become string attrs. */
|
|
591
|
+
interface ChapterApplication {
|
|
592
|
+
required?: readonly string[];
|
|
593
|
+
optional?: readonly string[];
|
|
594
|
+
/** Per-field CEL conditions — show a field, or demand it, based on the
|
|
595
|
+
* answers so far. Enforced server-side as well as rendered, so a hidden
|
|
596
|
+
* field is never demanded and a conditionally-required one cannot be
|
|
597
|
+
* skipped by posting directly. Validated when the chapter is defined. */
|
|
598
|
+
conditions?: Record<string, FieldConditions>;
|
|
599
|
+
/** Per-field character cap. Fields not listed use `defaultMaxLen`. */
|
|
600
|
+
maxLen?: Record<string, number>;
|
|
601
|
+
defaultMaxLen?: number;
|
|
602
|
+
/** Max JSON request body in UTF-8 bytes, enforced while streaming. Default 32768. */
|
|
603
|
+
bodyCap?: number;
|
|
604
|
+
/** Reject a submit that carries no truthy `disclaimerAck` (400), instead of
|
|
605
|
+
* writing a row with no consent record. Default `true`. Set `false`
|
|
606
|
+
* deliberately only when the site renders no consent control. */
|
|
607
|
+
requireDisclaimerAck?: boolean;
|
|
608
|
+
/** Allowlist of application fields mirrored into the Clerk account's
|
|
609
|
+
* backend-only `private_metadata.profile`. Default `[]`. Use this for small
|
|
610
|
+
* account/admin signals; the application row and CRM remain canonical. */
|
|
611
|
+
profileFields?: readonly string[];
|
|
612
|
+
/** Extra application fields carried into the one-way CRM projection, on top of
|
|
613
|
+
* the built-in identity/contact set. Each MUST be declared on your crm person
|
|
614
|
+
* type or the enrichment is dropped (the base person still projects). Default
|
|
615
|
+
* none. */
|
|
616
|
+
crmFields?: readonly string[];
|
|
617
|
+
/** Cap on the element count of array-valued fields (e.g. `focus`), so a client
|
|
618
|
+
* cannot post a 10k-element array into a row or into Clerk metadata.
|
|
619
|
+
* Non-primitive elements are dropped. Default 100. */
|
|
620
|
+
maxArrayLen?: number;
|
|
621
|
+
/** Validate that a field literally named `email` looks like an email address,
|
|
622
|
+
* returning a 400 rather than accepting input the downstream Clerk create will
|
|
623
|
+
* reject anyway. Default `true`; a valid application is never newly rejected. */
|
|
624
|
+
validateEmail?: boolean;
|
|
625
|
+
}
|
|
626
|
+
/** The fully-resolved application config carried on the {@link Chapter}. */
|
|
627
|
+
interface ResolvedApplication {
|
|
628
|
+
required: readonly string[];
|
|
629
|
+
optional: readonly string[];
|
|
630
|
+
maxLen: Record<string, number>;
|
|
631
|
+
defaultMaxLen: number;
|
|
632
|
+
bodyCap: number;
|
|
633
|
+
requireDisclaimerAck: boolean;
|
|
634
|
+
/** Resolved Clerk-metadata allowlist. Empty means no profile projection. */
|
|
635
|
+
profileFields: readonly string[];
|
|
636
|
+
crmFields: readonly string[];
|
|
637
|
+
maxArrayLen: number;
|
|
638
|
+
validateEmail: boolean;
|
|
639
|
+
/** Resolved per-field conditions. Empty means every field is unconditional. */
|
|
640
|
+
conditions: Record<string, FieldConditions>;
|
|
641
|
+
}
|
|
642
|
+
/** The `defineChapter()` config a site fills in. */
|
|
643
|
+
interface ChapterConfig {
|
|
644
|
+
/** Slug: app id, tenant, group id, worker name. `[a-z0-9-]`. */
|
|
645
|
+
id: string;
|
|
646
|
+
name: string;
|
|
647
|
+
url?: string;
|
|
648
|
+
/** Default `"chapter"`. */
|
|
649
|
+
mode?: ChapterMode;
|
|
650
|
+
/** A `defineCrm()` config or a resolved `Crm`. Omit for the per-mode default. */
|
|
651
|
+
crm?: CrmConfig | Crm;
|
|
652
|
+
brand?: ChapterBrand;
|
|
653
|
+
/** User-visible language for packaged Chapter surfaces. Recursively partial;
|
|
654
|
+
* `defineChapter()` resolves every omitted leaf from the package defaults. */
|
|
655
|
+
copy?: ChapterCopyInput;
|
|
656
|
+
/** Signed network edges: leader delivery targets and follower-owned,
|
|
657
|
+
* privacy-bounded record readers. Secret values stay in tenant vaults. */
|
|
658
|
+
network?: ChapterNetwork;
|
|
659
|
+
/** A separate signed edge that may control this chapter's complete signup. */
|
|
660
|
+
signupControl?: ChapterSignupControl;
|
|
661
|
+
/** Authenticated parent + chapter member pages with server-enforced tier access. */
|
|
662
|
+
memberContent?: ChapterMemberContent;
|
|
663
|
+
/** Signed-in member portal: profile, photos, courses, invite links and credit. */
|
|
664
|
+
memberPortal?: ChapterMemberPortal;
|
|
665
|
+
/** Private docs, Brand asset references, and links for authorized site-building agents. */
|
|
666
|
+
builderLibrary?: ChapterBuilderLibrary;
|
|
667
|
+
/** Network-scoped canonical Person registry. Shadow mode is additive only:
|
|
668
|
+
* Chapter workflows and access continue to read their existing authority. */
|
|
669
|
+
networkPersonRegistry?: ChapterNetworkPersonRegistry;
|
|
670
|
+
/** Read-only, rebuildable commercial projection from a shared Stripe account. */
|
|
671
|
+
networkCommercialReadModel?: ChapterNetworkCommercialReadModel;
|
|
672
|
+
/** Enable a public, host-rendered formation application workflow. */
|
|
673
|
+
formation?: LeaderFormationConfig;
|
|
674
|
+
thesis?: unknown;
|
|
675
|
+
/** Required in `chapter` mode, and the fallback when `tiers` is empty. */
|
|
676
|
+
prices?: ChapterPrices;
|
|
677
|
+
/** Membership tiers. A tier priced at 0 is free and never touches Stripe. */
|
|
678
|
+
tiers?: ChapterTierConfig[];
|
|
679
|
+
policy?: ChapterPolicy;
|
|
680
|
+
/** `notificationEmail` required in `chapter` mode. */
|
|
681
|
+
emails?: ChapterEmails;
|
|
682
|
+
scheduling?: ChapterScheduling;
|
|
683
|
+
/** Application status pipeline (stages + bookable/approvable subsets). */
|
|
684
|
+
pipeline?: ChapterPipeline;
|
|
685
|
+
/** Join-form validation (required/optional fields, max lengths, body cap). */
|
|
686
|
+
application?: ChapterApplication;
|
|
687
|
+
/** Role source + ladder + super-admin tier. Defaults by mode (see {@link ChapterAuth}). */
|
|
688
|
+
auth?: ChapterAuth;
|
|
689
|
+
/** odla services (db implied). Default `["db","calendar","o11y"]`. */
|
|
690
|
+
services?: readonly string[];
|
|
691
|
+
/** Apply-time account provisioning. Required in `chapter` mode so a site must
|
|
692
|
+
* make the decision explicitly. `"none"` provisions nothing, `"create"` makes
|
|
693
|
+
* the Clerk account server-side, and `"invite"` emails a real invitation.
|
|
694
|
+
* Both side-effecting models need a `clerk_secret_key` vault secret. */
|
|
695
|
+
account?: AccountModel;
|
|
696
|
+
/** WHEN lifecycle email fires. Addressing and content live on the group row
|
|
697
|
+
* (owner-editable at runtime); this is the trigger, which is a build-time
|
|
698
|
+
* decision. See {@link ChapterSends}. */
|
|
699
|
+
sends?: ChapterSends;
|
|
700
|
+
/** Site policy for admin operations (approve side effects, refund rules).
|
|
701
|
+
* See {@link ChapterOperations}. */
|
|
702
|
+
operations?: ChapterOperations;
|
|
703
|
+
}
|
|
704
|
+
/** Apply-time Clerk account provisioning model. */
|
|
705
|
+
type AccountModel = "invite" | "create" | "none";
|
|
706
|
+
/** Site policy for admin OPERATIONS: the DECISIONS, where the mechanics stay
|
|
707
|
+
* package-owned. Same model as {@link ChapterSends} — a site declares the rule,
|
|
708
|
+
* chapter enforces it. Distinct from {@link ChapterPolicy}, which is member-facing
|
|
709
|
+
* copy. (The privilege-escalation rules are NOT here: they are package-enforced
|
|
710
|
+
* in `canChangeRole`, gated on `auth.superAdmins`, so a site cannot weaken them.) */
|
|
711
|
+
interface ChapterOperations {
|
|
712
|
+
/** What approving an application does. */
|
|
713
|
+
onApprove?: {
|
|
714
|
+
/** Role to promote the applicant to in Clerk. Defaults to the ladder rung
|
|
715
|
+
* directly below admin (e.g. `"member"`); `false` promotes nobody. */
|
|
716
|
+
promoteTo?: string | false;
|
|
717
|
+
/** Group email template to send on approve. Default `"onboardingInvite"`;
|
|
718
|
+
* `false` sends nothing. */
|
|
719
|
+
send?: string | false;
|
|
720
|
+
};
|
|
721
|
+
/** Refund rules. */
|
|
722
|
+
refund?: {
|
|
723
|
+
/** Application statuses a refund may be issued from. Default: any status. */
|
|
724
|
+
allowedFrom?: readonly string[];
|
|
725
|
+
/** Also cancel the Stripe subscription. Default `true`. */
|
|
726
|
+
cancelSubscription?: boolean;
|
|
727
|
+
};
|
|
728
|
+
}
|
|
729
|
+
/** The fully-resolved {@link ChapterOperations} carried on the {@link Chapter}. */
|
|
730
|
+
interface ResolvedOperations {
|
|
731
|
+
onApprove: {
|
|
732
|
+
promoteTo: string | false;
|
|
733
|
+
send: string | false;
|
|
734
|
+
};
|
|
735
|
+
/** `allowedFrom: null` means "any status". */
|
|
736
|
+
refund: {
|
|
737
|
+
allowedFrom: readonly string[] | null;
|
|
738
|
+
cancelSubscription: boolean;
|
|
739
|
+
};
|
|
740
|
+
}
|
|
741
|
+
/** When the admin notification fires: on application `submit` (default), on the
|
|
742
|
+
* first successful `payment`, or `never` (the site drives it itself). */
|
|
743
|
+
type AdminNotificationTrigger = "submit" | "payment" | "never";
|
|
744
|
+
/** Send-policy config: the trigger for each lifecycle email chapter owns. */
|
|
745
|
+
interface ChapterSends {
|
|
746
|
+
adminNotification?: AdminNotificationTrigger;
|
|
747
|
+
}
|
|
748
|
+
/** Resolved send policy (every trigger present). */
|
|
749
|
+
interface ResolvedSends {
|
|
750
|
+
adminNotification: AdminNotificationTrigger;
|
|
751
|
+
}
|
|
752
|
+
/** The resolved engine `defineChapter()` returns. */
|
|
753
|
+
interface Chapter {
|
|
754
|
+
config: ChapterConfig;
|
|
755
|
+
id: string;
|
|
756
|
+
name: string;
|
|
757
|
+
url?: string;
|
|
758
|
+
mode: ChapterMode;
|
|
759
|
+
/** Resolved site identity. `wordmark` always falls back to `name`, so the
|
|
760
|
+
* admin/member UI never needs a second brand declaration. */
|
|
761
|
+
brand: ChapterBrand;
|
|
762
|
+
/** Fully resolved user-visible text for packaged Chapter surfaces. */
|
|
763
|
+
copy: ChapterCopy;
|
|
764
|
+
/** Validated follower targets for leader-driven record pushes. */
|
|
765
|
+
network: ResolvedNetwork;
|
|
766
|
+
/** Null unless this follower explicitly delegates signup authority. */
|
|
767
|
+
signupControl: ResolvedSignupControl | null;
|
|
768
|
+
/** Null unless the standard authenticated member-content surface is enabled. */
|
|
769
|
+
memberContent: ResolvedMemberContent | null;
|
|
770
|
+
/** Null unless the member portal is enabled. */
|
|
771
|
+
memberPortal: ResolvedMemberPortal | null;
|
|
772
|
+
/** Null unless the private scoped Builder Library is enabled. */
|
|
773
|
+
builderLibrary: ResolvedBuilderLibrary | null;
|
|
774
|
+
/** Null unless this hub explicitly enables the shadow Person registry. */
|
|
775
|
+
networkPersonRegistry: ResolvedNetworkPersonRegistry | null;
|
|
776
|
+
/** Null unless this hub explicitly enables the commercial shadow. */
|
|
777
|
+
networkCommercialReadModel: ResolvedNetworkCommercialReadModel | null;
|
|
778
|
+
/** Resolved public formation intake contract (disabled when omitted). */
|
|
779
|
+
formation: ResolvedLeaderFormation;
|
|
780
|
+
/** Resolved CRM engine (from `defineCrm`). */
|
|
781
|
+
crm: Crm;
|
|
782
|
+
/** Resolved auth policy (source, claim, ladder, super-admin tier). */
|
|
783
|
+
auth: ResolvedAuth;
|
|
784
|
+
/** Resolved application status pipeline (stages + bookable/approvable subsets). */
|
|
785
|
+
pipeline: ResolvedPipeline;
|
|
786
|
+
/** Resolved join-form validation config. */
|
|
787
|
+
application: ResolvedApplication;
|
|
788
|
+
/** The chapter's own odla-db namespaces (mode-dependent; excludes `crm_*`). */
|
|
789
|
+
schema: DbSchema;
|
|
790
|
+
rules: DbRules;
|
|
791
|
+
services: readonly string[];
|
|
792
|
+
/** Resolved apply-time account provisioning model. */
|
|
793
|
+
account: AccountModel;
|
|
794
|
+
/** Resolved send policy — when each lifecycle email fires. */
|
|
795
|
+
sends: ResolvedSends;
|
|
796
|
+
/** Resolved admin-operation policy (approve side effects, refund rules). */
|
|
797
|
+
operations: ResolvedOperations;
|
|
798
|
+
/** The seed `groups` row derived from config (chapter mode), else `null`. */
|
|
799
|
+
groupSeed(): Record<string, unknown> | null;
|
|
800
|
+
}
|
|
801
|
+
|
|
133
802
|
/** Configuration for the Clerk-gated, brand-scoped Chapter admin console. */
|
|
134
803
|
interface ChapterAdminProps {
|
|
135
804
|
chapter?: Chapter;
|