@odla-ai/chapter 0.37.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 CHANGED
@@ -554,6 +554,32 @@ join-config, application tier validation, and checkout already read. Public
554
554
  application contract, and theme. Applicant requests therefore use local
555
555
  last-known-good state and never depend on the hub being reachable.
556
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
+
557
583
  Materialization addresses provisioned groups and tiers by their unique public
558
584
  `id` attributes, not by assuming those values are odla-db entity ids. A failed
559
585
  materialization returns a bounded JSON `materialization_failed` rejection and
@@ -726,7 +752,8 @@ and relations by composing its own config before passing it to Chapter.
726
752
 
727
753
  `formation: {}` opts into a host-rendered public application:
728
754
 
729
- - `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.
730
757
  - `POST /api/formation/applications` accepts only those fields and creates a
731
758
  normal `chapter_application` CRM record.
732
759
  - A stable base64url `submissionId` makes ambiguous retries resolve to the same
@@ -1261,7 +1288,8 @@ These bite silently — a smoke test won't catch them:
1261
1288
  against a permissive email shape (400 on `"notanemail"`, so it fails cleanly
1262
1289
  here rather than at the downstream Clerk create) — set
1263
1290
  `application: { validateEmail: false }` to opt out; `isValidEmail` is exported.
1264
- 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.
1265
1293
  - **CRM enrichment.** `projectApplicant` writes the base identity/contact person.
1266
1294
  To carry more of the application into the CRM, list `application: { crmFields:
1267
1295
  [...] }`. Each name is **cross-checked against your crm `person` type at