@odla-ai/chapter 0.4.0 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +24 -0
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +9 -1
- package/dist/index.d.ts +9 -1
- package/dist/index.js +24 -0
- package/dist/index.js.map +1 -1
- package/dist/ui/index.d.ts +49 -2
- package/dist/ui/index.js +185 -91
- package/dist/ui/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -25,6 +25,7 @@ __export(index_exports, {
|
|
|
25
25
|
applicationBookingUpdate: () => applicationBookingUpdate,
|
|
26
26
|
applicationSummary: () => applicationSummary,
|
|
27
27
|
bookingDecision: () => bookingDecision,
|
|
28
|
+
brandTokens: () => brandTokens,
|
|
28
29
|
buildGroupSeed: () => buildGroupSeed,
|
|
29
30
|
canApprove: () => canApprove,
|
|
30
31
|
canBook: () => canBook,
|
|
@@ -798,6 +799,29 @@ function memberSession(user, opts) {
|
|
|
798
799
|
};
|
|
799
800
|
}
|
|
800
801
|
|
|
802
|
+
// src/brand.ts
|
|
803
|
+
function paletteVar(key) {
|
|
804
|
+
return key.startsWith("--") ? key : `--${key}`;
|
|
805
|
+
}
|
|
806
|
+
function cleanValue(value) {
|
|
807
|
+
return value.replace(/[<>{};]/g, "").trim();
|
|
808
|
+
}
|
|
809
|
+
function brandTokens(brand) {
|
|
810
|
+
if (!brand) return "";
|
|
811
|
+
const decls = [];
|
|
812
|
+
for (const [key, value] of Object.entries(brand.palette ?? {})) {
|
|
813
|
+
if (typeof value === "string" && value.trim()) decls.push(`${paletteVar(key)}: ${cleanValue(value)};`);
|
|
814
|
+
}
|
|
815
|
+
const fonts = brand.fonts;
|
|
816
|
+
if (fonts?.display) decls.push(`--ui-font-display: ${cleanValue(fonts.display)};`);
|
|
817
|
+
if (fonts?.body) decls.push(`--ui-font-sans: ${cleanValue(fonts.body)};`);
|
|
818
|
+
if (fonts?.numeral) decls.push(`--ui-font-numeral: ${cleanValue(fonts.numeral)};`);
|
|
819
|
+
return decls.length ? `:root {
|
|
820
|
+
${decls.join("\n ")}
|
|
821
|
+
}
|
|
822
|
+
` : "";
|
|
823
|
+
}
|
|
824
|
+
|
|
801
825
|
// src/scheduling.ts
|
|
802
826
|
var SCHEDULING_DEFAULTS = {
|
|
803
827
|
slotMinutes: 45,
|
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/descriptor.ts","../src/email.ts","../src/payments.ts","../src/network.ts","../src/session.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 } 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 { 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 } from \"./member\";\nexport { sharedPersonInput, projectSharedRecord } from \"./network\";\nexport { applicationSummary, memberApplication, memberSession } from \"./session\";\nexport {\n resolveScheduling,\n SCHEDULING_DEFAULTS,\n BOOKABLE_STATUSES,\n canBookFrom,\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, ProjectionDeps } from \"./network\";\nexport type {\n ApplicationRecord,\n MeetingRecord,\n ApplicationSummary,\n MemberApplication,\n MemberSession,\n SessionUser,\n} from \"./session\";\nexport type {\n ResolvedScheduling,\n ExistingMeeting,\n NewMeetingRow,\n MeetingReschedulePatch,\n ApplicationBookingPatch,\n} from \"./scheduling\";\nexport type {\n Chapter,\n ChapterConfig,\n ChapterMode,\n ChapterAuth,\n ResolvedAuth,\n ChapterPipeline,\n ResolvedPipeline,\n ChapterApplication,\n ResolvedApplication,\n DbOp,\n ChapterDb,\n ChapterBrand,\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\";\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 { Chapter, ChapterConfig, ChapterMode } 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\";\n\nconst SLUG = /^[a-z0-9][a-z0-9-]{1,62}$/;\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 }\n\n const auth = resolveAuth(mode, config.auth);\n const pipeline = resolvePipeline(config.pipeline);\n const application = resolveApplication(config.application);\n const { schema, rules } = chapterDb(mode, auth);\n const services = config.services ?? [\"db\", \"calendar\", \"o11y\"];\n\n const chapter: Chapter = {\n config,\n id,\n name,\n mode,\n crm,\n auth,\n pipeline,\n application,\n schema,\n rules,\n services,\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 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 exactly Silver & Salt's 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. `chapter` is a person lead pipeline\n// (fed from `applications`); `hub` adds businesses (people + companies).\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 per-mode default CRM config: `chapter` = a person lead pipeline;\n * `hub` = people + businesses with a works_at relation. */\nexport function defaultCrm(mode: ChapterMode): CrmConfig {\n if (mode === \"hub\") {\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 return {\n types: { person: personType() },\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 (Silver & Salt); `hub` → the `admins` allowlist table, no\n * super tier (Built Not Found). 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 Silver & Salt's 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 full Silver\n * & Salt 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// Silver & Salt's 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 return {\n required,\n optional,\n maxLen: a?.maxLen ?? {},\n defaultMaxLen: a?.defaultMaxLen ?? 2000,\n bodyCap: a?.bodyCap ?? 32768,\n };\n}\n\n/** A validated submission, or a 400-worthy validation error the route returns. */\nexport type SubmitResult =\n | { ok: true; id: string; duplicate: boolean; status: string }\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\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 = fields.focus;\n if (opts.groupId) row.groupId = opts.groupId;\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 };\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","// 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}\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 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 };\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","// 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 (S&S\n * lacked this; the package enforces it). */\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 hub → chapter people projection (push model). The network hub curates\n// prospects and pushes a person's contact data into THIS chapter's own\n// crm_record, so a chapter admin sees network prospects beside their applicants.\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: keyed by the hub's record id (a re-share updates, never\n// duplicates) AND unified by primaryEmail — a shared prospect who later\n// submits an application lands on the SAME crm_record, so the two projections\n// compose instead of forking the person.\n// - A person may be shared with many chapters; that fan-out is hub-side, so each\n// chapter's projection here is independent.\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 } from \"@odla-ai/crm\";\nimport type { ChapterDb } 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/** 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\n/**\n * Upsert a hub-shared prospect into this chapter's `crm_record` (push\n * projection). Resolves an existing person by lowercased `primaryEmail` and\n * updates it, else creates one with a `share:${hubRecordId}` mutationId. Returns\n * the chapter-side record id. Callers wrap this in `.catch` so a projection\n * failure never fails the hub's share request.\n */\nexport async function projectSharedRecord(deps: ProjectionDeps, person: SharedPerson): Promise<{ recordId: string }> {\n const email = person.email.toLowerCase();\n const input = sharedPersonInput(person);\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({\n crm_record: { $: { where: { type: \"person\", primaryEmail: email }, limit: 1 } },\n });\n const existing = crm_record?.[0];\n if (existing && typeof existing.id === \"string\") {\n await updateRecord(crmDeps, { id: existing.id, input });\n return { recordId: existing.id };\n }\n const created = await createRecord(crmDeps, { type: \"person\", input, mutationId: `share:${person.hubRecordId}` });\n return { recordId: created.id };\n}\n","// The member session — what GET /api/me returns to a signed-in applicant/member.\n// Ported from Silver & Salt's applicationSummary + /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","// Scheduling core — config resolution + the booking invariants, ported from the\n// proven Silver & Salt 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 an early\n// stage — see {@link canBookFrom} + {@link 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/** The S&S-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 the way S&S does 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 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 fail = (msg: string): never => {\n throw new Error(`scheduling: ${msg}`);\n };\n if (!(c.slotMinutes >= 15 && c.slotMinutes <= 240)) fail(\"slotMinutes must be 15–240\");\n if (!(c.windowDays >= 1 && c.windowDays <= 62)) fail(\"windowDays must be 1–62 (FreeBusy caps at 62)\");\n if (!(c.minNoticeHours >= 0 && c.minNoticeHours <= 336)) fail(\"minNoticeHours must be 0–336\");\n if (!(c.startHour >= 0 && c.startHour < c.endHour && c.endHour <= 24)) fail(\"require 0 ≤ startHour < endHour ≤ 24\");\n const days = [...c.days];\n if (!days.length || !days.every((n) => Number.isInteger(n) && n >= 0 && n <= 6)) {\n fail(\"days must be a non-empty list of weekday integers 0–6\");\n }\n if (typeof c.timezone !== \"string\" || !isValidTimeZone(c.timezone)) fail(`invalid IANA timezone \"${c.timezone}\"`);\n if (typeof c.summaryTemplate !== \"string\") fail(\"summaryTemplate must be a string\");\n return { ...c, days };\n}\n\n/** Statuses a member may book/reschedule from (early pipeline only). */\nexport const BOOKABLE_STATUSES: readonly string[] = [\"submitted\", \"paid_pending_vetting\", \"call_scheduled\"];\n\n/** Whether an application at `status` may book a call. */\nexport function canBookFrom(status: string): boolean {\n return BOOKABLE_STATUSES.includes(status);\n}\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","import_crm"]}
|
|
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/descriptor.ts","../src/email.ts","../src/payments.ts","../src/network.ts","../src/session.ts","../src/brand.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 } 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 { 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 } from \"./member\";\nexport { sharedPersonInput, projectSharedRecord } from \"./network\";\nexport { applicationSummary, memberApplication, memberSession } from \"./session\";\nexport { brandTokens } from \"./brand\";\nexport {\n resolveScheduling,\n SCHEDULING_DEFAULTS,\n BOOKABLE_STATUSES,\n canBookFrom,\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, ProjectionDeps } from \"./network\";\nexport type {\n ApplicationRecord,\n MeetingRecord,\n ApplicationSummary,\n MemberApplication,\n MemberSession,\n SessionUser,\n} from \"./session\";\nexport type {\n ResolvedScheduling,\n ExistingMeeting,\n NewMeetingRow,\n MeetingReschedulePatch,\n ApplicationBookingPatch,\n} from \"./scheduling\";\nexport type {\n Chapter,\n ChapterConfig,\n ChapterMode,\n ChapterAuth,\n ResolvedAuth,\n ChapterPipeline,\n ResolvedPipeline,\n ChapterApplication,\n ResolvedApplication,\n DbOp,\n ChapterDb,\n ChapterBrand,\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\";\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 { Chapter, ChapterConfig, ChapterMode } 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\";\n\nconst SLUG = /^[a-z0-9][a-z0-9-]{1,62}$/;\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 }\n\n const auth = resolveAuth(mode, config.auth);\n const pipeline = resolvePipeline(config.pipeline);\n const application = resolveApplication(config.application);\n const { schema, rules } = chapterDb(mode, auth);\n const services = config.services ?? [\"db\", \"calendar\", \"o11y\"];\n\n const chapter: Chapter = {\n config,\n id,\n name,\n mode,\n crm,\n auth,\n pipeline,\n application,\n schema,\n rules,\n services,\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 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 exactly Silver & Salt's 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. `chapter` is a person lead pipeline\n// (fed from `applications`); `hub` adds businesses (people + companies).\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 per-mode default CRM config: `chapter` = a person lead pipeline;\n * `hub` = people + businesses with a works_at relation. */\nexport function defaultCrm(mode: ChapterMode): CrmConfig {\n if (mode === \"hub\") {\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 return {\n types: { person: personType() },\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 (Silver & Salt); `hub` → the `admins` allowlist table, no\n * super tier (Built Not Found). 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 Silver & Salt's 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 full Silver\n * & Salt 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// Silver & Salt's 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 return {\n required,\n optional,\n maxLen: a?.maxLen ?? {},\n defaultMaxLen: a?.defaultMaxLen ?? 2000,\n bodyCap: a?.bodyCap ?? 32768,\n };\n}\n\n/** A validated submission, or a 400-worthy validation error the route returns. */\nexport type SubmitResult =\n | { ok: true; id: string; duplicate: boolean; status: string }\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\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 = fields.focus;\n if (opts.groupId) row.groupId = opts.groupId;\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 };\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","// 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}\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 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 };\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","// 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 (S&S\n * lacked this; the package enforces it). */\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 hub → chapter people projection (push model). The network hub curates\n// prospects and pushes a person's contact data into THIS chapter's own\n// crm_record, so a chapter admin sees network prospects beside their applicants.\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: keyed by the hub's record id (a re-share updates, never\n// duplicates) AND unified by primaryEmail — a shared prospect who later\n// submits an application lands on the SAME crm_record, so the two projections\n// compose instead of forking the person.\n// - A person may be shared with many chapters; that fan-out is hub-side, so each\n// chapter's projection here is independent.\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 } from \"@odla-ai/crm\";\nimport type { ChapterDb } 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/** 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\n/**\n * Upsert a hub-shared prospect into this chapter's `crm_record` (push\n * projection). Resolves an existing person by lowercased `primaryEmail` and\n * updates it, else creates one with a `share:${hubRecordId}` mutationId. Returns\n * the chapter-side record id. Callers wrap this in `.catch` so a projection\n * failure never fails the hub's share request.\n */\nexport async function projectSharedRecord(deps: ProjectionDeps, person: SharedPerson): Promise<{ recordId: string }> {\n const email = person.email.toLowerCase();\n const input = sharedPersonInput(person);\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({\n crm_record: { $: { where: { type: \"person\", primaryEmail: email }, limit: 1 } },\n });\n const existing = crm_record?.[0];\n if (existing && typeof existing.id === \"string\") {\n await updateRecord(crmDeps, { id: existing.id, input });\n return { recordId: existing.id };\n }\n const created = await createRecord(crmDeps, { type: \"person\", input, mutationId: `share:${person.hubRecordId}` });\n return { recordId: created.id };\n}\n","// The member session — what GET /api/me returns to a signed-in applicant/member.\n// Ported from Silver & Salt's applicationSummary + /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 } 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\n/**\n * Build the `:root { … }` CSS that maps a chapter's brand onto the design tokens\n * the UI reads: each `palette` entry becomes a custom property, and `fonts`\n * (display/body/numeral) map to `--ui-font-display` / `--ui-font-sans` /\n * `--ui-font-numeral`. Returns \"\" when there is nothing to theme.\n */\nexport function brandTokens(brand: ChapterBrand | undefined): string {\n if (!brand) return \"\";\n const decls: string[] = [];\n for (const [key, value] of Object.entries(brand.palette ?? {})) {\n if (typeof value === \"string\" && value.trim()) decls.push(`${paletteVar(key)}: ${cleanValue(value)};`);\n }\n const fonts = brand.fonts;\n if (fonts?.display) decls.push(`--ui-font-display: ${cleanValue(fonts.display)};`);\n if (fonts?.body) decls.push(`--ui-font-sans: ${cleanValue(fonts.body)};`);\n if (fonts?.numeral) decls.push(`--ui-font-numeral: ${cleanValue(fonts.numeral)};`);\n return decls.length ? `:root {\\n ${decls.join(\"\\n \")}\\n}\\n` : \"\";\n}\n","// Scheduling core — config resolution + the booking invariants, ported from the\n// proven Silver & Salt 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 an early\n// stage — see {@link canBookFrom} + {@link 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/** The S&S-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 the way S&S does 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 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 fail = (msg: string): never => {\n throw new Error(`scheduling: ${msg}`);\n };\n if (!(c.slotMinutes >= 15 && c.slotMinutes <= 240)) fail(\"slotMinutes must be 15–240\");\n if (!(c.windowDays >= 1 && c.windowDays <= 62)) fail(\"windowDays must be 1–62 (FreeBusy caps at 62)\");\n if (!(c.minNoticeHours >= 0 && c.minNoticeHours <= 336)) fail(\"minNoticeHours must be 0–336\");\n if (!(c.startHour >= 0 && c.startHour < c.endHour && c.endHour <= 24)) fail(\"require 0 ≤ startHour < endHour ≤ 24\");\n const days = [...c.days];\n if (!days.length || !days.every((n) => Number.isInteger(n) && n >= 0 && n <= 6)) {\n fail(\"days must be a non-empty list of weekday integers 0–6\");\n }\n if (typeof c.timezone !== \"string\" || !isValidTimeZone(c.timezone)) fail(`invalid IANA timezone \"${c.timezone}\"`);\n if (typeof c.summaryTemplate !== \"string\") fail(\"summaryTemplate must be a string\");\n return { ...c, days };\n}\n\n/** Statuses a member may book/reschedule from (early pipeline only). */\nexport const BOOKABLE_STATUSES: readonly string[] = [\"submitted\", \"paid_pending_vetting\", \"call_scheduled\"];\n\n/** Whether an application at `status` may book a call. */\nexport function canBookFrom(status: string): boolean {\n return BOOKABLE_STATUSES.includes(status);\n}\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","import_crm"]}
|
package/dist/index.d.cts
CHANGED
|
@@ -698,6 +698,14 @@ declare function memberSession(user: SessionUser, opts: {
|
|
|
698
698
|
superAdmin: boolean;
|
|
699
699
|
}): MemberSession;
|
|
700
700
|
|
|
701
|
+
/**
|
|
702
|
+
* Build the `:root { … }` CSS that maps a chapter's brand onto the design tokens
|
|
703
|
+
* the UI reads: each `palette` entry becomes a custom property, and `fonts`
|
|
704
|
+
* (display/body/numeral) map to `--ui-font-display` / `--ui-font-sans` /
|
|
705
|
+
* `--ui-font-numeral`. Returns "" when there is nothing to theme.
|
|
706
|
+
*/
|
|
707
|
+
declare function brandTokens(brand: ChapterBrand | undefined): string;
|
|
708
|
+
|
|
701
709
|
/** A fully-resolved scheduling config (every field present). */
|
|
702
710
|
interface ResolvedScheduling {
|
|
703
711
|
slotMinutes: number;
|
|
@@ -806,4 +814,4 @@ type ApplicationBookingPatch = {
|
|
|
806
814
|
* already there (never backward). */
|
|
807
815
|
declare function applicationBookingUpdate(currentStatus: string, startAt: number, htmlLink?: string | null): ApplicationBookingPatch;
|
|
808
816
|
|
|
809
|
-
export { type ApplicationBookingPatch, type ApplicationRecord, type ApplicationSummary, type Attr, type AttrType, BOOKABLE_STATUSES, type Chapter, type ChapterApplication, type ChapterAuth, type ChapterBrand, type ChapterConfig, type ChapterDb, type ChapterEmails, type ChapterIntegrationDescriptor, type ChapterIntegrationOptions, type ChapterMode, type ChapterPipeline, type ChapterPolicy, type ChapterPrices, type ChapterScheduling, type DbOp, type DbRules, type DbSchema, type DeliveryDecision, type EmailGroup, type EmailTemplate, type EmailTemplateRow, type Entity, type ExistingMeeting, type GuardResult, type JoinConfigGroup, type MeetingRecord, type MeetingReschedulePatch, type MemberApplication, type MemberSession, type NewMeetingRow, type PaymentsGroup, type ProjectionDeps, type ResolvedApplication, type ResolvedAuth, type ResolvedPipeline, type ResolvedScheduling, type RoleChangeContext, type Rule, SCHEDULING_DEFAULTS, type SecretStore, type SessionUser, type SharedPerson, type StripeEvent, type SubmitResult, type WebhookEvent, applicationBookingUpdate, applicationSummary, bookingDecision, buildGroupSeed, canApprove, canBook, canBookFrom, canChangeRole, canTransition, canceledPatch, chapterDb, createChapterIntegration, defaultCrm, defineChapter, endForSlot, findApplicationRef, firstPaymentPatch, getVaultSecret, introIdempotencyKey, isAdminRole, isAlreadySent, isSlotAvailable, joinConfig, meetingCreateRow, meetingRescheduleUpdate, memberApplication, memberSession, normalizeWebhookEvent, paymentsReady, planDelivery, projectSharedRecord, refundedPatch, render, renderSummary, renderTemplateBody, renewalPatch, resolveApplication, resolveAuth, resolvePipeline, resolveScheduling, roleFromClaim, sharedPersonInput, slotWindow, stageIndex, stripeForm, submitApplication, subscriptionIdempotencyKey, verifyStripeSignature, webhookMutationId };
|
|
817
|
+
export { type ApplicationBookingPatch, type ApplicationRecord, type ApplicationSummary, type Attr, type AttrType, BOOKABLE_STATUSES, type Chapter, type ChapterApplication, type ChapterAuth, type ChapterBrand, type ChapterConfig, type ChapterDb, type ChapterEmails, type ChapterIntegrationDescriptor, type ChapterIntegrationOptions, type ChapterMode, type ChapterPipeline, type ChapterPolicy, type ChapterPrices, type ChapterScheduling, type DbOp, type DbRules, type DbSchema, type DeliveryDecision, type EmailGroup, type EmailTemplate, type EmailTemplateRow, type Entity, type ExistingMeeting, type GuardResult, type JoinConfigGroup, type MeetingRecord, type MeetingReschedulePatch, type MemberApplication, type MemberSession, type NewMeetingRow, type PaymentsGroup, type ProjectionDeps, type ResolvedApplication, type ResolvedAuth, type ResolvedPipeline, type ResolvedScheduling, type RoleChangeContext, type Rule, SCHEDULING_DEFAULTS, type SecretStore, type SessionUser, type SharedPerson, type StripeEvent, type SubmitResult, type WebhookEvent, applicationBookingUpdate, applicationSummary, bookingDecision, brandTokens, buildGroupSeed, canApprove, canBook, canBookFrom, canChangeRole, canTransition, canceledPatch, chapterDb, createChapterIntegration, defaultCrm, defineChapter, endForSlot, findApplicationRef, firstPaymentPatch, getVaultSecret, introIdempotencyKey, isAdminRole, isAlreadySent, isSlotAvailable, joinConfig, meetingCreateRow, meetingRescheduleUpdate, memberApplication, memberSession, normalizeWebhookEvent, paymentsReady, planDelivery, projectSharedRecord, refundedPatch, render, renderSummary, renderTemplateBody, renewalPatch, resolveApplication, resolveAuth, resolvePipeline, resolveScheduling, roleFromClaim, sharedPersonInput, slotWindow, stageIndex, stripeForm, submitApplication, subscriptionIdempotencyKey, verifyStripeSignature, webhookMutationId };
|
package/dist/index.d.ts
CHANGED
|
@@ -698,6 +698,14 @@ declare function memberSession(user: SessionUser, opts: {
|
|
|
698
698
|
superAdmin: boolean;
|
|
699
699
|
}): MemberSession;
|
|
700
700
|
|
|
701
|
+
/**
|
|
702
|
+
* Build the `:root { … }` CSS that maps a chapter's brand onto the design tokens
|
|
703
|
+
* the UI reads: each `palette` entry becomes a custom property, and `fonts`
|
|
704
|
+
* (display/body/numeral) map to `--ui-font-display` / `--ui-font-sans` /
|
|
705
|
+
* `--ui-font-numeral`. Returns "" when there is nothing to theme.
|
|
706
|
+
*/
|
|
707
|
+
declare function brandTokens(brand: ChapterBrand | undefined): string;
|
|
708
|
+
|
|
701
709
|
/** A fully-resolved scheduling config (every field present). */
|
|
702
710
|
interface ResolvedScheduling {
|
|
703
711
|
slotMinutes: number;
|
|
@@ -806,4 +814,4 @@ type ApplicationBookingPatch = {
|
|
|
806
814
|
* already there (never backward). */
|
|
807
815
|
declare function applicationBookingUpdate(currentStatus: string, startAt: number, htmlLink?: string | null): ApplicationBookingPatch;
|
|
808
816
|
|
|
809
|
-
export { type ApplicationBookingPatch, type ApplicationRecord, type ApplicationSummary, type Attr, type AttrType, BOOKABLE_STATUSES, type Chapter, type ChapterApplication, type ChapterAuth, type ChapterBrand, type ChapterConfig, type ChapterDb, type ChapterEmails, type ChapterIntegrationDescriptor, type ChapterIntegrationOptions, type ChapterMode, type ChapterPipeline, type ChapterPolicy, type ChapterPrices, type ChapterScheduling, type DbOp, type DbRules, type DbSchema, type DeliveryDecision, type EmailGroup, type EmailTemplate, type EmailTemplateRow, type Entity, type ExistingMeeting, type GuardResult, type JoinConfigGroup, type MeetingRecord, type MeetingReschedulePatch, type MemberApplication, type MemberSession, type NewMeetingRow, type PaymentsGroup, type ProjectionDeps, type ResolvedApplication, type ResolvedAuth, type ResolvedPipeline, type ResolvedScheduling, type RoleChangeContext, type Rule, SCHEDULING_DEFAULTS, type SecretStore, type SessionUser, type SharedPerson, type StripeEvent, type SubmitResult, type WebhookEvent, applicationBookingUpdate, applicationSummary, bookingDecision, buildGroupSeed, canApprove, canBook, canBookFrom, canChangeRole, canTransition, canceledPatch, chapterDb, createChapterIntegration, defaultCrm, defineChapter, endForSlot, findApplicationRef, firstPaymentPatch, getVaultSecret, introIdempotencyKey, isAdminRole, isAlreadySent, isSlotAvailable, joinConfig, meetingCreateRow, meetingRescheduleUpdate, memberApplication, memberSession, normalizeWebhookEvent, paymentsReady, planDelivery, projectSharedRecord, refundedPatch, render, renderSummary, renderTemplateBody, renewalPatch, resolveApplication, resolveAuth, resolvePipeline, resolveScheduling, roleFromClaim, sharedPersonInput, slotWindow, stageIndex, stripeForm, submitApplication, subscriptionIdempotencyKey, verifyStripeSignature, webhookMutationId };
|
|
817
|
+
export { type ApplicationBookingPatch, type ApplicationRecord, type ApplicationSummary, type Attr, type AttrType, BOOKABLE_STATUSES, type Chapter, type ChapterApplication, type ChapterAuth, type ChapterBrand, type ChapterConfig, type ChapterDb, type ChapterEmails, type ChapterIntegrationDescriptor, type ChapterIntegrationOptions, type ChapterMode, type ChapterPipeline, type ChapterPolicy, type ChapterPrices, type ChapterScheduling, type DbOp, type DbRules, type DbSchema, type DeliveryDecision, type EmailGroup, type EmailTemplate, type EmailTemplateRow, type Entity, type ExistingMeeting, type GuardResult, type JoinConfigGroup, type MeetingRecord, type MeetingReschedulePatch, type MemberApplication, type MemberSession, type NewMeetingRow, type PaymentsGroup, type ProjectionDeps, type ResolvedApplication, type ResolvedAuth, type ResolvedPipeline, type ResolvedScheduling, type RoleChangeContext, type Rule, SCHEDULING_DEFAULTS, type SecretStore, type SessionUser, type SharedPerson, type StripeEvent, type SubmitResult, type WebhookEvent, applicationBookingUpdate, applicationSummary, bookingDecision, brandTokens, buildGroupSeed, canApprove, canBook, canBookFrom, canChangeRole, canTransition, canceledPatch, chapterDb, createChapterIntegration, defaultCrm, defineChapter, endForSlot, findApplicationRef, firstPaymentPatch, getVaultSecret, introIdempotencyKey, isAdminRole, isAlreadySent, isSlotAvailable, joinConfig, meetingCreateRow, meetingRescheduleUpdate, memberApplication, memberSession, normalizeWebhookEvent, paymentsReady, planDelivery, projectSharedRecord, refundedPatch, render, renderSummary, renderTemplateBody, renewalPatch, resolveApplication, resolveAuth, resolvePipeline, resolveScheduling, roleFromClaim, sharedPersonInput, slotWindow, stageIndex, stripeForm, submitApplication, subscriptionIdempotencyKey, verifyStripeSignature, webhookMutationId };
|
package/dist/index.js
CHANGED
|
@@ -722,6 +722,29 @@ function memberSession(user, opts) {
|
|
|
722
722
|
};
|
|
723
723
|
}
|
|
724
724
|
|
|
725
|
+
// src/brand.ts
|
|
726
|
+
function paletteVar(key) {
|
|
727
|
+
return key.startsWith("--") ? key : `--${key}`;
|
|
728
|
+
}
|
|
729
|
+
function cleanValue(value) {
|
|
730
|
+
return value.replace(/[<>{};]/g, "").trim();
|
|
731
|
+
}
|
|
732
|
+
function brandTokens(brand) {
|
|
733
|
+
if (!brand) return "";
|
|
734
|
+
const decls = [];
|
|
735
|
+
for (const [key, value] of Object.entries(brand.palette ?? {})) {
|
|
736
|
+
if (typeof value === "string" && value.trim()) decls.push(`${paletteVar(key)}: ${cleanValue(value)};`);
|
|
737
|
+
}
|
|
738
|
+
const fonts = brand.fonts;
|
|
739
|
+
if (fonts?.display) decls.push(`--ui-font-display: ${cleanValue(fonts.display)};`);
|
|
740
|
+
if (fonts?.body) decls.push(`--ui-font-sans: ${cleanValue(fonts.body)};`);
|
|
741
|
+
if (fonts?.numeral) decls.push(`--ui-font-numeral: ${cleanValue(fonts.numeral)};`);
|
|
742
|
+
return decls.length ? `:root {
|
|
743
|
+
${decls.join("\n ")}
|
|
744
|
+
}
|
|
745
|
+
` : "";
|
|
746
|
+
}
|
|
747
|
+
|
|
725
748
|
// src/scheduling.ts
|
|
726
749
|
var SCHEDULING_DEFAULTS = {
|
|
727
750
|
slotMinutes: 45,
|
|
@@ -823,6 +846,7 @@ export {
|
|
|
823
846
|
applicationBookingUpdate,
|
|
824
847
|
applicationSummary,
|
|
825
848
|
bookingDecision,
|
|
849
|
+
brandTokens,
|
|
826
850
|
buildGroupSeed,
|
|
827
851
|
canApprove,
|
|
828
852
|
canBook,
|