@odla-ai/chapter 0.25.2 → 0.25.3
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 +7 -6
- package/dist/{chunk-GMGTJIY3.js → chunk-WHA3MUDQ.js} +4 -3
- package/dist/chunk-WHA3MUDQ.js.map +1 -0
- package/dist/index.cjs +12 -1
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +4 -2
- package/dist/index.d.ts +4 -2
- package/dist/index.js +12 -1
- package/dist/index.js.map +1 -1
- package/dist/ui/index.js +1 -1
- package/dist/ui/member/index.d.ts +2 -1
- package/dist/ui/member/index.js +1 -1
- package/dist/worker/index.cjs +12 -1
- package/dist/worker/index.cjs.map +1 -1
- package/dist/worker/index.js +12 -1
- package/dist/worker/index.js.map +1 -1
- package/package.json +1 -1
- package/runbooks/adopt-existing.md +3 -3
- package/runbooks/greenfield.md +6 -6
- package/dist/chunk-GMGTJIY3.js.map +0 -1
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts","../src/config.ts","../src/schema.ts","../src/defaults.ts","../src/group.ts","../src/auth.ts","../src/pipeline.ts","../src/member.ts","../src/copy-defaults-admin.ts","../src/copy-defaults.ts","../src/copy-resolve.ts","../src/descriptor.ts","../src/email.ts","../src/notify.ts","../src/payments.ts","../src/network.ts","../src/crm-sync.ts","../src/series.ts","../src/payments-stripe.ts","../src/clerk.ts","../src/clerk-roles.ts","../src/clerk-integration.ts","../src/session.ts","../src/brand.ts","../src/reconcile.ts","../src/scheduling.ts"],"sourcesContent":["// @odla-ai/chapter — core (platform-neutral). The UI kit lives at ./ui.\n// chapterWorker (the full Cloudflare handler) lands with the worker port.\nexport { defineChapter } from \"./config\";\nexport { createChapterIntegration, type ChapterRunbookHints } from \"./descriptor\";\nexport { chapterDb } from \"./schema\";\nexport { defaultCrm } from \"./defaults\";\nexport { buildGroupSeed } from \"./group\";\nexport { resolveAuth, roleFromClaim, isAdminRole, canChangeRole, getVaultSecret } from \"./auth\";\nexport { render, renderTemplateBody, isAlreadySent, planDelivery } from \"./email\";\nexport { sendTemplated, emailGroupFrom } from \"./notify\";\nexport type { MailSender, NotifyDeps, NotifyInput, NotifyResult } from \"./notify\";\nexport { resolvePipeline, canTransition, canBook, canApprove, stageIndex } from \"./pipeline\";\nexport {\n verifyStripeSignature,\n paymentsReady,\n stripeForm,\n subscriptionIdempotencyKey,\n webhookMutationId,\n findApplicationRef,\n normalizeWebhookEvent,\n firstPaymentPatch,\n renewalPatch,\n refundedPatch,\n canceledPatch,\n} from \"./payments\";\nexport type { PaymentsGroup, StripeEvent, WebhookEvent } from \"./payments\";\nexport { resolveApplication, submitApplication, joinConfig, applicantProfile, hasDisclaimerAck, isValidEmail, clampArray } from \"./member\";\nexport {\n DEFAULT_SHARE_FIELDS,\n sharedPersonInput,\n normalizeSharedRecord,\n sharedRecordFromCrm,\n networkSourceTag,\n projectSharedRecord,\n projectApplicant,\n} from \"./network\";\nexport { syncApplicationToCrm, backfillCrm, personInputFromApp } from \"./crm-sync\";\nexport {\n bucketSeries,\n dashboardMetricData,\n subAnnualCents,\n type DashboardMetricData,\n type DashboardMetricSeries,\n} from \"./series\";\nexport { stripeCall } from \"./payments-stripe\";\nexport type { StripeResult } from \"./payments-stripe\";\nexport {\n clerkInviteRequest,\n createClerkInvitation,\n clerkUserRequest,\n createClerkUser,\n updateClerkUserMetadata,\n updateClerkUserMetadataByEmail,\n} from \"./clerk\";\nexport type { ClerkInviteInput, ClerkUserInput, ClerkMetadataInput, ClerkResult } from \"./clerk\";\nexport { clerkGetUserByEmail, clerkGetUser, clerkListUsers, clerkSetRole } from \"./clerk-roles\";\nexport type { ClerkUserRecord } from \"./clerk-roles\";\nexport { clerkIntegration } from \"./clerk-integration\";\nexport type {\n IntegrationDescriptor,\n IntegrationSetting,\n IntegrationSecret,\n IntegrationSync,\n IntegrationProvision,\n SecretMode,\n} from \"./clerk-integration\";\nexport { applicationSummary, memberApplication, memberSession } from \"./session\";\nexport { brandTokens, chapterBrandFromTokens } from \"./brand\";\nexport type { CompiledChapterBrandTokens } from \"./brand\";\nexport { DEFAULT_CHAPTER_COPY, resolveChapterCopy, formatChapterCopy } from \"./copy\";\nexport { reconcileMeetings, isReconcilable } from \"./reconcile\";\nexport type { MeetingForReconcile, LiveEvent, ReconcileDecision } from \"./reconcile\";\nexport {\n resolveScheduling,\n validateScheduling,\n SCHEDULING_DEFAULTS,\n slotWindow,\n endForSlot,\n isSlotAvailable,\n renderSummary,\n bookingDecision,\n introIdempotencyKey,\n meetingCreateRow,\n meetingRescheduleUpdate,\n applicationBookingUpdate,\n} from \"./scheduling\";\n\nexport type { ChapterIntegrationDescriptor, ChapterIntegrationOptions } from \"./descriptor\";\nexport type { RoleChangeContext, GuardResult, SecretStore } from \"./auth\";\nexport type { EmailTemplateRow, EmailGroup, DeliveryDecision } from \"./email\";\nexport type { SubmitResult, JoinConfigGroup } from \"./member\";\nexport type { SharedPerson, SharedBusiness, SharedRecord, ProjectionDeps, Applicant } from \"./network\";\nexport type {\n ApplicationRecord,\n MeetingRecord,\n ApplicationSummary,\n MemberApplication,\n MemberSession,\n SessionUser,\n} from \"./session\";\nexport type {\n ResolvedScheduling,\n SchedulingErrors,\n ExistingMeeting,\n NewMeetingRow,\n MeetingReschedulePatch,\n ApplicationBookingPatch,\n} from \"./scheduling\";\nexport type {\n Chapter,\n ChapterConfig,\n ChapterMode,\n AccountModel,\n ChapterSends,\n ResolvedSends,\n ChapterOperations,\n ResolvedOperations,\n AdminNotificationTrigger,\n ChapterAuth,\n ResolvedAuth,\n ChapterPipeline,\n ResolvedPipeline,\n ChapterApplication,\n ResolvedApplication,\n DbOp,\n ChapterDb,\n ChapterBrand,\n ChapterNetwork,\n ChapterNetworkTarget,\n ResolvedNetwork,\n ResolvedNetworkTarget,\n ChapterPrices,\n ChapterPolicy,\n ChapterEmails,\n ChapterScheduling,\n EmailTemplate,\n DbSchema,\n DbRules,\n Attr,\n AttrType,\n Entity,\n Rule,\n} from \"./types\";\nexport type { ChapterCopy, ChapterCopyInput } from \"./copy\";\n","// defineChapter — validate-at-import config, reusing @odla-ai/crm's defineCrm.\n// A bad config throws here (startup), never at request time.\nimport { defineCrm } from \"@odla-ai/crm\";\nimport type { CrmConfig, Crm } from \"@odla-ai/crm\";\nimport type {\n Chapter,\n ChapterConfig,\n ChapterMode,\n ResolvedNetwork,\n ResolvedNetworkTarget,\n ResolvedOperations,\n} from \"./types\";\nimport { chapterDb } from \"./schema\";\nimport { defaultCrm } from \"./defaults\";\nimport { buildGroupSeed } from \"./group\";\nimport { resolveAuth } from \"./auth\";\nimport { resolvePipeline } from \"./pipeline\";\nimport { resolveApplication } from \"./member\";\nimport { resolveChapterCopy } from \"./copy\";\n\nconst SLUG = /^[a-z0-9][a-z0-9-]{1,62}$/;\nconst FIELD = /^[a-z][a-zA-Z0-9_]*$/;\n\nfunction resolveNetwork(config: ChapterConfig, crm: Crm): ResolvedNetwork {\n const seen = new Set<string>();\n const targets: ResolvedNetworkTarget[] = [];\n for (const target of config.network?.targets ?? []) {\n if (!SLUG.test(target.id)) {\n throw new Error(`defineChapter.network.targets.id: must be a lowercase slug — got ${JSON.stringify(target.id)}`);\n }\n if (seen.has(target.id)) throw new Error(`defineChapter.network.targets: duplicate target \"${target.id}\"`);\n seen.add(target.id);\n let url: URL;\n try {\n url = new URL(target.url);\n } catch {\n throw new Error(`defineChapter.network.targets.${target.id}.url: must be an absolute URL`);\n }\n const local = url.hostname === \"localhost\" || url.hostname === \"127.0.0.1\" || url.hostname === \"::1\";\n if (url.protocol !== \"https:\" && !(local && url.protocol === \"http:\")) {\n throw new Error(`defineChapter.network.targets.${target.id}.url: must use https (http is allowed only for localhost)`);\n }\n const fields = target.fields;\n for (const [type, names] of Object.entries(fields ?? {})) {\n if (!FIELD.test(type) || names.length === 0 || names.some((name) => !FIELD.test(name))) {\n throw new Error(\n `defineChapter.network.targets.${target.id}.fields.${type}: type and field names must be non-empty CRM identifiers`,\n );\n }\n const def = crm.config.types[type];\n if (!def) throw new Error(`defineChapter.network.targets.${target.id}.fields: unknown leader CRM type \"${type}\"`);\n const unknown = names.filter((field) => !def.fields[field]);\n if (unknown.length > 0) {\n throw new Error(\n `defineChapter.network.targets.${target.id}.fields.${type}: ${unknown.map((field) => `\"${field}\"`).join(\", \")} ` +\n \"not declared on the leader CRM type\",\n );\n }\n const nameField = def.nameField ?? \"name\";\n if (!names.includes(nameField)) {\n throw new Error(\n `defineChapter.network.targets.${target.id}.fields.${type}: must include required name field \"${nameField}\"`,\n );\n }\n }\n const secretName = target.secretName?.trim() || `network_share_${target.id.replaceAll(\"-\", \"_\")}`;\n if (!/^[a-zA-Z][a-zA-Z0-9_-]{1,127}$/.test(secretName)) {\n throw new Error(`defineChapter.network.targets.${target.id}.secretName: must be a vault-key identifier`);\n }\n targets.push({\n id: target.id,\n name: target.name?.trim() || target.id,\n url: url.origin,\n secretName,\n ...(fields ? { fields } : {}),\n });\n }\n return { targets };\n}\n\nfunction isResolvedCrm(x: CrmConfig | Crm | undefined): x is Crm {\n return (\n !!x &&\n typeof x === \"object\" &&\n \"prepare\" in x &&\n typeof (x as { prepare?: unknown }).prepare === \"function\"\n );\n}\n\n/**\n * Validate a chapter/hub config and resolve its engine — the CRM, the chapter's\n * odla-db schema/rules, and the seed groups row. Throws at import on a bad\n * config (bad slug, wrong mode, missing chapter-mode prices/emails), never at\n * request time.\n */\nexport function defineChapter(config: ChapterConfig): Chapter {\n if (!config || typeof config !== \"object\") throw new Error(\"defineChapter: a config object is required\");\n const { id, name } = config;\n if (typeof id !== \"string\" || !SLUG.test(id)) {\n throw new Error(`defineChapter.id: must be a lowercase slug [a-z0-9-] (2-63 chars) — got ${JSON.stringify(id)}`);\n }\n if (typeof name !== \"string\" || name.trim() === \"\") throw new Error(\"defineChapter.name: a non-empty name is required\");\n\n const mode: ChapterMode = config.mode ?? \"chapter\";\n if (mode !== \"chapter\" && mode !== \"hub\") throw new Error(`defineChapter.mode: must be \"chapter\" or \"hub\" — got ${JSON.stringify(config.mode)}`);\n\n const crm: Crm = isResolvedCrm(config.crm) ? config.crm : defineCrm(config.crm ?? defaultCrm(mode));\n\n if (mode === \"chapter\") {\n if (!config.emails || typeof config.emails.notificationEmail !== \"string\" || config.emails.notificationEmail === \"\") {\n throw new Error(\"defineChapter.emails.notificationEmail: required in chapter mode\");\n }\n if (!config.prices || typeof config.prices.standardCents !== \"number\") {\n throw new Error(\"defineChapter.prices.standardCents: required in chapter mode\");\n }\n if (config.account === undefined) {\n throw new Error('defineChapter.account: required in chapter mode; choose \"none\", \"create\", or \"invite\" explicitly');\n }\n }\n\n const auth = resolveAuth(mode, config.auth);\n const pipeline = resolvePipeline(config.pipeline);\n const application = resolveApplication(config.application);\n const brand = { ...config.brand, wordmark: config.brand?.wordmark ?? name };\n const copy = resolveChapterCopy(config.copy);\n const network = resolveNetwork(config, crm);\n const { schema, rules } = chapterDb(mode, auth);\n const services = config.services ?? [\"db\", \"calendar\", \"o11y\"];\n // Hub mode has no public application flow, so its inert resolved value remains\n // `none`. Chapter mode was required to make this decision above.\n const account = config.account ?? \"none\";\n if (account !== \"invite\" && account !== \"create\" && account !== \"none\") {\n throw new Error(`defineChapter.account: must be \"invite\", \"create\", or \"none\" — got \"${String(account)}\"`);\n }\n // crmFields not declared on the CRM person type are dropped by the projection at\n // runtime (the base person still lands), so a typo is invisible. Both the\n // resolved crmFields and the resolved crm are in hand here — cross-check the\n // names and fail at resolve time instead.\n if (application.crmFields.length > 0) {\n let personFields: string[] | null = null;\n try {\n personFields = Object.keys(crm.type(\"person\").fields ?? {});\n } catch {\n // No introspectable \"person\" type — leave it to the projection.\n }\n const unknown = personFields ? application.crmFields.filter((f) => !personFields.includes(f)) : [];\n if (unknown.length > 0) {\n throw new Error(\n `defineChapter.application.crmFields: ${unknown.map((f) => `\"${f}\"`).join(\", \")} not declared on the CRM \"person\" type ` +\n `(declared: ${personFields?.join(\", \")}). An undeclared field is silently dropped from the CRM projection — ` +\n `add it to your crm config's person fields, or remove it from crmFields.`,\n );\n }\n }\n const adminNotification = config.sends?.adminNotification ?? \"submit\";\n if (adminNotification !== \"submit\" && adminNotification !== \"payment\" && adminNotification !== \"never\") {\n throw new Error(\n `defineChapter.sends.adminNotification: must be \"submit\", \"payment\", or \"never\" — got \"${String(adminNotification)}\"`,\n );\n }\n const sends = { adminNotification };\n\n // Admin-operation policy. The escalation RULES are not here — they are\n // package-enforced in canChangeRole (gated on auth.superAdmins), so a site\n // cannot weaken them. These two are genuine per-site decisions.\n const onApproveCfg = config.operations?.onApprove ?? {};\n // Default promotion target: the rung directly below admin, so a custom ladder\n // gets the right answer instead of a hardcoded \"member\".\n const belowAdmin = auth.ladder[auth.ladder.length - 2] ?? auth.adminRole;\n const promoteTo = onApproveCfg.promoteTo === undefined ? belowAdmin : onApproveCfg.promoteTo;\n if (promoteTo !== false && !auth.ladder.includes(promoteTo)) {\n throw new Error(\n `defineChapter.operations.onApprove.promoteTo: \"${String(promoteTo)}\" is not in the auth ladder (${auth.ladder.join(\", \")})`,\n );\n }\n const refundCfg = config.operations?.refund ?? {};\n if (refundCfg.allowedFrom !== undefined) {\n const unknown = refundCfg.allowedFrom.filter((s) => !pipeline.stages.includes(s));\n if (unknown.length > 0) {\n throw new Error(\n `defineChapter.operations.refund.allowedFrom: ${unknown.map((s) => `\"${s}\"`).join(\", \")} not in the pipeline (${pipeline.stages.join(\", \")})`,\n );\n }\n }\n const operations: ResolvedOperations = {\n onApprove: { promoteTo, send: onApproveCfg.send === undefined ? \"onboardingInvite\" : onApproveCfg.send },\n refund: { allowedFrom: refundCfg.allowedFrom ?? null, cancelSubscription: refundCfg.cancelSubscription ?? true },\n };\n\n const chapter: Chapter = {\n config,\n id,\n name,\n mode,\n brand,\n copy,\n network,\n crm,\n auth,\n pipeline,\n application,\n schema,\n rules,\n services,\n account,\n sends,\n operations,\n groupSeed: () => (mode === \"chapter\" ? buildGroupSeed(config) : null),\n };\n if (config.url !== undefined) chapter.url = config.url;\n return chapter;\n}\n","// The chapter's own odla-db namespaces: the operational membership tables\n// (applications, groups, meetings, emailLog) plus the auth tables the resolved\n// auth policy selects — the `admins` allowlist (source \"table\") and/or the\n// read-only `superAdmins` tier. All deny-all. The `crm_*` namespaces are\n// contributed separately by @odla-ai/crm's integration and merged by the CLI.\nimport type { Attr, AttrType, DbRules, DbSchema, Entity, ChapterMode, ResolvedAuth } from \"./types\";\n\nfunction attr(type: AttrType, flags: { unique?: boolean; indexed?: boolean; optional?: boolean } = {}): Attr {\n return {\n type,\n unique: flags.unique ?? false,\n indexed: flags.indexed ?? false,\n optional: flags.optional ?? false,\n };\n}\nconst id = (): Attr => attr(\"string\", { unique: true, indexed: true });\n\n// The allowlist that gates admin access in BOTH modes. Studio-write-only:\n// deny-all, and no worker route ever writes it. Creation time is odla-db's\n// built-in $createdAt. One row per admin, keyed by lowercased email.\nconst admins: Entity = {\n attrs: {\n id: id(),\n email: attr(\"string\", { unique: true, indexed: true }),\n name: attr(\"string\", { optional: true }),\n note: attr(\"string\", { optional: true }),\n },\n};\n\n// The super-admin tier: the ONLY tier that may create or modify admins. A\n// separate, app-READ-ONLY entity — deny-all like every namespace AND no worker\n// route ever writes it, so membership can only be set in the odla Studio data\n// browser, never from the app or an injected page script. One row per\n// super-admin, keyed by lowercased email.\nconst superAdmins: Entity = {\n attrs: {\n id: id(),\n email: attr(\"string\", { unique: true, indexed: true }),\n note: attr(\"string\", { optional: true }),\n createdAt: attr(\"number\", { indexed: true }),\n },\n};\n\n// One row per membership application (chapter mode). Field names mirror the\n// join form. status pipeline drives the provisional -> member promotion.\nconst applications: Entity = {\n attrs: {\n id: id(),\n firstName: attr(\"string\"),\n lastName: attr(\"string\"),\n email: attr(\"string\", { indexed: true }),\n referral: attr(\"string\"),\n referralName: attr(\"string\", { optional: true }),\n whoYouAre: attr(\"string\"),\n focus: attr(\"json\"),\n linkedin: attr(\"string\", { optional: true }),\n message: attr(\"string\"),\n status: attr(\"string\", { indexed: true }),\n createdAt: attr(\"number\", { indexed: true }),\n meetingAt: attr(\"number\", { indexed: true, optional: true }),\n meetingLink: attr(\"string\", { optional: true }),\n clerkUserId: attr(\"string\", { indexed: true, optional: true }),\n // One-time/effect-repair marker. Absent means /api/me should copy the\n // allowlisted application snapshot into Clerk private_metadata and remove\n // the former public_metadata copy.\n clerkPrivateMetadataSyncedAt: attr(\"number\", { optional: true }),\n phone: attr(\"string\", { optional: true }),\n state: attr(\"string\", { optional: true }),\n groupId: attr(\"string\", { indexed: true, optional: true }),\n stripeCustomerId: attr(\"string\", { indexed: true, optional: true }),\n stripeSubscriptionId: attr(\"string\", { indexed: true, optional: true }),\n renewalAt: attr(\"number\", { optional: true }),\n disclaimerAckAt: attr(\"number\", { optional: true }),\n refundPolicyAckAt: attr(\"number\", { optional: true }),\n prepEmailSentAt: attr(\"number\", { optional: true }),\n canceled: attr(\"boolean\", { optional: true }),\n },\n};\n\n// Per-group settings — prices, policy copy, email templates, scheduling — never\n// in code. Seeded once from the defineChapter config (see group.ts).\nconst groups: Entity = {\n attrs: {\n id: id(),\n name: attr(\"string\"),\n standardPriceCents: attr(\"number\"),\n foundingDiscountCents: attr(\"number\"),\n stripePriceId: attr(\"string\", { optional: true }),\n stripePublishableKey: attr(\"string\", { optional: true }),\n notificationEmail: attr(\"string\"),\n replyTo: attr(\"string\"),\n debugEmail: attr(\"string\", { optional: true }),\n calendarLink: attr(\"string\", { optional: true }),\n disclaimerText: attr(\"string\"),\n refundPolicyText: attr(\"string\"),\n trustCopy: attr(\"string\"),\n commitmentText: attr(\"string\", { optional: true }),\n normsText: attr(\"string\", { optional: true }),\n emailTemplates: attr(\"json\"),\n schedulingJson: attr(\"json\", { optional: true }),\n createdAt: attr(\"number\", { indexed: true }),\n },\n};\n\n// Intro-call meetings: the source of truth for scheduling; Google Calendar is a\n// projection. Drift fields record when Google disagrees with us.\nconst meetings: Entity = {\n attrs: {\n id: id(),\n applicationId: attr(\"string\", { indexed: true }),\n groupId: attr(\"string\", { indexed: true }),\n startAt: attr(\"number\", { indexed: true }),\n endAt: attr(\"number\"),\n timezone: attr(\"string\"),\n status: attr(\"string\", { indexed: true }),\n googleEventId: attr(\"string\", { indexed: true, optional: true }),\n meetUrl: attr(\"string\", { optional: true }),\n htmlLink: attr(\"string\", { optional: true }),\n drift: attr(\"string\", { indexed: true, optional: true }),\n driftGoogleStartAt: attr(\"number\", { optional: true }),\n driftDetectedAt: attr(\"number\", { optional: true }),\n adoptedFromGoogleAt: attr(\"number\", { optional: true }),\n createdAt: attr(\"number\", { indexed: true }),\n },\n};\n\n// Audit of every transactional send.\nconst emailLog: Entity = {\n attrs: {\n id: id(),\n groupId: attr(\"string\", { indexed: true }),\n applicationId: attr(\"string\", { indexed: true, optional: true }),\n to: attr(\"string\", { indexed: true }),\n template: attr(\"string\", { indexed: true }),\n subject: attr(\"string\"),\n body: attr(\"string\", { optional: true }),\n transport: attr(\"string\"),\n messageId: attr(\"string\", { optional: true }),\n redirected: attr(\"boolean\", { optional: true }),\n dedupeKey: attr(\"string\", { indexed: true, optional: true }),\n error: attr(\"string\", { optional: true }),\n sentAt: attr(\"number\", { indexed: true }),\n },\n};\n\n/** The chapter's own schema + deny-all rules for a mode + auth policy.\n *\n * Operational tables (`applications`/`groups`/`meetings`/`emailLog`) are added in\n * `chapter` mode only. The auth tables follow {@link ResolvedAuth}: `source:\n * \"table\"` adds the `admins` allowlist (hub/BNF); `superAdmins` adds the\n * read-only super-admin tier (default on for the `\"claim\"` ladder). A `\"claim\"`\n * chapter therefore emits the reference namespace set — `applications`,\n * `groups`, `meetings`, `emailLog`, `superAdmins` — with no `admins` table. */\nexport function chapterDb(mode: ChapterMode, auth: ResolvedAuth): { schema: DbSchema; rules: DbRules } {\n const entities: Record<string, Entity> = {};\n if (mode === \"chapter\") {\n entities.applications = applications;\n entities.groups = groups;\n entities.meetings = meetings;\n entities.emailLog = emailLog;\n }\n if (auth.source === \"table\") entities.admins = admins;\n if (auth.superAdmins) entities.superAdmins = superAdmins;\n const schema: DbSchema = { entities, links: {} };\n const rules: DbRules = {};\n for (const ns of Object.keys(entities)) {\n rules[ns] = { view: \"false\", create: \"false\", update: \"false\", delete: \"false\" };\n }\n return { schema, rules };\n}\n","// Per-mode default CRM configs. Consumers pass their own via defineChapter's\n// `crm`; these are sensible starting points. Both modes share the canonical\n// people + businesses graph, while `chapter` additionally feeds the person\n// pipeline from applications through the operational package routes.\nimport type { CrmConfig } from \"@odla-ai/crm\";\nimport type { ChapterMode } from \"./types\";\n\nconst TEMPLATES: CrmConfig[\"templates\"] = {\n personal: {\n class: \"transactional\",\n vars: [\"firstName\", \"subject\", \"body\"],\n defaults: { subject: \"{{subject}}\", text: \"{{body}}\" },\n },\n announcement: {\n class: \"marketing\",\n vars: [\"firstName\", \"subject\", \"body\", \"unsubscribeUrl\"],\n defaults: { subject: \"{{subject}}\", text: \"{{body}}\\n\\n{{unsubscribeUrl}}\" },\n },\n};\n\nfunction personType(): NonNullable<CrmConfig[\"types\"]>[string] {\n return {\n label: \"Person\",\n labelPlural: \"People\",\n nameField: \"name\",\n emailField: \"email\",\n fields: {\n name: { type: \"string\", label: \"Name\", required: true },\n email: { type: \"email\", label: \"Email\" },\n firstName: { type: \"string\", label: \"First name\" },\n lastName: { type: \"string\", label: \"Last name\" },\n phone: { type: \"string\", label: \"Phone\" },\n state: { type: \"string\", label: \"State\", slot: \"s1\" },\n whoYouAre: { type: \"string\", label: \"Who they are\", slot: \"s2\" },\n referral: { type: \"string\", label: \"Referral source\", slot: \"s3\" },\n linkedin: { type: \"string\", label: \"LinkedIn\" },\n focus: { type: \"json\", label: \"Focus areas\" },\n message: { type: \"string\", label: \"Intro message\" },\n },\n pipeline: {\n stages: [\n { id: \"submitted\", label: \"Submitted\" },\n { id: \"paid_pending_vetting\", label: \"Paid, pending vetting\" },\n { id: \"call_scheduled\", label: \"Call scheduled\" },\n { id: \"interviewed\", label: \"Interviewed\" },\n { id: \"approved\", label: \"Approved\" },\n { id: \"declined\", label: \"Declined\" },\n { id: \"refunded\", label: \"Refunded\" },\n ],\n },\n facets: { identity: true, email: true, rank: \"manual\" },\n };\n}\n\nfunction companyType(): NonNullable<CrmConfig[\"types\"]>[string] {\n return {\n label: \"Business\",\n labelPlural: \"Businesses\",\n nameField: \"name\",\n fields: {\n name: { type: \"string\", label: \"Name\", required: true },\n domain: { type: \"string\", label: \"Domain / website\", slot: \"s1\" },\n industry: { type: \"string\", label: \"Industry\", slot: \"s2\" },\n location: { type: \"string\", label: \"Location\", slot: \"s3\" },\n chapter: { type: \"string\", label: \"Chapter\", slot: \"s4\" },\n linkedin: { type: \"string\", label: \"LinkedIn\" },\n notes: { type: \"string\", label: \"Notes\" },\n },\n facets: { rank: \"manual\" },\n };\n}\n\n/** The default shared CRM graph: people (with the application/member pipeline)\n * plus businesses and the `works_at` relation, in both modes. */\nexport function defaultCrm(_mode: ChapterMode): CrmConfig {\n return {\n types: { person: personType(), company: companyType() },\n relations: { works_at: { from: \"person\", to: \"company\", label: \"works at\", reverseLabel: \"team\" } },\n templates: TEMPLATES,\n };\n}\n","// Build the seed `groups` row from a defineChapter config. This is the whole\n// per-chapter payload the worker reads at runtime (prices/policy/emails/\n// scheduling), so nothing brand-specific is hardcoded in the worker. createdAt\n// is stamped by the integration/seed layer, not here.\nimport type { ChapterConfig, ChapterScheduling } from \"./types\";\n\nconst DEFAULT_SCHEDULING: Required<Omit<ChapterScheduling, \"summaryTemplate\">> = {\n slotMinutes: 45,\n days: [1, 2, 3, 4, 5],\n startHour: 9,\n endHour: 17,\n timezone: \"America/Los_Angeles\",\n minNoticeHours: 24,\n windowDays: 14,\n};\n\nfunction defaultEmailTemplates(name: string): Record<string, { subject: string; text: string }> {\n const sign = `\\n\\nWarmly,\\n${name}`;\n return {\n adminNotification: {\n subject: `New application: {{firstName}} {{lastName}}`,\n text: `A new application came in for ${name}.\\n\\nName: {{firstName}} {{lastName}}\\nEmail: {{email}}`,\n },\n paymentConfirmation: {\n subject: `Welcome to ${name}`,\n text: `Hi {{firstName}},\\n\\nYour membership payment is confirmed. We'll be in touch to schedule your intro call.${sign}`,\n },\n prepEmail: {\n subject: `Your ${name} intro call`,\n text: `Hi {{firstName}},\\n\\nLooking forward to our call at {{meetingTime}}. {{meetingLink}}${sign}`,\n },\n onboardingInvite: {\n subject: `You're in, ${name}`,\n text: `Hi {{firstName}},\\n\\nWelcome to ${name}. Your member area is here: {{membersUrl}}${sign}`,\n },\n };\n}\n\n/** The `groups` row (attrs) for `chapter` mode. Missing config falls back to\n * empty copy / defaults, so a minimal config still provisions cleanly. */\nexport function buildGroupSeed(config: ChapterConfig): Record<string, unknown> {\n const prices = config.prices;\n const emails = config.emails;\n const policy = config.policy ?? {};\n const scheduling = {\n ...DEFAULT_SCHEDULING,\n ...(config.scheduling ?? {}),\n summaryTemplate:\n config.scheduling?.summaryTemplate ?? `${config.name}: introduction call with {{firstName}} {{lastName}}`,\n };\n const row: Record<string, unknown> = {\n id: config.id,\n name: config.name,\n standardPriceCents: prices?.standardCents ?? 0,\n foundingDiscountCents: prices?.foundingDiscountCents ?? 0,\n notificationEmail: emails?.notificationEmail ?? \"\",\n replyTo: emails?.replyTo ?? emails?.notificationEmail ?? \"\",\n disclaimerText: policy.disclaimerText ?? \"\",\n refundPolicyText: policy.refundPolicyText ?? \"\",\n trustCopy: policy.trustCopy ?? \"\",\n emailTemplates: emails?.templates ?? defaultEmailTemplates(config.name),\n schedulingJson: scheduling,\n };\n if (emails?.debugEmail) row.debugEmail = emails.debugEmail;\n if (policy.commitmentText) row.commitmentText = policy.commitmentText;\n if (policy.normsText) row.normsText = policy.normsText;\n return row;\n}\n","// Identity + authorization for a chapter/hub site — the pieces every membership\n// site needs and none should re-derive: a resolved role policy, role resolution\n// from a JWT claim, the privilege-escalation guard, and a tenant-vault read.\n// Everything here is pure or structural (no runtime @odla-ai/db import), so it is\n// trivially testable and the worker stays the only thing that talks to odla-db.\nimport type { ChapterAuth, ChapterMode, ResolvedAuth } from \"./types\";\n\n/**\n * Apply defaults + validate the auth config into a {@link ResolvedAuth}. Defaults\n * by mode: `chapter` → the `provisional/member/admin` claim ladder with the\n * `superAdmins` tier; `hub` → the `admins` allowlist table with no super tier.\n * Throws at import on a bad policy.\n */\nexport function resolveAuth(mode: ChapterMode, auth: ChapterAuth | undefined): ResolvedAuth {\n const a = auth ?? {};\n const source = a.source ?? (mode === \"hub\" ? \"table\" : \"claim\");\n if (source !== \"claim\" && source !== \"table\") {\n throw new Error(`defineChapter.auth.source: must be \"claim\" or \"table\" — got ${JSON.stringify(a.source)}`);\n }\n const claim = a.claim ?? \"role\";\n if (typeof claim !== \"string\" || claim === \"\") {\n throw new Error(\"defineChapter.auth.claim: must be a non-empty string\");\n }\n const ladder = a.ladder ?? [\"provisional\", \"member\", \"admin\"];\n if (!Array.isArray(ladder) || ladder.length === 0 || !ladder.every((r) => typeof r === \"string\" && r !== \"\")) {\n throw new Error(\"defineChapter.auth.ladder: must be a non-empty array of role strings\");\n }\n const adminRole = ladder[ladder.length - 1] as string;\n const superAdmins = a.superAdmins ?? source === \"claim\";\n return { source, claim, ladder, adminRole, superAdmins };\n}\n\n/** The role from a verified JWT payload, per the resolved policy. An unknown or\n * missing claim falls back to the lowest ladder rung (fail safe, never admin). */\nexport function roleFromClaim(payload: Record<string, unknown>, auth: ResolvedAuth): string {\n const raw = payload[auth.claim];\n return typeof raw === \"string\" && auth.ladder.includes(raw) ? raw : (auth.ladder[0] as string);\n}\n\n/** Does a role meet the admin bar (the highest ladder rung)? */\nexport function isAdminRole(role: string, auth: ResolvedAuth): boolean {\n return role === auth.adminRole;\n}\n\n/** Inputs to the role-change guard — resolved by the caller (route) from the\n * identity provider + the read-only `superAdmins` table. */\nexport interface RoleChangeContext {\n actorId: string;\n actorIsSuper: boolean;\n targetId: string;\n targetCurrentRole: string;\n targetIsSuper: boolean;\n newRole: string;\n auth: ResolvedAuth;\n}\n\n/** The result of {@link canChangeRole}: allow, or deny with the HTTP status +\n * message the route should return. */\nexport type GuardResult = { ok: true } | { ok: false; status: number; error: string };\n\n/**\n * The privilege-escalation guard — package-enforced so every site gets it and\n * none re-derives it. Denies: an out-of-ladder role; changing your own role;\n * touching a super-admin unless you are one; and (when a `superAdmins` tier\n * exists) creating or altering an admin unless you are a super-admin. Note the\n * super-admin tier itself is never writable here — it lives in the read-only\n * `superAdmins` table, set only in odla Studio.\n */\nexport function canChangeRole(ctx: RoleChangeContext): GuardResult {\n const { auth } = ctx;\n if (!auth.ladder.includes(ctx.newRole)) {\n return { ok: false, status: 400, error: `role must be one of: ${auth.ladder.join(\", \")}` };\n }\n if (ctx.actorId === ctx.targetId) {\n return { ok: false, status: 400, error: \"you cannot change your own role\" };\n }\n if (ctx.targetIsSuper && !ctx.actorIsSuper) {\n return { ok: false, status: 403, error: \"this person is a super-admin; their access is managed in odla Studio\" };\n }\n const touchesAdmin = ctx.newRole === auth.adminRole || ctx.targetCurrentRole === auth.adminRole;\n if (auth.superAdmins && touchesAdmin && !ctx.actorIsSuper) {\n return { ok: false, status: 403, error: `only super-admins can create or change an ${auth.adminRole}` };\n }\n return { ok: true };\n}\n\n/** Structural view of odla-db's tenant-vault read, so chapter takes no runtime\n * dependency on @odla-ai/db. The worker's admin client satisfies this. */\nexport interface SecretStore {\n secrets: { get(name: string): Promise<string> };\n}\n\n/**\n * Read a tenant-vault secret by name; `undefined` when it is absent or the vault\n * errors, so callers degrade gracefully (e.g. `paymentsReady: false`) rather than\n * throwing. Never logs the value.\n */\nexport async function getVaultSecret(db: SecretStore, name: string): Promise<string | undefined> {\n try {\n const value = await db.secrets.get(name);\n return typeof value === \"string\" && value !== \"\" ? value : undefined;\n } catch {\n return undefined;\n }\n}\n","// The application status pipeline — config, not code. Which statuses exist, which\n// a member can book an intro call from, and which an admin can approve from\n// differ per site; the one invariant every site wants is that status never moves\n// backwards. All of this is pure + tested here; the worker enforces it on every\n// status write, and the CRM record.stage mirrors application.status (never the\n// reverse). Defaults reproduce the proven membership pipeline exactly.\nimport type { ChapterPipeline, ResolvedPipeline } from \"./types\";\n\nconst DEFAULT_STAGES = [\n \"submitted\",\n \"paid_pending_vetting\",\n \"call_scheduled\",\n \"interviewed\",\n \"approved\",\n \"declined\",\n \"refunded\",\n] as const;\nconst DEFAULT_BOOKABLE = [\"submitted\", \"paid_pending_vetting\", \"call_scheduled\"] as const;\nconst DEFAULT_APPROVABLE = [\"paid_pending_vetting\", \"call_scheduled\", \"interviewed\"] as const;\n\n/**\n * Apply defaults + validate the pipeline config. With no config, the complete\n * reference pipeline. With `stages` given but the subsets omitted, the subsets\n * default to empty (a site opts in to bookable/approvable states explicitly).\n * Throws at import on a bad pipeline (empty/duplicate stages, an initial or a\n * subset entry not on the ladder).\n */\nexport function resolvePipeline(p: ChapterPipeline | undefined): ResolvedPipeline {\n const usingDefaults = !p?.stages;\n const stages = p?.stages ?? [...DEFAULT_STAGES];\n if (!Array.isArray(stages) || stages.length === 0 || !stages.every((s) => typeof s === \"string\" && s !== \"\")) {\n throw new Error(\"defineChapter.pipeline.stages: must be a non-empty array of status strings\");\n }\n if (new Set(stages).size !== stages.length) {\n throw new Error(\"defineChapter.pipeline.stages: statuses must be unique\");\n }\n const initial = p?.initial ?? (stages[0] as string);\n if (!stages.includes(initial)) {\n throw new Error(`defineChapter.pipeline.initial: \"${initial}\" is not one of the stages`);\n }\n const bookableFrom = p?.bookableFrom ?? (usingDefaults ? [...DEFAULT_BOOKABLE] : []);\n const approvableFrom = p?.approvableFrom ?? (usingDefaults ? [...DEFAULT_APPROVABLE] : []);\n for (const [name, subset] of [\n [\"bookableFrom\", bookableFrom],\n [\"approvableFrom\", approvableFrom],\n ] as const) {\n for (const s of subset) {\n if (!stages.includes(s)) throw new Error(`defineChapter.pipeline.${name}: \"${s}\" is not one of the stages`);\n }\n }\n return { stages, bookableFrom, approvableFrom, initial };\n}\n\n/** The ordinal of a status in the ladder, or -1 if unknown. */\nexport function stageIndex(status: string, p: ResolvedPipeline): number {\n return p.stages.indexOf(status);\n}\n\n/**\n * The status-never-moves-backwards invariant: a transition is allowed only when\n * both statuses are on the ladder and `to` is at or ahead of `from`. The worker\n * calls this before every status write; a violation is a 409, never a silent\n * downgrade.\n */\nexport function canTransition(from: string, to: string, p: ResolvedPipeline): boolean {\n const fi = p.stages.indexOf(from);\n const ti = p.stages.indexOf(to);\n return fi >= 0 && ti >= 0 && ti >= fi;\n}\n\n/** May an intro call be booked from this status? */\nexport function canBook(status: string, p: ResolvedPipeline): boolean {\n return p.bookableFrom.includes(status);\n}\n\n/** May an application be approved (→ member) from this status? */\nexport function canApprove(status: string, p: ResolvedPipeline): boolean {\n return p.approvableFrom.includes(status);\n}\n","// The public member surface logic: the join config a site's join page reads (B1)\n// and the idempotent application submit (B2 validation + B3 exactly-once). Both\n// take the structural ChapterDb, so they're tested against an in-memory fake and\n// carry no runtime @odla-ai/db import. The worker builds the real db client, does\n// Clerk verification, enforces the body cap, and mounts these on chapter routes.\nimport type { Chapter, ChapterApplication, ChapterDb, ResolvedApplication } from \"./types\";\n\n// The reference join form. `focus` (a json field) is always accepted.\nconst DEFAULT_REQUIRED = [\"firstName\", \"lastName\", \"email\", \"referral\", \"whoYouAre\", \"message\"];\nconst DEFAULT_OPTIONAL = [\"referralName\", \"linkedin\", \"phone\", \"state\"];\n\n/** Apply defaults + validate the application config. Throws at import on bad shape. */\nexport function resolveApplication(a: ChapterApplication | undefined): ResolvedApplication {\n const required = a?.required ?? DEFAULT_REQUIRED;\n const optional = a?.optional ?? DEFAULT_OPTIONAL;\n for (const [name, arr] of [[\"required\", required], [\"optional\", optional]] as const) {\n if (!Array.isArray(arr) || !arr.every((f) => typeof f === \"string\" && f !== \"\")) {\n throw new Error(`defineChapter.application.${name}: must be an array of field-name strings`);\n }\n }\n if (a?.profileFields !== undefined && (!Array.isArray(a.profileFields) || !a.profileFields.every((f) => typeof f === \"string\" && f !== \"\"))) {\n throw new Error(\"defineChapter.application.profileFields: must be an array of field-name strings\");\n }\n if (a?.crmFields !== undefined && (!Array.isArray(a.crmFields) || !a.crmFields.every((f) => typeof f === \"string\" && f !== \"\"))) {\n throw new Error(\"defineChapter.application.crmFields: must be an array of field-name strings\");\n }\n return {\n required,\n optional,\n maxLen: a?.maxLen ?? {},\n defaultMaxLen: a?.defaultMaxLen ?? 2000,\n bodyCap: a?.bodyCap ?? 32768,\n requireDisclaimerAck: a?.requireDisclaimerAck ?? true,\n profileFields: a?.profileFields ?? [],\n crmFields: a?.crmFields ?? [],\n maxArrayLen: a?.maxArrayLen ?? 100,\n validateEmail: a?.validateEmail ?? true,\n };\n}\n\n// A permissive email shape check: one @, a dot-bearing domain, no whitespace.\n// Deliberately not RFC-exhaustive — the goal is to reject \"notanemail\" before it\n// fails the downstream Clerk create, not to adjudicate exotic-but-valid addresses.\nconst EMAIL_RE = /^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$/;\n\n/** Whether a string looks like an email address (see {@link EMAIL_RE}). Exported\n * so a site building its own submit path applies the same rule chapter does. */\nexport function isValidEmail(value: unknown): boolean {\n return typeof value === \"string\" && EMAIL_RE.test(value);\n}\n\n/** Bound an array-valued field: drop non-primitive elements and cap the length,\n * so a client cannot post an unbounded array. Non-arrays pass through unchanged. */\nexport function clampArray(value: unknown, max: number): unknown {\n if (!Array.isArray(value)) return value;\n return value.filter((x) => typeof x === \"string\" || typeof x === \"number\" || typeof x === \"boolean\").slice(0, max);\n}\n\n/** Whether a submit body carries a genuine disclaimer acknowledgement. Accepts\n * the boolean an API client sends and the string a plain HTML form posts. */\nexport function hasDisclaimerAck(fields: Record<string, unknown>): boolean {\n return fields.disclaimerAck === true || fields.disclaimerAck === \"true\";\n}\n\n// Clerk carries these as first-class user fields, so repeating them in\n// public_metadata would be duplicated state that can drift.\nconst IDENTITY_FIELDS = new Set([\"email\", \"firstName\", \"lastName\"]);\n\n/**\n * The applicant profile written to the Clerk account's backend-only\n * `private_metadata.profile`. Projects each configured non-identity field, plus\n * `focus` (clamped), but ONLY those in `application.profileFields`. Derived\n * from config, so a site's own field names project without this package knowing\n * them. Pure; returns `undefined` when there is nothing to write.\n */\nexport function applicantProfile(\n chapter: Chapter,\n fields: Record<string, unknown>,\n): Record<string, unknown> | undefined {\n const app = chapter.application;\n const allowed = (f: string): boolean => app.profileFields.includes(f);\n const profile: Record<string, unknown> = {};\n for (const f of [...app.required, ...app.optional]) {\n if (IDENTITY_FIELDS.has(f) || !allowed(f)) continue;\n const v = fields[f];\n if (typeof v === \"string\" && v.trim() !== \"\") profile[f] = v.trim();\n }\n if (fields.focus !== undefined && allowed(\"focus\")) profile.focus = clampArray(fields.focus, app.maxArrayLen);\n return Object.keys(profile).length > 0 ? profile : undefined;\n}\n\n/** A validated submission, or a 400-worthy validation error the route returns.\n * `disclaimerAckAt` reports what THIS request recorded — a number when consent\n * was stamped, `null` when none was supplied. It is always present so a missing\n * consent record is visible in the response rather than silently absent from a\n * row nobody reads until an audit. */\nexport type SubmitResult =\n | { ok: true; id: string; duplicate: boolean; status: string; disclaimerAckAt: number | null }\n | { ok: false; error: string };\n\n/**\n * Submit a membership application (B2 + B3). Validates the configured required\n * fields + max lengths, writes the `applications` row at the pipeline's initial\n * status, and — when the client supplies a `submissionId` — stamps it as the\n * transaction's mutationId (`join:${submissionId}`) so a double-tap can never\n * create two applications (the second returns `duplicate: true`). Idempotency is\n * package-enforced. `now`/`newId` are injected (deterministic in tests).\n */\nexport async function submitApplication(\n db: ChapterDb,\n chapter: Chapter,\n fields: Record<string, unknown>,\n opts: { submissionId?: string; groupId?: string; now: number; newId: () => string },\n): Promise<SubmitResult> {\n const app = chapter.application;\n for (const f of app.required) {\n const v = fields[f];\n if (typeof v !== \"string\" || v.trim() === \"\") return { ok: false, error: `${f} is required` };\n }\n for (const f of [...app.required, ...app.optional]) {\n const v = fields[f];\n const cap = app.maxLen[f] ?? app.defaultMaxLen;\n if (typeof v === \"string\" && v.length > cap) return { ok: false, error: `${f} exceeds ${cap} characters` };\n }\n // A malformed email can only fail the downstream Clerk create; reject it here\n // as a clean 400. A valid application always has a valid email, so no genuine\n // submission is newly rejected.\n if (app.validateEmail && typeof fields.email === \"string\" && !isValidEmail(fields.email)) {\n return { ok: false, error: \"email must be a valid email address\" };\n }\n\n const acked = hasDisclaimerAck(fields);\n if (app.requireDisclaimerAck && !acked) {\n return { ok: false, error: \"disclaimerAck is required\" };\n }\n\n const id = opts.newId();\n const row: Record<string, unknown> = { id, status: chapter.pipeline.initial, createdAt: opts.now };\n for (const f of [...app.required, ...app.optional]) {\n if (typeof fields[f] === \"string\") row[f] = (fields[f] as string).trim();\n }\n if (fields.focus !== undefined) row.focus = clampArray(fields.focus, app.maxArrayLen);\n if (opts.groupId) row.groupId = opts.groupId;\n // The disclaimer acknowledgement is a compliance record, so it is stamped from\n // the server clock on a genuine ack and left absent otherwise — a client-supplied\n // timestamp would be forgeable, and a row that always carries one would record\n // consent nobody gave.\n if (acked) row.disclaimerAckAt = opts.now;\n\n const { duplicate } = await db.transact(\n [{ t: \"update\", ns: \"applications\", id, attrs: row }],\n opts.submissionId ? { mutationId: `join:${opts.submissionId}` } : undefined,\n );\n return { ok: true, id, duplicate, status: chapter.pipeline.initial, disclaimerAckAt: acked ? opts.now : null };\n}\n\n/** The `groups`-row fields the join config exposes. */\nexport interface JoinConfigGroup {\n id: string;\n name: string;\n standardPriceCents?: number;\n foundingDiscountCents?: number;\n disclaimerText?: string;\n refundPolicyText?: string;\n trustCopy?: string;\n commitmentText?: string;\n normsText?: string;\n}\n\n/**\n * The public join config (B1) a site's join page reads: copy + prices from the\n * group row plus `paymentsReady`. When payments aren't wired the join flow drops\n * the payment step (C2) — the worker computes `paymentsReady` from the group's\n * Stripe keys + vault secret. Pure.\n */\nexport function joinConfig(group: JoinConfigGroup, paymentsReady: boolean): Record<string, unknown> {\n return {\n id: group.id,\n name: group.name,\n standardPriceCents: group.standardPriceCents ?? 0,\n foundingDiscountCents: group.foundingDiscountCents ?? 0,\n disclaimerText: group.disclaimerText ?? \"\",\n refundPolicyText: group.refundPolicyText ?? \"\",\n trustCopy: group.trustCopy ?? \"\",\n commitmentText: group.commitmentText ?? \"\",\n normsText: group.normsText ?? \"\",\n paymentsReady,\n };\n}\n","import type { ChapterCopy } from \"./copy.js\";\n\nexport const DEFAULT_ADMIN_COPY: ChapterCopy[\"admin\"] = {\n auth: {\n checking: \"Checking access…\",\n notAuthorized: \"Not authorized\",\n accountNotAuthorized: \"{account} isn't on the admin list.\",\n thisAccount: \"This account\",\n signOut: \"Sign out\",\n loading: \"Loading…\",\n signInNotConfigured: \"Sign-in not configured\",\n missingPublishableKey: \"No Clerk publishable key is set for this environment yet.\",\n noWorkspaces: \"No admin workspaces are configured.\",\n signInTagline: \"Admin sign-in, invite only\",\n },\n shell: {\n adminRole: \"admin\",\n adminConsole: \"Admin Console\",\n adminName: \"Admin\",\n navigationLabel: \"Admin navigation\",\n },\n workspaces: {\n dashboard: \"Dashboard\",\n overview: \"Overview\",\n billing: \"Billing\",\n people: \"People\",\n settings: \"Settings\",\n calendar: \"Calendar\",\n email: \"Email\",\n dashboardViewsLabel: \"Dashboard views\",\n settingsViewsLabel: \"Settings views\",\n collectionsLabel: \"CRM collections\",\n },\n dashboard: {\n loading: \"Loading dashboard…\",\n loadFailed: \"Couldn't load the dashboard\",\n upcomingCalls: \"Upcoming calls\",\n callsNeedAttention: \"{count} need attention\",\n noUpcomingCalls: \"No upcoming calls.\",\n drift: \"drift\",\n open: \"Open\",\n meet: \"Meet\",\n applications: \"Applications\",\n newMembers: \"New members\",\n newMembersUnavailable: \"Appears once billing is connected.\",\n revenueAdded: \"Revenue · annual run rate added\",\n revenue: \"Revenue\",\n revenueUnavailable: \"Appears once billing is connected.\",\n pipeline: \"Pipeline\",\n pipelineLabel: \"Membership pipeline\",\n thisWeek: \"+{count} this week\",\n noChange: \"no change\",\n activeMemberships: \"Active memberships\",\n annualRunRate: \"Annual run rate\",\n testMode: \"test mode\",\n },\n billing: {\n loading: \"Loading…\",\n loadFailed: \"Couldn't load billing\",\n notConfigured: \"Billing isn't configured. No Stripe key is vaulted for this group.\",\n active: \"Active\",\n annualized: \"Annualized\",\n renewingSoon: \"Renewing within 60 days\",\n pastDue: \"Past due\",\n subscriptions: \"Subscriptions\",\n testMode: \"Stripe test mode\",\n truncated: \"Showing the first 100 subscriptions. The list is truncated.\",\n name: \"Name\",\n email: \"Email\",\n application: \"Application\",\n subscription: \"Subscription\",\n cancelling: \"cancelling\",\n amount: \"Amount\",\n renews: \"Renews\",\n },\n availability: {\n loading: \"Loading…\",\n loadFailed: \"Couldn't load availability\",\n saved: \"Saved.\",\n title: \"Booking availability\",\n days: \"Days\",\n startHour: \"Start hour (0–24)\",\n endHour: \"End hour (0–24)\",\n slotMinutes: \"Slot length (minutes)\",\n minNoticeHours: \"Minimum notice (hours)\",\n windowDays: \"Booking window (days)\",\n timezone: \"Timezone\",\n summaryTemplate: \"Event summary template\",\n save: \"Save availability\",\n dayLabels: [\"Sun\", \"Mon\", \"Tue\", \"Wed\", \"Thu\", \"Fri\", \"Sat\"],\n },\n email: {\n loading: \"Loading…\",\n loadFailed: \"Couldn't load email config\",\n saved: \"Saved.\",\n sent: \"Sent {template} to {address}{redirected}.\",\n testFailed: \"Test send did not deliver.\",\n delivery: \"Delivery\",\n notificationAddress: \"Notification address\",\n replyTo: \"Reply-to\",\n debugInbox: \"Debug inbox\",\n debugInboxHint: \"Non-production sends redirect here.\",\n sendTest: \"Send test\",\n enabled: \"Enabled\",\n subject: \"Subject\",\n body: \"Body\",\n save: \"Save changes\",\n sendLog: \"Send log\",\n sentColumn: \"Sent\",\n templateColumn: \"Template\",\n statusColumn: \"Status\",\n toColumn: \"To\",\n failed: \"failed\",\n redirected: \"redirected\",\n delivered: \"delivered\",\n },\n meetings: {\n loading: \"Loading…\",\n loadFailed: \"Couldn't load meetings\",\n cancelConfirm: \"Cancel this call? The calendar provider notifies the attendee.\",\n newStartPrompt: \"New start time (for example, 2026-08-01 14:00):\",\n parseFailed: \"Couldn't parse that time.\",\n agenda: \"Agenda\",\n empty: \"No meetings.\",\n unknown: \"(unknown)\",\n drift: \"drift\",\n meet: \"Meet\",\n reschedule: \"Reschedule\",\n cancel: \"Cancel\",\n },\n network: {\n title: \"Share to a follower\",\n allowlistDescription: \"Sends the target's allowlisted fields. Its pipeline and account state remain local.\",\n sharing: \"Sharing…\",\n shareWith: \"Share with {target}\",\n shared: \"Shared with {target}.\",\n shareFailed: \"Could not share with {target}.\",\n deliveryFailed: \"Delivery failed.\",\n },\n records: {\n workflowMissing: \"This record is not linked to an application workflow.\",\n comms: \"Comms\",\n commsHistory: \"Comms history\",\n scheduling: \"Scheduling\",\n billing: \"Billing\",\n notes: \"Notes\",\n access: \"Access\",\n sharing: \"Sharing\",\n lifecycle: \"Access & lifecycle\",\n role: \"Role\",\n superAdminHint: \"Super-admin, managed in odla Studio.\",\n noAccount: \"No linked account yet. The role is set once they sign in.\",\n approve: \"Approve\",\n refund: \"Refund\",\n approved: \"Approved.\",\n refunded: \"Refund issued.\",\n roleSet: \"Role set to {role}.\",\n messageSent: \"Message sent.\",\n messageNotSent: \"Message was not sent.\",\n loadingTemplates: \"Loading templates…\",\n template: \"Template\",\n chooseTemplate: \"Choose a template…\",\n sending: \"Sending…\",\n send: \"Send\",\n noMessages: \"No messages recorded.\",\n message: \"Message\",\n newStartPrompt: \"New start time:\",\n parseFailed: \"Couldn't parse that time.\",\n cancelConfirm: \"Cancel this call?\",\n callCancelled: \"Call cancelled.\",\n callRescheduled: \"Call rescheduled.\",\n noApplication: \"No application is linked to this record.\",\n loadingMeetings: \"Loading meetings…\",\n noMeetings: \"No meetings recorded.\",\n joinMeeting: \"Join meeting\",\n openCalendar: \"Open calendar\",\n reschedule: \"Reschedule\",\n cancel: \"Cancel\",\n },\n};\n","import type { ChapterCopy } from \"./copy.js\";\n\nexport const DEFAULT_COMMON_COPY: ChapterCopy[\"common\"] = {\n loading: \"Loading…\",\n saved: \"Saved.\",\n unknown: \"(unknown)\",\n};\n\nexport const DEFAULT_JOIN_COPY: ChapterCopy[\"join\"] = {\n form: {\n submit: \"Submit application\",\n submitting: \"Submitting…\",\n submitFailed: \"Your application could not be submitted.\",\n unexpectedFailure: \"Something went wrong. Please try again.\",\n },\n booking: {\n unavailable: \"Scheduling is briefly unavailable. We'll reach out by email to arrange your call.\",\n loadFailed: \"Times are briefly unavailable. Please try again.\",\n slotTaken: \"That time was just taken. Here are the current openings.\",\n failed: \"The booking could not be completed.\",\n loading: \"Loading available times…\",\n book: \"Book this time\",\n booking: \"Booking…\",\n },\n payment: {\n setupFailed: \"Payment could not be set up. Please try again.\",\n preparing: \"Preparing secure payment…\",\n pending: \"Confirming your payment…\",\n processing: \"Processing…\",\n payAndContinue: \"Pay and continue\",\n incomplete: \"The payment did not complete. Please try again.\",\n consent: \"I have read and agree to the pricing and refund policy above.\",\n },\n done: {\n label: \"You're booked\",\n calendarInvite: \"A calendar invitation with the video call link is on its way to your email.\",\n memberArea: \"Go to your member area\",\n },\n};\n\nexport const DEFAULT_MEMBERS_COPY: ChapterCopy[\"members\"] = {\n loadFailed: \"Sign in is briefly unavailable. Please refresh.\",\n account: {\n signOut: \"Sign out\",\n adminConsole: \"Admin console\",\n },\n provisional: {\n cardLabel: \"Your Application\",\n applicationNeeded: \"One step remains\",\n applicationNeededBody: \"Your account is ready, and the application that completes it takes a few minutes.\",\n apply: \"Apply for membership\",\n refunded: \"Membership refunded\",\n refundedBody: \"Your fee has been refunded in full and your membership is canceled.\",\n active: \"Your membership is active.\",\n renews: \"Your membership is active and renews {date}.\",\n introductionCall: \"Your introduction call\",\n calendarInvite: \"A calendar invitation with the video call link is in your email.\",\n joinCall: \"Join the video call\",\n bookCall: \"Book your introduction call\",\n bookCallBody: \"Your application is in. Choose a time below, and a calendar invitation will reach your email.\",\n chooseTime: \"Choose a time\",\n },\n full: {\n welcome: \"Welcome back.\",\n },\n reschedule: {\n changeTime: \"Change your time\",\n unavailable: \"Scheduling is briefly unavailable. We'll reach out by email to arrange your call.\",\n loadFailed: \"Times are briefly unavailable. Please try again.\",\n slotGone: \"That time is no longer available. Please pick another.\",\n loading: \"Loading available times…\",\n noTimes: \"No open times right now. Please check back soon.\",\n rescheduling: \"Rescheduling…\",\n keepTime: \"Keep my current time\",\n },\n};\n","import { DEFAULT_ADMIN_COPY } from \"./copy-defaults-admin.js\";\nimport {\n DEFAULT_COMMON_COPY,\n DEFAULT_JOIN_COPY,\n DEFAULT_MEMBERS_COPY,\n} from \"./copy-defaults.js\";\nimport type { ChapterCopy, ChapterCopyInput } from \"./copy.js\";\n\n/** Complete default language resolved beneath partial Chapter copy overrides. */\nexport const DEFAULT_CHAPTER_COPY: ChapterCopy = {\n common: DEFAULT_COMMON_COPY,\n join: DEFAULT_JOIN_COPY,\n members: DEFAULT_MEMBERS_COPY,\n admin: DEFAULT_ADMIN_COPY,\n};\n\nconst isObject = (value: unknown): value is Record<string, unknown> =>\n Boolean(value) && typeof value === \"object\" && !Array.isArray(value);\n\nfunction mergeCopy<T>(base: T, input: unknown, path: string): T {\n if (typeof base === \"string\") {\n if (input === undefined) return base;\n if (typeof input !== \"string\") throw new Error(`${path}: must be a string`);\n return input as T;\n }\n if (Array.isArray(base)) {\n if (input === undefined) return [...base] as T;\n if (!Array.isArray(input) || !input.every((item) => typeof item === \"string\")) {\n throw new Error(`${path}: must be an array of strings`);\n }\n return [...input] as T;\n }\n if (!isObject(base)) return base;\n if (input !== undefined && !isObject(input)) throw new Error(`${path}: must be an object`);\n const overrides = input as Record<string, unknown> | undefined;\n const result: Record<string, unknown> = {};\n for (const [key, value] of Object.entries(base)) {\n result[key] = mergeCopy(value, overrides?.[key], `${path}.${key}`);\n }\n return result as T;\n}\n\n/** Resolve a partial copy contract and validate every supplied leaf. */\nexport function resolveChapterCopy(input?: ChapterCopyInput): ChapterCopy {\n return mergeCopy(DEFAULT_CHAPTER_COPY, input, \"defineChapter.copy\");\n}\n\n/** Replace `{name}` placeholders without interpreting the resulting text as HTML. */\nexport function formatChapterCopy(\n template: string,\n values: Readonly<Record<string, string | number>>,\n): string {\n return template.replace(/\\{([a-zA-Z][a-zA-Z0-9]*)\\}/g, (match, key: string) =>\n Object.prototype.hasOwnProperty.call(values, key) ? String(values[key]) : match,\n );\n}\n","// The CLI-consumable provisioning descriptor. It composes @odla-ai/crm's\n// integration (crm_* namespaces + crm_config seed + route probe) with the\n// chapter's own namespaces and a guarded `groups`-row seed, so a site's\n// odla.config.mjs lists ONE integration. The CLI reads it structurally\n// (matching OdlaIntegration) and merges schema/rules by namespace.\nimport { createCrmIntegration } from \"@odla-ai/crm\";\nimport type { Chapter } from \"./types\";\n\n/** Options for {@link createChapterIntegration}. */\nexport interface ChapterIntegrationOptions {\n /** CRM route mount point. Default \"/api/crm\". */\n basePath?: string;\n /** Seed timestamp override for reproducible builds/tests. */\n now?: number;\n}\n\ninterface IntegrationSeed {\n id: string;\n ns: string;\n key: { attr: string; value: string };\n attrs: Record<string, unknown>;\n}\n\n/** Structural match for the CLI's `OdlaIntegration`. */\nexport interface ChapterIntegrationDescriptor {\n id: string;\n title: string;\n npm: string;\n schema: { entities: Record<string, unknown>; links: Record<string, unknown> };\n rules: Record<string, unknown>;\n seeds: IntegrationSeed[];\n probes: Array<{ path: string; expectedStatus: number }>;\n /** Runbooks that bear on THIS chapter's role, so tooling can point at the\n * right procedure without the operator knowing it exists. A chapter declaring\n * `network.targets` is a leader hub; one declaring none is a follower site,\n * and the two follow different procedures for the same words (\"connect\",\n * \"share records\"). */\n runbooks: ChapterRunbookHints;\n}\n\n/** Which runbooks apply, and why. */\nexport interface ChapterRunbookHints {\n role: \"leader\" | \"follower\";\n /** Platform runbook slugs, most relevant first. */\n slugs: string[];\n /** One line a human or agent can act on. */\n guidance: string;\n}\n\n/**\n * Build the one CLI-consumable integration for a chapter/hub: the crm_*\n * namespaces + crm_config seed + route probe, merged with the chapter's own\n * namespaces and a guarded `groups`-row seed. Drop it in odla.config.mjs's\n * `integrations` array.\n */\nexport function createChapterIntegration(\n chapter: Chapter,\n options: ChapterIntegrationOptions = {},\n): ChapterIntegrationDescriptor {\n const basePath = options.basePath ?? \"/api/crm\";\n const now = options.now ?? Date.now();\n const emails = chapter.config.emails;\n\n const crmDesc = createCrmIntegration(chapter.crm, {\n basePath,\n now,\n ...(emails?.notificationEmail ? { notificationEmail: emails.notificationEmail } : {}),\n ...(emails?.replyTo ? { replyTo: emails.replyTo } : {}),\n ...(emails?.debugEmail ? { debugEmail: emails.debugEmail } : {}),\n });\n\n const seeds: IntegrationSeed[] = [...(crmDesc.seeds ?? [])];\n const group = chapter.groupSeed();\n if (group) {\n seeds.push({ id: \"group\", ns: \"groups\", key: { attr: \"id\", value: chapter.id }, attrs: { ...group, createdAt: now } });\n }\n\n // A leader declares where it pushes records; a follower declares nothing and\n // simply receives. That one fact decides which procedure applies.\n const isLeader = (chapter.config.network?.targets?.length ?? 0) > 0;\n const runbooks: ChapterRunbookHints = isLeader\n ? {\n role: \"leader\",\n slugs: [\"chapter-leader\", \"chapter-network\", \"crm\"],\n guidance:\n `${chapter.name} is a LEADER hub: it holds the CRM and shares records with its chapters. ` +\n `Read it with \\`odla-ai runbook get chapter-leader\\`.`,\n }\n : {\n role: \"follower\",\n slugs: [\"chapter-follower\", \"chapter-network\"],\n guidance:\n `${chapter.name} is a FOLLOWER site: it receives people and records from a leader hub. ` +\n `Read it with \\`odla-ai runbook get chapter-follower\\`.`,\n };\n\n return {\n id: \"chapter\",\n title: `Chapter: ${chapter.name}`,\n npm: \"@odla-ai/chapter\",\n schema: {\n entities: { ...crmDesc.schema.entities, ...chapter.schema.entities },\n links: { ...crmDesc.schema.links, ...chapter.schema.links },\n },\n rules: { ...crmDesc.rules, ...chapter.rules },\n seeds,\n probes: [...(crmDesc.probes ?? [])],\n runbooks,\n };\n}\n","// The chapter email pipeline: exactly-once delivery, a non-production fail-safe,\n// and template rendering. Every property here is easy to get wrong and expensive\n// to get wrong, so the correctness-critical decisions — the dedupe check (E1),\n// the dev-redirect / log-only fail-safe (E2), and template rendering (E4) — are\n// PURE and fully tested in this module. The worker supplies the transport +\n// odla-db and performs the actual send + emailLog write around these decisions.\n//\n// Chapter's operational templates ({ subject, text, enabled? }) are\n// transactional lifecycle mail by construction — a site owner edits the copy in\n// Settings but cannot reclassify one as marketing. Consent-gated marketing blasts\n// go through @odla-ai/crm, which owns the transactional-vs-marketing template\n// class as code (E3), so relabeling copy can never bypass the consent gate.\n\n/** One owner-editable template row on the group. `enabled` absent = enabled. */\nexport interface EmailTemplateRow {\n subject: string;\n text: string;\n enabled?: boolean;\n}\n\n/** The `groups`-row fields the email pipeline reads. */\nexport interface EmailGroup {\n id: string;\n name: string;\n replyTo: string;\n /** Non-prod debug inbox: all mail redirects here outside prod (E2). */\n debugEmail?: string;\n refundPolicyText?: string;\n commitmentText?: string;\n normsText?: string;\n emailTemplates: Record<string, EmailTemplateRow>;\n}\n\n/** `{{placeholder}}` substitution; unknown placeholders render empty. */\nexport function render(template: string, vars: Record<string, string>): string {\n return template.replace(/\\{\\{(\\w+)\\}\\}/g, (_, key: string) => vars[key] ?? \"\");\n}\n\n/** Group-level vars every template receives, under the caller's vars. */\nfunction groupVars(group: EmailGroup, vars: Record<string, string>): Record<string, string> {\n return {\n ...vars,\n refundPolicyText: group.refundPolicyText ?? \"\",\n commitmentText: group.commitmentText ?? \"\",\n normsText: group.normsText ?? \"\",\n };\n}\n\n/**\n * Re-render a template's body for history/preview (E4): the CRM comms history\n * reads back emails whose body predates `emailLog.body` by rendering the current\n * template with the recipient's vars. Same substitution + group vars as the send\n * path. `null` for an unknown template. Reflects the copy as it reads today, not\n * necessarily the exact bytes originally sent (only `emailLog.body` is byte-exact).\n */\nexport function renderTemplateBody(group: EmailGroup, template: string, vars: Record<string, string>): string | null {\n const tpl = group.emailTemplates?.[template];\n if (!tpl) return null;\n return render(tpl.text, groupVars(group, vars));\n}\n\n/**\n * E1 (exactly-once): given the prior `emailLog` rows for a `dedupeKey`, has the\n * mail already been delivered? A prior row with **no error** means yes — the\n * caller short-circuits the resend. Failure rows (which carry an `error` and are\n * written without the dedupe mutationId) do not count, so a retry after a failure\n * can still succeed.\n */\nexport function isAlreadySent(priorRows: ReadonlyArray<{ error?: unknown }>): boolean {\n return priorRows.some((row) => !row.error);\n}\n\n/** The pure delivery decision produced by {@link planDelivery}. */\nexport type DeliveryDecision =\n | { deliver: false; reason: \"template-missing\" | \"disabled\" }\n | {\n deliver: true;\n /** Which transport to use — `log-only` records the send but delivers nothing. */\n transport: \"cloudflare\" | \"log-only\";\n to: string;\n subject: string;\n text: string;\n /** True when redirected to the non-prod debug inbox. */\n redirected: boolean;\n };\n\n/**\n * The pure delivery decision (E2 fail-safe + E3 enabled). Given the env, group,\n * template, recipient, and whether a real Cloudflare transport is wired:\n * - missing template → not delivered (`template-missing`);\n * - disabled template and not forced → not delivered (`disabled`);\n * - **non-prod with a debug inbox** → REDIRECT to it, `\"[dev] \"` subject prefix,\n * a dev-redirect note in the body, so test applicants never receive real mail;\n * - **non-prod with NO debug inbox** → force `log-only` (deliver nothing) — the\n * fail-safe that protects every site's test data;\n * - prod → deliver via the real transport (`cloudflare` if wired, else `log-only`).\n */\nexport function planDelivery(input: {\n envName: string;\n group: EmailGroup;\n template: string;\n to: string;\n vars: Record<string, string>;\n /** Whether a Cloudflare Email Service transport (binding + verified from) is wired. */\n cloudflareReady: boolean;\n /** The admin test route may send a disabled template. */\n force?: boolean;\n}): DeliveryDecision {\n const tpl = input.group.emailTemplates?.[input.template];\n if (!tpl) return { deliver: false, reason: \"template-missing\" };\n if (tpl.enabled === false && !input.force) return { deliver: false, reason: \"disabled\" };\n\n const vars = groupVars(input.group, input.vars);\n const isProd = input.envName === \"prod\";\n const redirect = !isProd && !!input.group.debugEmail;\n const transport: \"cloudflare\" | \"log-only\" =\n !isProd && !redirect ? \"log-only\" : input.cloudflareReady ? \"cloudflare\" : \"log-only\";\n const to = redirect ? (input.group.debugEmail as string) : input.to;\n const subject = (redirect ? \"[dev] \" : \"\") + render(tpl.subject, vars);\n const text = redirect\n ? `(dev redirect; original recipient: ${input.to})\\n\\n` + render(tpl.text, vars)\n : render(tpl.text, vars);\n return { deliver: true, transport, to, subject, text, redirected: redirect };\n}\n","// sendTemplated — the operational-email orchestration that wires the pure email\n// decisions (./email: isAlreadySent E1, planDelivery E2/E3) to odla-db + a\n// transport. Structural (takes a ChapterDb + a sender), so it's FakeDb-testable\n// like submitApplication/projectSharedRecord. The worker's lifecycle routes call\n// it to send the prep / payment-confirmation / admin-alert mail.\n//\n// Exactly-once is enforced twice: a prior successful emailLog row short-circuits\n// the resend (query + isAlreadySent), AND the success row is written under the\n// `email:${dedupeKey}` mutationId so a racing double-send dedupes at the db layer.\n// Failure rows omit the mutationId, so a retry after a transient failure can send.\nimport { isAlreadySent, planDelivery } from \"./email\";\nimport type { EmailGroup } from \"./email\";\nimport type { ChapterDb } from \"./types\";\n\n/** The canonical lifecycle email templates chapter fires, in send-order. Owner-\n * editable copy lives on the group row; this is the fixed set the admin email\n * editor + test route iterate. */\nexport const EMAIL_TEMPLATE_NAMES = [\"adminNotification\", \"paymentConfirmation\", \"prepEmail\", \"onboardingInvite\"] as const;\n\n/** One of {@link EMAIL_TEMPLATE_NAMES}. */\nexport type EmailTemplateName = (typeof EMAIL_TEMPLATE_NAMES)[number];\n\n/** The mail transport (the worker's Cloudflare SEND_EMAIL binding). */\nexport interface MailSender {\n send(payload: { from: string; to: string[]; subject: string; text?: string; replyTo?: string }): Promise<{ messageId: string }>;\n}\n\n/** Deps for {@link sendTemplated} — the db, the env name (drives the fail-safe),\n * the transport + from (absent ⇒ log-only), and injected clock/id. */\nexport interface NotifyDeps {\n db: ChapterDb;\n envName: string;\n sender?: MailSender;\n from?: string;\n now: () => number;\n newId: () => string;\n}\n\n/** One templated send. `dedupeKey` is the exactly-once key. */\nexport interface NotifyInput {\n group: EmailGroup;\n template: string;\n to: string;\n vars: Record<string, string>;\n dedupeKey: string;\n applicationId?: string;\n /** The admin test route may send a disabled template. */\n force?: boolean;\n}\n\n/** The outcome. `sent:true` includes the already-sent short-circuit. */\nexport interface NotifyResult {\n sent: boolean;\n reason?: string;\n}\n\n/**\n * Send a templated lifecycle email exactly once. Short-circuits on a prior\n * successful send; otherwise plans delivery (dev fail-safe applies), sends via the\n * transport when one is wired and the plan calls for it, and records an emailLog\n * row either way (success keyed for exactly-once, failure unkeyed for retry).\n */\nexport async function sendTemplated(deps: NotifyDeps, input: NotifyInput): Promise<NotifyResult> {\n const { emailLog } = await deps.db.query({ emailLog: { $: { where: { dedupeKey: input.dedupeKey } } } });\n const prior = (Array.isArray(emailLog) ? emailLog : []) as Array<{ error?: unknown }>;\n if (isAlreadySent(prior)) return { sent: true, reason: \"already-sent\" };\n\n const cloudflareReady = Boolean(deps.sender && deps.from);\n const decision = planDelivery({\n envName: deps.envName,\n group: input.group,\n template: input.template,\n to: input.to,\n vars: input.vars,\n cloudflareReady,\n force: input.force,\n });\n if (!decision.deliver) return { sent: false, reason: decision.reason };\n\n let error: string | undefined;\n let messageId: string | undefined;\n if (decision.transport === \"cloudflare\" && deps.sender && deps.from) {\n try {\n const res = await deps.sender.send({\n from: deps.from,\n to: [decision.to],\n subject: decision.subject,\n text: decision.text,\n replyTo: input.group.replyTo,\n });\n messageId = res.messageId;\n } catch (e) {\n error = e instanceof Error ? e.message : String(e);\n }\n }\n\n const id = deps.newId();\n const row: Record<string, unknown> = {\n id,\n groupId: input.group.id,\n to: decision.to,\n template: input.template,\n subject: decision.subject,\n body: decision.text,\n transport: decision.transport,\n redirected: decision.redirected,\n dedupeKey: input.dedupeKey,\n sentAt: deps.now(),\n ...(input.applicationId ? { applicationId: input.applicationId } : {}),\n ...(messageId ? { messageId } : {}),\n ...(error ? { error } : {}),\n };\n await deps.db.transact([{ t: \"update\", ns: \"emailLog\", id, attrs: row }], error ? undefined : { mutationId: `email:${input.dedupeKey}` });\n return error ? { sent: false, reason: error } : { sent: true };\n}\n\n/** Project a `groups` row into the {@link EmailGroup} the email pipeline reads. */\nexport function emailGroupFrom(row: Record<string, unknown>): EmailGroup {\n const str = (v: unknown): string | undefined => (typeof v === \"string\" ? v : undefined);\n const templates = row.emailTemplates && typeof row.emailTemplates === \"object\" ? row.emailTemplates : {};\n return {\n id: String(row.id),\n name: String(row.name ?? \"\"),\n replyTo: str(row.replyTo) ?? \"\",\n debugEmail: str(row.debugEmail),\n refundPolicyText: str(row.refundPolicyText),\n commitmentText: str(row.commitmentText),\n normsText: str(row.normsText),\n emailTemplates: templates as EmailGroup[\"emailTemplates\"],\n };\n}\n","// Payments primitives. The webhook-integrity check below is security-critical and\n// easy to get wrong, so it is pure and tested here; the worker wires a payments\n// provider (Stripe first — subscription create, webhook ingest, refund) around\n// it, and every resulting db write carries an event-derived mutationId for\n// exactly-once. Sites that don't charge omit payments entirely (paymentsReady:\n// false), so nothing here is imported unless a chapter runs the payment flow.\n\n/** Parse a Stripe-style `Stripe-Signature` header (`t=<unix>,v1=<hex>`). */\nfunction parseSigHeader(header: string): { t?: string; v1?: string } {\n const parts: Record<string, string> = {};\n for (const p of header.split(\",\")) {\n const [k, v] = p.split(\"=\", 2);\n if (k && v !== undefined) parts[k] = v;\n }\n return { t: parts.t, v1: parts.v1 };\n}\n\nfunction toHex(buf: ArrayBuffer): string {\n return [...new Uint8Array(buf)].map((b) => b.toString(16).padStart(2, \"0\")).join(\"\");\n}\n\n/** Constant-time compare of two equal-length hex strings. */\nfunction timingSafeEqual(a: string, b: string): boolean {\n if (a.length !== b.length) return false;\n let diff = 0;\n for (let i = 0; i < a.length; i++) diff |= a.charCodeAt(i) ^ b.charCodeAt(i);\n return diff === 0;\n}\n\n/**\n * Verify a Stripe webhook signature (C3): HMAC-SHA256 over `` `${t}.${payload}` ``\n * with the endpoint signing secret, a replay window (default 5 minutes), and a\n * constant-time compare. Package-enforced — never left to a site. Returns `false`\n * (never throws) on a malformed header, a non-numeric or stale timestamp, or a\n * signature mismatch. `now`/`toleranceSec` are injectable for tests.\n */\nexport async function verifyStripeSignature(\n payload: string,\n header: string,\n secret: string,\n opts: { now?: number; toleranceSec?: number } = {},\n): Promise<boolean> {\n const { t, v1 } = parseSigHeader(header);\n if (!t || !v1) return false;\n const ts = Number(t);\n if (!Number.isFinite(ts)) return false;\n const nowSec = (opts.now ?? Date.now()) / 1000;\n const tolerance = opts.toleranceSec ?? 300;\n if (Math.abs(nowSec - ts) > tolerance) return false;\n\n const enc = new TextEncoder();\n const key = await crypto.subtle.importKey(\"raw\", enc.encode(secret), { name: \"HMAC\", hash: \"SHA-256\" }, false, [\"sign\"]);\n const mac = await crypto.subtle.sign(\"HMAC\", key, enc.encode(`${t}.${payload}`));\n return timingSafeEqual(toHex(mac), v1);\n}\n\n// ── readiness + Stripe wire helpers ──\n\n/** A group row's payment configuration, as far as readiness cares. */\nexport interface PaymentsGroup {\n stripePublishableKey?: string | null;\n stripePriceId?: string | null;\n}\n\n/** Whether a group can take payment: a publishable key + a price id (both on the\n * group row) AND a secret key (the vault). Anything missing drops the join\n * flow's payment step (paymentsReady:false) rather than half-charging. */\nexport function paymentsReady(group: PaymentsGroup, hasSecretKey: boolean): boolean {\n return Boolean(group.stripePublishableKey && group.stripePriceId && hasSecretKey);\n}\n\n/** Form-encode params for Stripe's x-www-form-urlencoded API, expanding one level\n * of nested objects into bracket syntax (`metadata[applicationId]=...`). */\nexport function stripeForm(params: Record<string, unknown>): string {\n const out = new URLSearchParams();\n for (const [k, v] of Object.entries(params)) {\n if (v === undefined || v === null) continue;\n if (typeof v === \"object\") {\n for (const [k2, v2] of Object.entries(v as Record<string, unknown>)) {\n if (v2 !== undefined && v2 !== null) out.append(`${k}[${k2}]`, String(v2));\n }\n } else {\n out.append(k, String(v));\n }\n }\n return out.toString();\n}\n\n/** The Stripe idempotency key for creating an application's subscription — one\n * per application, so a client retry can't orphan a second subscription. */\nexport function subscriptionIdempotencyKey(applicationId: string): string {\n return `sub:${applicationId}`;\n}\n\n/** The db mutationId for a webhook-driven write — exactly-once per Stripe event,\n * so replays are deduped at the db layer. */\nexport function webhookMutationId(eventId: string): string {\n return `stripe:${eventId}`;\n}\n\n// ── webhook normalization (pure; the worker owns the db lookup + writes) ──\n\n/** A raw Stripe event, as far as normalization cares. */\nexport interface StripeEvent {\n id: string;\n type: string;\n data?: { object?: Record<string, unknown> };\n}\n\n/** A normalized, provider-agnostic webhook event. `kind` drives the db write;\n * the application is resolved from `applicationId` (metadata) or `customerId`. */\nexport type WebhookEvent =\n | { kind: \"first_payment\"; applicationId?: string; customerId?: string; renewalAt?: number }\n | { kind: \"renewal\"; applicationId?: string; customerId?: string; renewalAt?: number }\n | { kind: \"refunded\"; applicationId?: string; customerId?: string }\n | { kind: \"canceled\"; applicationId?: string; customerId?: string }\n | { kind: \"ignored\"; type: string };\n\n/** Resolve the application reference on a Stripe object: `applicationId` from\n * metadata (direct, then subscription_details, then nested\n * parent.subscription_details), plus the customer id for the db fallback. */\nexport function findApplicationRef(obj: Record<string, unknown>): { applicationId?: string; customerId?: string } {\n const metaOf = (v: unknown): Record<string, unknown> =>\n v && typeof v === \"object\" ? ((v as Record<string, unknown>).metadata as Record<string, unknown>) ?? {} : {};\n const pick = (m: Record<string, unknown>): string | undefined =>\n typeof m.applicationId === \"string\" ? m.applicationId : undefined;\n const applicationId =\n pick(metaOf(obj)) ?? pick(metaOf(obj.subscription_details)) ?? pick(metaOf((obj.parent as Record<string, unknown> | undefined)?.subscription_details));\n const customerId = typeof obj.customer === \"string\" ? obj.customer : undefined;\n return { ...(applicationId ? { applicationId } : {}), ...(customerId ? { customerId } : {}) };\n}\n\n/** Normalize a verified Stripe event into a {@link WebhookEvent}. `invoice.paid`\n * splits into first_payment vs renewal by `billing_reason`; refunds and\n * cancellations map directly; everything else is ignored (acked, not retried). */\nexport function normalizeWebhookEvent(event: StripeEvent): WebhookEvent {\n const obj = event.data?.object ?? {};\n const ref = findApplicationRef(obj);\n switch (event.type) {\n case \"invoice.paid\": {\n const lines = ((obj.lines as Record<string, unknown> | undefined)?.data as Array<Record<string, unknown>> | undefined) ?? [];\n const periodEnd = (lines[0]?.period as Record<string, unknown> | undefined)?.end;\n const renewalAt = typeof periodEnd === \"number\" ? periodEnd * 1000 : undefined;\n const kind = obj.billing_reason === \"subscription_create\" ? \"first_payment\" : \"renewal\";\n return { kind, ...ref, ...(renewalAt !== undefined ? { renewalAt } : {}) };\n }\n case \"charge.refunded\":\n return { kind: \"refunded\", ...ref };\n case \"customer.subscription.deleted\":\n return { kind: \"canceled\", ...ref };\n default:\n return { kind: \"ignored\", type: event.type };\n }\n}\n\n// ── webhook write-set builders (the authoritative writers of paid/refunded) ──\n\n/** First-payment patch: advance submitted→paid_pending_vetting (never any other\n * transition) and record the renewal date. Empty when nothing changed, so the\n * caller can skip the write. */\nexport function firstPaymentPatch(currentStatus: string, renewalAt?: number): { status?: \"paid_pending_vetting\"; renewalAt?: number } {\n return {\n ...(currentStatus === \"submitted\" ? { status: \"paid_pending_vetting\" as const } : {}),\n ...(renewalAt !== undefined ? { renewalAt } : {}),\n };\n}\n\n/** Renewal-invoice patch: just the new renewal date. */\nexport function renewalPatch(renewalAt: number): { renewalAt: number } {\n return { renewalAt };\n}\n\n/** Refund patch — the SOLE writer of status \"refunded\" (the admin refund route\n * issues the Stripe refund but never sets this; the webhook does). */\nexport function refundedPatch(): { status: \"refunded\" } {\n return { status: \"refunded\" };\n}\n\n/** Subscription-cancellation patch. */\nexport function canceledPatch(): { canceled: true } {\n return { canceled: true };\n}\n","// The leader → follower CRM projection (push model). The leader curates people,\n// businesses, and other configured CRM records and pushes a deliberately\n// allowlisted field set into THIS follower's crm_record.\n//\n// Invariants (package-enforced so no site re-derives them):\n// - One-way: the chapter never writes back to the hub through this path.\n// - Idempotent: every received record carries the leader's stable record id. A\n// provenance tag maps that id to the local CRM record, so a re-share updates\n// instead of duplicating even after an email/domain changes.\n// - Natural-key convergence: a person unifies by primary email and a business\n// by its configured domain slot (then name), so an arriving applicant and a\n// prior leader share compose into one person.\n// - Operational state stays local. Pipeline/account/billing state is not copied\n// into another site's authority; the push transfers the shared record fields.\n//\n// Reuses @odla-ai/crm's record ops (full validation via crm.prepare), driven by\n// the resolved chapter CRM engine + the structural ChapterDb.\nimport { createRecord, updateRecord } from \"@odla-ai/crm\";\nimport type { Crm, CrmRecord } from \"@odla-ai/crm\";\nimport type { ChapterDb, ResolvedNetworkTarget } from \"./types\";\n\n/** The contact data the hub shares for a prospect. `hubRecordId` is the stable\n * idempotency key (the hub's crm_record id). */\nexport interface SharedPerson {\n email: string;\n name?: string;\n firstName?: string;\n lastName?: string;\n phone?: string;\n linkedin?: string;\n hubRecordId: string;\n}\n\n/** Convenience wire shape for sharing the default `company` CRM type. */\nexport interface SharedBusiness {\n type: \"company\";\n name: string;\n domain?: string;\n industry?: string;\n location?: string;\n linkedin?: string;\n notes?: string;\n hubRecordId: string;\n}\n\n/** Versioned generic wire shape. `input` is validated against the follower's\n * own CRM type before any write, so a leader cannot smuggle undeclared fields. */\nexport interface SharedRecord {\n version: 1;\n type: string;\n hubRecordId: string;\n input: Record<string, unknown>;\n}\n\n/** Safe defaults when a target does not declare an explicit field allowlist. */\nexport const DEFAULT_SHARE_FIELDS: Readonly<Record<string, readonly string[]>> = {\n person: [\"name\", \"email\", \"firstName\", \"lastName\", \"phone\", \"linkedin\"],\n company: [\"name\", \"domain\", \"industry\", \"location\", \"linkedin\", \"notes\"],\n};\n\n/** Map a shared prospect to a crm `person` input (only the fields the default\n * person type accepts). Name falls back to first+last, then the email. */\nexport function sharedPersonInput(person: SharedPerson): Record<string, unknown> {\n const email = person.email.toLowerCase();\n const fullName = [person.firstName, person.lastName].filter(Boolean).join(\" \").trim();\n const input: Record<string, unknown> = { name: person.name ?? fullName ?? email, email };\n if (input.name === \"\") input.name = email;\n if (person.firstName) input.firstName = person.firstName;\n if (person.lastName) input.lastName = person.lastName;\n if (person.phone) input.phone = person.phone;\n if (person.linkedin) input.linkedin = person.linkedin;\n return input;\n}\n\n/** Deps for the projection — the resolved CRM engine, the structural db, and\n * injected clock/id (deterministic in tests). */\nexport interface ProjectionDeps {\n crm: Crm;\n db: ChapterDb;\n now: () => number;\n newId: () => string;\n}\n\nfunction shortHash(value: string): string {\n let a = 0x811c9dc5;\n let b = 0x9e3779b9;\n for (let i = 0; i < value.length; i += 1) {\n const n = value.charCodeAt(i);\n a = Math.imul(a ^ n, 0x01000193);\n b = Math.imul(b ^ n, 0x85ebca6b);\n }\n return `${(a >>> 0).toString(36)}${(b >>> 0).toString(36)}`;\n}\n\n/** CRM provenance tag used as the durable leader-id → local-record mapping. */\nexport function networkSourceTag(type: string, hubRecordId: string): string {\n const typeKey = type.toLowerCase();\n const readable = /^[a-z0-9_-]+$/.test(hubRecordId);\n const raw = `network:${typeKey}:${hubRecordId}`;\n if (readable && raw.length <= 64) return raw;\n return `network:${typeKey.slice(0, 20)}:${shortHash(`${type}\\u0000${hubRecordId}`)}`;\n}\n\n/** Normalize the backwards-compatible person/business shapes into the versioned\n * generic record envelope the receiver writes. */\nexport function normalizeSharedRecord(record: SharedPerson | SharedBusiness | SharedRecord): SharedRecord {\n if (\"input\" in record) return { version: 1, type: record.type, hubRecordId: record.hubRecordId, input: record.input };\n if (\"type\" in record && record.type === \"company\") {\n const input: Record<string, unknown> = { name: record.name };\n for (const key of [\"domain\", \"industry\", \"location\", \"linkedin\", \"notes\"] as const) {\n if (record[key]) input[key] = record[key];\n }\n return { version: 1, type: \"company\", hubRecordId: record.hubRecordId, input };\n }\n return { version: 1, type: \"person\", hubRecordId: record.hubRecordId, input: sharedPersonInput(record) };\n}\n\n/** Build the allowlisted payload sent from one leader CRM record to a target.\n * A target with an explicit `fields` map only accepts the types it lists. */\nexport function sharedRecordFromCrm(crm: Crm, record: CrmRecord, target: ResolvedNetworkTarget): SharedRecord {\n if (target.fields && !target.fields[record.type]) {\n throw new Error(`${target.name} does not accept \"${record.type}\" records`);\n }\n const def = crm.type(record.type);\n const fields = target.fields?.[record.type] ?? DEFAULT_SHARE_FIELDS[record.type];\n if (!fields) {\n throw new Error(`${target.name} requires an explicit field allowlist for \"${record.type}\" records`);\n }\n const nameField = def.nameField ?? \"name\";\n const input: Record<string, unknown> = {};\n for (const field of new Set([nameField, ...fields])) {\n const value = record.fields?.[field];\n if (value !== undefined) input[field] = value;\n }\n if (input[nameField] === undefined) input[nameField] = record.name;\n return { version: 1, type: record.type, hubRecordId: record.id, input };\n}\n\n// Resolve an existing crm_record person by lowercased primaryEmail and update it,\n// else create one under the given mutationId. The shared core of every one-way\n// person projection (hub share, arriving applicant).\nasync function upsertPerson(\n deps: ProjectionDeps,\n opts: { email: string; input: Record<string, unknown>; mutationId: string },\n): Promise<{ recordId: string }> {\n const email = opts.email.toLowerCase();\n const crmDeps = { crm: deps.crm, db: deps.db as never, now: deps.now, newId: deps.newId };\n const { crm_record } = await deps.db.query({ crm_record: { $: { where: { type: \"person\", primaryEmail: email }, limit: 1 } } });\n const existing = crm_record?.[0];\n if (existing && typeof existing.id === \"string\") {\n await updateRecord(crmDeps, { id: existing.id, input: opts.input });\n return { recordId: existing.id };\n }\n const created = await createRecord(crmDeps, { type: \"person\", input: opts.input, mutationId: opts.mutationId });\n return { recordId: created.id };\n}\n\nasync function findSharedRecord(\n deps: ProjectionDeps,\n record: SharedRecord,\n tag: string,\n): Promise<Record<string, unknown> | undefined> {\n const mapped = await deps.db.query({ crm_tag: { $: { where: { tag }, limit: 1 } } });\n const mappedId = mapped.crm_tag?.[0]?.recordId;\n if (typeof mappedId === \"string\") {\n const found = await deps.db.query({ crm_record: { $: { where: { id: mappedId }, limit: 1 } } });\n if (found.crm_record?.[0]) return found.crm_record[0];\n }\n const def = deps.crm.type(record.type);\n const emailField = def.emailField;\n if (emailField && typeof record.input[emailField] === \"string\") {\n const primaryEmail = record.input[emailField].toLowerCase();\n const found = await deps.db.query({\n crm_record: { $: { where: { type: record.type, primaryEmail }, limit: 1 } },\n });\n if (found.crm_record?.[0]) return found.crm_record[0];\n }\n const domain = record.input.domain;\n const domainSlot = def.fields.domain?.slot;\n if (typeof domain === \"string\" && domainSlot) {\n const found = await deps.db.query({\n crm_record: { $: { where: { type: record.type, [domainSlot]: domain }, limit: 1 } },\n });\n if (found.crm_record?.[0]) return found.crm_record[0];\n }\n const nameField = def.nameField ?? \"name\";\n const name = record.input[nameField];\n if (record.type === \"company\" && typeof name === \"string\" && name.trim()) {\n const found = await deps.db.query({\n crm_record: { $: { where: { type: record.type, name: name.trim() }, limit: 1 } },\n });\n if (found.crm_record?.[0]) return found.crm_record[0];\n }\n return undefined;\n}\n\n/**\n * Upsert a leader-shared CRM record into this follower. Accepts the original\n * person wire shape plus the versioned generic shape. Re-shares resolve through\n * a durable provenance tag; people/businesses also converge by natural key.\n */\nexport async function projectSharedRecord(\n deps: ProjectionDeps,\n shared: SharedPerson | SharedBusiness | SharedRecord,\n): Promise<{ recordId: string }> {\n const record = normalizeSharedRecord(shared);\n if (!record.type.trim() || !record.hubRecordId.trim()) {\n throw new Error(\"type and hubRecordId must be non-empty\");\n }\n const tag = networkSourceTag(record.type, record.hubRecordId);\n const crmDeps = { crm: deps.crm, db: deps.db as never, now: deps.now, newId: deps.newId };\n const existing = await findSharedRecord(deps, record, tag);\n let recordId: string;\n if (existing && typeof existing.id === \"string\") {\n // Do not use one permanent mutation id for updates: a later leader edit is a\n // new desired state and must not be mistaken for a retry of an older push.\n await updateRecord(crmDeps, { id: existing.id, input: record.input });\n recordId = existing.id;\n } else {\n // A deterministic id makes concurrent first deliveries converge even when\n // both requests observe no provenance row before either transaction lands.\n recordId = `network_${shortHash(`${record.type}\\u0000${record.hubRecordId}`)}`;\n await createRecord({ ...crmDeps, newId: () => recordId }, {\n type: record.type,\n input: record.input,\n mutationId: `share-create:${tag}`,\n });\n }\n await deps.db.transact(\n [{ t: \"update\", ns: \"crm_tag\", id: tag, attrs: { key: `${recordId}:${tag}`, recordId, tag, createdAt: deps.now() } }],\n { mutationId: `share-map:${tag}:${recordId}` },\n );\n return { recordId };\n}\n\n/** An arriving applicant, as far as the CRM projection cares. `extra` carries the\n * site-configured `crmFields` (values from the application), merged on top of the\n * built-in identity/contact set. */\nexport interface Applicant {\n applicationId: string;\n email: string;\n firstName?: string;\n lastName?: string;\n phone?: string;\n linkedin?: string;\n extra?: Record<string, unknown>;\n}\n\n/**\n * Project an arriving applicant into this chapter's `crm_record`, so a new\n * application shows up in the CRM immediately, unified by email with any prior\n * record. Idempotent per application (`apply:${applicationId}`). Best-effort at\n * the call site — a projection failure never fails the application.\n *\n * `extra` fields (a site's `crmFields`) are merged on top of the base person. If\n * an extra field is not on the crm person type, crm validation throws — so the\n * projection retries with the base person alone, ensuring a misconfigured\n * enrichment never silently drops the applicant from the CRM entirely.\n */\nexport async function projectApplicant(deps: ProjectionDeps, applicant: Applicant): Promise<{ recordId: string }> {\n const base = sharedPersonInput({\n email: applicant.email,\n firstName: applicant.firstName,\n lastName: applicant.lastName,\n phone: applicant.phone,\n linkedin: applicant.linkedin,\n hubRecordId: applicant.applicationId,\n });\n const mutationId = `apply:${applicant.applicationId}`;\n const extra = applicant.extra ?? {};\n if (Object.keys(extra).length === 0) return upsertPerson(deps, { email: applicant.email, input: base, mutationId });\n try {\n return await upsertPerson(deps, { email: applicant.email, input: { ...base, ...extra }, mutationId });\n } catch {\n return upsertPerson(deps, { email: applicant.email, input: base, mutationId });\n }\n}\n","// The OPERATIONAL slice of the one-way person projection: applications + $users\n// -> crm_record, mirroring pipeline stage, a billing snapshot, and the Clerk\n// identity link. `network.ts` projects identity/contact on submit; this adds the\n// operational state the admin surface and the hub read from, so the pipeline\n// stays authoritative in `applications.status` while the CRM carries one\n// relationship surface over the person. Fired AFTER the authoritative write and\n// wrapped in `.catch` at every call site: a CRM hiccup never 5xxs a lifecycle op.\n//\n// Idempotent by construction — every sync resolves the person by lowercased\n// primary email first, so re-running (including the backfill route) updates in\n// place. Generalized from the reference site: the field set is the site's\n// configured `crmFields`, not a hardcoded list.\nimport { createRecord, updateRecord, setStage, linkIdentity } from \"@odla-ai/crm\";\nimport { sharedPersonInput } from \"./network\";\nimport type { ProjectionDeps } from \"./network\";\nimport type { Chapter, ChapterDb } from \"./types\";\n\nconst str = (v: unknown): string => (typeof v === \"string\" ? v : v == null ? \"\" : String(v));\n\n/** Build the projection deps from a worker request's db + context.\n *\n * Shared so every route that writes `applications.status` mirrors it the same\n * way. Status is written from four places (admin approve, admin PATCH, the\n * Stripe webhook, and booking) and the admin dashboard reads its pipeline from\n * `crm_record.stage`, so a route that skips the mirror silently reports the\n * wrong funnel. */\nexport const crmDeps = (\n db: ChapterDb,\n ctx: { chapter: Chapter },\n): ProjectionDeps & { chapter: Chapter } => ({\n crm: ctx.chapter.crm,\n db,\n now: () => Date.now(),\n newId: () => crypto.randomUUID(),\n chapter: ctx.chapter,\n});\n\n/** The crm `person` input for one application row: the built-in identity/contact\n * fields plus each configured `crmFields` value present on the row. */\nexport function personInputFromApp(chapter: Chapter, app: Record<string, unknown>): Record<string, unknown> {\n const input = sharedPersonInput({\n email: str(app.email),\n firstName: str(app.firstName) || undefined,\n lastName: str(app.lastName) || undefined,\n phone: str(app.phone) || undefined,\n linkedin: str(app.linkedin) || undefined,\n hubRecordId: str(app.id),\n });\n for (const f of chapter.application.crmFields) {\n if (app[f] !== undefined) input[f] = app[f];\n }\n if (app.id !== undefined) input.applicationId = str(app.id);\n return input;\n}\n\n// Promoted billing-facet columns derived from the application's Stripe fields +\n// status. Written directly (not through createRecord/updateRecord, which never\n// touch promoted columns), so they never clobber the person input.\nfunction billingColumns(app: Record<string, unknown>): Record<string, unknown> {\n const status = str(app.status);\n const paid = Boolean(app.stripeSubscriptionId) && status !== \"refunded\";\n const billingStatus = status === \"refunded\" ? \"refunded\" : app.canceled === true ? \"canceled\" : paid ? \"active\" : \"none\";\n const cols: Record<string, unknown> = { billingStatus };\n if (app.stripeCustomerId) cols.stripeCustomerId = str(app.stripeCustomerId);\n if (app.stripeSubscriptionId) cols.subscriptionId = str(app.stripeSubscriptionId);\n if (typeof app.renewalAt === \"number\") cols.renewalAt = app.renewalAt;\n return cols;\n}\n\n/** Upsert the person record for one application (or a synthetic `{ email,\n * firstName }` account row) and mirror its stage, billing snapshot, and Clerk\n * identity. `stage` is the `applications.status` to mirror — omit for\n * account-only rows not in the pipeline. Throws on failure (the backfill route\n * counts; the operational call sites wrap in `.catch`). */\nexport async function syncApplicationToCrm(\n deps: ProjectionDeps & { chapter: Chapter },\n opts: { app: Record<string, unknown>; stage?: string },\n): Promise<string | null> {\n const emailKey = str(opts.app.email).toLowerCase();\n if (!emailKey) return null;\n // The narrower shape the crm record helpers take (no chapter).\n const recordDeps = { crm: deps.crm, db: deps.db as never, now: deps.now, newId: deps.newId };\n const input = personInputFromApp(deps.chapter, opts.app);\n\n const { crm_record } = await deps.db.query({\n crm_record: { $: { where: { type: \"person\", primaryEmail: emailKey }, limit: 1 } },\n });\n const existing = crm_record?.[0] ?? null;\n const stage = opts.stage || undefined;\n\n let recordId: string;\n if (existing && typeof existing.id === \"string\") {\n recordId = existing.id;\n await updateRecord(recordDeps, { id: recordId, input });\n } else {\n const created = await createRecord(recordDeps, { type: \"person\", input, ...(stage ? { stage } : {}) });\n recordId = created.id;\n }\n\n // Stage mirror: move only on an actual change, under a stable mutationId so a\n // replay never piles up duplicate stage_change activities. Best-effort — a\n // stage not on the crm person type must not fail the whole sync.\n if (existing && stage && existing.stage !== stage) {\n await setStage(recordDeps, { id: recordId, to: stage, authorId: \"system\", mutationId: `crm:stage:${recordId}:${stage}` }).catch(() => undefined);\n }\n\n await deps.db.transact([{ t: \"update\", ns: \"crm_record\", id: recordId, attrs: billingColumns(opts.app) }]);\n\n // Identity link: stamps clerkUserId when a $users row matches the email; a\n // no-op until the person has an account. Best-effort — a just-created record\n // can briefly lag the read, and the link retries on the next sync.\n await linkIdentity(recordDeps, { recordId, email: emailKey, mutationId: `crm:link:${recordId}:${emailKey}` }).catch(() => undefined);\n\n return recordId;\n}\n\n/** Backfill / repair: project every application (newest per person) and every\n * account-only `$users` row into the CRM. Idempotent (safe to re-run). Dev\n * volumes fit one 1000-row page. Returns `{ synced, errors }`. */\nexport async function backfillCrm(deps: ProjectionDeps & { chapter: Chapter }): Promise<{ synced: number; errors: Array<{ email: string; error: string }> }> {\n const [appsRes, usersRes] = await Promise.all([\n deps.db.query({ applications: { $: { order: { createdAt: \"desc\" }, limit: 1000 } } }),\n deps.db.query({ $users: { $: { limit: 1000 } } }),\n ]);\n const seen = new Set<string>();\n let synced = 0;\n const errors: Array<{ email: string; error: string }> = [];\n\n const run = async (app: Record<string, unknown>, stage?: string): Promise<void> => {\n const key = str(app.email).toLowerCase();\n if (!key || seen.has(key)) return; // newest-first: one record per person\n seen.add(key);\n try {\n await syncApplicationToCrm(deps, { app, stage });\n synced += 1;\n } catch (err) {\n errors.push({ email: key, error: err instanceof Error ? err.message : String(err) });\n }\n };\n\n for (const a of (appsRes.applications ?? []) as Array<Record<string, unknown>>) await run(a, str(a.status));\n for (const u of (usersRes.$users ?? []) as Array<Record<string, unknown>>) {\n if (u.deleted === true) continue;\n await run({ email: u.email, firstName: str(u.name) });\n }\n return { synced, errors };\n}\n","// Pure aggregation helpers for the admin dashboard: a weekly time-series bucketer\n// and the annualized-run-rate math for a Stripe subscription. Kept out of the\n// route so both dashboard and billing share one definition and it is unit-tested\n// without a network call.\n\n/** Bucket `{ t, v }` points into the last `weeks` weekly buckets ending at `now`\n * (epoch ms), summing `v` per bucket. Returns oldest→newest `{ weekStart, value\n * }`, so a caller renders a sparkline directly. Points outside the window are\n * ignored. */\nexport function bucketSeries(\n points: Array<{ t: number; v: number }>,\n now: number,\n weeks = 12,\n): Array<{ weekStart: number; value: number }> {\n const WEEK = 7 * 86_400_000;\n const end = now;\n const start = end - weeks * WEEK;\n const buckets = Array.from({ length: weeks }, (_, i) => ({ weekStart: start + i * WEEK, value: 0 }));\n for (const p of points) {\n if (!Number.isFinite(p.t) || p.t < start || p.t > end) continue;\n const idx = Math.min(weeks - 1, Math.floor((p.t - start) / WEEK));\n const bucket = buckets[idx];\n if (bucket) bucket.value += Number.isFinite(p.v) ? p.v : 0;\n }\n return buckets;\n}\n\n/** Labels and current/previous values for one dashboard comparison window. */\nexport interface DashboardMetricSeries {\n labels: string[];\n current: number[];\n previous: number[];\n}\n\n/** Week-over-week and month-over-month series consumed by MetricWidget. */\nexport interface DashboardMetricData {\n wow: DashboardMetricSeries;\n mom: DashboardMetricSeries;\n}\n\nfunction periodSeries(\n points: Array<{ t: number; v: number }>,\n now: number,\n count: number,\n periodMs: number,\n): DashboardMetricSeries {\n const start = now - count * periodMs;\n const previousStart = start - count * periodMs;\n const current = Array.from({ length: count }, () => 0);\n const previous = Array.from({ length: count }, () => 0);\n for (const point of points) {\n if (!Number.isFinite(point.t) || !Number.isFinite(point.v)) continue;\n if (point.t >= start && point.t <= now) {\n const index = Math.min(count - 1, Math.floor((point.t - start) / periodMs));\n current[index] = (current[index] ?? 0) + point.v;\n } else if (point.t >= previousStart && point.t < start) {\n const index = Math.min(count - 1, Math.floor((point.t - previousStart) / periodMs));\n previous[index] = (previous[index] ?? 0) + point.v;\n }\n }\n const labels = current.map((_, index) =>\n new Date(start + index * periodMs).toISOString().slice(5, 10));\n return { labels, current, previous };\n}\n\n/** MetricWidget-ready daily and weekly comparison windows. */\nexport function dashboardMetricData(\n points: Array<{ t: number; v: number }>,\n now: number,\n): DashboardMetricData {\n const DAY = 86_400_000;\n return {\n wow: periodSeries(points, now, 7, DAY),\n mom: periodSeries(points, now, 4, 7 * DAY),\n };\n}\n\n/** Annualized cents for a Stripe subscription: sum each item's\n * `unit_amount * quantity`, ×12 for monthly intervals. A yearly interval is\n * taken as-is. Returns 0 for a shape with no priced items. */\nexport function subAnnualCents(sub: Record<string, unknown>): number {\n const items = ((sub.items as { data?: Array<Record<string, unknown>> } | undefined)?.data ?? []);\n let cents = 0;\n for (const it of items) {\n const price = (it.price ?? {}) as { unit_amount?: number; recurring?: { interval?: string } };\n const per = (price.unit_amount ?? 0) * ((it.quantity as number) ?? 1);\n cents += price.recurring?.interval === \"month\" ? per * 12 : per;\n }\n return cents;\n}\n","// The Stripe payments provider — the only I/O half of payments. Talks to the\n// Stripe REST API over fetch (no SDK), and maps 1:1 onto the pure helpers in\n// ./payments (form encoding, signature verify, event normalization). The worker\n// resolves secrets from the vault per request and constructs this; the routes own\n// every db write so idempotency stays at the db layer.\nimport { normalizeWebhookEvent, stripeForm, verifyStripeSignature } from \"./payments\";\nimport type { StripeEvent, WebhookEvent } from \"./payments\";\n\n/** Inputs to create an application's founding-member subscription. */\nexport interface CreateSubscriptionInput {\n applicationId: string;\n groupId: string;\n email: string;\n name: string;\n priceId: string;\n existingCustomerId?: string;\n}\n\n/** The client secret to confirm card entry, plus the ids to persist. */\nexport interface CreateSubscriptionResult {\n customerId: string;\n subscriptionId: string;\n clientSecret: string;\n}\n\n/** The outcome of a full refund. */\nexport interface RefundResult {\n refundedCents: number | null;\n subscriptionCanceled: boolean;\n}\n\n/** The capabilities the payment routes depend on — Stripe is one impl. */\nexport interface PaymentsProvider {\n createSubscription(input: CreateSubscriptionInput): Promise<CreateSubscriptionResult>;\n ingestWebhook(\n rawBody: string,\n sigHeader: string,\n ): Promise<{ ok: true; eventId: string; event: WebhookEvent } | { ok: false; reason: \"bad_signature\" }>;\n refund(input: { customerId: string; subscriptionId: string }): Promise<RefundResult>;\n}\n\n/** The result of a Stripe Backend API call: ok + status + parsed JSON body. */\nexport type StripeResult = { ok: boolean; status: number; body: Record<string, unknown> };\n\n/** Call the Stripe Backend API (form-encoded, Bearer sk_). Exposed so the admin\n * billing/dashboard reads and the refund route share one Stripe client instead\n * of each re-deriving the auth + encoding. */\nexport async function stripeCall(\n sk: string,\n method: \"GET\" | \"POST\" | \"DELETE\",\n path: string,\n params?: Record<string, unknown>,\n idempotencyKey?: string,\n): Promise<StripeResult> {\n const qs = method === \"GET\" && params ? `?${stripeForm(params)}` : \"\";\n const headers: Record<string, string> = { authorization: `Bearer ${sk}` };\n if (idempotencyKey) headers[\"idempotency-key\"] = idempotencyKey;\n const init: RequestInit = { method, headers };\n if (method === \"POST\" && params) {\n headers[\"content-type\"] = \"application/x-www-form-urlencoded\";\n init.body = stripeForm(params);\n }\n const res = await fetch(`https://api.stripe.com${path}${qs}`, init);\n const body = (await res.json().catch(() => ({}))) as Record<string, unknown>;\n return { ok: res.ok, status: res.status, body };\n}\n\nfunction fail(op: string, r: StripeResult): never {\n const err = new Error(`stripe ${op} failed: ${r.status}`) as Error & { code: string };\n err.code = \"stripe_error\";\n throw err;\n}\n\nfunction clientSecretOf(sub: Record<string, unknown>): string | undefined {\n const inv = sub.latest_invoice as Record<string, unknown> | undefined;\n const confirmation = inv?.confirmation_secret as Record<string, unknown> | undefined;\n const intent = inv?.payment_intent as Record<string, unknown> | undefined;\n const secret = confirmation?.client_secret ?? intent?.client_secret;\n return typeof secret === \"string\" ? secret : undefined;\n}\n\nfunction requireSecret(secretKey: string | undefined): string {\n if (!secretKey) {\n const err = new Error(\"stripe secret key missing\") as Error & { code: string };\n err.code = \"not_configured\";\n throw err;\n }\n return secretKey;\n}\n\n/** Build the Stripe provider. `secretKey` powers charging/refunds; `webhookSecret`\n * powers webhook ingest. Each is resolved from the vault per request, so the\n * webhook route can construct an ingest-only provider without the secret key. */\nexport function createStripeProvider(config: { secretKey?: string; webhookSecret?: string }): PaymentsProvider {\n const { secretKey, webhookSecret } = config;\n return {\n async createSubscription(input) {\n const sk = requireSecret(secretKey);\n const meta = { applicationId: input.applicationId, groupId: input.groupId, email: input.email };\n let customerId = input.existingCustomerId;\n if (!customerId) {\n const cust = await stripeCall(\n sk,\n \"POST\",\n \"/v1/customers\",\n { email: input.email, name: input.name, metadata: meta },\n `cus:${input.applicationId}`,\n );\n if (!cust.ok) fail(\"customer create\", cust);\n customerId = String(cust.body.id);\n }\n const sub = await stripeCall(\n sk,\n \"POST\",\n \"/v1/subscriptions\",\n {\n customer: customerId,\n \"items[0][price]\": input.priceId,\n payment_behavior: \"default_incomplete\",\n \"payment_settings[save_default_payment_method]\": \"on_subscription\",\n \"payment_settings[payment_method_types][0]\": \"card\",\n \"expand[0]\": \"latest_invoice.confirmation_secret\",\n metadata: meta,\n },\n // Hardening: one subscription per application, so a client\n // retry between create and the db write can't orphan a second one.\n `sub:${input.applicationId}`,\n );\n if (!sub.ok) fail(\"subscription create\", sub);\n const clientSecret = clientSecretOf(sub.body);\n if (!clientSecret) fail(\"subscription confirmation-secret missing\", sub);\n return { customerId, subscriptionId: String(sub.body.id), clientSecret };\n },\n\n async ingestWebhook(rawBody, sigHeader) {\n if (!webhookSecret || !(await verifyStripeSignature(rawBody, sigHeader, webhookSecret))) {\n return { ok: false, reason: \"bad_signature\" };\n }\n const event = JSON.parse(rawBody) as StripeEvent;\n return { ok: true, eventId: event.id, event: normalizeWebhookEvent(event) };\n },\n\n async refund(input) {\n const sk = requireSecret(secretKey);\n const charges = await stripeCall(sk, \"GET\", \"/v1/charges\", { customer: input.customerId, limit: 100 });\n if (!charges.ok) fail(\"charges list\", charges);\n const rows = (charges.body.data as Array<Record<string, unknown>> | undefined) ?? [];\n const paid = rows.filter((c) => c.status === \"succeeded\" && c.refunded !== true);\n const charge = paid[paid.length - 1]; // Stripe returns newest-first; refund the earliest.\n if (!charge) {\n const err = new Error(\"no paid charge to refund\") as Error & { code: string };\n err.code = \"no_charge\";\n throw err;\n }\n const refund = await stripeCall(sk, \"POST\", \"/v1/refunds\", { charge: String(charge.id) });\n if (!refund.ok) fail(\"refund\", refund);\n const cancel = await stripeCall(sk, \"DELETE\", `/v1/subscriptions/${input.subscriptionId}`);\n const amount = refund.body.amount;\n return { refundedCents: typeof amount === \"number\" ? amount : null, subscriptionCanceled: cancel.ok };\n },\n };\n}\n","// Apply-time account provisioning. When an application arrives, the worker mints\n// a Clerk invitation so the applicant gets a path into their member area — the\n// generalized server-side account step. Chapter calls the Clerk\n// Backend API directly over fetch (vault clerk_secret_key), mirroring\n// @odla-ai/auth-clerk's createInvitation but self-contained, so it never pulls the\n// auth-clerk UI package into the worker bundle. Best-effort at the call site: a\n// provisioning failure never fails the application.\n\n/** The outcome of a Clerk provisioning call. `existed` marks the heal case: Clerk\n * rejected it because the account/invitation is already there, which is the end\n * state we wanted anyway. */\nexport interface ClerkResult {\n ok: boolean;\n status: number;\n existed?: boolean;\n /** Set when an `existed` heal also refreshed the account's metadata. */\n refreshed?: boolean;\n}\n\n// Clerk answers 422 when the email already has a user (or a pending invitation).\n// For apply-time provisioning that's success, not failure — the account exists.\nconst heal = (status: number): ClerkResult =>\n status === 422 ? { ok: true, status, existed: true } : { ok: false, status };\n\n/** Inputs for a Clerk invitation. */\nexport interface ClerkInviteInput {\n email: string;\n /** Where the accept-invitation link lands (usually the member area). */\n redirectUrl?: string;\n /** Written to the invitation's `public_metadata`. Clerk application\n * invitations cannot carry private metadata, so never put applicant profile\n * fields here. Chapter completes that backend-only write after acceptance. */\n publicMetadata?: Record<string, unknown>;\n}\n\n/** Build the Clerk Backend API invitation request (path + JSON body). Pure, so\n * the wire shape is testable without a network call. */\nexport function clerkInviteRequest(input: ClerkInviteInput): { path: string; body: Record<string, unknown> } {\n return {\n path: \"/v1/invitations\",\n body: {\n email_address: input.email,\n notify: true,\n ...(input.redirectUrl ? { redirect_url: input.redirectUrl } : {}),\n ...(input.publicMetadata ? { public_metadata: input.publicMetadata } : {}),\n },\n };\n}\n\n/** POST the invitation to the Clerk Backend API. A repeat invite for an\n * already-invited email heals to `{ ok: true, existed: true }` rather than\n * reporting a failure the caller would have to special-case. */\nexport async function createClerkInvitation(\n secretKey: string,\n input: ClerkInviteInput,\n fetchImpl: typeof fetch = fetch,\n): Promise<ClerkResult> {\n const { path, body } = clerkInviteRequest(input);\n const res = await fetchImpl(`https://api.clerk.com${path}`, {\n method: \"POST\",\n headers: { authorization: `Bearer ${secretKey}`, \"content-type\": \"application/json\" },\n body: JSON.stringify(body),\n });\n return res.ok ? { ok: true, status: res.status } : heal(res.status);\n}\n\n/** Inputs for a server-side Clerk user create. */\nexport interface ClerkUserInput {\n email: string;\n firstName?: string;\n lastName?: string;\n /** Backend-authored, browser-readable claims such as `role`. */\n publicMetadata?: Record<string, unknown>;\n /** Backend-only account signals and profile data. */\n privateMetadata?: Record<string, unknown>;\n /** Optional metadata merge used only when create returns 422 for an existing\n * account. This lets migrations remove legacy keys without putting tombstone\n * values on a newly-created user. Defaults to the create metadata. */\n repairMetadata?: ClerkMetadataInput;\n}\n\n/** Build the Clerk Backend API user-create request. The account is created\n * passwordless (the member signs in via the site's Clerk flow), so join step 3\n * can say the account is ready — the \"create\" alternative to an invitation. */\nexport function clerkUserRequest(input: ClerkUserInput): { path: string; body: Record<string, unknown> } {\n return {\n path: \"/v1/users\",\n body: {\n email_address: [input.email],\n skip_password_requirement: true,\n ...(input.firstName ? { first_name: input.firstName } : {}),\n ...(input.lastName ? { last_name: input.lastName } : {}),\n ...(input.publicMetadata !== undefined ? { public_metadata: input.publicMetadata } : {}),\n ...(input.privateMetadata !== undefined ? { private_metadata: input.privateMetadata } : {}),\n },\n };\n}\n\n/** One merge-PATCH to the metadata fields Clerk exposes on its Backend API. */\nexport interface ClerkMetadataInput {\n publicMetadata?: Record<string, unknown>;\n privateMetadata?: Record<string, unknown>;\n}\n\n/** Merge metadata into an existing Clerk user. Nested `null` values remove old\n * keys, which Chapter uses to migrate its former public profile into private\n * metadata without disturbing the public `role` claim. */\nexport async function updateClerkUserMetadata(\n secretKey: string,\n userId: string,\n input: ClerkMetadataInput,\n fetchImpl: typeof fetch = fetch,\n): Promise<boolean> {\n const res = await fetchImpl(`https://api.clerk.com/v1/users/${encodeURIComponent(userId)}/metadata`, {\n method: \"PATCH\",\n headers: { authorization: `Bearer ${secretKey}`, \"content-type\": \"application/json\" },\n body: JSON.stringify({\n ...(input.publicMetadata !== undefined ? { public_metadata: input.publicMetadata } : {}),\n ...(input.privateMetadata !== undefined ? { private_metadata: input.privateMetadata } : {}),\n }),\n });\n return res.ok;\n}\n\n/** Look a Clerk account up by email and merge its metadata.\n *\n * Returns `false` when no matching user can be resolved or Clerk rejects the\n * lookup or update. Chapter uses this for create-healing and invitation\n * adoption so a repeated application repairs the private account snapshot.\n */\nexport async function updateClerkUserMetadataByEmail(\n secretKey: string,\n email: string,\n input: ClerkMetadataInput,\n fetchImpl: typeof fetch = fetch,\n): Promise<boolean> {\n const auth = { authorization: `Bearer ${secretKey}` };\n const found = await fetchImpl(`https://api.clerk.com/v1/users?email_address=${encodeURIComponent(email)}&limit=1`, { headers: auth });\n if (!found.ok) return false;\n const users = (await found.json().catch(() => null)) as Array<{ id?: unknown }> | null;\n const id = Array.isArray(users) && typeof users[0]?.id === \"string\" ? users[0].id : undefined;\n if (!id) return false;\n return updateClerkUserMetadata(secretKey, id, input, fetchImpl);\n}\n\n/** Create the applicant's Clerk account server-side. A repeat for an email that\n * already has an account heals to `{ ok: true, existed: true }` — the account\n * exists, which is the state apply-time provisioning wanted — and refreshes\n * supplied public/private metadata on the existing account so a re-application\n * repairs a previously missed create (`refreshed: true`). */\nexport async function createClerkUser(\n secretKey: string,\n input: ClerkUserInput,\n fetchImpl: typeof fetch = fetch,\n): Promise<ClerkResult> {\n const { path, body } = clerkUserRequest(input);\n const res = await fetchImpl(`https://api.clerk.com${path}`, {\n method: \"POST\",\n headers: { authorization: `Bearer ${secretKey}`, \"content-type\": \"application/json\" },\n body: JSON.stringify(body),\n });\n if (res.ok) return { ok: true, status: res.status };\n const healed = heal(res.status);\n const repair = input.repairMetadata ?? {\n ...(input.publicMetadata !== undefined ? { publicMetadata: input.publicMetadata } : {}),\n ...(input.privateMetadata !== undefined ? { privateMetadata: input.privateMetadata } : {}),\n };\n if (\n !healed.existed ||\n (repair.publicMetadata === undefined && repair.privateMetadata === undefined)\n ) return healed;\n const refreshed = await updateClerkUserMetadataByEmail(secretKey, input.email, {\n ...(repair.publicMetadata !== undefined ? { publicMetadata: repair.publicMetadata } : {}),\n ...(repair.privateMetadata !== undefined ? { privateMetadata: repair.privateMetadata } : {}),\n }, fetchImpl).catch(() => false);\n return { ...healed, refreshed };\n}\n","// The odla->Clerk write half: server-side Clerk role read/list/write, the gap\n// beside clerk.ts's create/invite/heal. Roles live in `public_metadata.role`; an\n// absent role means the lowest rung (\"provisional\") — chapter never writes it at\n// create. Account profile signals live in backend-only `private_metadata`; the\n// role write is a MERGE-PATCH of only `{ role }`, so it cannot clobber them.\n// Chapter calls the Clerk Backend API over fetch with the vault\n// `clerk_secret_key`.\n\nconst CLERK_API = \"https://api.clerk.com\";\nconst DEFAULT_ROLE = \"provisional\";\nconst PAGE = 100;\n\n/** A Clerk user as chapter's role layer sees it. `role` defaults to the lowest\n * rung when `public_metadata` carries none. Raw public/private metadata is\n * returned only from this backend helper; `profile` and `applicationId` are the\n * normalized Chapter-owned private signals. */\nexport interface ClerkUserRecord {\n id: string;\n email?: string;\n role: string;\n publicMetadata: Record<string, unknown>;\n privateMetadata: Record<string, unknown>;\n profile: Record<string, unknown>;\n applicationId?: string;\n}\n\n// The Clerk Backend API user object — only the fields chapter reads.\ninterface ClerkApiUser {\n id?: unknown;\n email_addresses?: Array<{ email_address?: unknown }>;\n public_metadata?: Record<string, unknown>;\n private_metadata?: Record<string, unknown>;\n}\n\nfunction toRecord(u: ClerkApiUser): ClerkUserRecord | null {\n if (typeof u.id !== \"string\") return null;\n const pm = (u.public_metadata ?? {}) as Record<string, unknown>;\n const privateMetadata = (u.private_metadata ?? {}) as Record<string, unknown>;\n const rawProfile = privateMetadata.profile;\n const profile = rawProfile && typeof rawProfile === \"object\" && !Array.isArray(rawProfile)\n ? rawProfile as Record<string, unknown>\n : {};\n const applicationId = typeof privateMetadata.applicationId === \"string\"\n ? privateMetadata.applicationId\n : undefined;\n const role = typeof pm.role === \"string\" && pm.role ? pm.role : DEFAULT_ROLE;\n const email = u.email_addresses?.[0]?.email_address;\n return {\n id: u.id,\n email: typeof email === \"string\" ? email : undefined,\n role,\n publicMetadata: pm,\n privateMetadata,\n profile,\n ...(applicationId ? { applicationId } : {}),\n };\n}\n\nasync function clerkGet(path: string, secretKey: string, fetchImpl: typeof fetch): Promise<unknown> {\n const res = await fetchImpl(`${CLERK_API}${path}`, { headers: { authorization: `Bearer ${secretKey}` } });\n if (!res.ok) throw new Error(`clerk GET ${path} → ${res.status}`);\n return res.json();\n}\n\n/** Look a Clerk user up by email. `null` when no such user — or when the lookup\n * fails (a role gate treats an unresolvable user as absent, matching the site's\n * own fallback). Role defaults to provisional when unset. */\nexport async function clerkGetUserByEmail(secretKey: string, email: string, fetchImpl: typeof fetch = fetch): Promise<ClerkUserRecord | null> {\n const data = await clerkGet(`/v1/users?email_address=${encodeURIComponent(email)}&limit=1`, secretKey, fetchImpl).catch(() => null);\n const user = Array.isArray(data) ? (data[0] as ClerkApiUser | undefined) : undefined;\n return user ? toRecord(user) : null;\n}\n\n/** Fetch a Clerk user by id, for role-change gating. `null` when missing or on a\n * failed lookup. Role defaults to provisional when unset. */\nexport async function clerkGetUser(secretKey: string, id: string, fetchImpl: typeof fetch = fetch): Promise<ClerkUserRecord | null> {\n const data = await clerkGet(`/v1/users/${encodeURIComponent(id)}`, secretKey, fetchImpl).catch(() => null);\n return data ? toRecord(data as ClerkApiUser) : null;\n}\n\n/** List ALL Clerk users with their roles, auto-paginating. A membership community\n * outgrows one page, and a fixed `limit=100` would silently drop members from the\n * admin roster with no error — so this pages (offset in steps of {@link PAGE})\n * until a short page. A page fetch that fails THROWS rather than returning a\n * partial list, so the caller never mistakes a truncated roster for the whole. */\nexport async function clerkListUsers(secretKey: string, fetchImpl: typeof fetch = fetch): Promise<ClerkUserRecord[]> {\n const out: ClerkUserRecord[] = [];\n for (let offset = 0; ; offset += PAGE) {\n const data = await clerkGet(`/v1/users?limit=${PAGE}&offset=${offset}`, secretKey, fetchImpl);\n const page = Array.isArray(data) ? (data as ClerkApiUser[]) : [];\n for (const u of page) {\n const record = toRecord(u);\n if (record) out.push(record);\n }\n if (page.length < PAGE) break;\n }\n return out;\n}\n\n/** Write a user's role: a MERGE-PATCH of only `{ role }` on `public_metadata`.\n * The backend-only profile lives in `private_metadata`, so the two signals\n * cannot overwrite each other. Returns whether the role write stuck. */\nexport async function clerkSetRole(secretKey: string, id: string, role: string, fetchImpl: typeof fetch = fetch): Promise<boolean> {\n const res = await fetchImpl(`${CLERK_API}/v1/users/${encodeURIComponent(id)}/metadata`, {\n method: \"PATCH\",\n headers: { authorization: `Bearer ${secretKey}`, \"content-type\": \"application/json\" },\n body: JSON.stringify({ public_metadata: { role } }),\n });\n return res.ok;\n}\n","// The Clerk capability descriptor: documentation-as-data — \"what settings/secrets\n// does turning Clerk on need?\", \"what must the CLI provision?\", and \"what data\n// syncs, in which direction?\". It lives here (not in the browser package\n// @odla-ai/auth-clerk) because the sync contract is implemented by odla-db + the\n// platform webhook (provider->odla) and by chapter's clerk.ts (odla->provider),\n// neither of which is browser code. auth-clerk stays a pure UI package. Data only:\n// it describes the capability, it performs none of it.\n\n/** Which auth mode a secret is required for: `\"client\"` (publishable key only) or\n * `\"full\"` (server-side profile/role reads + writes, needing the vault secret). */\nexport type SecretMode = \"client\" | \"full\";\n\n/** A configuration value the capability needs (e.g. the publishable key). */\nexport interface IntegrationSetting {\n key: string;\n description: string;\n /** Safe to ship to the browser? (publishable keys are; secrets are not). */\n public: boolean;\n /** Expected value shape, e.g. a \"pk_\" prefix. */\n pattern?: string;\n /** Stored per environment (dev/prod)? */\n perEnv: boolean;\n /** Where the value is stored and read from. */\n source: string;\n}\n\n/** A secret the capability needs, held in the tenant vault. */\nexport interface IntegrationSecret {\n key: string;\n description: string;\n pattern?: string;\n /** Auth mode that requires this secret (\"full\" pulls user profiles). */\n mode: SecretMode;\n /** Stored in the tenant vault (never in graph rows or wrangler vars)? */\n vault: boolean;\n}\n\n/** One data-sync contract. A capability may declare several (e.g. an inbound\n * webhook mirror AND an outbound write), so {@link IntegrationDescriptor.syncs}\n * is a list. The mechanism is declared, not reimplemented here. */\nexport interface IntegrationSync {\n engine: string;\n direction: \"provider->odla\" | \"odla->provider\" | \"bidirectional\";\n entity: string;\n fields: string[];\n webhook?: string;\n onDelete?: string;\n}\n\n/** The provisioning split: human, CLI, and doctor-assertable steps. */\nexport interface IntegrationProvision {\n /** Steps a human performs (out-of-band, e.g. in the Clerk dashboard). */\n human: string[];\n /** Steps the odla CLI automates. */\n cli: string[];\n /** Checks `odla doctor` should assert. */\n doctor: string[];\n}\n\n/** Documentation-as-data for a provider capability, consumed by the odla CLI,\n * `odla doctor`, and the docs. */\nexport interface IntegrationDescriptor {\n id: string;\n title: string;\n npm: string;\n settings: IntegrationSetting[];\n secrets: IntegrationSecret[];\n /** Every data-sync contract the capability declares, each with its direction. */\n syncs: IntegrationSync[];\n provision: IntegrationProvision;\n}\n\n/**\n * The concrete {@link IntegrationDescriptor} for the Clerk capability. Declares\n * the one public `setting` (the `pk_*` publishable key, served to the SPA), the\n * `secrets` auth mode `\"full\"` needs in the tenant vault, BOTH sync directions —\n * the inbound `$users` webhook mirror (`provider->odla`) and the outbound\n * public role/private profile write via `clerk_secret_key` (`odla->provider`,\n * chapter's `clerk.ts`) — and the `provision` split. Data only; it performs\n * none of it.\n */\nexport const clerkIntegration: IntegrationDescriptor = {\n id: \"clerk\",\n title: \"Clerk end-user auth\",\n npm: \"@odla-ai/chapter\",\n settings: [\n {\n key: \"publishableKey\",\n description: \"Clerk publishable key (pk_*). Public by design; served to the SPA (loads clerk-js from Clerk's CDN via @odla-ai/auth-clerk).\",\n public: true,\n pattern: \"pk_\",\n perEnv: true,\n source: \"apps-registry auth[env].publishableKey (from odla.config.mjs auth.clerk.<env>)\",\n },\n ],\n secrets: [\n {\n key: \"CLERK_WEBHOOK_SECRET\",\n description: \"Svix signing secret for Clerk user webhooks — verifies $users sync events.\",\n pattern: \"whsec_\",\n mode: \"full\",\n vault: true,\n },\n {\n key: \"clerk_secret_key\",\n description: \"Clerk backend key (sk_*) — powers account creation, the public role claim, and backend-only private profile signals; it also lets odla-db resolve user email/name via the Clerk API.\",\n pattern: \"sk_\",\n mode: \"full\",\n vault: true,\n },\n ],\n syncs: [\n {\n engine: \"@odla-ai/db $users (Clerk webhook -> tenant graph)\",\n direction: \"provider->odla\",\n entity: \"$users\",\n // Matches the webhook mapper: the mirror carries id + primary email + name + avatar.\n fields: [\"id\", \"email\", \"name\", \"imageUrl\"],\n webhook: \"whsec_ (svix-signed)\",\n onDelete: \"tombstone (never row-delete)\",\n },\n {\n engine: \"@odla-ai/chapter clerk.ts (Clerk Backend API via vault clerk_secret_key)\",\n direction: \"odla->provider\",\n entity: \"clerk user\",\n // Separate metadata fields so a public role write cannot expose or\n // clobber backend-only account/profile signals.\n fields: [\n \"public_metadata.role\",\n \"private_metadata.applicationId\",\n \"private_metadata.profile\",\n ],\n onDelete: \"n/a (writes only)\",\n },\n ],\n provision: {\n human: [\n \"Run `npx clerk auth login` — the one interactive step; the agent then creates + configures the Clerk app with the Clerk CLI (no pk_ to hand-paste).\",\n 'For auth mode \"full\": create the Clerk user-sync webhook and store its whsec_, plus the sk_ as clerk_secret_key, in the tenant vault (Studio, write-only).',\n ],\n cli: [\n \"Clerk CLI (`npx clerk apps create/link/config patch`) creates + configures the instance and pulls the pk_.\",\n \"odla `provision` records it: setAuth(env, publishableKey) -> apps-registry (issuer/JWKS derived from the key).\",\n ],\n doctor: [\n \"registry auth[env] present when the app mounts <SignIn>?\",\n 'auth mode \"full\" => whsec_ and clerk_secret_key present in the tenant vault?',\n ],\n },\n};\n","// The member session — what GET /api/me returns to a signed-in applicant/member.\n// Ported from the reference application's summary + /api/me reconciliation. The\n// SHAPING is pure and package-enforced so no site re-derives it; the worker owns\n// only the I/O around it (locating the application by email, reconciling the\n// meeting against the calendar) and hands the resolved rows here.\n//\n// Two invariants live here, not in a site:\n// - `paid` is DERIVED, never a stored flag: a subscription exists and the\n// application wasn't refunded. Sites can't drift a stale boolean out of sync\n// with Stripe.\n// - the live meeting row wins: its startAt/meetUrl/timezone override whatever\n// the application row cached, and a non-scheduled meeting (a cancellation\n// adopted from the calendar) forces meetingAt back to null.\n\n/** An application row, as far as the session cares about it. */\nexport interface ApplicationRecord {\n id: string;\n firstName?: string | null;\n lastName?: string | null;\n email?: string | null;\n status: string;\n meetingAt?: number | null;\n meetingLink?: string | null;\n createdAt?: number | null;\n stripeSubscriptionId?: string | null;\n renewalAt?: number | null;\n canceled?: boolean;\n}\n\n/** The reconciled meeting row (already adopted against the calendar), or null. */\nexport interface MeetingRecord {\n status: string;\n startAt?: number | null;\n meetUrl?: string | null;\n timezone?: string | null;\n}\n\n/** The stable, non-meeting fields of an application (safe to expose to its own\n * owner). */\nexport interface ApplicationSummary {\n id: string;\n firstName: string | null;\n lastName: string | null;\n email: string | null;\n status: string;\n createdAt: number | null;\n meetingLink: string | null;\n paid: boolean;\n renewalAt: number | null;\n canceled: boolean;\n}\n\n/** A summary plus the reconciled meeting fields — the `application` the member\n * area renders. */\nexport interface MemberApplication extends ApplicationSummary {\n meetingAt: number | null;\n meetUrl: string | null;\n timezone: string;\n}\n\n/** The full GET /api/me payload for a signed-in user. */\nexport interface MemberSession {\n userId: string;\n email: string | null;\n role: string;\n superAdmin: boolean;\n application: MemberApplication | null;\n}\n\n/** Derive the summary fields from an application row. `paid` is computed, not\n * read, so it can never contradict Stripe. */\nexport function applicationSummary(app: ApplicationRecord): ApplicationSummary {\n return {\n id: app.id,\n firstName: app.firstName ?? null,\n lastName: app.lastName ?? null,\n email: app.email ?? null,\n status: app.status,\n createdAt: app.createdAt ?? null,\n meetingLink: app.meetingLink ?? null,\n paid: Boolean(app.stripeSubscriptionId) && app.status !== \"refunded\",\n renewalAt: app.renewalAt ?? null,\n canceled: app.canceled === true,\n };\n}\n\n/** Fold a (possibly absent, already-reconciled) meeting into the application the\n * member area renders. The live meeting overrides the application's cached\n * meeting fields; a non-`scheduled` meeting clears the booking. */\nexport function memberApplication(\n app: ApplicationRecord,\n meeting: MeetingRecord | null | undefined,\n defaultTimezone: string,\n): MemberApplication {\n const summary = applicationSummary(app);\n let meetingAt = app.meetingAt ?? null;\n let meetUrl: string | null = null;\n let timezone = defaultTimezone;\n if (meeting) {\n timezone = meeting.timezone ?? timezone;\n if (meeting.status === \"scheduled\") {\n meetingAt = meeting.startAt ?? null;\n meetUrl = meeting.meetUrl ?? null;\n } else {\n meetingAt = null;\n }\n }\n return { ...summary, meetingAt, meetUrl, timezone };\n}\n\n/** Identity of the signed-in user, from the verified session. */\nexport interface SessionUser {\n userId: string;\n email?: string | null;\n role: string;\n}\n\n/** Assemble the GET /api/me payload. `application` is null when the user has no\n * application on file (an admin who never applied, or a brand-new account). */\nexport function memberSession(\n user: SessionUser,\n opts: { application: MemberApplication | null; superAdmin: boolean },\n): MemberSession {\n return {\n userId: user.userId,\n email: user.email ?? null,\n role: user.role,\n superAdmin: opts.superAdmin,\n application: opts.application,\n };\n}\n","// Brand tokens (H4). defineChapter accepts a `brand` block; this turns it into a\n// `:root { --…: … }` CSS block, so a chapter re-skins the WHOLE UI — the\n// @odla-ai/ui components, the admin shell, and the member islands, which all read\n// --ui-* design tokens — from one config instead of hand-writing inline CSS.\n//\n// Pure string generation, so it is unit-testable and can be emitted at\n// build/SSR time into the page <head> (no flash of unstyled content), or via the\n// <BrandStyle> component from @odla-ai/chapter/ui.\nimport type { ChapterBrand, ChapterBrandTokens } from \"./types\";\n\n// A palette entry is either a direct custom property (already `--…`, e.g.\n// `--ui-accent` to retheme components) or a bare name we expose as `--<name>`\n// (e.g. `moss` → `--moss`, for a site to reference in its own CSS).\nfunction paletteVar(key: string): string {\n return key.startsWith(\"--\") ? key : `--${key}`;\n}\n\n// Strip characters that could break out of a `--var: value;` declaration or the\n// surrounding <style>. Brand config is trusted author input, so this is a\n// belt-and-suspenders guard, not a security boundary.\nfunction cleanValue(value: string): string {\n return value.replace(/[<>{};]/g, \"\").trim();\n}\n\nfunction paletteDecls(palette: Record<string, string> | undefined): string[] {\n const decls: string[] = [];\n for (const [key, value] of Object.entries(palette ?? {})) {\n if (typeof value === \"string\" && value.trim()) decls.push(`${paletteVar(key)}: ${cleanValue(value)};`);\n }\n return decls;\n}\n\nconst TOKEN_VARS: Record<keyof ChapterBrandTokens, string> = {\n background: \"--ui-bg\",\n surface: \"--ui-surface\",\n surface2: \"--ui-surface-2\",\n text: \"--ui-text\",\n textMuted: \"--ui-text-muted\",\n textFaint: \"--ui-text-faint\",\n border: \"--ui-border\",\n borderStrong: \"--ui-border-strong\",\n accent: \"--ui-accent\",\n accentStrong: \"--ui-accent-strong\",\n accentSoft: \"--ui-accent-soft\",\n onAccent: \"--ui-on-accent\",\n good: \"--ui-good\",\n warn: \"--ui-warn\",\n danger: \"--ui-danger\",\n chart1: \"--ui-chart-1\",\n chart2: \"--ui-chart-2\",\n chart3: \"--ui-chart-3\",\n chart4: \"--ui-chart-4\",\n chart5: \"--ui-chart-5\",\n chart6: \"--ui-chart-6\",\n chartPositive: \"--ui-chart-pos\",\n chartNegative: \"--ui-chart-neg\",\n contentWidth: \"--ui-content-width\",\n panelRadius: \"--ui-radius-lg\",\n panelShadow: \"--ui-shadow\",\n masterDetailColumns: \"--ui-master-detail-columns\",\n masterDetailMinHeight: \"--ui-master-detail-min-height\",\n pagePadding: \"--chapter-admin-page-padding\",\n workspacePadding: \"--chapter-admin-workspace-padding\",\n};\n\nfunction semanticDecls(tokens: ChapterBrandTokens | undefined): string[] {\n return Object.entries(tokens ?? {}).flatMap(([key, value]) =>\n typeof value === \"string\" && value.trim()\n ? [`${TOKEN_VARS[key as keyof ChapterBrandTokens]}: ${cleanValue(value)};`]\n : [],\n );\n}\n\n/**\n * Build the CSS that maps a chapter's brand onto the design tokens the UI reads:\n * each `palette` entry becomes a custom property (light, and dark unless\n * `paletteDark` overrides), and `fonts` (display/body/numeral) map to\n * `--ui-font-display` / `--ui-font-sans` / `--ui-font-numeral`. The dark block is\n * emitted under both `:root[data-theme=\"dark\"]` (the odla-ui theme toggle) and\n * `@media (prefers-color-scheme: dark)`. Returns \"\" when there is nothing to\n * theme. These are brand OVERRIDES on top of a base theme — they do not replace\n * the theme layer the components need (see {@link brandTokens} usage in the docs).\n */\nexport function brandTokens(\n brand: ChapterBrand | undefined,\n options: { selector?: string } = {},\n): string {\n if (!brand) return \"\";\n const light = [...paletteDecls(brand.palette), ...semanticDecls(brand.tokens)];\n const fonts = brand.fonts;\n if (fonts?.display) light.push(`--ui-font-display: ${cleanValue(fonts.display)};`);\n if (fonts?.body) light.push(`--ui-font-sans: ${cleanValue(fonts.body)};`);\n if (fonts?.numeral) light.push(`--ui-font-numeral: ${cleanValue(fonts.numeral)};`);\n const dark = [...paletteDecls(brand.paletteDark), ...semanticDecls(brand.tokensDark)];\n const invert = paletteDecls(brand.paletteInvert);\n const selector = options.selector ?? \":root\";\n const darkSelector = selector === \":root\" ? ':root[data-theme=\"dark\"]' : `${selector}[data-theme=\"dark\"]`;\n const systemSelector = selector === \":root\"\n ? ':root:not([data-theme=\"light\"])'\n : `${selector}:not([data-theme=\"light\"])`;\n\n let css = light.length ? `${selector} {\\n ${light.join(\"\\n \")}\\n}\\n` : \"\";\n if (dark.length) {\n const block = `{\\n ${dark.join(\"\\n \")}\\n}`;\n css += `${darkSelector} ${block}\\n@media (prefers-color-scheme: dark) {\\n ${systemSelector} ${block}\\n}\\n`;\n }\n if (invert.length) {\n const invertSelector = selector === \":root\"\n ? \".ui-invert\"\n : `${selector}.ui-invert, ${selector} .ui-invert`;\n css += `${invertSelector} {\\n color-scheme: dark;\\n ${invert.join(\"\\n \")}\\n}\\n`;\n }\n return css;\n}\n\n/** Structural output accepted from `@odla-ai/brand`'s pure token compiler. */\nexport interface CompiledChapterBrandTokens {\n light: Record<string, string>;\n dark: Record<string, string>;\n}\n\n/**\n * Turn a complete compiled token pair into Chapter's brand contract without\n * coupling Chapter's runtime to the brand-book/agent package. Explicit values in\n * `base` win, so a host can make final per-site adjustments after compilation.\n */\nexport function chapterBrandFromTokens(\n compiled: CompiledChapterBrandTokens,\n base: ChapterBrand = {},\n): ChapterBrand {\n return {\n ...base,\n palette: { ...compiled.light, ...base.palette },\n paletteDark: { ...compiled.dark, ...base.paletteDark },\n paletteInvert: { ...compiled.dark, ...base.paletteInvert },\n };\n}\n","// The Google → chapter half of the bidirectional calendar sync. Booking writes\n// events TO the platform calendar (0.4.0's create/reschedule); this ADOPTS edits\n// FROM it: when the owner moves or cancels the intro call directly in Google,\n// reconcile mirrors that onto our canonical `meetings` row and the `application`\n// projection. Chapter never calls Google — the worker reads live events through\n// @odla-ai/calendar's upcoming() (platform-brokered) and applies the ops this\n// returns. Pure + testable; adoption policy (owner edits win) matches the shipped\n// original production worker.\n\n/** A `meetings` row, as far as reconciliation cares. */\nexport interface MeetingForReconcile {\n id: string;\n applicationId: string;\n googleEventId?: string | null;\n status: string;\n startAt?: number | null;\n endAt?: number | null;\n}\n\n/** One live calendar event (a subset of @odla-ai/calendar's Booking). */\nexport interface LiveEvent {\n eventId: string;\n status?: string;\n startAt?: number;\n endAt?: number;\n}\n\n/** An adopt decision: mirror a Google move/cancel onto our rows. */\nexport interface ReconcileDecision {\n meetingId: string;\n applicationId: string;\n kind: \"cancelled\" | \"moved\";\n /** Attrs to write onto the `meetings` row. */\n meetingPatch: Record<string, unknown>;\n /** Attrs to write onto the `applications` row (the projection). */\n applicationPatch: Record<string, unknown>;\n}\n\n/** Meetings still worth reconciling: a scheduled booking with a Google event that\n * starts in the future (or within the last hour, to catch a just-passed edit). */\nexport function isReconcilable(meeting: MeetingForReconcile, now: number): boolean {\n return meeting.status === \"scheduled\" && Boolean(meeting.googleEventId) && (meeting.startAt ?? 0) > now - 3_600_000;\n}\n\n/**\n * Diff canonical `meetings` against the live calendar events and return the adopt\n * decisions — only for meetings that actually changed (a still-matching meeting\n * is omitted). A meeting whose event vanished or is `cancelled` in Google is\n * adopted as cancelled (application `meetingAt` zeroed — 0 means \"was booked,\n * then cancelled\"); a meeting whose event moved adopts the new window (duration\n * preserved when the event omits `endAt`).\n */\nexport function reconcileMeetings(\n meetings: readonly MeetingForReconcile[],\n events: readonly LiveEvent[],\n now: number,\n): ReconcileDecision[] {\n const byEvent = new Map(events.map((e) => [e.eventId, e]));\n const decisions: ReconcileDecision[] = [];\n for (const m of meetings) {\n if (!isReconcilable(m, now) || !m.googleEventId) continue;\n const g = byEvent.get(m.googleEventId);\n if (!g || g.status === \"cancelled\") {\n decisions.push({\n meetingId: m.id,\n applicationId: m.applicationId,\n kind: \"cancelled\",\n meetingPatch: { status: \"cancelled\", drift: \"none\", adoptedFromGoogleAt: now },\n applicationPatch: { meetingAt: 0, meetingLink: \"\" },\n });\n } else if (g.startAt !== undefined && g.startAt !== m.startAt) {\n const duration = (m.endAt ?? 0) - (m.startAt ?? 0);\n decisions.push({\n meetingId: m.id,\n applicationId: m.applicationId,\n kind: \"moved\",\n meetingPatch: { startAt: g.startAt, endAt: g.endAt ?? g.startAt + duration, drift: \"none\", adoptedFromGoogleAt: now },\n applicationPatch: { meetingAt: g.startAt },\n });\n }\n }\n return decisions;\n}\n","// Scheduling core — config resolution + the booking invariants, ported from the\n// proven production worker. Everything here is PURE (no @odla-ai/calendar, no\n// db), so the correctness properties are unit-testable and package-enforced; the\n// worker route owns only the I/O (FreeBusy, computeBookableSlots, calendar\n// create/reschedule, the db writes) and calls these.\n//\n// Package-enforced invariants:\n// - the `meetings` row is canonical; applications.meetingAt and the calendar\n// event are projections written from it.\n// - one intro event per application, forever: a rebooking RESCHEDULES the\n// existing event (preserving its Meet link + invite thread), never creates a\n// second — see {@link bookingDecision} + {@link introIdempotencyKey}.\n// - status never moves backward on booking; you can only book from a pipeline\n// stage in `bookableFrom` — see `canBook` in ./pipeline + applicationBookingUpdate.\n// - endAt is always derived server-side, never client-supplied — see\n// {@link endForSlot}.\nimport type { ChapterScheduling } from \"./types\";\n\n/** A fully-resolved scheduling config (every field present). */\nexport interface ResolvedScheduling {\n slotMinutes: number;\n days: readonly number[];\n startHour: number;\n endHour: number;\n timezone: string;\n minNoticeHours: number;\n windowDays: number;\n summaryTemplate: string;\n}\n\n/** Proven defaults: 45-minute weekday slots, 9–5 Pacific, 24h notice,\n * a 14-day window. The summary is generic (a chapter's group seed supplies a\n * name-branded one). */\nexport const SCHEDULING_DEFAULTS: ResolvedScheduling = {\n slotMinutes: 45,\n days: [1, 2, 3, 4, 5],\n startHour: 9,\n endHour: 17,\n timezone: \"America/Los_Angeles\",\n minNoticeHours: 24,\n windowDays: 14,\n summaryTemplate: \"Introduction call with {{firstName}} {{lastName}}\",\n};\n\nfunction isValidTimeZone(tz: string): boolean {\n try {\n new Intl.DateTimeFormat(undefined, { timeZone: tz });\n return true;\n } catch {\n return false;\n }\n}\n\n/**\n * Resolve a group's scheduling config against the defaults, validating every\n * bound at config-write time (throws on a bad config, so a\n * misconfiguration surfaces immediately rather than yielding empty slots).\n * `windowDays` is capped at 62 because Google FreeBusy is.\n */\nexport function resolveScheduling(config?: ChapterScheduling): ResolvedScheduling {\n const result = validateScheduling(config);\n if (result.ok) return result.value;\n const detail = Object.entries(result.errors)\n .map(([field, message]) => `${field}: ${message}`)\n .join(\" \");\n throw new Error(`scheduling: ${detail}`);\n}\n\n/** Validation messages keyed by form field. */\nexport type SchedulingErrors = Record<string, string>;\n\n/**\n * Validate a scheduling config field by field, collecting owner-readable messages\n * rather than throwing on the first problem. An admin settings form needs to say\n * *which* field is wrong and why — \"pick at least one day\" beats a stack trace —\n * so the admin route returns these directly. {@link resolveScheduling} is the\n * throwing wrapper for internal/config-time use.\n */\nexport function validateScheduling(\n config?: ChapterScheduling,\n): { ok: true; value: ResolvedScheduling } | { ok: false; errors: SchedulingErrors } {\n const d = config ?? {};\n const c: ResolvedScheduling = {\n slotMinutes: d.slotMinutes ?? SCHEDULING_DEFAULTS.slotMinutes,\n days: d.days ?? SCHEDULING_DEFAULTS.days,\n startHour: d.startHour ?? SCHEDULING_DEFAULTS.startHour,\n endHour: d.endHour ?? SCHEDULING_DEFAULTS.endHour,\n timezone: d.timezone ?? SCHEDULING_DEFAULTS.timezone,\n minNoticeHours: d.minNoticeHours ?? SCHEDULING_DEFAULTS.minNoticeHours,\n windowDays: d.windowDays ?? SCHEDULING_DEFAULTS.windowDays,\n summaryTemplate: d.summaryTemplate ?? SCHEDULING_DEFAULTS.summaryTemplate,\n };\n const errors: SchedulingErrors = {};\n if (!(c.slotMinutes >= 15 && c.slotMinutes <= 240)) {\n errors.slotMinutes = \"Slot length must be between 15 and 240 minutes.\";\n }\n if (!(c.windowDays >= 1 && c.windowDays <= 62)) {\n errors.windowDays = \"Booking window must be between 1 and 62 days (the calendar caps look-ahead at 62).\";\n }\n if (!(c.minNoticeHours >= 0 && c.minNoticeHours <= 336)) {\n errors.minNoticeHours = \"Minimum notice must be between 0 and 336 hours.\";\n }\n if (!(c.startHour >= 0 && c.startHour < c.endHour && c.endHour <= 24)) {\n errors.hours = \"Hours must satisfy 0 ≤ start < end ≤ 24.\";\n }\n const days = [...c.days];\n if (!days.length) errors.days = \"Pick at least one day.\";\n else if (!days.every((n) => Number.isInteger(n) && n >= 0 && n <= 6)) {\n errors.days = \"Days must be weekday numbers, 0 (Sunday) through 6 (Saturday).\";\n }\n if (typeof c.timezone !== \"string\" || !isValidTimeZone(c.timezone)) {\n errors.timezone = `\"${String(c.timezone)}\" is not a valid IANA timezone (for example \"America/Los_Angeles\").`;\n }\n if (typeof c.summaryTemplate !== \"string\") errors.summaryTemplate = \"Calendar summary template must be text.\";\n return Object.keys(errors).length > 0 ? { ok: false, errors } : { ok: true, value: { ...c, days } };\n}\n\n/** Statuses a member may book/reschedule from (early pipeline only). */\n// Booking eligibility is answered by the configured pipeline — canBook(status,\n// pipeline) in ./pipeline — not a hardcoded status list, so a chapter that\n// customizes its stages gets one consistent answer.\n\n/** The availability window: `[now, now + windowDays]` in epoch ms. */\nexport function slotWindow(now: number, windowDays: number): { from: number; to: number } {\n return { from: now, to: now + windowDays * 86_400_000 };\n}\n\n/** The slot's end instant, always derived from its start (never client-supplied). */\nexport function endForSlot(startAt: number, slotMinutes: number): number {\n return startAt + slotMinutes * 60_000;\n}\n\n/** Double-book pre-check: the requested start must land exactly on a currently\n * bookable slot boundary. */\nexport function isSlotAvailable(slots: readonly { startAt: number }[], startAt: number): boolean {\n return slots.some((s) => s.startAt === startAt);\n}\n\n/** Render a meeting summary from its template (`{{firstName}}`/`{{lastName}}`). */\nexport function renderSummary(template: string, app: { firstName?: string | null; lastName?: string | null }): string {\n return template.replace(\"{{firstName}}\", app.firstName ?? \"\").replace(\"{{lastName}}\", app.lastName ?? \"\");\n}\n\n/** The prior scheduled meeting for an application, as far as booking cares. */\nexport interface ExistingMeeting {\n id: string;\n googleEventId?: string | null;\n meetUrl?: string | null;\n htmlLink?: string | null;\n}\n\n/** Decide reschedule-vs-create: reschedule iff there's an existing event to move,\n * so the Meet link + invite thread survive and no second event is minted. */\nexport function bookingDecision(existing: ExistingMeeting | null | undefined): { reschedule: boolean; eventId: string | null } {\n const eventId = existing?.googleEventId ?? null;\n return { reschedule: Boolean(eventId), eventId };\n}\n\n/** The create idempotency key — one intro event per application, forever, so a\n * retried create returns the same booking rather than a duplicate. */\nexport function introIdempotencyKey(applicationId: string): string {\n return `application:${applicationId}:intro`;\n}\n\n/** The canonical `meetings` row for a first booking. A `type` (not `interface`)\n * so it stays assignable to a db op's `attrs` (Record<string, unknown>). */\nexport type NewMeetingRow = {\n id: string;\n applicationId: string;\n groupId: string;\n startAt: number;\n endAt: number;\n timezone: string;\n status: \"scheduled\";\n googleEventId: string;\n meetUrl?: string;\n htmlLink?: string;\n drift: \"none\";\n createdAt: number;\n};\n\n/** Build the new `meetings` row after the calendar created the event. Optional\n * scalars are omitted (never null), per the odla-db porting rule. */\nexport function meetingCreateRow(i: {\n meetingId: string;\n applicationId: string;\n groupId: string;\n startAt: number;\n endAt: number;\n timezone: string;\n googleEventId: string;\n meetUrl?: string | null;\n htmlLink?: string | null;\n createdAt: number;\n}): NewMeetingRow {\n return {\n id: i.meetingId,\n applicationId: i.applicationId,\n groupId: i.groupId,\n startAt: i.startAt,\n endAt: i.endAt,\n timezone: i.timezone,\n status: \"scheduled\",\n googleEventId: i.googleEventId,\n ...(i.meetUrl ? { meetUrl: i.meetUrl } : {}),\n ...(i.htmlLink ? { htmlLink: i.htmlLink } : {}),\n drift: \"none\",\n createdAt: i.createdAt,\n };\n}\n\n/** The `meetings`-row patch for a reschedule (same row id, moved in place). */\nexport type MeetingReschedulePatch = {\n startAt: number;\n endAt: number;\n drift: \"none\";\n};\n\n/** Patch to move an existing meeting to a new window. */\nexport function meetingRescheduleUpdate(startAt: number, endAt: number): MeetingReschedulePatch {\n return { startAt, endAt, drift: \"none\" };\n}\n\n/** The `applications`-row patch after a booking. */\nexport type ApplicationBookingPatch = {\n meetingAt: number;\n meetingLink?: string;\n status?: \"call_scheduled\";\n};\n\n/** Project the booking onto the application row: cache the time, adopt the\n * calendar link if any, and advance the status to `call_scheduled` unless it is\n * already there (never backward). */\nexport function applicationBookingUpdate(\n currentStatus: string,\n startAt: number,\n htmlLink?: string | null,\n): ApplicationBookingPatch {\n return {\n meetingAt: startAt,\n ...(htmlLink ? { meetingLink: htmlLink } : {}),\n ...(currentStatus !== \"call_scheduled\" ? { status: \"call_scheduled\" as const } : {}),\n };\n}\n"],"mappings":"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","names":["superAdmins","id","paymentsReady","id","import_crm","emailLog","id","str","import_crm","import_crm","id","id","meetings"]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/config.ts","../src/schema.ts","../src/defaults.ts","../src/group.ts","../src/auth.ts","../src/pipeline.ts","../src/application-id.ts","../src/member.ts","../src/copy-defaults-admin.ts","../src/copy-defaults.ts","../src/copy-resolve.ts","../src/descriptor.ts","../src/email.ts","../src/notify.ts","../src/payments.ts","../src/network.ts","../src/crm-sync.ts","../src/series.ts","../src/payments-stripe.ts","../src/clerk.ts","../src/clerk-roles.ts","../src/clerk-integration.ts","../src/session.ts","../src/brand.ts","../src/reconcile.ts","../src/scheduling.ts"],"sourcesContent":["// @odla-ai/chapter — core (platform-neutral). The UI kit lives at ./ui.\n// chapterWorker (the full Cloudflare handler) lands with the worker port.\nexport { defineChapter } from \"./config\";\nexport { createChapterIntegration, type ChapterRunbookHints } from \"./descriptor\";\nexport { chapterDb } from \"./schema\";\nexport { defaultCrm } from \"./defaults\";\nexport { buildGroupSeed } from \"./group\";\nexport { resolveAuth, roleFromClaim, isAdminRole, canChangeRole, getVaultSecret } from \"./auth\";\nexport { render, renderTemplateBody, isAlreadySent, planDelivery } from \"./email\";\nexport { sendTemplated, emailGroupFrom } from \"./notify\";\nexport type { MailSender, NotifyDeps, NotifyInput, NotifyResult } from \"./notify\";\nexport { resolvePipeline, canTransition, canBook, canApprove, stageIndex } from \"./pipeline\";\nexport {\n verifyStripeSignature,\n paymentsReady,\n stripeForm,\n subscriptionIdempotencyKey,\n webhookMutationId,\n findApplicationRef,\n normalizeWebhookEvent,\n firstPaymentPatch,\n renewalPatch,\n refundedPatch,\n canceledPatch,\n} from \"./payments\";\nexport type { PaymentsGroup, StripeEvent, WebhookEvent } from \"./payments\";\nexport { resolveApplication, submitApplication, joinConfig, applicantProfile, hasDisclaimerAck, isValidEmail, clampArray } from \"./member\";\nexport {\n DEFAULT_SHARE_FIELDS,\n sharedPersonInput,\n normalizeSharedRecord,\n sharedRecordFromCrm,\n networkSourceTag,\n projectSharedRecord,\n projectApplicant,\n} from \"./network\";\nexport { syncApplicationToCrm, backfillCrm, personInputFromApp } from \"./crm-sync\";\nexport {\n bucketSeries,\n dashboardMetricData,\n subAnnualCents,\n type DashboardMetricData,\n type DashboardMetricSeries,\n} from \"./series\";\nexport { stripeCall } from \"./payments-stripe\";\nexport type { StripeResult } from \"./payments-stripe\";\nexport {\n clerkInviteRequest,\n createClerkInvitation,\n clerkUserRequest,\n createClerkUser,\n updateClerkUserMetadata,\n updateClerkUserMetadataByEmail,\n} from \"./clerk\";\nexport type { ClerkInviteInput, ClerkUserInput, ClerkMetadataInput, ClerkResult } from \"./clerk\";\nexport { clerkGetUserByEmail, clerkGetUser, clerkListUsers, clerkSetRole } from \"./clerk-roles\";\nexport type { ClerkUserRecord } from \"./clerk-roles\";\nexport { clerkIntegration } from \"./clerk-integration\";\nexport type {\n IntegrationDescriptor,\n IntegrationSetting,\n IntegrationSecret,\n IntegrationSync,\n IntegrationProvision,\n SecretMode,\n} from \"./clerk-integration\";\nexport { applicationSummary, memberApplication, memberSession } from \"./session\";\nexport { brandTokens, chapterBrandFromTokens } from \"./brand\";\nexport type { CompiledChapterBrandTokens } from \"./brand\";\nexport { DEFAULT_CHAPTER_COPY, resolveChapterCopy, formatChapterCopy } from \"./copy\";\nexport { reconcileMeetings, isReconcilable } from \"./reconcile\";\nexport type { MeetingForReconcile, LiveEvent, ReconcileDecision } from \"./reconcile\";\nexport {\n resolveScheduling,\n validateScheduling,\n SCHEDULING_DEFAULTS,\n slotWindow,\n endForSlot,\n isSlotAvailable,\n renderSummary,\n bookingDecision,\n introIdempotencyKey,\n meetingCreateRow,\n meetingRescheduleUpdate,\n applicationBookingUpdate,\n} from \"./scheduling\";\n\nexport type { ChapterIntegrationDescriptor, ChapterIntegrationOptions } from \"./descriptor\";\nexport type { RoleChangeContext, GuardResult, SecretStore } from \"./auth\";\nexport type { EmailTemplateRow, EmailGroup, DeliveryDecision } from \"./email\";\nexport type { SubmitResult, JoinConfigGroup } from \"./member\";\nexport type { SharedPerson, SharedBusiness, SharedRecord, ProjectionDeps, Applicant } from \"./network\";\nexport type {\n ApplicationRecord,\n MeetingRecord,\n ApplicationSummary,\n MemberApplication,\n MemberSession,\n SessionUser,\n} from \"./session\";\nexport type {\n ResolvedScheduling,\n SchedulingErrors,\n ExistingMeeting,\n NewMeetingRow,\n MeetingReschedulePatch,\n ApplicationBookingPatch,\n} from \"./scheduling\";\nexport type {\n Chapter,\n ChapterConfig,\n ChapterMode,\n AccountModel,\n ChapterSends,\n ResolvedSends,\n ChapterOperations,\n ResolvedOperations,\n AdminNotificationTrigger,\n ChapterAuth,\n ResolvedAuth,\n ChapterPipeline,\n ResolvedPipeline,\n ChapterApplication,\n ResolvedApplication,\n DbOp,\n ChapterDb,\n ChapterBrand,\n ChapterNetwork,\n ChapterNetworkTarget,\n ResolvedNetwork,\n ResolvedNetworkTarget,\n ChapterPrices,\n ChapterPolicy,\n ChapterEmails,\n ChapterScheduling,\n EmailTemplate,\n DbSchema,\n DbRules,\n Attr,\n AttrType,\n Entity,\n Rule,\n} from \"./types\";\nexport type { ChapterCopy, ChapterCopyInput } from \"./copy\";\n","// defineChapter — validate-at-import config, reusing @odla-ai/crm's defineCrm.\n// A bad config throws here (startup), never at request time.\nimport { defineCrm } from \"@odla-ai/crm\";\nimport type { CrmConfig, Crm } from \"@odla-ai/crm\";\nimport type {\n Chapter,\n ChapterConfig,\n ChapterMode,\n ResolvedNetwork,\n ResolvedNetworkTarget,\n ResolvedOperations,\n} from \"./types\";\nimport { chapterDb } from \"./schema\";\nimport { defaultCrm } from \"./defaults\";\nimport { buildGroupSeed } from \"./group\";\nimport { resolveAuth } from \"./auth\";\nimport { resolvePipeline } from \"./pipeline\";\nimport { resolveApplication } from \"./member\";\nimport { resolveChapterCopy } from \"./copy\";\n\nconst SLUG = /^[a-z0-9][a-z0-9-]{1,62}$/;\nconst FIELD = /^[a-z][a-zA-Z0-9_]*$/;\n\nfunction resolveNetwork(config: ChapterConfig, crm: Crm): ResolvedNetwork {\n const seen = new Set<string>();\n const targets: ResolvedNetworkTarget[] = [];\n for (const target of config.network?.targets ?? []) {\n if (!SLUG.test(target.id)) {\n throw new Error(`defineChapter.network.targets.id: must be a lowercase slug — got ${JSON.stringify(target.id)}`);\n }\n if (seen.has(target.id)) throw new Error(`defineChapter.network.targets: duplicate target \"${target.id}\"`);\n seen.add(target.id);\n let url: URL;\n try {\n url = new URL(target.url);\n } catch {\n throw new Error(`defineChapter.network.targets.${target.id}.url: must be an absolute URL`);\n }\n const local = url.hostname === \"localhost\" || url.hostname === \"127.0.0.1\" || url.hostname === \"::1\";\n if (url.protocol !== \"https:\" && !(local && url.protocol === \"http:\")) {\n throw new Error(`defineChapter.network.targets.${target.id}.url: must use https (http is allowed only for localhost)`);\n }\n const fields = target.fields;\n for (const [type, names] of Object.entries(fields ?? {})) {\n if (!FIELD.test(type) || names.length === 0 || names.some((name) => !FIELD.test(name))) {\n throw new Error(\n `defineChapter.network.targets.${target.id}.fields.${type}: type and field names must be non-empty CRM identifiers`,\n );\n }\n const def = crm.config.types[type];\n if (!def) throw new Error(`defineChapter.network.targets.${target.id}.fields: unknown leader CRM type \"${type}\"`);\n const unknown = names.filter((field) => !def.fields[field]);\n if (unknown.length > 0) {\n throw new Error(\n `defineChapter.network.targets.${target.id}.fields.${type}: ${unknown.map((field) => `\"${field}\"`).join(\", \")} ` +\n \"not declared on the leader CRM type\",\n );\n }\n const nameField = def.nameField ?? \"name\";\n if (!names.includes(nameField)) {\n throw new Error(\n `defineChapter.network.targets.${target.id}.fields.${type}: must include required name field \"${nameField}\"`,\n );\n }\n }\n const secretName = target.secretName?.trim() || `network_share_${target.id.replaceAll(\"-\", \"_\")}`;\n if (!/^[a-zA-Z][a-zA-Z0-9_-]{1,127}$/.test(secretName)) {\n throw new Error(`defineChapter.network.targets.${target.id}.secretName: must be a vault-key identifier`);\n }\n targets.push({\n id: target.id,\n name: target.name?.trim() || target.id,\n url: url.origin,\n secretName,\n ...(fields ? { fields } : {}),\n });\n }\n return { targets };\n}\n\nfunction isResolvedCrm(x: CrmConfig | Crm | undefined): x is Crm {\n return (\n !!x &&\n typeof x === \"object\" &&\n \"prepare\" in x &&\n typeof (x as { prepare?: unknown }).prepare === \"function\"\n );\n}\n\n/**\n * Validate a chapter/hub config and resolve its engine — the CRM, the chapter's\n * odla-db schema/rules, and the seed groups row. Throws at import on a bad\n * config (bad slug, wrong mode, missing chapter-mode prices/emails), never at\n * request time.\n */\nexport function defineChapter(config: ChapterConfig): Chapter {\n if (!config || typeof config !== \"object\") throw new Error(\"defineChapter: a config object is required\");\n const { id, name } = config;\n if (typeof id !== \"string\" || !SLUG.test(id)) {\n throw new Error(`defineChapter.id: must be a lowercase slug [a-z0-9-] (2-63 chars) — got ${JSON.stringify(id)}`);\n }\n if (typeof name !== \"string\" || name.trim() === \"\") throw new Error(\"defineChapter.name: a non-empty name is required\");\n\n const mode: ChapterMode = config.mode ?? \"chapter\";\n if (mode !== \"chapter\" && mode !== \"hub\") throw new Error(`defineChapter.mode: must be \"chapter\" or \"hub\" — got ${JSON.stringify(config.mode)}`);\n\n const crm: Crm = isResolvedCrm(config.crm) ? config.crm : defineCrm(config.crm ?? defaultCrm(mode));\n\n if (mode === \"chapter\") {\n if (!config.emails || typeof config.emails.notificationEmail !== \"string\" || config.emails.notificationEmail === \"\") {\n throw new Error(\"defineChapter.emails.notificationEmail: required in chapter mode\");\n }\n if (!config.prices || typeof config.prices.standardCents !== \"number\") {\n throw new Error(\"defineChapter.prices.standardCents: required in chapter mode\");\n }\n if (config.account === undefined) {\n throw new Error('defineChapter.account: required in chapter mode; choose \"none\", \"create\", or \"invite\" explicitly');\n }\n }\n\n const auth = resolveAuth(mode, config.auth);\n const pipeline = resolvePipeline(config.pipeline);\n const application = resolveApplication(config.application);\n const brand = { ...config.brand, wordmark: config.brand?.wordmark ?? name };\n const copy = resolveChapterCopy(config.copy);\n const network = resolveNetwork(config, crm);\n const { schema, rules } = chapterDb(mode, auth);\n const services = config.services ?? [\"db\", \"calendar\", \"o11y\"];\n // Hub mode has no public application flow, so its inert resolved value remains\n // `none`. Chapter mode was required to make this decision above.\n const account = config.account ?? \"none\";\n if (account !== \"invite\" && account !== \"create\" && account !== \"none\") {\n throw new Error(`defineChapter.account: must be \"invite\", \"create\", or \"none\" — got \"${String(account)}\"`);\n }\n // crmFields not declared on the CRM person type are dropped by the projection at\n // runtime (the base person still lands), so a typo is invisible. Both the\n // resolved crmFields and the resolved crm are in hand here — cross-check the\n // names and fail at resolve time instead.\n if (application.crmFields.length > 0) {\n let personFields: string[] | null = null;\n try {\n personFields = Object.keys(crm.type(\"person\").fields ?? {});\n } catch {\n // No introspectable \"person\" type — leave it to the projection.\n }\n const unknown = personFields ? application.crmFields.filter((f) => !personFields.includes(f)) : [];\n if (unknown.length > 0) {\n throw new Error(\n `defineChapter.application.crmFields: ${unknown.map((f) => `\"${f}\"`).join(\", \")} not declared on the CRM \"person\" type ` +\n `(declared: ${personFields?.join(\", \")}). An undeclared field is silently dropped from the CRM projection — ` +\n `add it to your crm config's person fields, or remove it from crmFields.`,\n );\n }\n }\n const adminNotification = config.sends?.adminNotification ?? \"submit\";\n if (adminNotification !== \"submit\" && adminNotification !== \"payment\" && adminNotification !== \"never\") {\n throw new Error(\n `defineChapter.sends.adminNotification: must be \"submit\", \"payment\", or \"never\" — got \"${String(adminNotification)}\"`,\n );\n }\n const sends = { adminNotification };\n\n // Admin-operation policy. The escalation RULES are not here — they are\n // package-enforced in canChangeRole (gated on auth.superAdmins), so a site\n // cannot weaken them. These two are genuine per-site decisions.\n const onApproveCfg = config.operations?.onApprove ?? {};\n // Default promotion target: the rung directly below admin, so a custom ladder\n // gets the right answer instead of a hardcoded \"member\".\n const belowAdmin = auth.ladder[auth.ladder.length - 2] ?? auth.adminRole;\n const promoteTo = onApproveCfg.promoteTo === undefined ? belowAdmin : onApproveCfg.promoteTo;\n if (promoteTo !== false && !auth.ladder.includes(promoteTo)) {\n throw new Error(\n `defineChapter.operations.onApprove.promoteTo: \"${String(promoteTo)}\" is not in the auth ladder (${auth.ladder.join(\", \")})`,\n );\n }\n const refundCfg = config.operations?.refund ?? {};\n if (refundCfg.allowedFrom !== undefined) {\n const unknown = refundCfg.allowedFrom.filter((s) => !pipeline.stages.includes(s));\n if (unknown.length > 0) {\n throw new Error(\n `defineChapter.operations.refund.allowedFrom: ${unknown.map((s) => `\"${s}\"`).join(\", \")} not in the pipeline (${pipeline.stages.join(\", \")})`,\n );\n }\n }\n const operations: ResolvedOperations = {\n onApprove: { promoteTo, send: onApproveCfg.send === undefined ? \"onboardingInvite\" : onApproveCfg.send },\n refund: { allowedFrom: refundCfg.allowedFrom ?? null, cancelSubscription: refundCfg.cancelSubscription ?? true },\n };\n\n const chapter: Chapter = {\n config,\n id,\n name,\n mode,\n brand,\n copy,\n network,\n crm,\n auth,\n pipeline,\n application,\n schema,\n rules,\n services,\n account,\n sends,\n operations,\n groupSeed: () => (mode === \"chapter\" ? buildGroupSeed(config) : null),\n };\n if (config.url !== undefined) chapter.url = config.url;\n return chapter;\n}\n","// The chapter's own odla-db namespaces: the operational membership tables\n// (applications, groups, meetings, emailLog) plus the auth tables the resolved\n// auth policy selects — the `admins` allowlist (source \"table\") and/or the\n// read-only `superAdmins` tier. All deny-all. The `crm_*` namespaces are\n// contributed separately by @odla-ai/crm's integration and merged by the CLI.\nimport type { Attr, AttrType, DbRules, DbSchema, Entity, ChapterMode, ResolvedAuth } from \"./types\";\n\nfunction attr(type: AttrType, flags: { unique?: boolean; indexed?: boolean; optional?: boolean } = {}): Attr {\n return {\n type,\n unique: flags.unique ?? false,\n indexed: flags.indexed ?? false,\n optional: flags.optional ?? false,\n };\n}\nconst id = (): Attr => attr(\"string\", { unique: true, indexed: true });\n\n// The allowlist that gates admin access in BOTH modes. Studio-write-only:\n// deny-all, and no worker route ever writes it. Creation time is odla-db's\n// built-in $createdAt. One row per admin, keyed by lowercased email.\nconst admins: Entity = {\n attrs: {\n id: id(),\n email: attr(\"string\", { unique: true, indexed: true }),\n name: attr(\"string\", { optional: true }),\n note: attr(\"string\", { optional: true }),\n },\n};\n\n// The super-admin tier: the ONLY tier that may create or modify admins. A\n// separate, app-READ-ONLY entity — deny-all like every namespace AND no worker\n// route ever writes it, so membership can only be set in the odla Studio data\n// browser, never from the app or an injected page script. One row per\n// super-admin, keyed by lowercased email.\nconst superAdmins: Entity = {\n attrs: {\n id: id(),\n email: attr(\"string\", { unique: true, indexed: true }),\n note: attr(\"string\", { optional: true }),\n createdAt: attr(\"number\", { indexed: true }),\n },\n};\n\n// One row per membership application (chapter mode). Field names mirror the\n// join form. status pipeline drives the provisional -> member promotion.\nconst applications: Entity = {\n attrs: {\n id: id(),\n firstName: attr(\"string\"),\n lastName: attr(\"string\"),\n email: attr(\"string\", { indexed: true }),\n referral: attr(\"string\"),\n referralName: attr(\"string\", { optional: true }),\n whoYouAre: attr(\"string\"),\n focus: attr(\"json\"),\n linkedin: attr(\"string\", { optional: true }),\n message: attr(\"string\"),\n status: attr(\"string\", { indexed: true }),\n createdAt: attr(\"number\", { indexed: true }),\n meetingAt: attr(\"number\", { indexed: true, optional: true }),\n meetingLink: attr(\"string\", { optional: true }),\n clerkUserId: attr(\"string\", { indexed: true, optional: true }),\n // One-time/effect-repair marker. Absent means /api/me should copy the\n // allowlisted application snapshot into Clerk private_metadata and remove\n // the former public_metadata copy.\n clerkPrivateMetadataSyncedAt: attr(\"number\", { optional: true }),\n phone: attr(\"string\", { optional: true }),\n state: attr(\"string\", { optional: true }),\n groupId: attr(\"string\", { indexed: true, optional: true }),\n stripeCustomerId: attr(\"string\", { indexed: true, optional: true }),\n stripeSubscriptionId: attr(\"string\", { indexed: true, optional: true }),\n renewalAt: attr(\"number\", { optional: true }),\n disclaimerAckAt: attr(\"number\", { optional: true }),\n refundPolicyAckAt: attr(\"number\", { optional: true }),\n prepEmailSentAt: attr(\"number\", { optional: true }),\n canceled: attr(\"boolean\", { optional: true }),\n },\n};\n\n// Per-group settings — prices, policy copy, email templates, scheduling — never\n// in code. Seeded once from the defineChapter config (see group.ts).\nconst groups: Entity = {\n attrs: {\n id: id(),\n name: attr(\"string\"),\n standardPriceCents: attr(\"number\"),\n foundingDiscountCents: attr(\"number\"),\n stripePriceId: attr(\"string\", { optional: true }),\n stripePublishableKey: attr(\"string\", { optional: true }),\n notificationEmail: attr(\"string\"),\n replyTo: attr(\"string\"),\n debugEmail: attr(\"string\", { optional: true }),\n calendarLink: attr(\"string\", { optional: true }),\n disclaimerText: attr(\"string\"),\n refundPolicyText: attr(\"string\"),\n trustCopy: attr(\"string\"),\n commitmentText: attr(\"string\", { optional: true }),\n normsText: attr(\"string\", { optional: true }),\n emailTemplates: attr(\"json\"),\n schedulingJson: attr(\"json\", { optional: true }),\n createdAt: attr(\"number\", { indexed: true }),\n },\n};\n\n// Intro-call meetings: the source of truth for scheduling; Google Calendar is a\n// projection. Drift fields record when Google disagrees with us.\nconst meetings: Entity = {\n attrs: {\n id: id(),\n applicationId: attr(\"string\", { indexed: true }),\n groupId: attr(\"string\", { indexed: true }),\n startAt: attr(\"number\", { indexed: true }),\n endAt: attr(\"number\"),\n timezone: attr(\"string\"),\n status: attr(\"string\", { indexed: true }),\n googleEventId: attr(\"string\", { indexed: true, optional: true }),\n meetUrl: attr(\"string\", { optional: true }),\n htmlLink: attr(\"string\", { optional: true }),\n drift: attr(\"string\", { indexed: true, optional: true }),\n driftGoogleStartAt: attr(\"number\", { optional: true }),\n driftDetectedAt: attr(\"number\", { optional: true }),\n adoptedFromGoogleAt: attr(\"number\", { optional: true }),\n createdAt: attr(\"number\", { indexed: true }),\n },\n};\n\n// Audit of every transactional send.\nconst emailLog: Entity = {\n attrs: {\n id: id(),\n groupId: attr(\"string\", { indexed: true }),\n applicationId: attr(\"string\", { indexed: true, optional: true }),\n to: attr(\"string\", { indexed: true }),\n template: attr(\"string\", { indexed: true }),\n subject: attr(\"string\"),\n body: attr(\"string\", { optional: true }),\n transport: attr(\"string\"),\n messageId: attr(\"string\", { optional: true }),\n redirected: attr(\"boolean\", { optional: true }),\n dedupeKey: attr(\"string\", { indexed: true, optional: true }),\n error: attr(\"string\", { optional: true }),\n sentAt: attr(\"number\", { indexed: true }),\n },\n};\n\n/** The chapter's own schema + deny-all rules for a mode + auth policy.\n *\n * Operational tables (`applications`/`groups`/`meetings`/`emailLog`) are added in\n * `chapter` mode only. The auth tables follow {@link ResolvedAuth}: `source:\n * \"table\"` adds the `admins` allowlist (hub/BNF); `superAdmins` adds the\n * read-only super-admin tier (default on for the `\"claim\"` ladder). A `\"claim\"`\n * chapter therefore emits the reference namespace set — `applications`,\n * `groups`, `meetings`, `emailLog`, `superAdmins` — with no `admins` table. */\nexport function chapterDb(mode: ChapterMode, auth: ResolvedAuth): { schema: DbSchema; rules: DbRules } {\n const entities: Record<string, Entity> = {};\n if (mode === \"chapter\") {\n entities.applications = applications;\n entities.groups = groups;\n entities.meetings = meetings;\n entities.emailLog = emailLog;\n }\n if (auth.source === \"table\") entities.admins = admins;\n if (auth.superAdmins) entities.superAdmins = superAdmins;\n const schema: DbSchema = { entities, links: {} };\n const rules: DbRules = {};\n for (const ns of Object.keys(entities)) {\n rules[ns] = { view: \"false\", create: \"false\", update: \"false\", delete: \"false\" };\n }\n return { schema, rules };\n}\n","// Per-mode default CRM configs. Consumers pass their own via defineChapter's\n// `crm`; these are sensible starting points. Both modes share the canonical\n// people + businesses graph, while `chapter` additionally feeds the person\n// pipeline from applications through the operational package routes.\nimport type { CrmConfig } from \"@odla-ai/crm\";\nimport type { ChapterMode } from \"./types\";\n\nconst TEMPLATES: CrmConfig[\"templates\"] = {\n personal: {\n class: \"transactional\",\n vars: [\"firstName\", \"subject\", \"body\"],\n defaults: { subject: \"{{subject}}\", text: \"{{body}}\" },\n },\n announcement: {\n class: \"marketing\",\n vars: [\"firstName\", \"subject\", \"body\", \"unsubscribeUrl\"],\n defaults: { subject: \"{{subject}}\", text: \"{{body}}\\n\\n{{unsubscribeUrl}}\" },\n },\n};\n\nfunction personType(): NonNullable<CrmConfig[\"types\"]>[string] {\n return {\n label: \"Person\",\n labelPlural: \"People\",\n nameField: \"name\",\n emailField: \"email\",\n fields: {\n name: { type: \"string\", label: \"Name\", required: true },\n email: { type: \"email\", label: \"Email\" },\n firstName: { type: \"string\", label: \"First name\" },\n lastName: { type: \"string\", label: \"Last name\" },\n phone: { type: \"string\", label: \"Phone\" },\n state: { type: \"string\", label: \"State\", slot: \"s1\" },\n whoYouAre: { type: \"string\", label: \"Who they are\", slot: \"s2\" },\n referral: { type: \"string\", label: \"Referral source\", slot: \"s3\" },\n linkedin: { type: \"string\", label: \"LinkedIn\" },\n focus: { type: \"json\", label: \"Focus areas\" },\n message: { type: \"string\", label: \"Intro message\" },\n },\n pipeline: {\n stages: [\n { id: \"submitted\", label: \"Submitted\" },\n { id: \"paid_pending_vetting\", label: \"Paid, pending vetting\" },\n { id: \"call_scheduled\", label: \"Call scheduled\" },\n { id: \"interviewed\", label: \"Interviewed\" },\n { id: \"approved\", label: \"Approved\" },\n { id: \"declined\", label: \"Declined\" },\n { id: \"refunded\", label: \"Refunded\" },\n ],\n },\n facets: { identity: true, email: true, rank: \"manual\" },\n };\n}\n\nfunction companyType(): NonNullable<CrmConfig[\"types\"]>[string] {\n return {\n label: \"Business\",\n labelPlural: \"Businesses\",\n nameField: \"name\",\n fields: {\n name: { type: \"string\", label: \"Name\", required: true },\n domain: { type: \"string\", label: \"Domain / website\", slot: \"s1\" },\n industry: { type: \"string\", label: \"Industry\", slot: \"s2\" },\n location: { type: \"string\", label: \"Location\", slot: \"s3\" },\n chapter: { type: \"string\", label: \"Chapter\", slot: \"s4\" },\n linkedin: { type: \"string\", label: \"LinkedIn\" },\n notes: { type: \"string\", label: \"Notes\" },\n },\n facets: { rank: \"manual\" },\n };\n}\n\n/** The default shared CRM graph: people (with the application/member pipeline)\n * plus businesses and the `works_at` relation, in both modes. */\nexport function defaultCrm(_mode: ChapterMode): CrmConfig {\n return {\n types: { person: personType(), company: companyType() },\n relations: { works_at: { from: \"person\", to: \"company\", label: \"works at\", reverseLabel: \"team\" } },\n templates: TEMPLATES,\n };\n}\n","// Build the seed `groups` row from a defineChapter config. This is the whole\n// per-chapter payload the worker reads at runtime (prices/policy/emails/\n// scheduling), so nothing brand-specific is hardcoded in the worker. createdAt\n// is stamped by the integration/seed layer, not here.\nimport type { ChapterConfig, ChapterScheduling } from \"./types\";\n\nconst DEFAULT_SCHEDULING: Required<Omit<ChapterScheduling, \"summaryTemplate\">> = {\n slotMinutes: 45,\n days: [1, 2, 3, 4, 5],\n startHour: 9,\n endHour: 17,\n timezone: \"America/Los_Angeles\",\n minNoticeHours: 24,\n windowDays: 14,\n};\n\nfunction defaultEmailTemplates(name: string): Record<string, { subject: string; text: string }> {\n const sign = `\\n\\nWarmly,\\n${name}`;\n return {\n adminNotification: {\n subject: `New application: {{firstName}} {{lastName}}`,\n text: `A new application came in for ${name}.\\n\\nName: {{firstName}} {{lastName}}\\nEmail: {{email}}`,\n },\n paymentConfirmation: {\n subject: `Welcome to ${name}`,\n text: `Hi {{firstName}},\\n\\nYour membership payment is confirmed. We'll be in touch to schedule your intro call.${sign}`,\n },\n prepEmail: {\n subject: `Your ${name} intro call`,\n text: `Hi {{firstName}},\\n\\nLooking forward to our call at {{meetingTime}}. {{meetingLink}}${sign}`,\n },\n onboardingInvite: {\n subject: `You're in, ${name}`,\n text: `Hi {{firstName}},\\n\\nWelcome to ${name}. Your member area is here: {{membersUrl}}${sign}`,\n },\n };\n}\n\n/** The `groups` row (attrs) for `chapter` mode. Missing config falls back to\n * empty copy / defaults, so a minimal config still provisions cleanly. */\nexport function buildGroupSeed(config: ChapterConfig): Record<string, unknown> {\n const prices = config.prices;\n const emails = config.emails;\n const policy = config.policy ?? {};\n const scheduling = {\n ...DEFAULT_SCHEDULING,\n ...(config.scheduling ?? {}),\n summaryTemplate:\n config.scheduling?.summaryTemplate ?? `${config.name}: introduction call with {{firstName}} {{lastName}}`,\n };\n const row: Record<string, unknown> = {\n id: config.id,\n name: config.name,\n standardPriceCents: prices?.standardCents ?? 0,\n foundingDiscountCents: prices?.foundingDiscountCents ?? 0,\n notificationEmail: emails?.notificationEmail ?? \"\",\n replyTo: emails?.replyTo ?? emails?.notificationEmail ?? \"\",\n disclaimerText: policy.disclaimerText ?? \"\",\n refundPolicyText: policy.refundPolicyText ?? \"\",\n trustCopy: policy.trustCopy ?? \"\",\n emailTemplates: emails?.templates ?? defaultEmailTemplates(config.name),\n schedulingJson: scheduling,\n };\n if (emails?.debugEmail) row.debugEmail = emails.debugEmail;\n if (policy.commitmentText) row.commitmentText = policy.commitmentText;\n if (policy.normsText) row.normsText = policy.normsText;\n return row;\n}\n","// Identity + authorization for a chapter/hub site — the pieces every membership\n// site needs and none should re-derive: a resolved role policy, role resolution\n// from a JWT claim, the privilege-escalation guard, and a tenant-vault read.\n// Everything here is pure or structural (no runtime @odla-ai/db import), so it is\n// trivially testable and the worker stays the only thing that talks to odla-db.\nimport type { ChapterAuth, ChapterMode, ResolvedAuth } from \"./types\";\n\n/**\n * Apply defaults + validate the auth config into a {@link ResolvedAuth}. Defaults\n * by mode: `chapter` → the `provisional/member/admin` claim ladder with the\n * `superAdmins` tier; `hub` → the `admins` allowlist table with no super tier.\n * Throws at import on a bad policy.\n */\nexport function resolveAuth(mode: ChapterMode, auth: ChapterAuth | undefined): ResolvedAuth {\n const a = auth ?? {};\n const source = a.source ?? (mode === \"hub\" ? \"table\" : \"claim\");\n if (source !== \"claim\" && source !== \"table\") {\n throw new Error(`defineChapter.auth.source: must be \"claim\" or \"table\" — got ${JSON.stringify(a.source)}`);\n }\n const claim = a.claim ?? \"role\";\n if (typeof claim !== \"string\" || claim === \"\") {\n throw new Error(\"defineChapter.auth.claim: must be a non-empty string\");\n }\n const ladder = a.ladder ?? [\"provisional\", \"member\", \"admin\"];\n if (!Array.isArray(ladder) || ladder.length === 0 || !ladder.every((r) => typeof r === \"string\" && r !== \"\")) {\n throw new Error(\"defineChapter.auth.ladder: must be a non-empty array of role strings\");\n }\n const adminRole = ladder[ladder.length - 1] as string;\n const superAdmins = a.superAdmins ?? source === \"claim\";\n return { source, claim, ladder, adminRole, superAdmins };\n}\n\n/** The role from a verified JWT payload, per the resolved policy. An unknown or\n * missing claim falls back to the lowest ladder rung (fail safe, never admin). */\nexport function roleFromClaim(payload: Record<string, unknown>, auth: ResolvedAuth): string {\n const raw = payload[auth.claim];\n return typeof raw === \"string\" && auth.ladder.includes(raw) ? raw : (auth.ladder[0] as string);\n}\n\n/** Does a role meet the admin bar (the highest ladder rung)? */\nexport function isAdminRole(role: string, auth: ResolvedAuth): boolean {\n return role === auth.adminRole;\n}\n\n/** Inputs to the role-change guard — resolved by the caller (route) from the\n * identity provider + the read-only `superAdmins` table. */\nexport interface RoleChangeContext {\n actorId: string;\n actorIsSuper: boolean;\n targetId: string;\n targetCurrentRole: string;\n targetIsSuper: boolean;\n newRole: string;\n auth: ResolvedAuth;\n}\n\n/** The result of {@link canChangeRole}: allow, or deny with the HTTP status +\n * message the route should return. */\nexport type GuardResult = { ok: true } | { ok: false; status: number; error: string };\n\n/**\n * The privilege-escalation guard — package-enforced so every site gets it and\n * none re-derives it. Denies: an out-of-ladder role; changing your own role;\n * touching a super-admin unless you are one; and (when a `superAdmins` tier\n * exists) creating or altering an admin unless you are a super-admin. Note the\n * super-admin tier itself is never writable here — it lives in the read-only\n * `superAdmins` table, set only in odla Studio.\n */\nexport function canChangeRole(ctx: RoleChangeContext): GuardResult {\n const { auth } = ctx;\n if (!auth.ladder.includes(ctx.newRole)) {\n return { ok: false, status: 400, error: `role must be one of: ${auth.ladder.join(\", \")}` };\n }\n if (ctx.actorId === ctx.targetId) {\n return { ok: false, status: 400, error: \"you cannot change your own role\" };\n }\n if (ctx.targetIsSuper && !ctx.actorIsSuper) {\n return { ok: false, status: 403, error: \"this person is a super-admin; their access is managed in odla Studio\" };\n }\n const touchesAdmin = ctx.newRole === auth.adminRole || ctx.targetCurrentRole === auth.adminRole;\n if (auth.superAdmins && touchesAdmin && !ctx.actorIsSuper) {\n return { ok: false, status: 403, error: `only super-admins can create or change an ${auth.adminRole}` };\n }\n return { ok: true };\n}\n\n/** Structural view of odla-db's tenant-vault read, so chapter takes no runtime\n * dependency on @odla-ai/db. The worker's admin client satisfies this. */\nexport interface SecretStore {\n secrets: { get(name: string): Promise<string> };\n}\n\n/**\n * Read a tenant-vault secret by name; `undefined` when it is absent or the vault\n * errors, so callers degrade gracefully (e.g. `paymentsReady: false`) rather than\n * throwing. Never logs the value.\n */\nexport async function getVaultSecret(db: SecretStore, name: string): Promise<string | undefined> {\n try {\n const value = await db.secrets.get(name);\n return typeof value === \"string\" && value !== \"\" ? value : undefined;\n } catch {\n return undefined;\n }\n}\n","// The application status pipeline — config, not code. Which statuses exist, which\n// a member can book an intro call from, and which an admin can approve from\n// differ per site; the one invariant every site wants is that status never moves\n// backwards. All of this is pure + tested here; the worker enforces it on every\n// status write, and the CRM record.stage mirrors application.status (never the\n// reverse). Defaults reproduce the proven membership pipeline exactly.\nimport type { ChapterPipeline, ResolvedPipeline } from \"./types\";\n\nconst DEFAULT_STAGES = [\n \"submitted\",\n \"paid_pending_vetting\",\n \"call_scheduled\",\n \"interviewed\",\n \"approved\",\n \"declined\",\n \"refunded\",\n] as const;\nconst DEFAULT_BOOKABLE = [\"submitted\", \"paid_pending_vetting\", \"call_scheduled\"] as const;\nconst DEFAULT_APPROVABLE = [\"paid_pending_vetting\", \"call_scheduled\", \"interviewed\"] as const;\n\n/**\n * Apply defaults + validate the pipeline config. With no config, the complete\n * reference pipeline. With `stages` given but the subsets omitted, the subsets\n * default to empty (a site opts in to bookable/approvable states explicitly).\n * Throws at import on a bad pipeline (empty/duplicate stages, an initial or a\n * subset entry not on the ladder).\n */\nexport function resolvePipeline(p: ChapterPipeline | undefined): ResolvedPipeline {\n const usingDefaults = !p?.stages;\n const stages = p?.stages ?? [...DEFAULT_STAGES];\n if (!Array.isArray(stages) || stages.length === 0 || !stages.every((s) => typeof s === \"string\" && s !== \"\")) {\n throw new Error(\"defineChapter.pipeline.stages: must be a non-empty array of status strings\");\n }\n if (new Set(stages).size !== stages.length) {\n throw new Error(\"defineChapter.pipeline.stages: statuses must be unique\");\n }\n const initial = p?.initial ?? (stages[0] as string);\n if (!stages.includes(initial)) {\n throw new Error(`defineChapter.pipeline.initial: \"${initial}\" is not one of the stages`);\n }\n const bookableFrom = p?.bookableFrom ?? (usingDefaults ? [...DEFAULT_BOOKABLE] : []);\n const approvableFrom = p?.approvableFrom ?? (usingDefaults ? [...DEFAULT_APPROVABLE] : []);\n for (const [name, subset] of [\n [\"bookableFrom\", bookableFrom],\n [\"approvableFrom\", approvableFrom],\n ] as const) {\n for (const s of subset) {\n if (!stages.includes(s)) throw new Error(`defineChapter.pipeline.${name}: \"${s}\" is not one of the stages`);\n }\n }\n return { stages, bookableFrom, approvableFrom, initial };\n}\n\n/** The ordinal of a status in the ladder, or -1 if unknown. */\nexport function stageIndex(status: string, p: ResolvedPipeline): number {\n return p.stages.indexOf(status);\n}\n\n/**\n * The status-never-moves-backwards invariant: a transition is allowed only when\n * both statuses are on the ladder and `to` is at or ahead of `from`. The worker\n * calls this before every status write; a violation is a 409, never a silent\n * downgrade.\n */\nexport function canTransition(from: string, to: string, p: ResolvedPipeline): boolean {\n const fi = p.stages.indexOf(from);\n const ti = p.stages.indexOf(to);\n return fi >= 0 && ti >= 0 && ti >= fi;\n}\n\n/** May an intro call be booked from this status? */\nexport function canBook(status: string, p: ResolvedPipeline): boolean {\n return p.bookableFrom.includes(status);\n}\n\n/** May an application be approved (→ member) from this status? */\nexport function canApprove(status: string, p: ResolvedPipeline): boolean {\n return p.approvableFrom.includes(status);\n}\n","// Application creates use a caller-stable submission id as their database\n// mutation id. The entity id must be derived from that same stable input: if a\n// committed request is retried, the database correctly skips the new ops and\n// Chapter must still return the id of the row written by the first request.\n\nconst APPLICATION_ID_DOMAIN = \"odla-ai/chapter/application/v1:\";\n\n/**\n * Derive the UUID-shaped capability for an idempotent application submission.\n * SHA-256 prevents a caller from choosing a submission id that overwrites a\n * known application id; UUIDv8 bits preserve the identifier shape consumers\n * already expect.\n */\nexport async function applicationIdForSubmission(submissionId: string): Promise<string> {\n const input = new TextEncoder().encode(`${APPLICATION_ID_DOMAIN}${submissionId}`);\n const bytes = new Uint8Array(await crypto.subtle.digest(\"SHA-256\", input)).slice(0, 16);\n bytes[6] = (bytes[6]! & 0x0f) | 0x80;\n bytes[8] = (bytes[8]! & 0x3f) | 0x80;\n const hex = [...bytes].map((byte) => byte.toString(16).padStart(2, \"0\")).join(\"\");\n return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`;\n}\n","// The public member surface logic: the join config a site's join page reads (B1)\n// and the idempotent application submit (B2 validation + B3 exactly-once). Both\n// take the structural ChapterDb, so they're tested against an in-memory fake and\n// carry no runtime @odla-ai/db import. The worker builds the real db client, does\n// Clerk verification, enforces the body cap, and mounts these on chapter routes.\nimport type { Chapter, ChapterApplication, ChapterDb, ResolvedApplication } from \"./types\";\nimport { applicationIdForSubmission } from \"./application-id\";\n\n// The reference join form. `focus` (a json field) is always accepted.\nconst DEFAULT_REQUIRED = [\"firstName\", \"lastName\", \"email\", \"referral\", \"whoYouAre\", \"message\"];\nconst DEFAULT_OPTIONAL = [\"referralName\", \"linkedin\", \"phone\", \"state\"];\n\n/** Apply defaults + validate the application config. Throws at import on bad shape. */\nexport function resolveApplication(a: ChapterApplication | undefined): ResolvedApplication {\n const required = a?.required ?? DEFAULT_REQUIRED;\n const optional = a?.optional ?? DEFAULT_OPTIONAL;\n for (const [name, arr] of [[\"required\", required], [\"optional\", optional]] as const) {\n if (!Array.isArray(arr) || !arr.every((f) => typeof f === \"string\" && f !== \"\")) {\n throw new Error(`defineChapter.application.${name}: must be an array of field-name strings`);\n }\n }\n if (a?.profileFields !== undefined && (!Array.isArray(a.profileFields) || !a.profileFields.every((f) => typeof f === \"string\" && f !== \"\"))) {\n throw new Error(\"defineChapter.application.profileFields: must be an array of field-name strings\");\n }\n if (a?.crmFields !== undefined && (!Array.isArray(a.crmFields) || !a.crmFields.every((f) => typeof f === \"string\" && f !== \"\"))) {\n throw new Error(\"defineChapter.application.crmFields: must be an array of field-name strings\");\n }\n return {\n required,\n optional,\n maxLen: a?.maxLen ?? {},\n defaultMaxLen: a?.defaultMaxLen ?? 2000,\n bodyCap: a?.bodyCap ?? 32768,\n requireDisclaimerAck: a?.requireDisclaimerAck ?? true,\n profileFields: a?.profileFields ?? [],\n crmFields: a?.crmFields ?? [],\n maxArrayLen: a?.maxArrayLen ?? 100,\n validateEmail: a?.validateEmail ?? true,\n };\n}\n\n// A permissive email shape check: one @, a dot-bearing domain, no whitespace.\n// Deliberately not RFC-exhaustive — the goal is to reject \"notanemail\" before it\n// fails the downstream Clerk create, not to adjudicate exotic-but-valid addresses.\nconst EMAIL_RE = /^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$/;\n\n/** Whether a string looks like an email address (see {@link EMAIL_RE}). Exported\n * so a site building its own submit path applies the same rule chapter does. */\nexport function isValidEmail(value: unknown): boolean {\n return typeof value === \"string\" && EMAIL_RE.test(value);\n}\n\n/** Bound an array-valued field: drop non-primitive elements and cap the length,\n * so a client cannot post an unbounded array. Non-arrays pass through unchanged. */\nexport function clampArray(value: unknown, max: number): unknown {\n if (!Array.isArray(value)) return value;\n return value.filter((x) => typeof x === \"string\" || typeof x === \"number\" || typeof x === \"boolean\").slice(0, max);\n}\n\n/** Whether a submit body carries a genuine disclaimer acknowledgement. Accepts\n * the boolean an API client sends and the string a plain HTML form posts. */\nexport function hasDisclaimerAck(fields: Record<string, unknown>): boolean {\n return fields.disclaimerAck === true || fields.disclaimerAck === \"true\";\n}\n\n// Clerk carries these as first-class user fields, so repeating them in\n// public_metadata would be duplicated state that can drift.\nconst IDENTITY_FIELDS = new Set([\"email\", \"firstName\", \"lastName\"]);\n\n/**\n * The applicant profile written to the Clerk account's backend-only\n * `private_metadata.profile`. Projects each configured non-identity field, plus\n * `focus` (clamped), but ONLY those in `application.profileFields`. Derived\n * from config, so a site's own field names project without this package knowing\n * them. Pure; returns `undefined` when there is nothing to write.\n */\nexport function applicantProfile(\n chapter: Chapter,\n fields: Record<string, unknown>,\n): Record<string, unknown> | undefined {\n const app = chapter.application;\n const allowed = (f: string): boolean => app.profileFields.includes(f);\n const profile: Record<string, unknown> = {};\n for (const f of [...app.required, ...app.optional]) {\n if (IDENTITY_FIELDS.has(f) || !allowed(f)) continue;\n const v = fields[f];\n if (typeof v === \"string\" && v.trim() !== \"\") profile[f] = v.trim();\n }\n if (fields.focus !== undefined && allowed(\"focus\")) profile.focus = clampArray(fields.focus, app.maxArrayLen);\n return Object.keys(profile).length > 0 ? profile : undefined;\n}\n\n/** A validated submission, or a 400-worthy validation error the route returns.\n * `disclaimerAckAt` reports what THIS request recorded — a number when consent\n * was stamped, `null` when none was supplied. It is always present so a missing\n * consent record is visible in the response rather than silently absent from a\n * row nobody reads until an audit. */\nexport type SubmitResult =\n | { ok: true; id: string; duplicate: boolean; status: string; disclaimerAckAt: number | null }\n | { ok: false; error: string };\n\n/**\n * Submit a membership application (B2 + B3). Validates the configured required\n * fields + max lengths, writes the `applications` row at the pipeline's initial\n * status, and — when the client supplies a `submissionId` — stamps it as the\n * transaction's mutationId (`join:${submissionId}`) so a double-tap can never\n * create two applications. The application id is derived from that same stable\n * input, so the duplicate response returns the first request's canonical id\n * rather than a fresh, nonexistent one. Idempotency is package-enforced.\n * `now`/`newId` are injected (deterministic in tests).\n */\nexport async function submitApplication(\n db: ChapterDb,\n chapter: Chapter,\n fields: Record<string, unknown>,\n opts: { submissionId?: string; groupId?: string; now: number; newId: () => string },\n): Promise<SubmitResult> {\n const app = chapter.application;\n for (const f of app.required) {\n const v = fields[f];\n if (typeof v !== \"string\" || v.trim() === \"\") return { ok: false, error: `${f} is required` };\n }\n for (const f of [...app.required, ...app.optional]) {\n const v = fields[f];\n const cap = app.maxLen[f] ?? app.defaultMaxLen;\n if (typeof v === \"string\" && v.length > cap) return { ok: false, error: `${f} exceeds ${cap} characters` };\n }\n // A malformed email can only fail the downstream Clerk create; reject it here\n // as a clean 400. A valid application always has a valid email, so no genuine\n // submission is newly rejected.\n if (app.validateEmail && typeof fields.email === \"string\" && !isValidEmail(fields.email)) {\n return { ok: false, error: \"email must be a valid email address\" };\n }\n\n const acked = hasDisclaimerAck(fields);\n if (app.requireDisclaimerAck && !acked) {\n return { ok: false, error: \"disclaimerAck is required\" };\n }\n\n const id = opts.submissionId\n ? await applicationIdForSubmission(opts.submissionId)\n : opts.newId();\n const row: Record<string, unknown> = { id, status: chapter.pipeline.initial, createdAt: opts.now };\n for (const f of [...app.required, ...app.optional]) {\n if (typeof fields[f] === \"string\") row[f] = (fields[f] as string).trim();\n }\n if (fields.focus !== undefined) row.focus = clampArray(fields.focus, app.maxArrayLen);\n if (opts.groupId) row.groupId = opts.groupId;\n // The disclaimer acknowledgement is a compliance record, so it is stamped from\n // the server clock on a genuine ack and left absent otherwise — a client-supplied\n // timestamp would be forgeable, and a row that always carries one would record\n // consent nobody gave.\n if (acked) row.disclaimerAckAt = opts.now;\n\n const { duplicate } = await db.transact(\n [{ t: \"update\", ns: \"applications\", id, attrs: row }],\n opts.submissionId ? { mutationId: `join:${opts.submissionId}` } : undefined,\n );\n return { ok: true, id, duplicate, status: chapter.pipeline.initial, disclaimerAckAt: acked ? opts.now : null };\n}\n\n/** The `groups`-row fields the join config exposes. */\nexport interface JoinConfigGroup {\n id: string;\n name: string;\n standardPriceCents?: number;\n foundingDiscountCents?: number;\n disclaimerText?: string;\n refundPolicyText?: string;\n trustCopy?: string;\n commitmentText?: string;\n normsText?: string;\n}\n\n/**\n * The public join config (B1) a site's join page reads: copy + prices from the\n * group row plus `paymentsReady`. When payments aren't wired the join flow drops\n * the payment step (C2) — the worker computes `paymentsReady` from the group's\n * Stripe keys + vault secret. Pure.\n */\nexport function joinConfig(group: JoinConfigGroup, paymentsReady: boolean): Record<string, unknown> {\n return {\n id: group.id,\n name: group.name,\n standardPriceCents: group.standardPriceCents ?? 0,\n foundingDiscountCents: group.foundingDiscountCents ?? 0,\n disclaimerText: group.disclaimerText ?? \"\",\n refundPolicyText: group.refundPolicyText ?? \"\",\n trustCopy: group.trustCopy ?? \"\",\n commitmentText: group.commitmentText ?? \"\",\n normsText: group.normsText ?? \"\",\n paymentsReady,\n };\n}\n","import type { ChapterCopy } from \"./copy.js\";\n\nexport const DEFAULT_ADMIN_COPY: ChapterCopy[\"admin\"] = {\n auth: {\n checking: \"Checking access…\",\n notAuthorized: \"Not authorized\",\n accountNotAuthorized: \"{account} isn't on the admin list.\",\n thisAccount: \"This account\",\n signOut: \"Sign out\",\n loading: \"Loading…\",\n signInNotConfigured: \"Sign-in not configured\",\n missingPublishableKey: \"No Clerk publishable key is set for this environment yet.\",\n noWorkspaces: \"No admin workspaces are configured.\",\n signInTagline: \"Admin sign-in, invite only\",\n },\n shell: {\n adminRole: \"admin\",\n adminConsole: \"Admin Console\",\n adminName: \"Admin\",\n navigationLabel: \"Admin navigation\",\n },\n workspaces: {\n dashboard: \"Dashboard\",\n overview: \"Overview\",\n billing: \"Billing\",\n people: \"People\",\n settings: \"Settings\",\n calendar: \"Calendar\",\n email: \"Email\",\n dashboardViewsLabel: \"Dashboard views\",\n settingsViewsLabel: \"Settings views\",\n collectionsLabel: \"CRM collections\",\n },\n dashboard: {\n loading: \"Loading dashboard…\",\n loadFailed: \"Couldn't load the dashboard\",\n upcomingCalls: \"Upcoming calls\",\n callsNeedAttention: \"{count} need attention\",\n noUpcomingCalls: \"No upcoming calls.\",\n drift: \"drift\",\n open: \"Open\",\n meet: \"Meet\",\n applications: \"Applications\",\n newMembers: \"New members\",\n newMembersUnavailable: \"Appears once billing is connected.\",\n revenueAdded: \"Revenue · annual run rate added\",\n revenue: \"Revenue\",\n revenueUnavailable: \"Appears once billing is connected.\",\n pipeline: \"Pipeline\",\n pipelineLabel: \"Membership pipeline\",\n thisWeek: \"+{count} this week\",\n noChange: \"no change\",\n activeMemberships: \"Active memberships\",\n annualRunRate: \"Annual run rate\",\n testMode: \"test mode\",\n },\n billing: {\n loading: \"Loading…\",\n loadFailed: \"Couldn't load billing\",\n notConfigured: \"Billing isn't configured. No Stripe key is vaulted for this group.\",\n active: \"Active\",\n annualized: \"Annualized\",\n renewingSoon: \"Renewing within 60 days\",\n pastDue: \"Past due\",\n subscriptions: \"Subscriptions\",\n testMode: \"Stripe test mode\",\n truncated: \"Showing the first 100 subscriptions. The list is truncated.\",\n name: \"Name\",\n email: \"Email\",\n application: \"Application\",\n subscription: \"Subscription\",\n cancelling: \"cancelling\",\n amount: \"Amount\",\n renews: \"Renews\",\n },\n availability: {\n loading: \"Loading…\",\n loadFailed: \"Couldn't load availability\",\n saved: \"Saved.\",\n title: \"Booking availability\",\n days: \"Days\",\n startHour: \"Start hour (0–24)\",\n endHour: \"End hour (0–24)\",\n slotMinutes: \"Slot length (minutes)\",\n minNoticeHours: \"Minimum notice (hours)\",\n windowDays: \"Booking window (days)\",\n timezone: \"Timezone\",\n summaryTemplate: \"Event summary template\",\n save: \"Save availability\",\n dayLabels: [\"Sun\", \"Mon\", \"Tue\", \"Wed\", \"Thu\", \"Fri\", \"Sat\"],\n },\n email: {\n loading: \"Loading…\",\n loadFailed: \"Couldn't load email config\",\n saved: \"Saved.\",\n sent: \"Sent {template} to {address}{redirected}.\",\n testFailed: \"Test send did not deliver.\",\n delivery: \"Delivery\",\n notificationAddress: \"Notification address\",\n replyTo: \"Reply-to\",\n debugInbox: \"Debug inbox\",\n debugInboxHint: \"Non-production sends redirect here.\",\n sendTest: \"Send test\",\n enabled: \"Enabled\",\n subject: \"Subject\",\n body: \"Body\",\n save: \"Save changes\",\n sendLog: \"Send log\",\n sentColumn: \"Sent\",\n templateColumn: \"Template\",\n statusColumn: \"Status\",\n toColumn: \"To\",\n failed: \"failed\",\n redirected: \"redirected\",\n delivered: \"delivered\",\n },\n meetings: {\n loading: \"Loading…\",\n loadFailed: \"Couldn't load meetings\",\n cancelConfirm: \"Cancel this call? The calendar provider notifies the attendee.\",\n newStartPrompt: \"New start time (for example, 2026-08-01 14:00):\",\n parseFailed: \"Couldn't parse that time.\",\n agenda: \"Agenda\",\n empty: \"No meetings.\",\n unknown: \"(unknown)\",\n drift: \"drift\",\n meet: \"Meet\",\n reschedule: \"Reschedule\",\n cancel: \"Cancel\",\n },\n network: {\n title: \"Share to a follower\",\n allowlistDescription: \"Sends the target's allowlisted fields. Its pipeline and account state remain local.\",\n sharing: \"Sharing…\",\n shareWith: \"Share with {target}\",\n shared: \"Shared with {target}.\",\n shareFailed: \"Could not share with {target}.\",\n deliveryFailed: \"Delivery failed.\",\n },\n records: {\n workflowMissing: \"This record is not linked to an application workflow.\",\n comms: \"Comms\",\n commsHistory: \"Comms history\",\n scheduling: \"Scheduling\",\n billing: \"Billing\",\n notes: \"Notes\",\n access: \"Access\",\n sharing: \"Sharing\",\n lifecycle: \"Access & lifecycle\",\n role: \"Role\",\n superAdminHint: \"Super-admin, managed in odla Studio.\",\n noAccount: \"No linked account yet. The role is set once they sign in.\",\n approve: \"Approve\",\n refund: \"Refund\",\n approved: \"Approved.\",\n refunded: \"Refund issued.\",\n roleSet: \"Role set to {role}.\",\n messageSent: \"Message sent.\",\n messageNotSent: \"Message was not sent.\",\n loadingTemplates: \"Loading templates…\",\n template: \"Template\",\n chooseTemplate: \"Choose a template…\",\n sending: \"Sending…\",\n send: \"Send\",\n noMessages: \"No messages recorded.\",\n message: \"Message\",\n newStartPrompt: \"New start time:\",\n parseFailed: \"Couldn't parse that time.\",\n cancelConfirm: \"Cancel this call?\",\n callCancelled: \"Call cancelled.\",\n callRescheduled: \"Call rescheduled.\",\n noApplication: \"No application is linked to this record.\",\n loadingMeetings: \"Loading meetings…\",\n noMeetings: \"No meetings recorded.\",\n joinMeeting: \"Join meeting\",\n openCalendar: \"Open calendar\",\n reschedule: \"Reschedule\",\n cancel: \"Cancel\",\n },\n};\n","import type { ChapterCopy } from \"./copy.js\";\n\nexport const DEFAULT_COMMON_COPY: ChapterCopy[\"common\"] = {\n loading: \"Loading…\",\n saved: \"Saved.\",\n unknown: \"(unknown)\",\n};\n\nexport const DEFAULT_JOIN_COPY: ChapterCopy[\"join\"] = {\n form: {\n submit: \"Submit application\",\n submitting: \"Submitting…\",\n submitFailed: \"Your application could not be submitted.\",\n unexpectedFailure: \"Something went wrong. Please try again.\",\n },\n booking: {\n unavailable: \"Scheduling is briefly unavailable. We'll reach out by email to arrange your call.\",\n loadFailed: \"Times are briefly unavailable. Please try again.\",\n slotTaken: \"That time was just taken. Here are the current openings.\",\n failed: \"The booking could not be completed.\",\n loading: \"Loading available times…\",\n book: \"Book this time\",\n booking: \"Booking…\",\n },\n payment: {\n setupFailed: \"Payment could not be set up. Please try again.\",\n preparing: \"Preparing secure payment…\",\n pending: \"Confirming your payment…\",\n processing: \"Processing…\",\n payAndContinue: \"Pay and continue\",\n incomplete: \"The payment did not complete. Please try again.\",\n consent: \"I have read and agree to the pricing and refund policy above.\",\n },\n done: {\n label: \"You're booked\",\n calendarInvite: \"A calendar invitation with the video call link is on its way to your email.\",\n memberArea: \"Go to your member area\",\n },\n};\n\nexport const DEFAULT_MEMBERS_COPY: ChapterCopy[\"members\"] = {\n loadFailed: \"Sign in is briefly unavailable. Please refresh.\",\n account: {\n signOut: \"Sign out\",\n adminConsole: \"Admin console\",\n },\n provisional: {\n cardLabel: \"Your Application\",\n applicationNeeded: \"One step remains\",\n applicationNeededBody: \"Your account is ready, and the application that completes it takes a few minutes.\",\n apply: \"Apply for membership\",\n refunded: \"Membership refunded\",\n refundedBody: \"Your fee has been refunded in full and your membership is canceled.\",\n active: \"Your membership is active.\",\n renews: \"Your membership is active and renews {date}.\",\n introductionCall: \"Your introduction call\",\n calendarInvite: \"A calendar invitation with the video call link is in your email.\",\n joinCall: \"Join the video call\",\n bookCall: \"Book your introduction call\",\n bookCallBody: \"Your application is in. Choose a time below, and a calendar invitation will reach your email.\",\n chooseTime: \"Choose a time\",\n },\n full: {\n welcome: \"Welcome back.\",\n },\n reschedule: {\n changeTime: \"Change your time\",\n unavailable: \"Scheduling is briefly unavailable. We'll reach out by email to arrange your call.\",\n loadFailed: \"Times are briefly unavailable. Please try again.\",\n slotGone: \"That time is no longer available. Please pick another.\",\n loading: \"Loading available times…\",\n noTimes: \"No open times right now. Please check back soon.\",\n rescheduling: \"Rescheduling…\",\n keepTime: \"Keep my current time\",\n },\n};\n","import { DEFAULT_ADMIN_COPY } from \"./copy-defaults-admin.js\";\nimport {\n DEFAULT_COMMON_COPY,\n DEFAULT_JOIN_COPY,\n DEFAULT_MEMBERS_COPY,\n} from \"./copy-defaults.js\";\nimport type { ChapterCopy, ChapterCopyInput } from \"./copy.js\";\n\n/** Complete default language resolved beneath partial Chapter copy overrides. */\nexport const DEFAULT_CHAPTER_COPY: ChapterCopy = {\n common: DEFAULT_COMMON_COPY,\n join: DEFAULT_JOIN_COPY,\n members: DEFAULT_MEMBERS_COPY,\n admin: DEFAULT_ADMIN_COPY,\n};\n\nconst isObject = (value: unknown): value is Record<string, unknown> =>\n Boolean(value) && typeof value === \"object\" && !Array.isArray(value);\n\nfunction mergeCopy<T>(base: T, input: unknown, path: string): T {\n if (typeof base === \"string\") {\n if (input === undefined) return base;\n if (typeof input !== \"string\") throw new Error(`${path}: must be a string`);\n return input as T;\n }\n if (Array.isArray(base)) {\n if (input === undefined) return [...base] as T;\n if (!Array.isArray(input) || !input.every((item) => typeof item === \"string\")) {\n throw new Error(`${path}: must be an array of strings`);\n }\n return [...input] as T;\n }\n if (!isObject(base)) return base;\n if (input !== undefined && !isObject(input)) throw new Error(`${path}: must be an object`);\n const overrides = input as Record<string, unknown> | undefined;\n const result: Record<string, unknown> = {};\n for (const [key, value] of Object.entries(base)) {\n result[key] = mergeCopy(value, overrides?.[key], `${path}.${key}`);\n }\n return result as T;\n}\n\n/** Resolve a partial copy contract and validate every supplied leaf. */\nexport function resolveChapterCopy(input?: ChapterCopyInput): ChapterCopy {\n return mergeCopy(DEFAULT_CHAPTER_COPY, input, \"defineChapter.copy\");\n}\n\n/** Replace `{name}` placeholders without interpreting the resulting text as HTML. */\nexport function formatChapterCopy(\n template: string,\n values: Readonly<Record<string, string | number>>,\n): string {\n return template.replace(/\\{([a-zA-Z][a-zA-Z0-9]*)\\}/g, (match, key: string) =>\n Object.prototype.hasOwnProperty.call(values, key) ? String(values[key]) : match,\n );\n}\n","// The CLI-consumable provisioning descriptor. It composes @odla-ai/crm's\n// integration (crm_* namespaces + crm_config seed + route probe) with the\n// chapter's own namespaces and a guarded `groups`-row seed, so a site's\n// odla.config.mjs lists ONE integration. The CLI reads it structurally\n// (matching OdlaIntegration) and merges schema/rules by namespace.\nimport { createCrmIntegration } from \"@odla-ai/crm\";\nimport type { Chapter } from \"./types\";\n\n/** Options for {@link createChapterIntegration}. */\nexport interface ChapterIntegrationOptions {\n /** CRM route mount point. Default \"/api/crm\". */\n basePath?: string;\n /** Seed timestamp override for reproducible builds/tests. */\n now?: number;\n}\n\ninterface IntegrationSeed {\n id: string;\n ns: string;\n key: { attr: string; value: string };\n attrs: Record<string, unknown>;\n}\n\n/** Structural match for the CLI's `OdlaIntegration`. */\nexport interface ChapterIntegrationDescriptor {\n id: string;\n title: string;\n npm: string;\n schema: { entities: Record<string, unknown>; links: Record<string, unknown> };\n rules: Record<string, unknown>;\n seeds: IntegrationSeed[];\n probes: Array<{ path: string; expectedStatus: number }>;\n /** Runbooks that bear on THIS chapter's role, so tooling can point at the\n * right procedure without the operator knowing it exists. A chapter declaring\n * `network.targets` is a leader hub; one declaring none is a follower site,\n * and the two follow different procedures for the same words (\"connect\",\n * \"share records\"). */\n runbooks: ChapterRunbookHints;\n}\n\n/** Which runbooks apply, and why. */\nexport interface ChapterRunbookHints {\n role: \"leader\" | \"follower\";\n /** Platform runbook slugs, most relevant first. */\n slugs: string[];\n /** One line a human or agent can act on. */\n guidance: string;\n}\n\n/**\n * Build the one CLI-consumable integration for a chapter/hub: the crm_*\n * namespaces + crm_config seed + route probe, merged with the chapter's own\n * namespaces and a guarded `groups`-row seed. Drop it in odla.config.mjs's\n * `integrations` array.\n */\nexport function createChapterIntegration(\n chapter: Chapter,\n options: ChapterIntegrationOptions = {},\n): ChapterIntegrationDescriptor {\n const basePath = options.basePath ?? \"/api/crm\";\n const now = options.now ?? Date.now();\n const emails = chapter.config.emails;\n\n const crmDesc = createCrmIntegration(chapter.crm, {\n basePath,\n now,\n ...(emails?.notificationEmail ? { notificationEmail: emails.notificationEmail } : {}),\n ...(emails?.replyTo ? { replyTo: emails.replyTo } : {}),\n ...(emails?.debugEmail ? { debugEmail: emails.debugEmail } : {}),\n });\n\n const seeds: IntegrationSeed[] = [...(crmDesc.seeds ?? [])];\n const group = chapter.groupSeed();\n if (group) {\n seeds.push({ id: \"group\", ns: \"groups\", key: { attr: \"id\", value: chapter.id }, attrs: { ...group, createdAt: now } });\n }\n\n // A leader declares where it pushes records; a follower declares nothing and\n // simply receives. That one fact decides which procedure applies.\n const isLeader = (chapter.config.network?.targets?.length ?? 0) > 0;\n const runbooks: ChapterRunbookHints = isLeader\n ? {\n role: \"leader\",\n slugs: [\"chapter-leader\", \"chapter-network\", \"crm\"],\n guidance:\n `${chapter.name} is a LEADER hub: it holds the CRM and shares records with its chapters. ` +\n `Read it with \\`odla-ai runbook get chapter-leader\\`.`,\n }\n : {\n role: \"follower\",\n slugs: [\"chapter-follower\", \"chapter-network\"],\n guidance:\n `${chapter.name} is a FOLLOWER site: it receives people and records from a leader hub. ` +\n `Read it with \\`odla-ai runbook get chapter-follower\\`.`,\n };\n\n return {\n id: \"chapter\",\n title: `Chapter: ${chapter.name}`,\n npm: \"@odla-ai/chapter\",\n schema: {\n entities: { ...crmDesc.schema.entities, ...chapter.schema.entities },\n links: { ...crmDesc.schema.links, ...chapter.schema.links },\n },\n rules: { ...crmDesc.rules, ...chapter.rules },\n seeds,\n probes: [...(crmDesc.probes ?? [])],\n runbooks,\n };\n}\n","// The chapter email pipeline: exactly-once delivery, a non-production fail-safe,\n// and template rendering. Every property here is easy to get wrong and expensive\n// to get wrong, so the correctness-critical decisions — the dedupe check (E1),\n// the dev-redirect / log-only fail-safe (E2), and template rendering (E4) — are\n// PURE and fully tested in this module. The worker supplies the transport +\n// odla-db and performs the actual send + emailLog write around these decisions.\n//\n// Chapter's operational templates ({ subject, text, enabled? }) are\n// transactional lifecycle mail by construction — a site owner edits the copy in\n// Settings but cannot reclassify one as marketing. Consent-gated marketing blasts\n// go through @odla-ai/crm, which owns the transactional-vs-marketing template\n// class as code (E3), so relabeling copy can never bypass the consent gate.\n\n/** One owner-editable template row on the group. `enabled` absent = enabled. */\nexport interface EmailTemplateRow {\n subject: string;\n text: string;\n enabled?: boolean;\n}\n\n/** The `groups`-row fields the email pipeline reads. */\nexport interface EmailGroup {\n id: string;\n name: string;\n replyTo: string;\n /** Non-prod debug inbox: all mail redirects here outside prod (E2). */\n debugEmail?: string;\n refundPolicyText?: string;\n commitmentText?: string;\n normsText?: string;\n emailTemplates: Record<string, EmailTemplateRow>;\n}\n\n/** `{{placeholder}}` substitution; unknown placeholders render empty. */\nexport function render(template: string, vars: Record<string, string>): string {\n return template.replace(/\\{\\{(\\w+)\\}\\}/g, (_, key: string) => vars[key] ?? \"\");\n}\n\n/** Group-level vars every template receives, under the caller's vars. */\nfunction groupVars(group: EmailGroup, vars: Record<string, string>): Record<string, string> {\n return {\n ...vars,\n refundPolicyText: group.refundPolicyText ?? \"\",\n commitmentText: group.commitmentText ?? \"\",\n normsText: group.normsText ?? \"\",\n };\n}\n\n/**\n * Re-render a template's body for history/preview (E4): the CRM comms history\n * reads back emails whose body predates `emailLog.body` by rendering the current\n * template with the recipient's vars. Same substitution + group vars as the send\n * path. `null` for an unknown template. Reflects the copy as it reads today, not\n * necessarily the exact bytes originally sent (only `emailLog.body` is byte-exact).\n */\nexport function renderTemplateBody(group: EmailGroup, template: string, vars: Record<string, string>): string | null {\n const tpl = group.emailTemplates?.[template];\n if (!tpl) return null;\n return render(tpl.text, groupVars(group, vars));\n}\n\n/**\n * E1 (exactly-once): given the prior `emailLog` rows for a `dedupeKey`, has the\n * mail already been delivered? A prior row with **no error** means yes — the\n * caller short-circuits the resend. Failure rows (which carry an `error` and are\n * written without the dedupe mutationId) do not count, so a retry after a failure\n * can still succeed.\n */\nexport function isAlreadySent(priorRows: ReadonlyArray<{ error?: unknown }>): boolean {\n return priorRows.some((row) => !row.error);\n}\n\n/** The pure delivery decision produced by {@link planDelivery}. */\nexport type DeliveryDecision =\n | { deliver: false; reason: \"template-missing\" | \"disabled\" }\n | {\n deliver: true;\n /** Which transport to use — `log-only` records the send but delivers nothing. */\n transport: \"cloudflare\" | \"log-only\";\n to: string;\n subject: string;\n text: string;\n /** True when redirected to the non-prod debug inbox. */\n redirected: boolean;\n };\n\n/**\n * The pure delivery decision (E2 fail-safe + E3 enabled). Given the env, group,\n * template, recipient, and whether a real Cloudflare transport is wired:\n * - missing template → not delivered (`template-missing`);\n * - disabled template and not forced → not delivered (`disabled`);\n * - **non-prod with a debug inbox** → REDIRECT to it, `\"[dev] \"` subject prefix,\n * a dev-redirect note in the body, so test applicants never receive real mail;\n * - **non-prod with NO debug inbox** → force `log-only` (deliver nothing) — the\n * fail-safe that protects every site's test data;\n * - prod → deliver via the real transport (`cloudflare` if wired, else `log-only`).\n */\nexport function planDelivery(input: {\n envName: string;\n group: EmailGroup;\n template: string;\n to: string;\n vars: Record<string, string>;\n /** Whether a Cloudflare Email Service transport (binding + verified from) is wired. */\n cloudflareReady: boolean;\n /** The admin test route may send a disabled template. */\n force?: boolean;\n}): DeliveryDecision {\n const tpl = input.group.emailTemplates?.[input.template];\n if (!tpl) return { deliver: false, reason: \"template-missing\" };\n if (tpl.enabled === false && !input.force) return { deliver: false, reason: \"disabled\" };\n\n const vars = groupVars(input.group, input.vars);\n const isProd = input.envName === \"prod\";\n const redirect = !isProd && !!input.group.debugEmail;\n const transport: \"cloudflare\" | \"log-only\" =\n !isProd && !redirect ? \"log-only\" : input.cloudflareReady ? \"cloudflare\" : \"log-only\";\n const to = redirect ? (input.group.debugEmail as string) : input.to;\n const subject = (redirect ? \"[dev] \" : \"\") + render(tpl.subject, vars);\n const text = redirect\n ? `(dev redirect; original recipient: ${input.to})\\n\\n` + render(tpl.text, vars)\n : render(tpl.text, vars);\n return { deliver: true, transport, to, subject, text, redirected: redirect };\n}\n","// sendTemplated — the operational-email orchestration that wires the pure email\n// decisions (./email: isAlreadySent E1, planDelivery E2/E3) to odla-db + a\n// transport. Structural (takes a ChapterDb + a sender), so it's FakeDb-testable\n// like submitApplication/projectSharedRecord. The worker's lifecycle routes call\n// it to send the prep / payment-confirmation / admin-alert mail.\n//\n// Exactly-once is enforced twice: a prior successful emailLog row short-circuits\n// the resend (query + isAlreadySent), AND the success row is written under the\n// `email:${dedupeKey}` mutationId so a racing double-send dedupes at the db layer.\n// Failure rows omit the mutationId, so a retry after a transient failure can send.\nimport { isAlreadySent, planDelivery } from \"./email\";\nimport type { EmailGroup } from \"./email\";\nimport type { ChapterDb } from \"./types\";\n\n/** The canonical lifecycle email templates chapter fires, in send-order. Owner-\n * editable copy lives on the group row; this is the fixed set the admin email\n * editor + test route iterate. */\nexport const EMAIL_TEMPLATE_NAMES = [\"adminNotification\", \"paymentConfirmation\", \"prepEmail\", \"onboardingInvite\"] as const;\n\n/** One of {@link EMAIL_TEMPLATE_NAMES}. */\nexport type EmailTemplateName = (typeof EMAIL_TEMPLATE_NAMES)[number];\n\n/** The mail transport (the worker's Cloudflare SEND_EMAIL binding). */\nexport interface MailSender {\n send(payload: { from: string; to: string[]; subject: string; text?: string; replyTo?: string }): Promise<{ messageId: string }>;\n}\n\n/** Deps for {@link sendTemplated} — the db, the env name (drives the fail-safe),\n * the transport + from (absent ⇒ log-only), and injected clock/id. */\nexport interface NotifyDeps {\n db: ChapterDb;\n envName: string;\n sender?: MailSender;\n from?: string;\n now: () => number;\n newId: () => string;\n}\n\n/** One templated send. `dedupeKey` is the exactly-once key. */\nexport interface NotifyInput {\n group: EmailGroup;\n template: string;\n to: string;\n vars: Record<string, string>;\n dedupeKey: string;\n applicationId?: string;\n /** The admin test route may send a disabled template. */\n force?: boolean;\n}\n\n/** The outcome. `sent:true` includes the already-sent short-circuit. */\nexport interface NotifyResult {\n sent: boolean;\n reason?: string;\n}\n\n/**\n * Send a templated lifecycle email exactly once. Short-circuits on a prior\n * successful send; otherwise plans delivery (dev fail-safe applies), sends via the\n * transport when one is wired and the plan calls for it, and records an emailLog\n * row either way (success keyed for exactly-once, failure unkeyed for retry).\n */\nexport async function sendTemplated(deps: NotifyDeps, input: NotifyInput): Promise<NotifyResult> {\n const { emailLog } = await deps.db.query({ emailLog: { $: { where: { dedupeKey: input.dedupeKey } } } });\n const prior = (Array.isArray(emailLog) ? emailLog : []) as Array<{ error?: unknown }>;\n if (isAlreadySent(prior)) return { sent: true, reason: \"already-sent\" };\n\n const cloudflareReady = Boolean(deps.sender && deps.from);\n const decision = planDelivery({\n envName: deps.envName,\n group: input.group,\n template: input.template,\n to: input.to,\n vars: input.vars,\n cloudflareReady,\n force: input.force,\n });\n if (!decision.deliver) return { sent: false, reason: decision.reason };\n\n let error: string | undefined;\n let messageId: string | undefined;\n if (decision.transport === \"cloudflare\" && deps.sender && deps.from) {\n try {\n const res = await deps.sender.send({\n from: deps.from,\n to: [decision.to],\n subject: decision.subject,\n text: decision.text,\n replyTo: input.group.replyTo,\n });\n messageId = res.messageId;\n } catch (e) {\n error = e instanceof Error ? e.message : String(e);\n }\n }\n\n const id = deps.newId();\n const row: Record<string, unknown> = {\n id,\n groupId: input.group.id,\n to: decision.to,\n template: input.template,\n subject: decision.subject,\n body: decision.text,\n transport: decision.transport,\n redirected: decision.redirected,\n dedupeKey: input.dedupeKey,\n sentAt: deps.now(),\n ...(input.applicationId ? { applicationId: input.applicationId } : {}),\n ...(messageId ? { messageId } : {}),\n ...(error ? { error } : {}),\n };\n await deps.db.transact([{ t: \"update\", ns: \"emailLog\", id, attrs: row }], error ? undefined : { mutationId: `email:${input.dedupeKey}` });\n return error ? { sent: false, reason: error } : { sent: true };\n}\n\n/** Project a `groups` row into the {@link EmailGroup} the email pipeline reads. */\nexport function emailGroupFrom(row: Record<string, unknown>): EmailGroup {\n const str = (v: unknown): string | undefined => (typeof v === \"string\" ? v : undefined);\n const templates = row.emailTemplates && typeof row.emailTemplates === \"object\" ? row.emailTemplates : {};\n return {\n id: String(row.id),\n name: String(row.name ?? \"\"),\n replyTo: str(row.replyTo) ?? \"\",\n debugEmail: str(row.debugEmail),\n refundPolicyText: str(row.refundPolicyText),\n commitmentText: str(row.commitmentText),\n normsText: str(row.normsText),\n emailTemplates: templates as EmailGroup[\"emailTemplates\"],\n };\n}\n","// Payments primitives. The webhook-integrity check below is security-critical and\n// easy to get wrong, so it is pure and tested here; the worker wires a payments\n// provider (Stripe first — subscription create, webhook ingest, refund) around\n// it, and every resulting db write carries an event-derived mutationId for\n// exactly-once. Sites that don't charge omit payments entirely (paymentsReady:\n// false), so nothing here is imported unless a chapter runs the payment flow.\n\n/** Parse a Stripe-style `Stripe-Signature` header (`t=<unix>,v1=<hex>`). */\nfunction parseSigHeader(header: string): { t?: string; v1?: string } {\n const parts: Record<string, string> = {};\n for (const p of header.split(\",\")) {\n const [k, v] = p.split(\"=\", 2);\n if (k && v !== undefined) parts[k] = v;\n }\n return { t: parts.t, v1: parts.v1 };\n}\n\nfunction toHex(buf: ArrayBuffer): string {\n return [...new Uint8Array(buf)].map((b) => b.toString(16).padStart(2, \"0\")).join(\"\");\n}\n\n/** Constant-time compare of two equal-length hex strings. */\nfunction timingSafeEqual(a: string, b: string): boolean {\n if (a.length !== b.length) return false;\n let diff = 0;\n for (let i = 0; i < a.length; i++) diff |= a.charCodeAt(i) ^ b.charCodeAt(i);\n return diff === 0;\n}\n\n/**\n * Verify a Stripe webhook signature (C3): HMAC-SHA256 over `` `${t}.${payload}` ``\n * with the endpoint signing secret, a replay window (default 5 minutes), and a\n * constant-time compare. Package-enforced — never left to a site. Returns `false`\n * (never throws) on a malformed header, a non-numeric or stale timestamp, or a\n * signature mismatch. `now`/`toleranceSec` are injectable for tests.\n */\nexport async function verifyStripeSignature(\n payload: string,\n header: string,\n secret: string,\n opts: { now?: number; toleranceSec?: number } = {},\n): Promise<boolean> {\n const { t, v1 } = parseSigHeader(header);\n if (!t || !v1) return false;\n const ts = Number(t);\n if (!Number.isFinite(ts)) return false;\n const nowSec = (opts.now ?? Date.now()) / 1000;\n const tolerance = opts.toleranceSec ?? 300;\n if (Math.abs(nowSec - ts) > tolerance) return false;\n\n const enc = new TextEncoder();\n const key = await crypto.subtle.importKey(\"raw\", enc.encode(secret), { name: \"HMAC\", hash: \"SHA-256\" }, false, [\"sign\"]);\n const mac = await crypto.subtle.sign(\"HMAC\", key, enc.encode(`${t}.${payload}`));\n return timingSafeEqual(toHex(mac), v1);\n}\n\n// ── readiness + Stripe wire helpers ──\n\n/** A group row's payment configuration, as far as readiness cares. */\nexport interface PaymentsGroup {\n stripePublishableKey?: string | null;\n stripePriceId?: string | null;\n}\n\n/** Whether a group can take payment: a publishable key + a price id (both on the\n * group row) AND a secret key (the vault). Anything missing drops the join\n * flow's payment step (paymentsReady:false) rather than half-charging. */\nexport function paymentsReady(group: PaymentsGroup, hasSecretKey: boolean): boolean {\n return Boolean(group.stripePublishableKey && group.stripePriceId && hasSecretKey);\n}\n\n/** Form-encode params for Stripe's x-www-form-urlencoded API, expanding one level\n * of nested objects into bracket syntax (`metadata[applicationId]=...`). */\nexport function stripeForm(params: Record<string, unknown>): string {\n const out = new URLSearchParams();\n for (const [k, v] of Object.entries(params)) {\n if (v === undefined || v === null) continue;\n if (typeof v === \"object\") {\n for (const [k2, v2] of Object.entries(v as Record<string, unknown>)) {\n if (v2 !== undefined && v2 !== null) out.append(`${k}[${k2}]`, String(v2));\n }\n } else {\n out.append(k, String(v));\n }\n }\n return out.toString();\n}\n\n/** The Stripe idempotency key for creating an application's subscription — one\n * per application, so a client retry can't orphan a second subscription. */\nexport function subscriptionIdempotencyKey(applicationId: string): string {\n return `sub:${applicationId}`;\n}\n\n/** The db mutationId for a webhook-driven write — exactly-once per Stripe event,\n * so replays are deduped at the db layer. */\nexport function webhookMutationId(eventId: string): string {\n return `stripe:${eventId}`;\n}\n\n// ── webhook normalization (pure; the worker owns the db lookup + writes) ──\n\n/** A raw Stripe event, as far as normalization cares. */\nexport interface StripeEvent {\n id: string;\n type: string;\n data?: { object?: Record<string, unknown> };\n}\n\n/** A normalized, provider-agnostic webhook event. `kind` drives the db write;\n * the application is resolved from `applicationId` (metadata) or `customerId`. */\nexport type WebhookEvent =\n | { kind: \"first_payment\"; applicationId?: string; customerId?: string; renewalAt?: number }\n | { kind: \"renewal\"; applicationId?: string; customerId?: string; renewalAt?: number }\n | { kind: \"refunded\"; applicationId?: string; customerId?: string }\n | { kind: \"canceled\"; applicationId?: string; customerId?: string }\n | { kind: \"ignored\"; type: string };\n\n/** Resolve the application reference on a Stripe object: `applicationId` from\n * metadata (direct, then subscription_details, then nested\n * parent.subscription_details), plus the customer id for the db fallback. */\nexport function findApplicationRef(obj: Record<string, unknown>): { applicationId?: string; customerId?: string } {\n const metaOf = (v: unknown): Record<string, unknown> =>\n v && typeof v === \"object\" ? ((v as Record<string, unknown>).metadata as Record<string, unknown>) ?? {} : {};\n const pick = (m: Record<string, unknown>): string | undefined =>\n typeof m.applicationId === \"string\" ? m.applicationId : undefined;\n const applicationId =\n pick(metaOf(obj)) ?? pick(metaOf(obj.subscription_details)) ?? pick(metaOf((obj.parent as Record<string, unknown> | undefined)?.subscription_details));\n const customerId = typeof obj.customer === \"string\" ? obj.customer : undefined;\n return { ...(applicationId ? { applicationId } : {}), ...(customerId ? { customerId } : {}) };\n}\n\n/** Normalize a verified Stripe event into a {@link WebhookEvent}. `invoice.paid`\n * splits into first_payment vs renewal by `billing_reason`; refunds and\n * cancellations map directly; everything else is ignored (acked, not retried). */\nexport function normalizeWebhookEvent(event: StripeEvent): WebhookEvent {\n const obj = event.data?.object ?? {};\n const ref = findApplicationRef(obj);\n switch (event.type) {\n case \"invoice.paid\": {\n const lines = ((obj.lines as Record<string, unknown> | undefined)?.data as Array<Record<string, unknown>> | undefined) ?? [];\n const periodEnd = (lines[0]?.period as Record<string, unknown> | undefined)?.end;\n const renewalAt = typeof periodEnd === \"number\" ? periodEnd * 1000 : undefined;\n const kind = obj.billing_reason === \"subscription_create\" ? \"first_payment\" : \"renewal\";\n return { kind, ...ref, ...(renewalAt !== undefined ? { renewalAt } : {}) };\n }\n case \"charge.refunded\":\n return { kind: \"refunded\", ...ref };\n case \"customer.subscription.deleted\":\n return { kind: \"canceled\", ...ref };\n default:\n return { kind: \"ignored\", type: event.type };\n }\n}\n\n// ── webhook write-set builders (the authoritative writers of paid/refunded) ──\n\n/** First-payment patch: advance submitted→paid_pending_vetting (never any other\n * transition) and record the renewal date. Empty when nothing changed, so the\n * caller can skip the write. */\nexport function firstPaymentPatch(currentStatus: string, renewalAt?: number): { status?: \"paid_pending_vetting\"; renewalAt?: number } {\n return {\n ...(currentStatus === \"submitted\" ? { status: \"paid_pending_vetting\" as const } : {}),\n ...(renewalAt !== undefined ? { renewalAt } : {}),\n };\n}\n\n/** Renewal-invoice patch: just the new renewal date. */\nexport function renewalPatch(renewalAt: number): { renewalAt: number } {\n return { renewalAt };\n}\n\n/** Refund patch — the SOLE writer of status \"refunded\" (the admin refund route\n * issues the Stripe refund but never sets this; the webhook does). */\nexport function refundedPatch(): { status: \"refunded\" } {\n return { status: \"refunded\" };\n}\n\n/** Subscription-cancellation patch. */\nexport function canceledPatch(): { canceled: true } {\n return { canceled: true };\n}\n","// The leader → follower CRM projection (push model). The leader curates people,\n// businesses, and other configured CRM records and pushes a deliberately\n// allowlisted field set into THIS follower's crm_record.\n//\n// Invariants (package-enforced so no site re-derives them):\n// - One-way: the chapter never writes back to the hub through this path.\n// - Idempotent: every received record carries the leader's stable record id. A\n// provenance tag maps that id to the local CRM record, so a re-share updates\n// instead of duplicating even after an email/domain changes.\n// - Natural-key convergence: a person unifies by primary email and a business\n// by its configured domain slot (then name), so an arriving applicant and a\n// prior leader share compose into one person.\n// - Operational state stays local. Pipeline/account/billing state is not copied\n// into another site's authority; the push transfers the shared record fields.\n//\n// Reuses @odla-ai/crm's record ops (full validation via crm.prepare), driven by\n// the resolved chapter CRM engine + the structural ChapterDb.\nimport { createRecord, updateRecord } from \"@odla-ai/crm\";\nimport type { Crm, CrmRecord } from \"@odla-ai/crm\";\nimport type { ChapterDb, ResolvedNetworkTarget } from \"./types\";\n\n/** The contact data the hub shares for a prospect. `hubRecordId` is the stable\n * idempotency key (the hub's crm_record id). */\nexport interface SharedPerson {\n email: string;\n name?: string;\n firstName?: string;\n lastName?: string;\n phone?: string;\n linkedin?: string;\n hubRecordId: string;\n}\n\n/** Convenience wire shape for sharing the default `company` CRM type. */\nexport interface SharedBusiness {\n type: \"company\";\n name: string;\n domain?: string;\n industry?: string;\n location?: string;\n linkedin?: string;\n notes?: string;\n hubRecordId: string;\n}\n\n/** Versioned generic wire shape. `input` is validated against the follower's\n * own CRM type before any write, so a leader cannot smuggle undeclared fields. */\nexport interface SharedRecord {\n version: 1;\n type: string;\n hubRecordId: string;\n input: Record<string, unknown>;\n}\n\n/** Safe defaults when a target does not declare an explicit field allowlist. */\nexport const DEFAULT_SHARE_FIELDS: Readonly<Record<string, readonly string[]>> = {\n person: [\"name\", \"email\", \"firstName\", \"lastName\", \"phone\", \"linkedin\"],\n company: [\"name\", \"domain\", \"industry\", \"location\", \"linkedin\", \"notes\"],\n};\n\n/** Map a shared prospect to a crm `person` input (only the fields the default\n * person type accepts). Name falls back to first+last, then the email. */\nexport function sharedPersonInput(person: SharedPerson): Record<string, unknown> {\n const email = person.email.toLowerCase();\n const fullName = [person.firstName, person.lastName].filter(Boolean).join(\" \").trim();\n const input: Record<string, unknown> = { name: person.name ?? fullName ?? email, email };\n if (input.name === \"\") input.name = email;\n if (person.firstName) input.firstName = person.firstName;\n if (person.lastName) input.lastName = person.lastName;\n if (person.phone) input.phone = person.phone;\n if (person.linkedin) input.linkedin = person.linkedin;\n return input;\n}\n\n/** Deps for the projection — the resolved CRM engine, the structural db, and\n * injected clock/id (deterministic in tests). */\nexport interface ProjectionDeps {\n crm: Crm;\n db: ChapterDb;\n now: () => number;\n newId: () => string;\n}\n\nfunction shortHash(value: string): string {\n let a = 0x811c9dc5;\n let b = 0x9e3779b9;\n for (let i = 0; i < value.length; i += 1) {\n const n = value.charCodeAt(i);\n a = Math.imul(a ^ n, 0x01000193);\n b = Math.imul(b ^ n, 0x85ebca6b);\n }\n return `${(a >>> 0).toString(36)}${(b >>> 0).toString(36)}`;\n}\n\n/** CRM provenance tag used as the durable leader-id → local-record mapping. */\nexport function networkSourceTag(type: string, hubRecordId: string): string {\n const typeKey = type.toLowerCase();\n const readable = /^[a-z0-9_-]+$/.test(hubRecordId);\n const raw = `network:${typeKey}:${hubRecordId}`;\n if (readable && raw.length <= 64) return raw;\n return `network:${typeKey.slice(0, 20)}:${shortHash(`${type}\\u0000${hubRecordId}`)}`;\n}\n\n/** Normalize the backwards-compatible person/business shapes into the versioned\n * generic record envelope the receiver writes. */\nexport function normalizeSharedRecord(record: SharedPerson | SharedBusiness | SharedRecord): SharedRecord {\n if (\"input\" in record) return { version: 1, type: record.type, hubRecordId: record.hubRecordId, input: record.input };\n if (\"type\" in record && record.type === \"company\") {\n const input: Record<string, unknown> = { name: record.name };\n for (const key of [\"domain\", \"industry\", \"location\", \"linkedin\", \"notes\"] as const) {\n if (record[key]) input[key] = record[key];\n }\n return { version: 1, type: \"company\", hubRecordId: record.hubRecordId, input };\n }\n return { version: 1, type: \"person\", hubRecordId: record.hubRecordId, input: sharedPersonInput(record) };\n}\n\n/** Build the allowlisted payload sent from one leader CRM record to a target.\n * A target with an explicit `fields` map only accepts the types it lists. */\nexport function sharedRecordFromCrm(crm: Crm, record: CrmRecord, target: ResolvedNetworkTarget): SharedRecord {\n if (target.fields && !target.fields[record.type]) {\n throw new Error(`${target.name} does not accept \"${record.type}\" records`);\n }\n const def = crm.type(record.type);\n const fields = target.fields?.[record.type] ?? DEFAULT_SHARE_FIELDS[record.type];\n if (!fields) {\n throw new Error(`${target.name} requires an explicit field allowlist for \"${record.type}\" records`);\n }\n const nameField = def.nameField ?? \"name\";\n const input: Record<string, unknown> = {};\n for (const field of new Set([nameField, ...fields])) {\n const value = record.fields?.[field];\n if (value !== undefined) input[field] = value;\n }\n if (input[nameField] === undefined) input[nameField] = record.name;\n return { version: 1, type: record.type, hubRecordId: record.id, input };\n}\n\n// Resolve an existing crm_record person by lowercased primaryEmail and update it,\n// else create one under the given mutationId. The shared core of every one-way\n// person projection (hub share, arriving applicant).\nasync function upsertPerson(\n deps: ProjectionDeps,\n opts: { email: string; input: Record<string, unknown>; mutationId: string },\n): Promise<{ recordId: string }> {\n const email = opts.email.toLowerCase();\n const crmDeps = { crm: deps.crm, db: deps.db as never, now: deps.now, newId: deps.newId };\n const { crm_record } = await deps.db.query({ crm_record: { $: { where: { type: \"person\", primaryEmail: email }, limit: 1 } } });\n const existing = crm_record?.[0];\n if (existing && typeof existing.id === \"string\") {\n await updateRecord(crmDeps, { id: existing.id, input: opts.input });\n return { recordId: existing.id };\n }\n const created = await createRecord(crmDeps, { type: \"person\", input: opts.input, mutationId: opts.mutationId });\n return { recordId: created.id };\n}\n\nasync function findSharedRecord(\n deps: ProjectionDeps,\n record: SharedRecord,\n tag: string,\n): Promise<Record<string, unknown> | undefined> {\n const mapped = await deps.db.query({ crm_tag: { $: { where: { tag }, limit: 1 } } });\n const mappedId = mapped.crm_tag?.[0]?.recordId;\n if (typeof mappedId === \"string\") {\n const found = await deps.db.query({ crm_record: { $: { where: { id: mappedId }, limit: 1 } } });\n if (found.crm_record?.[0]) return found.crm_record[0];\n }\n const def = deps.crm.type(record.type);\n const emailField = def.emailField;\n if (emailField && typeof record.input[emailField] === \"string\") {\n const primaryEmail = record.input[emailField].toLowerCase();\n const found = await deps.db.query({\n crm_record: { $: { where: { type: record.type, primaryEmail }, limit: 1 } },\n });\n if (found.crm_record?.[0]) return found.crm_record[0];\n }\n const domain = record.input.domain;\n const domainSlot = def.fields.domain?.slot;\n if (typeof domain === \"string\" && domainSlot) {\n const found = await deps.db.query({\n crm_record: { $: { where: { type: record.type, [domainSlot]: domain }, limit: 1 } },\n });\n if (found.crm_record?.[0]) return found.crm_record[0];\n }\n const nameField = def.nameField ?? \"name\";\n const name = record.input[nameField];\n if (record.type === \"company\" && typeof name === \"string\" && name.trim()) {\n const found = await deps.db.query({\n crm_record: { $: { where: { type: record.type, name: name.trim() }, limit: 1 } },\n });\n if (found.crm_record?.[0]) return found.crm_record[0];\n }\n return undefined;\n}\n\n/**\n * Upsert a leader-shared CRM record into this follower. Accepts the original\n * person wire shape plus the versioned generic shape. Re-shares resolve through\n * a durable provenance tag; people/businesses also converge by natural key.\n */\nexport async function projectSharedRecord(\n deps: ProjectionDeps,\n shared: SharedPerson | SharedBusiness | SharedRecord,\n): Promise<{ recordId: string }> {\n const record = normalizeSharedRecord(shared);\n if (!record.type.trim() || !record.hubRecordId.trim()) {\n throw new Error(\"type and hubRecordId must be non-empty\");\n }\n const tag = networkSourceTag(record.type, record.hubRecordId);\n const crmDeps = { crm: deps.crm, db: deps.db as never, now: deps.now, newId: deps.newId };\n const existing = await findSharedRecord(deps, record, tag);\n let recordId: string;\n if (existing && typeof existing.id === \"string\") {\n // Do not use one permanent mutation id for updates: a later leader edit is a\n // new desired state and must not be mistaken for a retry of an older push.\n await updateRecord(crmDeps, { id: existing.id, input: record.input });\n recordId = existing.id;\n } else {\n // A deterministic id makes concurrent first deliveries converge even when\n // both requests observe no provenance row before either transaction lands.\n recordId = `network_${shortHash(`${record.type}\\u0000${record.hubRecordId}`)}`;\n await createRecord({ ...crmDeps, newId: () => recordId }, {\n type: record.type,\n input: record.input,\n mutationId: `share-create:${tag}`,\n });\n }\n await deps.db.transact(\n [{ t: \"update\", ns: \"crm_tag\", id: tag, attrs: { key: `${recordId}:${tag}`, recordId, tag, createdAt: deps.now() } }],\n { mutationId: `share-map:${tag}:${recordId}` },\n );\n return { recordId };\n}\n\n/** An arriving applicant, as far as the CRM projection cares. `extra` carries the\n * site-configured `crmFields` (values from the application), merged on top of the\n * built-in identity/contact set. */\nexport interface Applicant {\n applicationId: string;\n email: string;\n firstName?: string;\n lastName?: string;\n phone?: string;\n linkedin?: string;\n extra?: Record<string, unknown>;\n}\n\n/**\n * Project an arriving applicant into this chapter's `crm_record`, so a new\n * application shows up in the CRM immediately, unified by email with any prior\n * record. Idempotent per application (`apply:${applicationId}`). Best-effort at\n * the call site — a projection failure never fails the application.\n *\n * `extra` fields (a site's `crmFields`) are merged on top of the base person. If\n * an extra field is not on the crm person type, crm validation throws — so the\n * projection retries with the base person alone, ensuring a misconfigured\n * enrichment never silently drops the applicant from the CRM entirely.\n */\nexport async function projectApplicant(deps: ProjectionDeps, applicant: Applicant): Promise<{ recordId: string }> {\n const base = sharedPersonInput({\n email: applicant.email,\n firstName: applicant.firstName,\n lastName: applicant.lastName,\n phone: applicant.phone,\n linkedin: applicant.linkedin,\n hubRecordId: applicant.applicationId,\n });\n const mutationId = `apply:${applicant.applicationId}`;\n const extra = applicant.extra ?? {};\n if (Object.keys(extra).length === 0) return upsertPerson(deps, { email: applicant.email, input: base, mutationId });\n try {\n return await upsertPerson(deps, { email: applicant.email, input: { ...base, ...extra }, mutationId });\n } catch {\n return upsertPerson(deps, { email: applicant.email, input: base, mutationId });\n }\n}\n","// The OPERATIONAL slice of the one-way person projection: applications + $users\n// -> crm_record, mirroring pipeline stage, a billing snapshot, and the Clerk\n// identity link. `network.ts` projects identity/contact on submit; this adds the\n// operational state the admin surface and the hub read from, so the pipeline\n// stays authoritative in `applications.status` while the CRM carries one\n// relationship surface over the person. Fired AFTER the authoritative write and\n// wrapped in `.catch` at every call site: a CRM hiccup never 5xxs a lifecycle op.\n//\n// Idempotent by construction — every sync resolves the person by lowercased\n// primary email first, so re-running (including the backfill route) updates in\n// place. Generalized from the reference site: the field set is the site's\n// configured `crmFields`, not a hardcoded list.\nimport { createRecord, updateRecord, setStage, linkIdentity } from \"@odla-ai/crm\";\nimport { sharedPersonInput } from \"./network\";\nimport type { ProjectionDeps } from \"./network\";\nimport type { Chapter, ChapterDb } from \"./types\";\n\nconst str = (v: unknown): string => (typeof v === \"string\" ? v : v == null ? \"\" : String(v));\n\n/** Build the projection deps from a worker request's db + context.\n *\n * Shared so every route that writes `applications.status` mirrors it the same\n * way. Status is written from four places (admin approve, admin PATCH, the\n * Stripe webhook, and booking) and the admin dashboard reads its pipeline from\n * `crm_record.stage`, so a route that skips the mirror silently reports the\n * wrong funnel. */\nexport const crmDeps = (\n db: ChapterDb,\n ctx: { chapter: Chapter },\n): ProjectionDeps & { chapter: Chapter } => ({\n crm: ctx.chapter.crm,\n db,\n now: () => Date.now(),\n newId: () => crypto.randomUUID(),\n chapter: ctx.chapter,\n});\n\n/** The crm `person` input for one application row: the built-in identity/contact\n * fields plus each configured `crmFields` value present on the row. */\nexport function personInputFromApp(chapter: Chapter, app: Record<string, unknown>): Record<string, unknown> {\n const input = sharedPersonInput({\n email: str(app.email),\n firstName: str(app.firstName) || undefined,\n lastName: str(app.lastName) || undefined,\n phone: str(app.phone) || undefined,\n linkedin: str(app.linkedin) || undefined,\n hubRecordId: str(app.id),\n });\n for (const f of chapter.application.crmFields) {\n if (app[f] !== undefined) input[f] = app[f];\n }\n if (app.id !== undefined) input.applicationId = str(app.id);\n return input;\n}\n\n// Promoted billing-facet columns derived from the application's Stripe fields +\n// status. Written directly (not through createRecord/updateRecord, which never\n// touch promoted columns), so they never clobber the person input.\nfunction billingColumns(app: Record<string, unknown>): Record<string, unknown> {\n const status = str(app.status);\n const paid = Boolean(app.stripeSubscriptionId) && status !== \"refunded\";\n const billingStatus = status === \"refunded\" ? \"refunded\" : app.canceled === true ? \"canceled\" : paid ? \"active\" : \"none\";\n const cols: Record<string, unknown> = { billingStatus };\n if (app.stripeCustomerId) cols.stripeCustomerId = str(app.stripeCustomerId);\n if (app.stripeSubscriptionId) cols.subscriptionId = str(app.stripeSubscriptionId);\n if (typeof app.renewalAt === \"number\") cols.renewalAt = app.renewalAt;\n return cols;\n}\n\n/** Upsert the person record for one application (or a synthetic `{ email,\n * firstName }` account row) and mirror its stage, billing snapshot, and Clerk\n * identity. `stage` is the `applications.status` to mirror — omit for\n * account-only rows not in the pipeline. Throws on failure (the backfill route\n * counts; the operational call sites wrap in `.catch`). */\nexport async function syncApplicationToCrm(\n deps: ProjectionDeps & { chapter: Chapter },\n opts: { app: Record<string, unknown>; stage?: string },\n): Promise<string | null> {\n const emailKey = str(opts.app.email).toLowerCase();\n if (!emailKey) return null;\n // The narrower shape the crm record helpers take (no chapter).\n const recordDeps = { crm: deps.crm, db: deps.db as never, now: deps.now, newId: deps.newId };\n const input = personInputFromApp(deps.chapter, opts.app);\n\n const { crm_record } = await deps.db.query({\n crm_record: { $: { where: { type: \"person\", primaryEmail: emailKey }, limit: 1 } },\n });\n const existing = crm_record?.[0] ?? null;\n const stage = opts.stage || undefined;\n\n let recordId: string;\n if (existing && typeof existing.id === \"string\") {\n recordId = existing.id;\n await updateRecord(recordDeps, { id: recordId, input });\n } else {\n const created = await createRecord(recordDeps, { type: \"person\", input, ...(stage ? { stage } : {}) });\n recordId = created.id;\n }\n\n // Stage mirror: move only on an actual change, under a stable mutationId so a\n // replay never piles up duplicate stage_change activities. Best-effort — a\n // stage not on the crm person type must not fail the whole sync.\n if (existing && stage && existing.stage !== stage) {\n await setStage(recordDeps, { id: recordId, to: stage, authorId: \"system\", mutationId: `crm:stage:${recordId}:${stage}` }).catch(() => undefined);\n }\n\n await deps.db.transact([{ t: \"update\", ns: \"crm_record\", id: recordId, attrs: billingColumns(opts.app) }]);\n\n // Identity link: stamps clerkUserId when a $users row matches the email; a\n // no-op until the person has an account. Best-effort — a just-created record\n // can briefly lag the read, and the link retries on the next sync.\n await linkIdentity(recordDeps, { recordId, email: emailKey, mutationId: `crm:link:${recordId}:${emailKey}` }).catch(() => undefined);\n\n return recordId;\n}\n\n/** Backfill / repair: project every application (newest per person) and every\n * account-only `$users` row into the CRM. Idempotent (safe to re-run). Dev\n * volumes fit one 1000-row page. Returns `{ synced, errors }`. */\nexport async function backfillCrm(deps: ProjectionDeps & { chapter: Chapter }): Promise<{ synced: number; errors: Array<{ email: string; error: string }> }> {\n const [appsRes, usersRes] = await Promise.all([\n deps.db.query({ applications: { $: { order: { createdAt: \"desc\" }, limit: 1000 } } }),\n deps.db.query({ $users: { $: { limit: 1000 } } }),\n ]);\n const seen = new Set<string>();\n let synced = 0;\n const errors: Array<{ email: string; error: string }> = [];\n\n const run = async (app: Record<string, unknown>, stage?: string): Promise<void> => {\n const key = str(app.email).toLowerCase();\n if (!key || seen.has(key)) return; // newest-first: one record per person\n seen.add(key);\n try {\n await syncApplicationToCrm(deps, { app, stage });\n synced += 1;\n } catch (err) {\n errors.push({ email: key, error: err instanceof Error ? err.message : String(err) });\n }\n };\n\n for (const a of (appsRes.applications ?? []) as Array<Record<string, unknown>>) await run(a, str(a.status));\n for (const u of (usersRes.$users ?? []) as Array<Record<string, unknown>>) {\n if (u.deleted === true) continue;\n await run({ email: u.email, firstName: str(u.name) });\n }\n return { synced, errors };\n}\n","// Pure aggregation helpers for the admin dashboard: a weekly time-series bucketer\n// and the annualized-run-rate math for a Stripe subscription. Kept out of the\n// route so both dashboard and billing share one definition and it is unit-tested\n// without a network call.\n\n/** Bucket `{ t, v }` points into the last `weeks` weekly buckets ending at `now`\n * (epoch ms), summing `v` per bucket. Returns oldest→newest `{ weekStart, value\n * }`, so a caller renders a sparkline directly. Points outside the window are\n * ignored. */\nexport function bucketSeries(\n points: Array<{ t: number; v: number }>,\n now: number,\n weeks = 12,\n): Array<{ weekStart: number; value: number }> {\n const WEEK = 7 * 86_400_000;\n const end = now;\n const start = end - weeks * WEEK;\n const buckets = Array.from({ length: weeks }, (_, i) => ({ weekStart: start + i * WEEK, value: 0 }));\n for (const p of points) {\n if (!Number.isFinite(p.t) || p.t < start || p.t > end) continue;\n const idx = Math.min(weeks - 1, Math.floor((p.t - start) / WEEK));\n const bucket = buckets[idx];\n if (bucket) bucket.value += Number.isFinite(p.v) ? p.v : 0;\n }\n return buckets;\n}\n\n/** Labels and current/previous values for one dashboard comparison window. */\nexport interface DashboardMetricSeries {\n labels: string[];\n current: number[];\n previous: number[];\n}\n\n/** Week-over-week and month-over-month series consumed by MetricWidget. */\nexport interface DashboardMetricData {\n wow: DashboardMetricSeries;\n mom: DashboardMetricSeries;\n}\n\nfunction periodSeries(\n points: Array<{ t: number; v: number }>,\n now: number,\n count: number,\n periodMs: number,\n): DashboardMetricSeries {\n const start = now - count * periodMs;\n const previousStart = start - count * periodMs;\n const current = Array.from({ length: count }, () => 0);\n const previous = Array.from({ length: count }, () => 0);\n for (const point of points) {\n if (!Number.isFinite(point.t) || !Number.isFinite(point.v)) continue;\n if (point.t >= start && point.t <= now) {\n const index = Math.min(count - 1, Math.floor((point.t - start) / periodMs));\n current[index] = (current[index] ?? 0) + point.v;\n } else if (point.t >= previousStart && point.t < start) {\n const index = Math.min(count - 1, Math.floor((point.t - previousStart) / periodMs));\n previous[index] = (previous[index] ?? 0) + point.v;\n }\n }\n const labels = current.map((_, index) =>\n new Date(start + index * periodMs).toISOString().slice(5, 10));\n return { labels, current, previous };\n}\n\n/** MetricWidget-ready daily and weekly comparison windows. */\nexport function dashboardMetricData(\n points: Array<{ t: number; v: number }>,\n now: number,\n): DashboardMetricData {\n const DAY = 86_400_000;\n return {\n wow: periodSeries(points, now, 7, DAY),\n mom: periodSeries(points, now, 4, 7 * DAY),\n };\n}\n\n/** Annualized cents for a Stripe subscription: sum each item's\n * `unit_amount * quantity`, ×12 for monthly intervals. A yearly interval is\n * taken as-is. Returns 0 for a shape with no priced items. */\nexport function subAnnualCents(sub: Record<string, unknown>): number {\n const items = ((sub.items as { data?: Array<Record<string, unknown>> } | undefined)?.data ?? []);\n let cents = 0;\n for (const it of items) {\n const price = (it.price ?? {}) as { unit_amount?: number; recurring?: { interval?: string } };\n const per = (price.unit_amount ?? 0) * ((it.quantity as number) ?? 1);\n cents += price.recurring?.interval === \"month\" ? per * 12 : per;\n }\n return cents;\n}\n","// The Stripe payments provider — the only I/O half of payments. Talks to the\n// Stripe REST API over fetch (no SDK), and maps 1:1 onto the pure helpers in\n// ./payments (form encoding, signature verify, event normalization). The worker\n// resolves secrets from the vault per request and constructs this; the routes own\n// every db write so idempotency stays at the db layer.\nimport { normalizeWebhookEvent, stripeForm, verifyStripeSignature } from \"./payments\";\nimport type { StripeEvent, WebhookEvent } from \"./payments\";\n\n/** Inputs to create an application's founding-member subscription. */\nexport interface CreateSubscriptionInput {\n applicationId: string;\n groupId: string;\n email: string;\n name: string;\n priceId: string;\n existingCustomerId?: string;\n}\n\n/** The client secret to confirm card entry, plus the ids to persist. */\nexport interface CreateSubscriptionResult {\n customerId: string;\n subscriptionId: string;\n clientSecret: string;\n}\n\n/** The outcome of a full refund. */\nexport interface RefundResult {\n refundedCents: number | null;\n subscriptionCanceled: boolean;\n}\n\n/** The capabilities the payment routes depend on — Stripe is one impl. */\nexport interface PaymentsProvider {\n createSubscription(input: CreateSubscriptionInput): Promise<CreateSubscriptionResult>;\n ingestWebhook(\n rawBody: string,\n sigHeader: string,\n ): Promise<{ ok: true; eventId: string; event: WebhookEvent } | { ok: false; reason: \"bad_signature\" }>;\n refund(input: { customerId: string; subscriptionId: string }): Promise<RefundResult>;\n}\n\n/** The result of a Stripe Backend API call: ok + status + parsed JSON body. */\nexport type StripeResult = { ok: boolean; status: number; body: Record<string, unknown> };\n\n/** Call the Stripe Backend API (form-encoded, Bearer sk_). Exposed so the admin\n * billing/dashboard reads and the refund route share one Stripe client instead\n * of each re-deriving the auth + encoding. */\nexport async function stripeCall(\n sk: string,\n method: \"GET\" | \"POST\" | \"DELETE\",\n path: string,\n params?: Record<string, unknown>,\n idempotencyKey?: string,\n): Promise<StripeResult> {\n const qs = method === \"GET\" && params ? `?${stripeForm(params)}` : \"\";\n const headers: Record<string, string> = { authorization: `Bearer ${sk}` };\n if (idempotencyKey) headers[\"idempotency-key\"] = idempotencyKey;\n const init: RequestInit = { method, headers };\n if (method === \"POST\" && params) {\n headers[\"content-type\"] = \"application/x-www-form-urlencoded\";\n init.body = stripeForm(params);\n }\n const res = await fetch(`https://api.stripe.com${path}${qs}`, init);\n const body = (await res.json().catch(() => ({}))) as Record<string, unknown>;\n return { ok: res.ok, status: res.status, body };\n}\n\nfunction fail(op: string, r: StripeResult): never {\n const err = new Error(`stripe ${op} failed: ${r.status}`) as Error & { code: string };\n err.code = \"stripe_error\";\n throw err;\n}\n\nfunction clientSecretOf(sub: Record<string, unknown>): string | undefined {\n const inv = sub.latest_invoice as Record<string, unknown> | undefined;\n const confirmation = inv?.confirmation_secret as Record<string, unknown> | undefined;\n const intent = inv?.payment_intent as Record<string, unknown> | undefined;\n const secret = confirmation?.client_secret ?? intent?.client_secret;\n return typeof secret === \"string\" ? secret : undefined;\n}\n\nfunction requireSecret(secretKey: string | undefined): string {\n if (!secretKey) {\n const err = new Error(\"stripe secret key missing\") as Error & { code: string };\n err.code = \"not_configured\";\n throw err;\n }\n return secretKey;\n}\n\n/** Build the Stripe provider. `secretKey` powers charging/refunds; `webhookSecret`\n * powers webhook ingest. Each is resolved from the vault per request, so the\n * webhook route can construct an ingest-only provider without the secret key. */\nexport function createStripeProvider(config: { secretKey?: string; webhookSecret?: string }): PaymentsProvider {\n const { secretKey, webhookSecret } = config;\n return {\n async createSubscription(input) {\n const sk = requireSecret(secretKey);\n const meta = { applicationId: input.applicationId, groupId: input.groupId, email: input.email };\n let customerId = input.existingCustomerId;\n if (!customerId) {\n const cust = await stripeCall(\n sk,\n \"POST\",\n \"/v1/customers\",\n { email: input.email, name: input.name, metadata: meta },\n `cus:${input.applicationId}`,\n );\n if (!cust.ok) fail(\"customer create\", cust);\n customerId = String(cust.body.id);\n }\n const sub = await stripeCall(\n sk,\n \"POST\",\n \"/v1/subscriptions\",\n {\n customer: customerId,\n \"items[0][price]\": input.priceId,\n payment_behavior: \"default_incomplete\",\n \"payment_settings[save_default_payment_method]\": \"on_subscription\",\n \"payment_settings[payment_method_types][0]\": \"card\",\n \"expand[0]\": \"latest_invoice.confirmation_secret\",\n metadata: meta,\n },\n // Hardening: one subscription per application, so a client\n // retry between create and the db write can't orphan a second one.\n `sub:${input.applicationId}`,\n );\n if (!sub.ok) fail(\"subscription create\", sub);\n const clientSecret = clientSecretOf(sub.body);\n if (!clientSecret) fail(\"subscription confirmation-secret missing\", sub);\n return { customerId, subscriptionId: String(sub.body.id), clientSecret };\n },\n\n async ingestWebhook(rawBody, sigHeader) {\n if (!webhookSecret || !(await verifyStripeSignature(rawBody, sigHeader, webhookSecret))) {\n return { ok: false, reason: \"bad_signature\" };\n }\n const event = JSON.parse(rawBody) as StripeEvent;\n return { ok: true, eventId: event.id, event: normalizeWebhookEvent(event) };\n },\n\n async refund(input) {\n const sk = requireSecret(secretKey);\n const charges = await stripeCall(sk, \"GET\", \"/v1/charges\", { customer: input.customerId, limit: 100 });\n if (!charges.ok) fail(\"charges list\", charges);\n const rows = (charges.body.data as Array<Record<string, unknown>> | undefined) ?? [];\n const paid = rows.filter((c) => c.status === \"succeeded\" && c.refunded !== true);\n const charge = paid[paid.length - 1]; // Stripe returns newest-first; refund the earliest.\n if (!charge) {\n const err = new Error(\"no paid charge to refund\") as Error & { code: string };\n err.code = \"no_charge\";\n throw err;\n }\n const refund = await stripeCall(sk, \"POST\", \"/v1/refunds\", { charge: String(charge.id) });\n if (!refund.ok) fail(\"refund\", refund);\n const cancel = await stripeCall(sk, \"DELETE\", `/v1/subscriptions/${input.subscriptionId}`);\n const amount = refund.body.amount;\n return { refundedCents: typeof amount === \"number\" ? amount : null, subscriptionCanceled: cancel.ok };\n },\n };\n}\n","// Apply-time account provisioning. When an application arrives, the worker mints\n// a Clerk invitation so the applicant gets a path into their member area — the\n// generalized server-side account step. Chapter calls the Clerk\n// Backend API directly over fetch (vault clerk_secret_key), mirroring\n// @odla-ai/auth-clerk's createInvitation but self-contained, so it never pulls the\n// auth-clerk UI package into the worker bundle. Best-effort at the call site: a\n// provisioning failure never fails the application.\n\n/** The outcome of a Clerk provisioning call. `existed` marks the heal case: Clerk\n * rejected it because the account/invitation is already there, which is the end\n * state we wanted anyway. */\nexport interface ClerkResult {\n ok: boolean;\n status: number;\n existed?: boolean;\n /** Set when an `existed` heal also refreshed the account's metadata. */\n refreshed?: boolean;\n}\n\n// Clerk answers 422 when the email already has a user (or a pending invitation).\n// For apply-time provisioning that's success, not failure — the account exists.\nconst heal = (status: number): ClerkResult =>\n status === 422 ? { ok: true, status, existed: true } : { ok: false, status };\n\n/** Inputs for a Clerk invitation. */\nexport interface ClerkInviteInput {\n email: string;\n /** Where the accept-invitation link lands (usually the member area). */\n redirectUrl?: string;\n /** Written to the invitation's `public_metadata`. Clerk application\n * invitations cannot carry private metadata, so never put applicant profile\n * fields here. Chapter completes that backend-only write after acceptance. */\n publicMetadata?: Record<string, unknown>;\n}\n\n/** Build the Clerk Backend API invitation request (path + JSON body). Pure, so\n * the wire shape is testable without a network call. */\nexport function clerkInviteRequest(input: ClerkInviteInput): { path: string; body: Record<string, unknown> } {\n return {\n path: \"/v1/invitations\",\n body: {\n email_address: input.email,\n notify: true,\n ...(input.redirectUrl ? { redirect_url: input.redirectUrl } : {}),\n ...(input.publicMetadata ? { public_metadata: input.publicMetadata } : {}),\n },\n };\n}\n\n/** POST the invitation to the Clerk Backend API. A repeat invite for an\n * already-invited email heals to `{ ok: true, existed: true }` rather than\n * reporting a failure the caller would have to special-case. */\nexport async function createClerkInvitation(\n secretKey: string,\n input: ClerkInviteInput,\n fetchImpl: typeof fetch = fetch,\n): Promise<ClerkResult> {\n const { path, body } = clerkInviteRequest(input);\n const res = await fetchImpl(`https://api.clerk.com${path}`, {\n method: \"POST\",\n headers: { authorization: `Bearer ${secretKey}`, \"content-type\": \"application/json\" },\n body: JSON.stringify(body),\n });\n return res.ok ? { ok: true, status: res.status } : heal(res.status);\n}\n\n/** Inputs for a server-side Clerk user create. */\nexport interface ClerkUserInput {\n email: string;\n firstName?: string;\n lastName?: string;\n /** Backend-authored, browser-readable claims such as `role`. */\n publicMetadata?: Record<string, unknown>;\n /** Backend-only account signals and profile data. */\n privateMetadata?: Record<string, unknown>;\n /** Optional metadata merge used only when create returns 422 for an existing\n * account. This lets migrations remove legacy keys without putting tombstone\n * values on a newly-created user. Defaults to the create metadata. */\n repairMetadata?: ClerkMetadataInput;\n}\n\n/** Build the Clerk Backend API user-create request. The account is created\n * passwordless (the member signs in via the site's Clerk flow), so join step 3\n * can say the account is ready — the \"create\" alternative to an invitation. */\nexport function clerkUserRequest(input: ClerkUserInput): { path: string; body: Record<string, unknown> } {\n return {\n path: \"/v1/users\",\n body: {\n email_address: [input.email],\n skip_password_requirement: true,\n ...(input.firstName ? { first_name: input.firstName } : {}),\n ...(input.lastName ? { last_name: input.lastName } : {}),\n ...(input.publicMetadata !== undefined ? { public_metadata: input.publicMetadata } : {}),\n ...(input.privateMetadata !== undefined ? { private_metadata: input.privateMetadata } : {}),\n },\n };\n}\n\n/** One merge-PATCH to the metadata fields Clerk exposes on its Backend API. */\nexport interface ClerkMetadataInput {\n publicMetadata?: Record<string, unknown>;\n privateMetadata?: Record<string, unknown>;\n}\n\n/** Merge metadata into an existing Clerk user. Nested `null` values remove old\n * keys, which Chapter uses to migrate its former public profile into private\n * metadata without disturbing the public `role` claim. */\nexport async function updateClerkUserMetadata(\n secretKey: string,\n userId: string,\n input: ClerkMetadataInput,\n fetchImpl: typeof fetch = fetch,\n): Promise<boolean> {\n const res = await fetchImpl(`https://api.clerk.com/v1/users/${encodeURIComponent(userId)}/metadata`, {\n method: \"PATCH\",\n headers: { authorization: `Bearer ${secretKey}`, \"content-type\": \"application/json\" },\n body: JSON.stringify({\n ...(input.publicMetadata !== undefined ? { public_metadata: input.publicMetadata } : {}),\n ...(input.privateMetadata !== undefined ? { private_metadata: input.privateMetadata } : {}),\n }),\n });\n return res.ok;\n}\n\n/** Look a Clerk account up by email and merge its metadata.\n *\n * Returns `false` when no matching user can be resolved or Clerk rejects the\n * lookup or update. Chapter uses this for create-healing and invitation\n * adoption so a repeated application repairs the private account snapshot.\n */\nexport async function updateClerkUserMetadataByEmail(\n secretKey: string,\n email: string,\n input: ClerkMetadataInput,\n fetchImpl: typeof fetch = fetch,\n): Promise<boolean> {\n const auth = { authorization: `Bearer ${secretKey}` };\n const found = await fetchImpl(`https://api.clerk.com/v1/users?email_address=${encodeURIComponent(email)}&limit=1`, { headers: auth });\n if (!found.ok) return false;\n const users = (await found.json().catch(() => null)) as Array<{ id?: unknown }> | null;\n const id = Array.isArray(users) && typeof users[0]?.id === \"string\" ? users[0].id : undefined;\n if (!id) return false;\n return updateClerkUserMetadata(secretKey, id, input, fetchImpl);\n}\n\n/** Create the applicant's Clerk account server-side. A repeat for an email that\n * already has an account heals to `{ ok: true, existed: true }` — the account\n * exists, which is the state apply-time provisioning wanted — and refreshes\n * supplied public/private metadata on the existing account so a re-application\n * repairs a previously missed create (`refreshed: true`). */\nexport async function createClerkUser(\n secretKey: string,\n input: ClerkUserInput,\n fetchImpl: typeof fetch = fetch,\n): Promise<ClerkResult> {\n const { path, body } = clerkUserRequest(input);\n const res = await fetchImpl(`https://api.clerk.com${path}`, {\n method: \"POST\",\n headers: { authorization: `Bearer ${secretKey}`, \"content-type\": \"application/json\" },\n body: JSON.stringify(body),\n });\n if (res.ok) return { ok: true, status: res.status };\n const healed = heal(res.status);\n const repair = input.repairMetadata ?? {\n ...(input.publicMetadata !== undefined ? { publicMetadata: input.publicMetadata } : {}),\n ...(input.privateMetadata !== undefined ? { privateMetadata: input.privateMetadata } : {}),\n };\n if (\n !healed.existed ||\n (repair.publicMetadata === undefined && repair.privateMetadata === undefined)\n ) return healed;\n const refreshed = await updateClerkUserMetadataByEmail(secretKey, input.email, {\n ...(repair.publicMetadata !== undefined ? { publicMetadata: repair.publicMetadata } : {}),\n ...(repair.privateMetadata !== undefined ? { privateMetadata: repair.privateMetadata } : {}),\n }, fetchImpl).catch(() => false);\n return { ...healed, refreshed };\n}\n","// The odla->Clerk write half: server-side Clerk role read/list/write, the gap\n// beside clerk.ts's create/invite/heal. Roles live in `public_metadata.role`; an\n// absent role means the lowest rung (\"provisional\") — chapter never writes it at\n// create. Account profile signals live in backend-only `private_metadata`; the\n// role write is a MERGE-PATCH of only `{ role }`, so it cannot clobber them.\n// Chapter calls the Clerk Backend API over fetch with the vault\n// `clerk_secret_key`.\n\nconst CLERK_API = \"https://api.clerk.com\";\nconst DEFAULT_ROLE = \"provisional\";\nconst PAGE = 100;\n\n/** A Clerk user as chapter's role layer sees it. `role` defaults to the lowest\n * rung when `public_metadata` carries none. Raw public/private metadata is\n * returned only from this backend helper; `profile` and `applicationId` are the\n * normalized Chapter-owned private signals. */\nexport interface ClerkUserRecord {\n id: string;\n email?: string;\n role: string;\n publicMetadata: Record<string, unknown>;\n privateMetadata: Record<string, unknown>;\n profile: Record<string, unknown>;\n applicationId?: string;\n}\n\n// The Clerk Backend API user object — only the fields chapter reads.\ninterface ClerkApiUser {\n id?: unknown;\n email_addresses?: Array<{ email_address?: unknown }>;\n public_metadata?: Record<string, unknown>;\n private_metadata?: Record<string, unknown>;\n}\n\nfunction toRecord(u: ClerkApiUser): ClerkUserRecord | null {\n if (typeof u.id !== \"string\") return null;\n const pm = (u.public_metadata ?? {}) as Record<string, unknown>;\n const privateMetadata = (u.private_metadata ?? {}) as Record<string, unknown>;\n const rawProfile = privateMetadata.profile;\n const profile = rawProfile && typeof rawProfile === \"object\" && !Array.isArray(rawProfile)\n ? rawProfile as Record<string, unknown>\n : {};\n const applicationId = typeof privateMetadata.applicationId === \"string\"\n ? privateMetadata.applicationId\n : undefined;\n const role = typeof pm.role === \"string\" && pm.role ? pm.role : DEFAULT_ROLE;\n const email = u.email_addresses?.[0]?.email_address;\n return {\n id: u.id,\n email: typeof email === \"string\" ? email : undefined,\n role,\n publicMetadata: pm,\n privateMetadata,\n profile,\n ...(applicationId ? { applicationId } : {}),\n };\n}\n\nasync function clerkGet(path: string, secretKey: string, fetchImpl: typeof fetch): Promise<unknown> {\n const res = await fetchImpl(`${CLERK_API}${path}`, { headers: { authorization: `Bearer ${secretKey}` } });\n if (!res.ok) throw new Error(`clerk GET ${path} → ${res.status}`);\n return res.json();\n}\n\n/** Look a Clerk user up by email. `null` when no such user — or when the lookup\n * fails (a role gate treats an unresolvable user as absent, matching the site's\n * own fallback). Role defaults to provisional when unset. */\nexport async function clerkGetUserByEmail(secretKey: string, email: string, fetchImpl: typeof fetch = fetch): Promise<ClerkUserRecord | null> {\n const data = await clerkGet(`/v1/users?email_address=${encodeURIComponent(email)}&limit=1`, secretKey, fetchImpl).catch(() => null);\n const user = Array.isArray(data) ? (data[0] as ClerkApiUser | undefined) : undefined;\n return user ? toRecord(user) : null;\n}\n\n/** Fetch a Clerk user by id, for role-change gating. `null` when missing or on a\n * failed lookup. Role defaults to provisional when unset. */\nexport async function clerkGetUser(secretKey: string, id: string, fetchImpl: typeof fetch = fetch): Promise<ClerkUserRecord | null> {\n const data = await clerkGet(`/v1/users/${encodeURIComponent(id)}`, secretKey, fetchImpl).catch(() => null);\n return data ? toRecord(data as ClerkApiUser) : null;\n}\n\n/** List ALL Clerk users with their roles, auto-paginating. A membership community\n * outgrows one page, and a fixed `limit=100` would silently drop members from the\n * admin roster with no error — so this pages (offset in steps of {@link PAGE})\n * until a short page. A page fetch that fails THROWS rather than returning a\n * partial list, so the caller never mistakes a truncated roster for the whole. */\nexport async function clerkListUsers(secretKey: string, fetchImpl: typeof fetch = fetch): Promise<ClerkUserRecord[]> {\n const out: ClerkUserRecord[] = [];\n for (let offset = 0; ; offset += PAGE) {\n const data = await clerkGet(`/v1/users?limit=${PAGE}&offset=${offset}`, secretKey, fetchImpl);\n const page = Array.isArray(data) ? (data as ClerkApiUser[]) : [];\n for (const u of page) {\n const record = toRecord(u);\n if (record) out.push(record);\n }\n if (page.length < PAGE) break;\n }\n return out;\n}\n\n/** Write a user's role: a MERGE-PATCH of only `{ role }` on `public_metadata`.\n * The backend-only profile lives in `private_metadata`, so the two signals\n * cannot overwrite each other. Returns whether the role write stuck. */\nexport async function clerkSetRole(secretKey: string, id: string, role: string, fetchImpl: typeof fetch = fetch): Promise<boolean> {\n const res = await fetchImpl(`${CLERK_API}/v1/users/${encodeURIComponent(id)}/metadata`, {\n method: \"PATCH\",\n headers: { authorization: `Bearer ${secretKey}`, \"content-type\": \"application/json\" },\n body: JSON.stringify({ public_metadata: { role } }),\n });\n return res.ok;\n}\n","// The Clerk capability descriptor: documentation-as-data — \"what settings/secrets\n// does turning Clerk on need?\", \"what must the CLI provision?\", and \"what data\n// syncs, in which direction?\". It lives here (not in the browser package\n// @odla-ai/auth-clerk) because the sync contract is implemented by odla-db + the\n// platform webhook (provider->odla) and by chapter's clerk.ts (odla->provider),\n// neither of which is browser code. auth-clerk stays a pure UI package. Data only:\n// it describes the capability, it performs none of it.\n\n/** Which auth mode a secret is required for: `\"client\"` (publishable key only) or\n * `\"full\"` (server-side profile/role reads + writes, needing the vault secret). */\nexport type SecretMode = \"client\" | \"full\";\n\n/** A configuration value the capability needs (e.g. the publishable key). */\nexport interface IntegrationSetting {\n key: string;\n description: string;\n /** Safe to ship to the browser? (publishable keys are; secrets are not). */\n public: boolean;\n /** Expected value shape, e.g. a \"pk_\" prefix. */\n pattern?: string;\n /** Stored per environment (dev/prod)? */\n perEnv: boolean;\n /** Where the value is stored and read from. */\n source: string;\n}\n\n/** A secret the capability needs, held in the tenant vault. */\nexport interface IntegrationSecret {\n key: string;\n description: string;\n pattern?: string;\n /** Auth mode that requires this secret (\"full\" pulls user profiles). */\n mode: SecretMode;\n /** Stored in the tenant vault (never in graph rows or wrangler vars)? */\n vault: boolean;\n}\n\n/** One data-sync contract. A capability may declare several (e.g. an inbound\n * webhook mirror AND an outbound write), so {@link IntegrationDescriptor.syncs}\n * is a list. The mechanism is declared, not reimplemented here. */\nexport interface IntegrationSync {\n engine: string;\n direction: \"provider->odla\" | \"odla->provider\" | \"bidirectional\";\n entity: string;\n fields: string[];\n webhook?: string;\n onDelete?: string;\n}\n\n/** The provisioning split: human, CLI, and doctor-assertable steps. */\nexport interface IntegrationProvision {\n /** Steps a human performs (out-of-band, e.g. in the Clerk dashboard). */\n human: string[];\n /** Steps the odla CLI automates. */\n cli: string[];\n /** Checks `odla doctor` should assert. */\n doctor: string[];\n}\n\n/** Documentation-as-data for a provider capability, consumed by the odla CLI,\n * `odla doctor`, and the docs. */\nexport interface IntegrationDescriptor {\n id: string;\n title: string;\n npm: string;\n settings: IntegrationSetting[];\n secrets: IntegrationSecret[];\n /** Every data-sync contract the capability declares, each with its direction. */\n syncs: IntegrationSync[];\n provision: IntegrationProvision;\n}\n\n/**\n * The concrete {@link IntegrationDescriptor} for the Clerk capability. Declares\n * the one public `setting` (the `pk_*` publishable key, served to the SPA), the\n * `secrets` auth mode `\"full\"` needs in the tenant vault, BOTH sync directions —\n * the inbound `$users` webhook mirror (`provider->odla`) and the outbound\n * public role/private profile write via `clerk_secret_key` (`odla->provider`,\n * chapter's `clerk.ts`) — and the `provision` split. Data only; it performs\n * none of it.\n */\nexport const clerkIntegration: IntegrationDescriptor = {\n id: \"clerk\",\n title: \"Clerk end-user auth\",\n npm: \"@odla-ai/chapter\",\n settings: [\n {\n key: \"publishableKey\",\n description: \"Clerk publishable key (pk_*). Public by design; served to the SPA (loads clerk-js from Clerk's CDN via @odla-ai/auth-clerk).\",\n public: true,\n pattern: \"pk_\",\n perEnv: true,\n source: \"apps-registry auth[env].publishableKey (from odla.config.mjs auth.clerk.<env>)\",\n },\n ],\n secrets: [\n {\n key: \"CLERK_WEBHOOK_SECRET\",\n description: \"Svix signing secret for Clerk user webhooks — verifies $users sync events.\",\n pattern: \"whsec_\",\n mode: \"full\",\n vault: true,\n },\n {\n key: \"clerk_secret_key\",\n description: \"Clerk backend key (sk_*) — powers account creation, the public role claim, and backend-only private profile signals; it also lets odla-db resolve user email/name via the Clerk API.\",\n pattern: \"sk_\",\n mode: \"full\",\n vault: true,\n },\n ],\n syncs: [\n {\n engine: \"@odla-ai/db $users (Clerk webhook -> tenant graph)\",\n direction: \"provider->odla\",\n entity: \"$users\",\n // Matches the webhook mapper: the mirror carries id + primary email + name + avatar.\n fields: [\"id\", \"email\", \"name\", \"imageUrl\"],\n webhook: \"whsec_ (svix-signed)\",\n onDelete: \"tombstone (never row-delete)\",\n },\n {\n engine: \"@odla-ai/chapter clerk.ts (Clerk Backend API via vault clerk_secret_key)\",\n direction: \"odla->provider\",\n entity: \"clerk user\",\n // Separate metadata fields so a public role write cannot expose or\n // clobber backend-only account/profile signals.\n fields: [\n \"public_metadata.role\",\n \"private_metadata.applicationId\",\n \"private_metadata.profile\",\n ],\n onDelete: \"n/a (writes only)\",\n },\n ],\n provision: {\n human: [\n \"Run `npx clerk auth login` — the one interactive step; the agent then creates + configures the Clerk app with the Clerk CLI (no pk_ to hand-paste).\",\n 'For auth mode \"full\": create the Clerk user-sync webhook and store its whsec_, plus the sk_ as clerk_secret_key, in the tenant vault (Studio, write-only).',\n ],\n cli: [\n \"Clerk CLI (`npx clerk apps create/link/config patch`) creates + configures the instance and pulls the pk_.\",\n \"odla `provision` records it: setAuth(env, publishableKey) -> apps-registry (issuer/JWKS derived from the key).\",\n ],\n doctor: [\n \"registry auth[env] present when the app mounts <SignIn>?\",\n 'auth mode \"full\" => whsec_ and clerk_secret_key present in the tenant vault?',\n ],\n },\n};\n","// The member session — what GET /api/me returns to a signed-in applicant/member.\n// Ported from the reference application's summary + /api/me reconciliation. The\n// SHAPING is pure and package-enforced so no site re-derives it; the worker owns\n// only the I/O around it (locating the application by email, reconciling the\n// meeting against the calendar) and hands the resolved rows here.\n//\n// Two invariants live here, not in a site:\n// - `paid` is DERIVED, never a stored flag: a subscription exists and the\n// application wasn't refunded. Sites can't drift a stale boolean out of sync\n// with Stripe.\n// - the live meeting row wins: its startAt/meetUrl/timezone override whatever\n// the application row cached, and a non-scheduled meeting (a cancellation\n// adopted from the calendar) forces meetingAt back to null.\n\n/** An application row, as far as the session cares about it. */\nexport interface ApplicationRecord {\n id: string;\n firstName?: string | null;\n lastName?: string | null;\n email?: string | null;\n status: string;\n meetingAt?: number | null;\n meetingLink?: string | null;\n createdAt?: number | null;\n stripeSubscriptionId?: string | null;\n renewalAt?: number | null;\n canceled?: boolean;\n}\n\n/** The reconciled meeting row (already adopted against the calendar), or null. */\nexport interface MeetingRecord {\n status: string;\n startAt?: number | null;\n meetUrl?: string | null;\n timezone?: string | null;\n}\n\n/** The stable, non-meeting fields of an application (safe to expose to its own\n * owner). */\nexport interface ApplicationSummary {\n id: string;\n firstName: string | null;\n lastName: string | null;\n email: string | null;\n status: string;\n createdAt: number | null;\n meetingLink: string | null;\n paid: boolean;\n renewalAt: number | null;\n canceled: boolean;\n}\n\n/** A summary plus the reconciled meeting fields — the `application` the member\n * area renders. */\nexport interface MemberApplication extends ApplicationSummary {\n meetingAt: number | null;\n meetUrl: string | null;\n timezone: string;\n}\n\n/** The full GET /api/me payload for a signed-in user. */\nexport interface MemberSession {\n userId: string;\n email: string | null;\n role: string;\n superAdmin: boolean;\n application: MemberApplication | null;\n}\n\n/** Derive the summary fields from an application row. `paid` is computed, not\n * read, so it can never contradict Stripe. */\nexport function applicationSummary(app: ApplicationRecord): ApplicationSummary {\n return {\n id: app.id,\n firstName: app.firstName ?? null,\n lastName: app.lastName ?? null,\n email: app.email ?? null,\n status: app.status,\n createdAt: app.createdAt ?? null,\n meetingLink: app.meetingLink ?? null,\n paid: Boolean(app.stripeSubscriptionId) && app.status !== \"refunded\",\n renewalAt: app.renewalAt ?? null,\n canceled: app.canceled === true,\n };\n}\n\n/** Fold a (possibly absent, already-reconciled) meeting into the application the\n * member area renders. The live meeting overrides the application's cached\n * meeting fields; a non-`scheduled` meeting clears the booking. */\nexport function memberApplication(\n app: ApplicationRecord,\n meeting: MeetingRecord | null | undefined,\n defaultTimezone: string,\n): MemberApplication {\n const summary = applicationSummary(app);\n let meetingAt = app.meetingAt ?? null;\n let meetUrl: string | null = null;\n let timezone = defaultTimezone;\n if (meeting) {\n timezone = meeting.timezone ?? timezone;\n if (meeting.status === \"scheduled\") {\n meetingAt = meeting.startAt ?? null;\n meetUrl = meeting.meetUrl ?? null;\n } else {\n meetingAt = null;\n }\n }\n return { ...summary, meetingAt, meetUrl, timezone };\n}\n\n/** Identity of the signed-in user, from the verified session. */\nexport interface SessionUser {\n userId: string;\n email?: string | null;\n role: string;\n}\n\n/** Assemble the GET /api/me payload. `application` is null when the user has no\n * application on file (an admin who never applied, or a brand-new account). */\nexport function memberSession(\n user: SessionUser,\n opts: { application: MemberApplication | null; superAdmin: boolean },\n): MemberSession {\n return {\n userId: user.userId,\n email: user.email ?? null,\n role: user.role,\n superAdmin: opts.superAdmin,\n application: opts.application,\n };\n}\n","// Brand tokens (H4). defineChapter accepts a `brand` block; this turns it into a\n// `:root { --…: … }` CSS block, so a chapter re-skins the WHOLE UI — the\n// @odla-ai/ui components, the admin shell, and the member islands, which all read\n// --ui-* design tokens — from one config instead of hand-writing inline CSS.\n//\n// Pure string generation, so it is unit-testable and can be emitted at\n// build/SSR time into the page <head> (no flash of unstyled content), or via the\n// <BrandStyle> component from @odla-ai/chapter/ui.\nimport type { ChapterBrand, ChapterBrandTokens } from \"./types\";\n\n// A palette entry is either a direct custom property (already `--…`, e.g.\n// `--ui-accent` to retheme components) or a bare name we expose as `--<name>`\n// (e.g. `moss` → `--moss`, for a site to reference in its own CSS).\nfunction paletteVar(key: string): string {\n return key.startsWith(\"--\") ? key : `--${key}`;\n}\n\n// Strip characters that could break out of a `--var: value;` declaration or the\n// surrounding <style>. Brand config is trusted author input, so this is a\n// belt-and-suspenders guard, not a security boundary.\nfunction cleanValue(value: string): string {\n return value.replace(/[<>{};]/g, \"\").trim();\n}\n\nfunction paletteDecls(palette: Record<string, string> | undefined): string[] {\n const decls: string[] = [];\n for (const [key, value] of Object.entries(palette ?? {})) {\n if (typeof value === \"string\" && value.trim()) decls.push(`${paletteVar(key)}: ${cleanValue(value)};`);\n }\n return decls;\n}\n\nconst TOKEN_VARS: Record<keyof ChapterBrandTokens, string> = {\n background: \"--ui-bg\",\n surface: \"--ui-surface\",\n surface2: \"--ui-surface-2\",\n text: \"--ui-text\",\n textMuted: \"--ui-text-muted\",\n textFaint: \"--ui-text-faint\",\n border: \"--ui-border\",\n borderStrong: \"--ui-border-strong\",\n accent: \"--ui-accent\",\n accentStrong: \"--ui-accent-strong\",\n accentSoft: \"--ui-accent-soft\",\n onAccent: \"--ui-on-accent\",\n good: \"--ui-good\",\n warn: \"--ui-warn\",\n danger: \"--ui-danger\",\n chart1: \"--ui-chart-1\",\n chart2: \"--ui-chart-2\",\n chart3: \"--ui-chart-3\",\n chart4: \"--ui-chart-4\",\n chart5: \"--ui-chart-5\",\n chart6: \"--ui-chart-6\",\n chartPositive: \"--ui-chart-pos\",\n chartNegative: \"--ui-chart-neg\",\n contentWidth: \"--ui-content-width\",\n panelRadius: \"--ui-radius-lg\",\n panelShadow: \"--ui-shadow\",\n masterDetailColumns: \"--ui-master-detail-columns\",\n masterDetailMinHeight: \"--ui-master-detail-min-height\",\n pagePadding: \"--chapter-admin-page-padding\",\n workspacePadding: \"--chapter-admin-workspace-padding\",\n};\n\nfunction semanticDecls(tokens: ChapterBrandTokens | undefined): string[] {\n return Object.entries(tokens ?? {}).flatMap(([key, value]) =>\n typeof value === \"string\" && value.trim()\n ? [`${TOKEN_VARS[key as keyof ChapterBrandTokens]}: ${cleanValue(value)};`]\n : [],\n );\n}\n\n/**\n * Build the CSS that maps a chapter's brand onto the design tokens the UI reads:\n * each `palette` entry becomes a custom property (light, and dark unless\n * `paletteDark` overrides), and `fonts` (display/body/numeral) map to\n * `--ui-font-display` / `--ui-font-sans` / `--ui-font-numeral`. The dark block is\n * emitted under both `:root[data-theme=\"dark\"]` (the odla-ui theme toggle) and\n * `@media (prefers-color-scheme: dark)`. Returns \"\" when there is nothing to\n * theme. These are brand OVERRIDES on top of a base theme — they do not replace\n * the theme layer the components need (see {@link brandTokens} usage in the docs).\n */\nexport function brandTokens(\n brand: ChapterBrand | undefined,\n options: { selector?: string } = {},\n): string {\n if (!brand) return \"\";\n const light = [...paletteDecls(brand.palette), ...semanticDecls(brand.tokens)];\n const fonts = brand.fonts;\n if (fonts?.display) light.push(`--ui-font-display: ${cleanValue(fonts.display)};`);\n if (fonts?.body) light.push(`--ui-font-sans: ${cleanValue(fonts.body)};`);\n if (fonts?.numeral) light.push(`--ui-font-numeral: ${cleanValue(fonts.numeral)};`);\n const dark = [...paletteDecls(brand.paletteDark), ...semanticDecls(brand.tokensDark)];\n const invert = paletteDecls(brand.paletteInvert);\n const selector = options.selector ?? \":root\";\n const darkSelector = selector === \":root\" ? ':root[data-theme=\"dark\"]' : `${selector}[data-theme=\"dark\"]`;\n const systemSelector = selector === \":root\"\n ? ':root:not([data-theme=\"light\"])'\n : `${selector}:not([data-theme=\"light\"])`;\n\n let css = light.length ? `${selector} {\\n ${light.join(\"\\n \")}\\n}\\n` : \"\";\n if (dark.length) {\n const block = `{\\n ${dark.join(\"\\n \")}\\n}`;\n css += `${darkSelector} ${block}\\n@media (prefers-color-scheme: dark) {\\n ${systemSelector} ${block}\\n}\\n`;\n }\n if (invert.length) {\n const invertSelector = selector === \":root\"\n ? \".ui-invert\"\n : `${selector}.ui-invert, ${selector} .ui-invert`;\n css += `${invertSelector} {\\n color-scheme: dark;\\n ${invert.join(\"\\n \")}\\n}\\n`;\n }\n return css;\n}\n\n/** Structural output accepted from `@odla-ai/brand`'s pure token compiler. */\nexport interface CompiledChapterBrandTokens {\n light: Record<string, string>;\n dark: Record<string, string>;\n}\n\n/**\n * Turn a complete compiled token pair into Chapter's brand contract without\n * coupling Chapter's runtime to the brand-book/agent package. Explicit values in\n * `base` win, so a host can make final per-site adjustments after compilation.\n */\nexport function chapterBrandFromTokens(\n compiled: CompiledChapterBrandTokens,\n base: ChapterBrand = {},\n): ChapterBrand {\n return {\n ...base,\n palette: { ...compiled.light, ...base.palette },\n paletteDark: { ...compiled.dark, ...base.paletteDark },\n paletteInvert: { ...compiled.dark, ...base.paletteInvert },\n };\n}\n","// The Google → chapter half of the bidirectional calendar sync. Booking writes\n// events TO the platform calendar (0.4.0's create/reschedule); this ADOPTS edits\n// FROM it: when the owner moves or cancels the intro call directly in Google,\n// reconcile mirrors that onto our canonical `meetings` row and the `application`\n// projection. Chapter never calls Google — the worker reads live events through\n// @odla-ai/calendar's upcoming() (platform-brokered) and applies the ops this\n// returns. Pure + testable; adoption policy (owner edits win) matches the shipped\n// original production worker.\n\n/** A `meetings` row, as far as reconciliation cares. */\nexport interface MeetingForReconcile {\n id: string;\n applicationId: string;\n googleEventId?: string | null;\n status: string;\n startAt?: number | null;\n endAt?: number | null;\n}\n\n/** One live calendar event (a subset of @odla-ai/calendar's Booking). */\nexport interface LiveEvent {\n eventId: string;\n status?: string;\n startAt?: number;\n endAt?: number;\n}\n\n/** An adopt decision: mirror a Google move/cancel onto our rows. */\nexport interface ReconcileDecision {\n meetingId: string;\n applicationId: string;\n kind: \"cancelled\" | \"moved\";\n /** Attrs to write onto the `meetings` row. */\n meetingPatch: Record<string, unknown>;\n /** Attrs to write onto the `applications` row (the projection). */\n applicationPatch: Record<string, unknown>;\n}\n\n/** Meetings still worth reconciling: a scheduled booking with a Google event that\n * starts in the future (or within the last hour, to catch a just-passed edit). */\nexport function isReconcilable(meeting: MeetingForReconcile, now: number): boolean {\n return meeting.status === \"scheduled\" && Boolean(meeting.googleEventId) && (meeting.startAt ?? 0) > now - 3_600_000;\n}\n\n/**\n * Diff canonical `meetings` against the live calendar events and return the adopt\n * decisions — only for meetings that actually changed (a still-matching meeting\n * is omitted). A meeting whose event vanished or is `cancelled` in Google is\n * adopted as cancelled (application `meetingAt` zeroed — 0 means \"was booked,\n * then cancelled\"); a meeting whose event moved adopts the new window (duration\n * preserved when the event omits `endAt`).\n */\nexport function reconcileMeetings(\n meetings: readonly MeetingForReconcile[],\n events: readonly LiveEvent[],\n now: number,\n): ReconcileDecision[] {\n const byEvent = new Map(events.map((e) => [e.eventId, e]));\n const decisions: ReconcileDecision[] = [];\n for (const m of meetings) {\n if (!isReconcilable(m, now) || !m.googleEventId) continue;\n const g = byEvent.get(m.googleEventId);\n if (!g || g.status === \"cancelled\") {\n decisions.push({\n meetingId: m.id,\n applicationId: m.applicationId,\n kind: \"cancelled\",\n meetingPatch: { status: \"cancelled\", drift: \"none\", adoptedFromGoogleAt: now },\n applicationPatch: { meetingAt: 0, meetingLink: \"\" },\n });\n } else if (g.startAt !== undefined && g.startAt !== m.startAt) {\n const duration = (m.endAt ?? 0) - (m.startAt ?? 0);\n decisions.push({\n meetingId: m.id,\n applicationId: m.applicationId,\n kind: \"moved\",\n meetingPatch: { startAt: g.startAt, endAt: g.endAt ?? g.startAt + duration, drift: \"none\", adoptedFromGoogleAt: now },\n applicationPatch: { meetingAt: g.startAt },\n });\n }\n }\n return decisions;\n}\n","// Scheduling core — config resolution + the booking invariants, ported from the\n// proven production worker. Everything here is PURE (no @odla-ai/calendar, no\n// db), so the correctness properties are unit-testable and package-enforced; the\n// worker route owns only the I/O (FreeBusy, computeBookableSlots, calendar\n// create/reschedule, the db writes) and calls these.\n//\n// Package-enforced invariants:\n// - the `meetings` row is canonical; applications.meetingAt and the calendar\n// event are projections written from it.\n// - one intro event per application, forever: a rebooking RESCHEDULES the\n// existing event (preserving its Meet link + invite thread), never creates a\n// second — see {@link bookingDecision} + {@link introIdempotencyKey}.\n// - status never moves backward on booking; you can only book from a pipeline\n// stage in `bookableFrom` — see `canBook` in ./pipeline + applicationBookingUpdate.\n// - endAt is always derived server-side, never client-supplied — see\n// {@link endForSlot}.\nimport type { ChapterScheduling } from \"./types\";\n\n/** A fully-resolved scheduling config (every field present). */\nexport interface ResolvedScheduling {\n slotMinutes: number;\n days: readonly number[];\n startHour: number;\n endHour: number;\n timezone: string;\n minNoticeHours: number;\n windowDays: number;\n summaryTemplate: string;\n}\n\n/** Proven defaults: 45-minute weekday slots, 9–5 Pacific, 24h notice,\n * a 14-day window. The summary is generic (a chapter's group seed supplies a\n * name-branded one). */\nexport const SCHEDULING_DEFAULTS: ResolvedScheduling = {\n slotMinutes: 45,\n days: [1, 2, 3, 4, 5],\n startHour: 9,\n endHour: 17,\n timezone: \"America/Los_Angeles\",\n minNoticeHours: 24,\n windowDays: 14,\n summaryTemplate: \"Introduction call with {{firstName}} {{lastName}}\",\n};\n\nfunction isValidTimeZone(tz: string): boolean {\n try {\n new Intl.DateTimeFormat(undefined, { timeZone: tz });\n return true;\n } catch {\n return false;\n }\n}\n\n/**\n * Resolve a group's scheduling config against the defaults, validating every\n * bound at config-write time (throws on a bad config, so a\n * misconfiguration surfaces immediately rather than yielding empty slots).\n * `windowDays` is capped at 62 because Google FreeBusy is.\n */\nexport function resolveScheduling(config?: ChapterScheduling): ResolvedScheduling {\n const result = validateScheduling(config);\n if (result.ok) return result.value;\n const detail = Object.entries(result.errors)\n .map(([field, message]) => `${field}: ${message}`)\n .join(\" \");\n throw new Error(`scheduling: ${detail}`);\n}\n\n/** Validation messages keyed by form field. */\nexport type SchedulingErrors = Record<string, string>;\n\n/**\n * Validate a scheduling config field by field, collecting owner-readable messages\n * rather than throwing on the first problem. An admin settings form needs to say\n * *which* field is wrong and why — \"pick at least one day\" beats a stack trace —\n * so the admin route returns these directly. {@link resolveScheduling} is the\n * throwing wrapper for internal/config-time use.\n */\nexport function validateScheduling(\n config?: ChapterScheduling,\n): { ok: true; value: ResolvedScheduling } | { ok: false; errors: SchedulingErrors } {\n const d = config ?? {};\n const c: ResolvedScheduling = {\n slotMinutes: d.slotMinutes ?? SCHEDULING_DEFAULTS.slotMinutes,\n days: d.days ?? SCHEDULING_DEFAULTS.days,\n startHour: d.startHour ?? SCHEDULING_DEFAULTS.startHour,\n endHour: d.endHour ?? SCHEDULING_DEFAULTS.endHour,\n timezone: d.timezone ?? SCHEDULING_DEFAULTS.timezone,\n minNoticeHours: d.minNoticeHours ?? SCHEDULING_DEFAULTS.minNoticeHours,\n windowDays: d.windowDays ?? SCHEDULING_DEFAULTS.windowDays,\n summaryTemplate: d.summaryTemplate ?? SCHEDULING_DEFAULTS.summaryTemplate,\n };\n const errors: SchedulingErrors = {};\n if (!(c.slotMinutes >= 15 && c.slotMinutes <= 240)) {\n errors.slotMinutes = \"Slot length must be between 15 and 240 minutes.\";\n }\n if (!(c.windowDays >= 1 && c.windowDays <= 62)) {\n errors.windowDays = \"Booking window must be between 1 and 62 days (the calendar caps look-ahead at 62).\";\n }\n if (!(c.minNoticeHours >= 0 && c.minNoticeHours <= 336)) {\n errors.minNoticeHours = \"Minimum notice must be between 0 and 336 hours.\";\n }\n if (!(c.startHour >= 0 && c.startHour < c.endHour && c.endHour <= 24)) {\n errors.hours = \"Hours must satisfy 0 ≤ start < end ≤ 24.\";\n }\n const days = [...c.days];\n if (!days.length) errors.days = \"Pick at least one day.\";\n else if (!days.every((n) => Number.isInteger(n) && n >= 0 && n <= 6)) {\n errors.days = \"Days must be weekday numbers, 0 (Sunday) through 6 (Saturday).\";\n }\n if (typeof c.timezone !== \"string\" || !isValidTimeZone(c.timezone)) {\n errors.timezone = `\"${String(c.timezone)}\" is not a valid IANA timezone (for example \"America/Los_Angeles\").`;\n }\n if (typeof c.summaryTemplate !== \"string\") errors.summaryTemplate = \"Calendar summary template must be text.\";\n return Object.keys(errors).length > 0 ? { ok: false, errors } : { ok: true, value: { ...c, days } };\n}\n\n/** Statuses a member may book/reschedule from (early pipeline only). */\n// Booking eligibility is answered by the configured pipeline — canBook(status,\n// pipeline) in ./pipeline — not a hardcoded status list, so a chapter that\n// customizes its stages gets one consistent answer.\n\n/** The availability window: `[now, now + windowDays]` in epoch ms. */\nexport function slotWindow(now: number, windowDays: number): { from: number; to: number } {\n return { from: now, to: now + windowDays * 86_400_000 };\n}\n\n/** The slot's end instant, always derived from its start (never client-supplied). */\nexport function endForSlot(startAt: number, slotMinutes: number): number {\n return startAt + slotMinutes * 60_000;\n}\n\n/** Double-book pre-check: the requested start must land exactly on a currently\n * bookable slot boundary. */\nexport function isSlotAvailable(slots: readonly { startAt: number }[], startAt: number): boolean {\n return slots.some((s) => s.startAt === startAt);\n}\n\n/** Render a meeting summary from its template (`{{firstName}}`/`{{lastName}}`). */\nexport function renderSummary(template: string, app: { firstName?: string | null; lastName?: string | null }): string {\n return template.replace(\"{{firstName}}\", app.firstName ?? \"\").replace(\"{{lastName}}\", app.lastName ?? \"\");\n}\n\n/** The prior scheduled meeting for an application, as far as booking cares. */\nexport interface ExistingMeeting {\n id: string;\n googleEventId?: string | null;\n meetUrl?: string | null;\n htmlLink?: string | null;\n}\n\n/** Decide reschedule-vs-create: reschedule iff there's an existing event to move,\n * so the Meet link + invite thread survive and no second event is minted. */\nexport function bookingDecision(existing: ExistingMeeting | null | undefined): { reschedule: boolean; eventId: string | null } {\n const eventId = existing?.googleEventId ?? null;\n return { reschedule: Boolean(eventId), eventId };\n}\n\n/** The create idempotency key — one intro event per application, forever, so a\n * retried create returns the same booking rather than a duplicate. */\nexport function introIdempotencyKey(applicationId: string): string {\n return `application:${applicationId}:intro`;\n}\n\n/** The canonical `meetings` row for a first booking. A `type` (not `interface`)\n * so it stays assignable to a db op's `attrs` (Record<string, unknown>). */\nexport type NewMeetingRow = {\n id: string;\n applicationId: string;\n groupId: string;\n startAt: number;\n endAt: number;\n timezone: string;\n status: \"scheduled\";\n googleEventId: string;\n meetUrl?: string;\n htmlLink?: string;\n drift: \"none\";\n createdAt: number;\n};\n\n/** Build the new `meetings` row after the calendar created the event. Optional\n * scalars are omitted (never null), per the odla-db porting rule. */\nexport function meetingCreateRow(i: {\n meetingId: string;\n applicationId: string;\n groupId: string;\n startAt: number;\n endAt: number;\n timezone: string;\n googleEventId: string;\n meetUrl?: string | null;\n htmlLink?: string | null;\n createdAt: number;\n}): NewMeetingRow {\n return {\n id: i.meetingId,\n applicationId: i.applicationId,\n groupId: i.groupId,\n startAt: i.startAt,\n endAt: i.endAt,\n timezone: i.timezone,\n status: \"scheduled\",\n googleEventId: i.googleEventId,\n ...(i.meetUrl ? { meetUrl: i.meetUrl } : {}),\n ...(i.htmlLink ? { htmlLink: i.htmlLink } : {}),\n drift: \"none\",\n createdAt: i.createdAt,\n };\n}\n\n/** The `meetings`-row patch for a reschedule (same row id, moved in place). */\nexport type MeetingReschedulePatch = {\n startAt: number;\n endAt: number;\n drift: \"none\";\n};\n\n/** Patch to move an existing meeting to a new window. */\nexport function meetingRescheduleUpdate(startAt: number, endAt: number): MeetingReschedulePatch {\n return { startAt, endAt, drift: \"none\" };\n}\n\n/** The `applications`-row patch after a booking. */\nexport type ApplicationBookingPatch = {\n meetingAt: number;\n meetingLink?: string;\n status?: \"call_scheduled\";\n};\n\n/** Project the booking onto the application row: cache the time, adopt the\n * calendar link if any, and advance the status to `call_scheduled` unless it is\n * already there (never backward). */\nexport function applicationBookingUpdate(\n currentStatus: string,\n startAt: number,\n htmlLink?: string | null,\n): ApplicationBookingPatch {\n return {\n meetingAt: startAt,\n ...(htmlLink ? { meetingLink: htmlLink } : {}),\n ...(currentStatus !== \"call_scheduled\" ? { status: \"call_scheduled\" as const } : {}),\n };\n}\n"],"mappings":"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","names":["superAdmins","id","paymentsReady","id","import_crm","emailLog","id","str","import_crm","import_crm","id","id","meetings"]}
|