@odla-ai/chapter 0.10.0 → 0.11.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/dist/index.d.cts CHANGED
@@ -175,6 +175,12 @@ interface ChapterApplication {
175
175
  defaultMaxLen?: number;
176
176
  /** Max JSON request body in bytes. Default 32768. */
177
177
  bodyCap?: number;
178
+ /** Reject a submit that carries no truthy `disclaimerAck` (400), instead of
179
+ * writing a row with no consent record. Default `false` for back-compat —
180
+ * but turn it on if the disclaimer is a compliance record: a missing ack is
181
+ * otherwise silent, permanent and unreconstructible. Failure is deterministic
182
+ * and surfaces on the first test submit, not intermittently in production. */
183
+ requireDisclaimerAck?: boolean;
178
184
  }
179
185
  /** The fully-resolved application config carried on the {@link Chapter}. */
180
186
  interface ResolvedApplication {
@@ -183,6 +189,7 @@ interface ResolvedApplication {
183
189
  maxLen: Record<string, number>;
184
190
  defaultMaxLen: number;
185
191
  bodyCap: number;
192
+ requireDisclaimerAck: boolean;
186
193
  }
187
194
  /** The `defineChapter()` config a site fills in. */
188
195
  interface ChapterConfig {
@@ -625,12 +632,28 @@ declare function canceledPatch(): {
625
632
 
626
633
  /** Apply defaults + validate the application config. Throws at import on bad shape. */
627
634
  declare function resolveApplication(a: ChapterApplication | undefined): ResolvedApplication;
628
- /** A validated submission, or a 400-worthy validation error the route returns. */
635
+ /** Whether a submit body carries a genuine disclaimer acknowledgement. Accepts
636
+ * the boolean an API client sends and the string a plain HTML form posts. */
637
+ declare function hasDisclaimerAck(fields: Record<string, unknown>): boolean;
638
+ /**
639
+ * The applicant profile written to the Clerk account's `public_metadata.profile`:
640
+ * every configured application field Clerk does not already carry natively, plus
641
+ * `focus`. Derived from `application.required`/`optional`, so a site's own field
642
+ * names project without this package knowing them. Pure; returns `undefined` when
643
+ * there is nothing to write.
644
+ */
645
+ declare function applicantProfile(chapter: Chapter, fields: Record<string, unknown>): Record<string, unknown> | undefined;
646
+ /** A validated submission, or a 400-worthy validation error the route returns.
647
+ * `disclaimerAckAt` reports what THIS request recorded — a number when consent
648
+ * was stamped, `null` when none was supplied. It is always present so a missing
649
+ * consent record is visible in the response rather than silently absent from a
650
+ * row nobody reads until an audit. */
629
651
  type SubmitResult = {
630
652
  ok: true;
631
653
  id: string;
632
654
  duplicate: boolean;
633
655
  status: string;
656
+ disclaimerAckAt: number | null;
634
657
  } | {
635
658
  ok: false;
636
659
  error: string;
@@ -1008,4 +1031,4 @@ type ApplicationBookingPatch = {
1008
1031
  * already there (never backward). */
1009
1032
  declare function applicationBookingUpdate(currentStatus: string, startAt: number, htmlLink?: string | null): ApplicationBookingPatch;
1010
1033
 
1011
- export { type AccountModel, type AdminNotificationTrigger, type Applicant, type ApplicationBookingPatch, type ApplicationRecord, type ApplicationSummary, type Attr, type AttrType, type Chapter, type ChapterApplication, type ChapterAuth, type ChapterBrand, type ChapterConfig, type ChapterDb, type ChapterEmails, type ChapterIntegrationDescriptor, type ChapterIntegrationOptions, type ChapterMode, type ChapterPipeline, type ChapterPolicy, type ChapterPrices, type ChapterScheduling, type ChapterSends, type ClerkInviteInput, type ClerkResult, type ClerkUserInput, type DbOp, type DbRules, type DbSchema, type DeliveryDecision, type EmailGroup, type EmailTemplate, type EmailTemplateRow, type Entity, type ExistingMeeting, type GuardResult, type JoinConfigGroup, type LiveEvent, type MailSender, type MeetingForReconcile, type MeetingRecord, type MeetingReschedulePatch, type MemberApplication, type MemberSession, type NewMeetingRow, type NotifyDeps, type NotifyInput, type NotifyResult, type PaymentsGroup, type ProjectionDeps, type ReconcileDecision, type ResolvedApplication, type ResolvedAuth, type ResolvedPipeline, type ResolvedScheduling, type ResolvedSends, type RoleChangeContext, type Rule, SCHEDULING_DEFAULTS, type SchedulingErrors, type SecretStore, type SessionUser, type SharedPerson, type StripeEvent, type SubmitResult, type WebhookEvent, applicationBookingUpdate, applicationSummary, bookingDecision, brandTokens, buildGroupSeed, canApprove, canBook, canChangeRole, canTransition, canceledPatch, chapterDb, clerkInviteRequest, clerkUserRequest, createChapterIntegration, createClerkInvitation, createClerkUser, defaultCrm, defineChapter, emailGroupFrom, endForSlot, findApplicationRef, firstPaymentPatch, getVaultSecret, introIdempotencyKey, isAdminRole, isAlreadySent, isReconcilable, isSlotAvailable, joinConfig, meetingCreateRow, meetingRescheduleUpdate, memberApplication, memberSession, normalizeWebhookEvent, paymentsReady, planDelivery, projectApplicant, projectSharedRecord, reconcileMeetings, refundedPatch, render, renderSummary, renderTemplateBody, renewalPatch, resolveApplication, resolveAuth, resolvePipeline, resolveScheduling, roleFromClaim, sendTemplated, sharedPersonInput, slotWindow, stageIndex, stripeForm, submitApplication, subscriptionIdempotencyKey, validateScheduling, verifyStripeSignature, webhookMutationId };
1034
+ export { type AccountModel, type AdminNotificationTrigger, type Applicant, type ApplicationBookingPatch, type ApplicationRecord, type ApplicationSummary, type Attr, type AttrType, type Chapter, type ChapterApplication, type ChapterAuth, type ChapterBrand, type ChapterConfig, type ChapterDb, type ChapterEmails, type ChapterIntegrationDescriptor, type ChapterIntegrationOptions, type ChapterMode, type ChapterPipeline, type ChapterPolicy, type ChapterPrices, type ChapterScheduling, type ChapterSends, type ClerkInviteInput, type ClerkResult, type ClerkUserInput, type DbOp, type DbRules, type DbSchema, type DeliveryDecision, type EmailGroup, type EmailTemplate, type EmailTemplateRow, type Entity, type ExistingMeeting, type GuardResult, type JoinConfigGroup, type LiveEvent, type MailSender, type MeetingForReconcile, type MeetingRecord, type MeetingReschedulePatch, type MemberApplication, type MemberSession, type NewMeetingRow, type NotifyDeps, type NotifyInput, type NotifyResult, type PaymentsGroup, type ProjectionDeps, type ReconcileDecision, type ResolvedApplication, type ResolvedAuth, type ResolvedPipeline, type ResolvedScheduling, type ResolvedSends, type RoleChangeContext, type Rule, SCHEDULING_DEFAULTS, type SchedulingErrors, type SecretStore, type SessionUser, type SharedPerson, type StripeEvent, type SubmitResult, type WebhookEvent, applicantProfile, applicationBookingUpdate, applicationSummary, bookingDecision, brandTokens, buildGroupSeed, canApprove, canBook, canChangeRole, canTransition, canceledPatch, chapterDb, clerkInviteRequest, clerkUserRequest, createChapterIntegration, createClerkInvitation, createClerkUser, defaultCrm, defineChapter, emailGroupFrom, endForSlot, findApplicationRef, firstPaymentPatch, getVaultSecret, hasDisclaimerAck, introIdempotencyKey, isAdminRole, isAlreadySent, isReconcilable, isSlotAvailable, joinConfig, meetingCreateRow, meetingRescheduleUpdate, memberApplication, memberSession, normalizeWebhookEvent, paymentsReady, planDelivery, projectApplicant, projectSharedRecord, reconcileMeetings, refundedPatch, render, renderSummary, renderTemplateBody, renewalPatch, resolveApplication, resolveAuth, resolvePipeline, resolveScheduling, roleFromClaim, sendTemplated, sharedPersonInput, slotWindow, stageIndex, stripeForm, submitApplication, subscriptionIdempotencyKey, validateScheduling, verifyStripeSignature, webhookMutationId };
package/dist/index.d.ts CHANGED
@@ -175,6 +175,12 @@ interface ChapterApplication {
175
175
  defaultMaxLen?: number;
176
176
  /** Max JSON request body in bytes. Default 32768. */
177
177
  bodyCap?: number;
178
+ /** Reject a submit that carries no truthy `disclaimerAck` (400), instead of
179
+ * writing a row with no consent record. Default `false` for back-compat —
180
+ * but turn it on if the disclaimer is a compliance record: a missing ack is
181
+ * otherwise silent, permanent and unreconstructible. Failure is deterministic
182
+ * and surfaces on the first test submit, not intermittently in production. */
183
+ requireDisclaimerAck?: boolean;
178
184
  }
179
185
  /** The fully-resolved application config carried on the {@link Chapter}. */
180
186
  interface ResolvedApplication {
@@ -183,6 +189,7 @@ interface ResolvedApplication {
183
189
  maxLen: Record<string, number>;
184
190
  defaultMaxLen: number;
185
191
  bodyCap: number;
192
+ requireDisclaimerAck: boolean;
186
193
  }
187
194
  /** The `defineChapter()` config a site fills in. */
188
195
  interface ChapterConfig {
@@ -625,12 +632,28 @@ declare function canceledPatch(): {
625
632
 
626
633
  /** Apply defaults + validate the application config. Throws at import on bad shape. */
627
634
  declare function resolveApplication(a: ChapterApplication | undefined): ResolvedApplication;
628
- /** A validated submission, or a 400-worthy validation error the route returns. */
635
+ /** Whether a submit body carries a genuine disclaimer acknowledgement. Accepts
636
+ * the boolean an API client sends and the string a plain HTML form posts. */
637
+ declare function hasDisclaimerAck(fields: Record<string, unknown>): boolean;
638
+ /**
639
+ * The applicant profile written to the Clerk account's `public_metadata.profile`:
640
+ * every configured application field Clerk does not already carry natively, plus
641
+ * `focus`. Derived from `application.required`/`optional`, so a site's own field
642
+ * names project without this package knowing them. Pure; returns `undefined` when
643
+ * there is nothing to write.
644
+ */
645
+ declare function applicantProfile(chapter: Chapter, fields: Record<string, unknown>): Record<string, unknown> | undefined;
646
+ /** A validated submission, or a 400-worthy validation error the route returns.
647
+ * `disclaimerAckAt` reports what THIS request recorded — a number when consent
648
+ * was stamped, `null` when none was supplied. It is always present so a missing
649
+ * consent record is visible in the response rather than silently absent from a
650
+ * row nobody reads until an audit. */
629
651
  type SubmitResult = {
630
652
  ok: true;
631
653
  id: string;
632
654
  duplicate: boolean;
633
655
  status: string;
656
+ disclaimerAckAt: number | null;
634
657
  } | {
635
658
  ok: false;
636
659
  error: string;
@@ -1008,4 +1031,4 @@ type ApplicationBookingPatch = {
1008
1031
  * already there (never backward). */
1009
1032
  declare function applicationBookingUpdate(currentStatus: string, startAt: number, htmlLink?: string | null): ApplicationBookingPatch;
1010
1033
 
1011
- export { type AccountModel, type AdminNotificationTrigger, type Applicant, type ApplicationBookingPatch, type ApplicationRecord, type ApplicationSummary, type Attr, type AttrType, type Chapter, type ChapterApplication, type ChapterAuth, type ChapterBrand, type ChapterConfig, type ChapterDb, type ChapterEmails, type ChapterIntegrationDescriptor, type ChapterIntegrationOptions, type ChapterMode, type ChapterPipeline, type ChapterPolicy, type ChapterPrices, type ChapterScheduling, type ChapterSends, type ClerkInviteInput, type ClerkResult, type ClerkUserInput, type DbOp, type DbRules, type DbSchema, type DeliveryDecision, type EmailGroup, type EmailTemplate, type EmailTemplateRow, type Entity, type ExistingMeeting, type GuardResult, type JoinConfigGroup, type LiveEvent, type MailSender, type MeetingForReconcile, type MeetingRecord, type MeetingReschedulePatch, type MemberApplication, type MemberSession, type NewMeetingRow, type NotifyDeps, type NotifyInput, type NotifyResult, type PaymentsGroup, type ProjectionDeps, type ReconcileDecision, type ResolvedApplication, type ResolvedAuth, type ResolvedPipeline, type ResolvedScheduling, type ResolvedSends, type RoleChangeContext, type Rule, SCHEDULING_DEFAULTS, type SchedulingErrors, type SecretStore, type SessionUser, type SharedPerson, type StripeEvent, type SubmitResult, type WebhookEvent, applicationBookingUpdate, applicationSummary, bookingDecision, brandTokens, buildGroupSeed, canApprove, canBook, canChangeRole, canTransition, canceledPatch, chapterDb, clerkInviteRequest, clerkUserRequest, createChapterIntegration, createClerkInvitation, createClerkUser, defaultCrm, defineChapter, emailGroupFrom, endForSlot, findApplicationRef, firstPaymentPatch, getVaultSecret, introIdempotencyKey, isAdminRole, isAlreadySent, isReconcilable, isSlotAvailable, joinConfig, meetingCreateRow, meetingRescheduleUpdate, memberApplication, memberSession, normalizeWebhookEvent, paymentsReady, planDelivery, projectApplicant, projectSharedRecord, reconcileMeetings, refundedPatch, render, renderSummary, renderTemplateBody, renewalPatch, resolveApplication, resolveAuth, resolvePipeline, resolveScheduling, roleFromClaim, sendTemplated, sharedPersonInput, slotWindow, stageIndex, stripeForm, submitApplication, subscriptionIdempotencyKey, validateScheduling, verifyStripeSignature, webhookMutationId };
1034
+ export { type AccountModel, type AdminNotificationTrigger, type Applicant, type ApplicationBookingPatch, type ApplicationRecord, type ApplicationSummary, type Attr, type AttrType, type Chapter, type ChapterApplication, type ChapterAuth, type ChapterBrand, type ChapterConfig, type ChapterDb, type ChapterEmails, type ChapterIntegrationDescriptor, type ChapterIntegrationOptions, type ChapterMode, type ChapterPipeline, type ChapterPolicy, type ChapterPrices, type ChapterScheduling, type ChapterSends, type ClerkInviteInput, type ClerkResult, type ClerkUserInput, type DbOp, type DbRules, type DbSchema, type DeliveryDecision, type EmailGroup, type EmailTemplate, type EmailTemplateRow, type Entity, type ExistingMeeting, type GuardResult, type JoinConfigGroup, type LiveEvent, type MailSender, type MeetingForReconcile, type MeetingRecord, type MeetingReschedulePatch, type MemberApplication, type MemberSession, type NewMeetingRow, type NotifyDeps, type NotifyInput, type NotifyResult, type PaymentsGroup, type ProjectionDeps, type ReconcileDecision, type ResolvedApplication, type ResolvedAuth, type ResolvedPipeline, type ResolvedScheduling, type ResolvedSends, type RoleChangeContext, type Rule, SCHEDULING_DEFAULTS, type SchedulingErrors, type SecretStore, type SessionUser, type SharedPerson, type StripeEvent, type SubmitResult, type WebhookEvent, applicantProfile, applicationBookingUpdate, applicationSummary, bookingDecision, brandTokens, buildGroupSeed, canApprove, canBook, canChangeRole, canTransition, canceledPatch, chapterDb, clerkInviteRequest, clerkUserRequest, createChapterIntegration, createClerkInvitation, createClerkUser, defaultCrm, defineChapter, emailGroupFrom, endForSlot, findApplicationRef, firstPaymentPatch, getVaultSecret, hasDisclaimerAck, introIdempotencyKey, isAdminRole, isAlreadySent, isReconcilable, isSlotAvailable, joinConfig, meetingCreateRow, meetingRescheduleUpdate, memberApplication, memberSession, normalizeWebhookEvent, paymentsReady, planDelivery, projectApplicant, projectSharedRecord, reconcileMeetings, refundedPatch, render, renderSummary, renderTemplateBody, renewalPatch, resolveApplication, resolveAuth, resolvePipeline, resolveScheduling, roleFromClaim, sendTemplated, sharedPersonInput, slotWindow, stageIndex, stripeForm, submitApplication, subscriptionIdempotencyKey, validateScheduling, verifyStripeSignature, webhookMutationId };
package/dist/index.js CHANGED
@@ -400,9 +400,24 @@ function resolveApplication(a) {
400
400
  optional,
401
401
  maxLen: a?.maxLen ?? {},
402
402
  defaultMaxLen: a?.defaultMaxLen ?? 2e3,
403
- bodyCap: a?.bodyCap ?? 32768
403
+ bodyCap: a?.bodyCap ?? 32768,
404
+ requireDisclaimerAck: a?.requireDisclaimerAck ?? false
404
405
  };
405
406
  }
407
+ function hasDisclaimerAck(fields) {
408
+ return fields.disclaimerAck === true || fields.disclaimerAck === "true";
409
+ }
410
+ var IDENTITY_FIELDS = /* @__PURE__ */ new Set(["email", "firstName", "lastName"]);
411
+ function applicantProfile(chapter, fields) {
412
+ const profile = {};
413
+ for (const f of [...chapter.application.required, ...chapter.application.optional]) {
414
+ if (IDENTITY_FIELDS.has(f)) continue;
415
+ const v = fields[f];
416
+ if (typeof v === "string" && v.trim() !== "") profile[f] = v.trim();
417
+ }
418
+ if (fields.focus !== void 0) profile.focus = fields.focus;
419
+ return Object.keys(profile).length > 0 ? profile : void 0;
420
+ }
406
421
  async function submitApplication(db, chapter, fields, opts) {
407
422
  const app = chapter.application;
408
423
  for (const f of app.required) {
@@ -414,6 +429,10 @@ async function submitApplication(db, chapter, fields, opts) {
414
429
  const cap = app.maxLen[f] ?? app.defaultMaxLen;
415
430
  if (typeof v === "string" && v.length > cap) return { ok: false, error: `${f} exceeds ${cap} characters` };
416
431
  }
432
+ const acked = hasDisclaimerAck(fields);
433
+ if (app.requireDisclaimerAck && !acked) {
434
+ return { ok: false, error: "disclaimerAck is required" };
435
+ }
417
436
  const id2 = opts.newId();
418
437
  const row = { id: id2, status: chapter.pipeline.initial, createdAt: opts.now };
419
438
  for (const f of [...app.required, ...app.optional]) {
@@ -421,11 +440,12 @@ async function submitApplication(db, chapter, fields, opts) {
421
440
  }
422
441
  if (fields.focus !== void 0) row.focus = fields.focus;
423
442
  if (opts.groupId) row.groupId = opts.groupId;
443
+ if (acked) row.disclaimerAckAt = opts.now;
424
444
  const { duplicate } = await db.transact(
425
445
  [{ t: "update", ns: "applications", id: id2, attrs: row }],
426
446
  opts.submissionId ? { mutationId: `join:${opts.submissionId}` } : void 0
427
447
  );
428
- return { ok: true, id: id2, duplicate, status: chapter.pipeline.initial };
448
+ return { ok: true, id: id2, duplicate, status: chapter.pipeline.initial, disclaimerAckAt: acked ? opts.now : null };
429
449
  }
430
450
  function joinConfig(group, paymentsReady2) {
431
451
  return {
@@ -1037,6 +1057,7 @@ function applicationBookingUpdate(currentStatus, startAt, htmlLink) {
1037
1057
  }
1038
1058
  export {
1039
1059
  SCHEDULING_DEFAULTS,
1060
+ applicantProfile,
1040
1061
  applicationBookingUpdate,
1041
1062
  applicationSummary,
1042
1063
  bookingDecision,
@@ -1060,6 +1081,7 @@ export {
1060
1081
  findApplicationRef,
1061
1082
  firstPaymentPatch,
1062
1083
  getVaultSecret,
1084
+ hasDisclaimerAck,
1063
1085
  introIdempotencyKey,
1064
1086
  isAdminRole,
1065
1087
  isAlreadySent,