@odla-ai/chapter 0.10.1 → 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,6 +632,9 @@ 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;
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;
628
638
  /**
629
639
  * The applicant profile written to the Clerk account's `public_metadata.profile`:
630
640
  * every configured application field Clerk does not already carry natively, plus
@@ -633,12 +643,17 @@ declare function resolveApplication(a: ChapterApplication | undefined): Resolved
633
643
  * there is nothing to write.
634
644
  */
635
645
  declare function applicantProfile(chapter: Chapter, fields: Record<string, unknown>): Record<string, unknown> | undefined;
636
- /** A validated submission, or a 400-worthy validation error the route returns. */
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. */
637
651
  type SubmitResult = {
638
652
  ok: true;
639
653
  id: string;
640
654
  duplicate: boolean;
641
655
  status: string;
656
+ disclaimerAckAt: number | null;
642
657
  } | {
643
658
  ok: false;
644
659
  error: string;
@@ -1016,4 +1031,4 @@ type ApplicationBookingPatch = {
1016
1031
  * already there (never backward). */
1017
1032
  declare function applicationBookingUpdate(currentStatus: string, startAt: number, htmlLink?: string | null): ApplicationBookingPatch;
1018
1033
 
1019
- 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, 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,6 +632,9 @@ 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;
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;
628
638
  /**
629
639
  * The applicant profile written to the Clerk account's `public_metadata.profile`:
630
640
  * every configured application field Clerk does not already carry natively, plus
@@ -633,12 +643,17 @@ declare function resolveApplication(a: ChapterApplication | undefined): Resolved
633
643
  * there is nothing to write.
634
644
  */
635
645
  declare function applicantProfile(chapter: Chapter, fields: Record<string, unknown>): Record<string, unknown> | undefined;
636
- /** A validated submission, or a 400-worthy validation error the route returns. */
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. */
637
651
  type SubmitResult = {
638
652
  ok: true;
639
653
  id: string;
640
654
  duplicate: boolean;
641
655
  status: string;
656
+ disclaimerAckAt: number | null;
642
657
  } | {
643
658
  ok: false;
644
659
  error: string;
@@ -1016,4 +1031,4 @@ type ApplicationBookingPatch = {
1016
1031
  * already there (never backward). */
1017
1032
  declare function applicationBookingUpdate(currentStatus: string, startAt: number, htmlLink?: string | null): ApplicationBookingPatch;
1018
1033
 
1019
- 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, 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,13 @@ 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
+ }
406
410
  var IDENTITY_FIELDS = /* @__PURE__ */ new Set(["email", "firstName", "lastName"]);
407
411
  function applicantProfile(chapter, fields) {
408
412
  const profile = {};
@@ -425,6 +429,10 @@ async function submitApplication(db, chapter, fields, opts) {
425
429
  const cap = app.maxLen[f] ?? app.defaultMaxLen;
426
430
  if (typeof v === "string" && v.length > cap) return { ok: false, error: `${f} exceeds ${cap} characters` };
427
431
  }
432
+ const acked = hasDisclaimerAck(fields);
433
+ if (app.requireDisclaimerAck && !acked) {
434
+ return { ok: false, error: "disclaimerAck is required" };
435
+ }
428
436
  const id2 = opts.newId();
429
437
  const row = { id: id2, status: chapter.pipeline.initial, createdAt: opts.now };
430
438
  for (const f of [...app.required, ...app.optional]) {
@@ -432,12 +440,12 @@ async function submitApplication(db, chapter, fields, opts) {
432
440
  }
433
441
  if (fields.focus !== void 0) row.focus = fields.focus;
434
442
  if (opts.groupId) row.groupId = opts.groupId;
435
- if (fields.disclaimerAck === true || fields.disclaimerAck === "true") row.disclaimerAckAt = opts.now;
443
+ if (acked) row.disclaimerAckAt = opts.now;
436
444
  const { duplicate } = await db.transact(
437
445
  [{ t: "update", ns: "applications", id: id2, attrs: row }],
438
446
  opts.submissionId ? { mutationId: `join:${opts.submissionId}` } : void 0
439
447
  );
440
- 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 };
441
449
  }
442
450
  function joinConfig(group, paymentsReady2) {
443
451
  return {
@@ -1073,6 +1081,7 @@ export {
1073
1081
  findApplicationRef,
1074
1082
  firstPaymentPatch,
1075
1083
  getVaultSecret,
1084
+ hasDisclaimerAck,
1076
1085
  introIdempotencyKey,
1077
1086
  isAdminRole,
1078
1087
  isAlreadySent,