@odla-ai/chapter 0.33.1 → 0.34.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
@@ -445,6 +445,11 @@ join-config, application tier validation, and checkout already read. Public
445
445
  application contract, and theme. Applicant requests therefore use local
446
446
  last-known-good state and never depend on the hub being reachable.
447
447
 
448
+ Materialization addresses provisioned groups and tiers by their unique public
449
+ `id` attributes, not by assuming those values are odla-db entity ids. A failed
450
+ materialization returns a bounded JSON `materialization_failed` rejection and
451
+ the transaction leaves the prior active revision unchanged.
452
+
448
453
  The first delivery is empty-history safe: Chapter checks a bounded aggregate
449
454
  count before asking odla-db to order revision history. A freshly provisioned
450
455
  follower therefore acknowledges revision 1 immediately instead of depending on
package/dist/index.cjs CHANGED
@@ -20,6 +20,7 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
20
20
  // src/index.ts
21
21
  var index_exports = {};
22
22
  __export(index_exports, {
23
+ CHAPTER_PROMOTION_ADAPTER: () => CHAPTER_PROMOTION_ADAPTER,
23
24
  DEFAULT_CHAPTER_COPY: () => DEFAULT_CHAPTER_COPY,
24
25
  DEFAULT_SHARE_FIELDS: () => DEFAULT_SHARE_FIELDS,
25
26
  MEMBERSHIP_PAGE_RENDERER: () => MEMBERSHIP_PAGE_RENDERER,
@@ -61,6 +62,7 @@ __export(index_exports, {
61
62
  defineChapter: () => defineChapter,
62
63
  emailGroupFrom: () => emailGroupFrom,
63
64
  endForSlot: () => endForSlot,
65
+ exportChapterPromotion: () => exportChapterPromotion,
64
66
  findApplicationRef: () => findApplicationRef,
65
67
  findTier: () => findTier,
66
68
  firstPaymentPatch: () => firstPaymentPatch,
@@ -3200,4 +3202,95 @@ function membershipTierPresentationFromMarkdown(presentation, source) {
3200
3202
  if (!result.ok) throw new TypeError(`Invalid membership Markdown: ${result.errors.join("; ")}`);
3201
3203
  return result.value;
3202
3204
  }
3205
+
3206
+ // src/promotion.ts
3207
+ var import_promotion = require("@odla-ai/promotion");
3208
+ var CHAPTER_PROMOTION_ADAPTER = "@odla-ai/chapter/v1";
3209
+ var exclusions = (chapterId, paidTierIds) => [
3210
+ {
3211
+ kind: "chapter.providers",
3212
+ id: chapterId,
3213
+ reason: "provider_identity",
3214
+ paths: [
3215
+ ...paidTierIds.map((id2) => `tiers[${id2}].stripePriceId`),
3216
+ "groups.stripePublishableKey",
3217
+ "applications.stripeCustomerId",
3218
+ "applications.stripeSubscriptionId",
3219
+ "meetings.googleEventId"
3220
+ ],
3221
+ detail: "Stripe, Clerk, and Google identities are resolved independently in each target environment."
3222
+ },
3223
+ {
3224
+ kind: "chapter.runtime",
3225
+ id: chapterId,
3226
+ reason: "runtime_state",
3227
+ paths: ["applications.*", "meetings.*", "emailLog.*", "crm_*.*"],
3228
+ detail: "Applicants, meetings, messages, and CRM rows are operational state, not managed signup content."
3229
+ },
3230
+ {
3231
+ kind: "chapter.access",
3232
+ id: chapterId,
3233
+ reason: "personal_data",
3234
+ paths: ["admins.*", "superAdmins.*", "networkNotes.*"],
3235
+ detail: "People, access lists, and private network notes remain local to the target environment."
3236
+ },
3237
+ {
3238
+ kind: "chapter.history",
3239
+ id: chapterId,
3240
+ reason: "authority_history",
3241
+ paths: ["signupControls[active=false]", "signupControls.*At", "signupControls.environment"],
3242
+ detail: "The source retains immutable delivery history; only selected published content is exported."
3243
+ }
3244
+ ];
3245
+ async function exportChapterPromotion(input) {
3246
+ if (!/^sha256:[0-9a-f]{64}$/.test(input.digest)) throw new TypeError("chapter promotion digest is invalid");
3247
+ const checked = validateSignupControlDocument(input.document);
3248
+ if (!checked.ok) throw new TypeError(checked.errors.join("; "));
3249
+ const document = checked.value;
3250
+ if (await signupControlDigest(document) !== input.digest) throw new TypeError("chapter promotion digest does not match its document");
3251
+ const revision = `chapter:${document.target.chapterId}:${document.revision}`;
3252
+ const promotionProvenance = {
3253
+ adapter: CHAPTER_PROMOTION_ADAPTER,
3254
+ sourceIds: [`signup-control:${document.revision}`, document.source.appId, document.target.chapterId].sort(),
3255
+ receiptDigests: [input.digest]
3256
+ };
3257
+ const artifact = (kind, id2, content) => ({
3258
+ kind,
3259
+ id: id2,
3260
+ sourceRevision: revision,
3261
+ provenance: promotionProvenance,
3262
+ content: (0, import_promotion.canonicalPromotionValue)(content)
3263
+ });
3264
+ const chapterId = document.target.chapterId;
3265
+ const artifacts = [
3266
+ artifact("chapter.catalog", chapterId, {
3267
+ chapterId,
3268
+ tiers: document.tiers.map(({ stripePriceId: _providerId, ...tier }) => tier)
3269
+ }),
3270
+ artifact("chapter.membership-page", chapterId, document.membershipPage),
3271
+ ...document.application ? [artifact("chapter.application", chapterId, document.application)] : [],
3272
+ ...document.policy ? [artifact("chapter.policy", chapterId, document.policy)] : [],
3273
+ ...document.scheduling ? [artifact("chapter.scheduling", chapterId, document.scheduling)] : [],
3274
+ ...document.theme ? [artifact("chapter.theme", chapterId, document.theme)] : []
3275
+ ];
3276
+ return {
3277
+ adapter: CHAPTER_PROMOTION_ADAPTER,
3278
+ artifacts,
3279
+ exclusions: exclusions(chapterId, document.tiers.filter((tier) => tier.stripePriceId).map((tier) => tier.id)),
3280
+ preconditions: [
3281
+ {
3282
+ code: "chapter.signup-control.schema",
3283
+ path: "services.chapter.signupControl.schemaVersion",
3284
+ expected: document.schemaVersion,
3285
+ detail: "The target must support this Chapter signup-control document version."
3286
+ },
3287
+ {
3288
+ code: "chapter.identity",
3289
+ path: "app.appId",
3290
+ expected: chapterId,
3291
+ detail: "Chapter content can only target the chapter named by the signed source revision."
3292
+ }
3293
+ ]
3294
+ };
3295
+ }
3203
3296
  //# sourceMappingURL=index.cjs.map