@odla-ai/chapter 0.36.0 → 0.38.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 +136 -2
- package/dist/{chunk-I45VYBUV.js → chunk-BIYWPPH6.js} +233 -140
- package/dist/chunk-BIYWPPH6.js.map +1 -0
- package/dist/{chunk-LWM32REV.js → chunk-SI3JHSEX.js} +211 -64
- package/dist/chunk-SI3JHSEX.js.map +1 -0
- package/dist/{copy-context-0k0-yWHL.d.ts → copy-context-Pbs3keLx.d.ts} +174 -28
- package/dist/index.cjs +807 -345
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +295 -36
- package/dist/index.d.ts +295 -36
- package/dist/index.js +807 -345
- package/dist/index.js.map +1 -1
- package/dist/ui/admin/index.d.ts +27 -3
- package/dist/ui/admin/index.js +5 -1
- package/dist/ui/index.d.ts +3 -3
- package/dist/ui/index.js +8 -2
- package/dist/ui/member/index.d.ts +63 -4
- package/dist/ui/member/index.js +3 -1
- package/dist/worker/index.cjs +1032 -354
- package/dist/worker/index.cjs.map +1 -1
- package/dist/worker/index.d.cts +151 -28
- package/dist/worker/index.d.ts +151 -28
- package/dist/worker/index.js +1025 -347
- package/dist/worker/index.js.map +1 -1
- package/package.json +1 -1
- package/runbooks/greenfield.md +27 -0
- package/dist/chunk-I45VYBUV.js.map +0 -1
- package/dist/chunk-LWM32REV.js.map +0 -1
package/README.md
CHANGED
|
@@ -404,6 +404,112 @@ Simulation is deliberately a UI/test boundary, not acceptance evidence for a
|
|
|
404
404
|
live Chapter. A follower still has to prove its real application write, Stripe
|
|
405
405
|
webhook, and calendar booking journey before production.
|
|
406
406
|
|
|
407
|
+
### Authenticated member pages
|
|
408
|
+
|
|
409
|
+
Enable the standard member catalog explicitly:
|
|
410
|
+
|
|
411
|
+
```ts
|
|
412
|
+
const chapter = defineChapter({
|
|
413
|
+
// ...normal chapter config...
|
|
414
|
+
memberContent: {
|
|
415
|
+
localAuthoring: true,
|
|
416
|
+
parent: {
|
|
417
|
+
sourceId: "built-not-found",
|
|
418
|
+
// default follower vault key: member_content_control_secret
|
|
419
|
+
},
|
|
420
|
+
},
|
|
421
|
+
});
|
|
422
|
+
```
|
|
423
|
+
|
|
424
|
+
This creates two deliberately separate authoring layers. The Parent delivers
|
|
425
|
+
immutable signed revisions; the Chapter admin edits one local revision. The
|
|
426
|
+
effective catalog merges them without permitting a Parent replay to replace a
|
|
427
|
+
Chapter page. Each page declares a stable id/slug, navigation section/order,
|
|
428
|
+
safe Markdown, and either `all-members` or an explicit set of tier ids. Tier
|
|
429
|
+
sets are not treated as an ordinal ladder.
|
|
430
|
+
|
|
431
|
+
```ts
|
|
432
|
+
const parentDocument = {
|
|
433
|
+
schemaVersion: 1,
|
|
434
|
+
revision: 4,
|
|
435
|
+
generatedAt: Date.now(),
|
|
436
|
+
source: { type: "parent", appId: "built-not-found" },
|
|
437
|
+
target: { chapterId: "example-chapter", environment: "dev" },
|
|
438
|
+
pages: [{
|
|
439
|
+
id: "steward-library",
|
|
440
|
+
slug: "steward-library",
|
|
441
|
+
title: "Steward library",
|
|
442
|
+
navigation: { section: "Network", order: 20 },
|
|
443
|
+
audience: { kind: "tiers", tierIds: ["steward"] },
|
|
444
|
+
teaserMarkdown: "Advanced material for stewards.",
|
|
445
|
+
bodyMarkdown: "The **private** operating library.",
|
|
446
|
+
// Built Not Found delegates only this offer to the Chapter admin.
|
|
447
|
+
upsell: { owner: "chapter" },
|
|
448
|
+
}],
|
|
449
|
+
};
|
|
450
|
+
```
|
|
451
|
+
|
|
452
|
+
An upsell references only a live tier id and bounded copy. Authored content
|
|
453
|
+
cannot set price, Stripe ids, checkout state, HTML, CSS, or arbitrary links.
|
|
454
|
+
The Worker attaches the currently offerable tier name and price. A Parent page
|
|
455
|
+
may own its offer (`owner: "parent"`) or declare `owner: "chapter"`; the local
|
|
456
|
+
document then uses `upsellOverrides` for that exact page. Chapter-authored
|
|
457
|
+
pages always keep Chapter offer authority.
|
|
458
|
+
|
|
459
|
+
`GET /api/member-content` requires a signed-in member. Chapter resolves the
|
|
460
|
+
member's tier from their local application and removes every unauthorized
|
|
461
|
+
`bodyMarkdown` before serializing JSON. Locked metadata may include a teaser
|
|
462
|
+
and resolved offer. Admins may preview every body through the same projection.
|
|
463
|
+
Client-side hiding is not an authorization boundary.
|
|
464
|
+
|
|
465
|
+
Render the projection through the stock member area:
|
|
466
|
+
|
|
467
|
+
```tsx
|
|
468
|
+
<MembersArea api={api} signOut={signOut} memberPages />
|
|
469
|
+
```
|
|
470
|
+
|
|
471
|
+
Or compose `MemberContentArea` directly and provide a host-owned upgrade URL or
|
|
472
|
+
callback. The package does not pretend a second application is an upgrade:
|
|
473
|
+
|
|
474
|
+
```tsx
|
|
475
|
+
<MemberContentArea
|
|
476
|
+
api={api}
|
|
477
|
+
upsellHref={(tierId) => approvedUpgradeRoute(tierId)}
|
|
478
|
+
/>
|
|
479
|
+
```
|
|
480
|
+
|
|
481
|
+
The standard Chapter admin keeps the top-level Dashboard/People/Settings
|
|
482
|
+
grammar and adds a **Member content** tab under Settings. It labels inherited
|
|
483
|
+
pages as read-only, shows source/access/offer ownership, and edits the complete
|
|
484
|
+
local JSON contract with revision guards and actionable validation errors.
|
|
485
|
+
Agents may use the same authenticated `/api/admin/member-content` GET/PUT
|
|
486
|
+
contract. Page bodies remain out of CRM; CRM continues to own people and
|
|
487
|
+
lifecycle state.
|
|
488
|
+
|
|
489
|
+
For central delivery, pair `createMemberContentEnvelope(document)` with
|
|
490
|
+
`deliverMemberContent` from `@odla-ai/chapter/worker`. The dedicated signed
|
|
491
|
+
route rejects wrong source/target/environment, stale or conflicting revisions,
|
|
492
|
+
bad digests, undeclared tiers, and any Parent revision that would invalidate
|
|
493
|
+
existing Chapter-owned content. The CRM edge and signup-control secret are
|
|
494
|
+
never reused.
|
|
495
|
+
|
|
496
|
+
Agents can run the same contract through the supported CLI. `tiers.json`
|
|
497
|
+
contains a JSON array of the live safe authority projection (`id`, `name`,
|
|
498
|
+
`priceCents`, and `active`) and deliberately excludes provider Price ids:
|
|
499
|
+
|
|
500
|
+
```bash
|
|
501
|
+
odla-ai chapter member-content validate --file parent.json --tiers tiers.json --json
|
|
502
|
+
odla-ai chapter member-content compose --parent parent.json --chapter chapter.json --tiers tiers.json --json
|
|
503
|
+
odla-ai chapter member-content project --parent parent.json --chapter chapter.json --tiers tiers.json --tier community --json
|
|
504
|
+
odla-ai chapter member-content deliver --file parent.json --tiers tiers.json \
|
|
505
|
+
--url https://chapter.example --from-env MEMBER_CONTENT_SECRET --json
|
|
506
|
+
```
|
|
507
|
+
|
|
508
|
+
Use `--stdin` instead of `--from-env` when the signing secret comes from a
|
|
509
|
+
producer process. The value is never accepted as an argument. `project` is the
|
|
510
|
+
server-disclosure proof and omits locked bodies; `compose` is a local operator
|
|
511
|
+
authoring view and contains the supplied content.
|
|
512
|
+
|
|
407
513
|
### Hub-authoritative live signup control
|
|
408
514
|
|
|
409
515
|
When a hub must control the live membership page as well as preview it, use the
|
|
@@ -448,6 +554,32 @@ join-config, application tier validation, and checkout already read. Public
|
|
|
448
554
|
application contract, and theme. Applicant requests therefore use local
|
|
449
555
|
last-known-good state and never depend on the hub being reachable.
|
|
450
556
|
|
|
557
|
+
#### One field contract for follower forms and CMS management
|
|
558
|
+
|
|
559
|
+
Chapter owns field identity and storage authority; a hub such as Built Not
|
|
560
|
+
Found owns the editor. Declare the follower's maximum application field set in
|
|
561
|
+
`defineChapter({ application: { required, optional, conditions, maxLen } })`.
|
|
562
|
+
A signed signup-control revision may select, reorder, and change requiredness
|
|
563
|
+
only within that declared set. It cannot invent a field, change its value type,
|
|
564
|
+
or expose an operational application attribute.
|
|
565
|
+
|
|
566
|
+
`chapterFlowFieldContract(chapter, "join")` and
|
|
567
|
+
`chapterFlowFieldContract(chapter, "formation")` return the same versioned,
|
|
568
|
+
ordered shape: stable field id, label, CRM value type, requiredness, order,
|
|
569
|
+
length cap, enum options, and conditions where the flow supports them.
|
|
570
|
+
`GET /api/join-config` exposes the effective `fieldContract` after active
|
|
571
|
+
signup control is applied. `GET /api/formation/config` exposes the same
|
|
572
|
+
`fieldContract` and retains its legacy `fields` array as a compatibility alias.
|
|
573
|
+
Use that document for a CMS preview or host renderer; submit to the packaged
|
|
574
|
+
route so the server enforces the same effective selection.
|
|
575
|
+
|
|
576
|
+
Email identity is literal. `normalizeChapterEmail()` trims and lowercases only;
|
|
577
|
+
it never applies Gmail/provider alias rules. Consequently
|
|
578
|
+
`foo+bar@gmail.com` and `foo@gmail.com` are distinct application, admission,
|
|
579
|
+
purchase, and gift-beneficiary identities even if a mailbox provider delivers
|
|
580
|
+
both to the same inbox. Email fields advertise `emailIdentity: "literal"` in
|
|
581
|
+
the flow contract so editors and test fixtures do not collapse them.
|
|
582
|
+
|
|
451
583
|
Materialization addresses provisioned groups and tiers by their unique public
|
|
452
584
|
`id` attributes, not by assuming those values are odla-db entity ids. A failed
|
|
453
585
|
materialization returns a bounded JSON `materialization_failed` rejection and
|
|
@@ -620,7 +752,8 @@ and relations by composing its own config before passing it to Chapter.
|
|
|
620
752
|
|
|
621
753
|
`formation: {}` opts into a host-rendered public application:
|
|
622
754
|
|
|
623
|
-
- `GET /api/formation/config` returns only configured public field metadata
|
|
755
|
+
- `GET /api/formation/config` returns only configured public field metadata in
|
|
756
|
+
the same versioned `fieldContract` shape as the join flow.
|
|
624
757
|
- `POST /api/formation/applications` accepts only those fields and creates a
|
|
625
758
|
normal `chapter_application` CRM record.
|
|
626
759
|
- A stable base64url `submissionId` makes ambiguous retries resolve to the same
|
|
@@ -1155,7 +1288,8 @@ These bite silently — a smoke test won't catch them:
|
|
|
1155
1288
|
against a permissive email shape (400 on `"notanemail"`, so it fails cleanly
|
|
1156
1289
|
here rather than at the downstream Clerk create) — set
|
|
1157
1290
|
`application: { validateEmail: false }` to opt out; `isValidEmail` is exported.
|
|
1158
|
-
A valid application is never newly rejected.
|
|
1291
|
+
A valid application is never newly rejected. Identity normalization trims
|
|
1292
|
+
and lowercases but keeps the full local part: plus-addresses remain distinct.
|
|
1159
1293
|
- **CRM enrichment.** `projectApplicant` writes the base identity/contact person.
|
|
1160
1294
|
To carry more of the application into the CRM, list `application: { crmFields:
|
|
1161
1295
|
[...] }`. Each name is **cross-checked against your crm `person` type at
|