@aglyn/aglyn 1.0.0-beta.230 → 1.0.0-beta.231

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.
Files changed (112) hide show
  1. package/package.json +11 -11
  2. package/src/lib/app-utils/crm.d.ts +14 -1
  3. package/src/lib/app-utils/crm.js +19 -2
  4. package/src/lib/app-utils/crm.js.map +1 -1
  5. package/src/lib/app-utils/docs-help.generated.d.ts +80 -2
  6. package/src/lib/app-utils/docs-help.generated.js +209 -1
  7. package/src/lib/app-utils/docs-help.generated.js.map +1 -1
  8. package/src/lib/app-utils/docs-index.generated.js +517 -20
  9. package/src/lib/app-utils/docs-index.generated.js.map +1 -1
  10. package/src/lib/app-utils/host-status.d.ts +85 -0
  11. package/src/lib/app-utils/host-status.js +115 -0
  12. package/src/lib/app-utils/host-status.js.map +1 -0
  13. package/src/lib/app-utils/lockdown.js +1 -1
  14. package/src/lib/app-utils/lockdown.js.map +1 -1
  15. package/src/lib/app-utils/media-filter.d.ts +136 -0
  16. package/src/lib/app-utils/media-filter.js +400 -0
  17. package/src/lib/app-utils/media-filter.js.map +1 -0
  18. package/src/lib/app-utils/mobile-push.d.ts +98 -0
  19. package/src/lib/app-utils/mobile-push.js +97 -0
  20. package/src/lib/app-utils/mobile-push.js.map +1 -0
  21. package/src/lib/app-utils/notification-push.d.ts +38 -0
  22. package/src/lib/app-utils/notification-push.js +54 -0
  23. package/src/lib/app-utils/notification-push.js.map +1 -0
  24. package/src/lib/app-utils/notifications.d.ts +7 -0
  25. package/src/lib/app-utils/notifications.js.map +1 -1
  26. package/src/lib/app-utils/organizations.js +5 -2
  27. package/src/lib/app-utils/organizations.js.map +1 -1
  28. package/src/lib/app-utils/plugin-host-events.generated.d.ts +1 -1
  29. package/src/lib/app-utils/plugin-host-events.generated.js +149 -0
  30. package/src/lib/app-utils/plugin-host-events.generated.js.map +1 -1
  31. package/src/lib/app-utils/plugin-release-flags.generated.d.ts +1 -1
  32. package/src/lib/app-utils/plugin-release-flags.generated.js +7 -0
  33. package/src/lib/app-utils/plugin-release-flags.generated.js.map +1 -1
  34. package/src/lib/app-utils/realm-host-surface.generated.js +3 -0
  35. package/src/lib/app-utils/realm-host-surface.generated.js.map +1 -1
  36. package/src/lib/app-utils/release-flags.js +6 -2
  37. package/src/lib/app-utils/release-flags.js.map +1 -1
  38. package/src/lib/app-utils/scope-tokens.d.ts +16 -1
  39. package/src/lib/app-utils/scope-tokens.js +15 -1
  40. package/src/lib/app-utils/scope-tokens.js.map +1 -1
  41. package/src/lib/app-utils/site-list-query.d.ts +47 -0
  42. package/src/lib/app-utils/site-list-query.js +142 -0
  43. package/src/lib/app-utils/site-list-query.js.map +1 -0
  44. package/src/lib/app-utils/site-wide-outbox.d.ts +95 -0
  45. package/src/lib/app-utils/site-wide-outbox.js +117 -0
  46. package/src/lib/app-utils/site-wide-outbox.js.map +1 -0
  47. package/src/lib/app-utils/transfer-launcher-context.d.ts +6 -0
  48. package/src/lib/app-utils/transfer-launcher-context.js.map +1 -1
  49. package/src/lib/app-utils/upload-inspection.js +7 -0
  50. package/src/lib/app-utils/upload-inspection.js.map +1 -1
  51. package/src/lib/app-utils/webhook-delivery.js +4 -1
  52. package/src/lib/app-utils/webhook-delivery.js.map +1 -1
  53. package/src/lib/foundation/definitions/org-billing.types.d.ts +8 -0
  54. package/src/lib/foundation/definitions/org-billing.types.js.map +1 -1
  55. package/src/lib/foundation/definitions/organization.types.d.ts +18 -7
  56. package/src/lib/foundation/definitions/organization.types.js.map +1 -1
  57. package/src/lib/foundation/definitions/write-deny-coverage.util.d.ts +4 -1
  58. package/src/lib/foundation/definitions/write-deny-coverage.util.js +12 -2
  59. package/src/lib/foundation/definitions/write-deny-coverage.util.js.map +1 -1
  60. package/src/lib/plugin-manager/enabled-plugins.js +4 -2
  61. package/src/lib/plugin-manager/enabled-plugins.js.map +1 -1
  62. package/src/lib/plugin-manager/feature-plugins.d.ts +88 -0
  63. package/src/lib/plugin-manager/feature-plugins.js +44 -0
  64. package/src/lib/plugin-manager/feature-plugins.js.map +1 -1
  65. package/src/lib/plugin-manager/first-party-plugins.generated.js +222 -2
  66. package/src/lib/plugin-manager/first-party-plugins.generated.js.map +1 -1
  67. package/src/lib/plugin-manager/plugin-checkout-extras.d.ts +168 -0
  68. package/src/lib/plugin-manager/plugin-checkout-extras.js +172 -0
  69. package/src/lib/plugin-manager/plugin-checkout-extras.js.map +1 -0
  70. package/src/lib/plugin-manager/plugin-contributions.d.ts +7 -0
  71. package/src/lib/plugin-manager/plugin-contributions.js +1 -1
  72. package/src/lib/plugin-manager/plugin-contributions.js.map +1 -1
  73. package/src/lib/plugin-manager/plugin-domain-events.d.ts +138 -0
  74. package/src/lib/plugin-manager/plugin-domain-events.js +148 -0
  75. package/src/lib/plugin-manager/plugin-domain-events.js.map +1 -0
  76. package/src/lib/plugin-manager/plugin-events.d.ts +30 -0
  77. package/src/lib/plugin-manager/plugin-events.js +4 -0
  78. package/src/lib/plugin-manager/plugin-events.js.map +1 -1
  79. package/src/lib/plugin-manager/plugin-fulfillment-providers.d.ts +101 -0
  80. package/src/lib/plugin-manager/plugin-fulfillment-providers.js +83 -0
  81. package/src/lib/plugin-manager/plugin-fulfillment-providers.js.map +1 -0
  82. package/src/lib/plugin-manager/plugin-permissions.js +21 -5
  83. package/src/lib/plugin-manager/plugin-permissions.js.map +1 -1
  84. package/src/lib/plugin-manager/plugin-person-records.d.ts +90 -0
  85. package/src/lib/plugin-manager/plugin-person-records.js +26 -0
  86. package/src/lib/plugin-manager/plugin-person-records.js.map +1 -1
  87. package/src/lib/plugin-manager/plugin-product-catalog.d.ts +204 -0
  88. package/src/lib/plugin-manager/plugin-product-catalog.js +43 -0
  89. package/src/lib/plugin-manager/plugin-product-catalog.js.map +1 -0
  90. package/src/lib/plugin-manager/plugin-shipment-records.d.ts +210 -0
  91. package/src/lib/plugin-manager/plugin-shipment-records.js +63 -0
  92. package/src/lib/plugin-manager/plugin-shipment-records.js.map +1 -0
  93. package/src/lib/plugin-manager/plugin-shipping-rates.d.ts +151 -0
  94. package/src/lib/plugin-manager/plugin-shipping-rates.js +62 -0
  95. package/src/lib/plugin-manager/plugin-shipping-rates.js.map +1 -0
  96. package/src/lib/plugin-manager/plugin-sms-messaging.d.ts +103 -0
  97. package/src/lib/plugin-manager/plugin-sms-messaging.js +39 -0
  98. package/src/lib/plugin-manager/plugin-sms-messaging.js.map +1 -0
  99. package/src/lib/plugin-manager/plugin-stock-levels.d.ts +81 -0
  100. package/src/lib/plugin-manager/plugin-stock-levels.js +32 -0
  101. package/src/lib/plugin-manager/plugin-stock-levels.js.map +1 -0
  102. package/src/lib/plugin-manager/plugin-tax-profile.d.ts +154 -0
  103. package/src/lib/plugin-manager/plugin-tax-profile.js +56 -0
  104. package/src/lib/plugin-manager/plugin-tax-profile.js.map +1 -1
  105. package/src/lib/plugin-manager/plugin-theme-font-catalog.d.ts +59 -0
  106. package/src/lib/plugin-manager/plugin-theme-font-catalog.js +40 -0
  107. package/src/lib/plugin-manager/plugin-theme-font-catalog.js.map +1 -0
  108. package/src/lib/plugin-manager/plugin-tracking-pages.d.ts +55 -0
  109. package/src/lib/plugin-manager/plugin-tracking-pages.js +76 -0
  110. package/src/lib/plugin-manager/plugin-tracking-pages.js.map +1 -0
  111. package/src/lib/plugin-manager/realm-host-aglyn.generated.js +3 -0
  112. package/src/lib/plugin-manager/realm-host-aglyn.generated.js.map +1 -1
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../../libs/aglyn/src/lib/app-utils/crm.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n/**\n * Contacts CRM v2 (AGL-2595): the records that sit BESIDE a contact.\n *\n * v1 is one collection — `contacts` — and everything a holder knows about a\n * person lives in that holder's facet on the shared row. That shape is right\n * for the person and wrong for everything a sales team keeps AROUND the\n * person: a company is known by several contacts, a deal moves through\n * stages on its own clock, a task is due whether or not anybody opens the\n * contact it hangs off, and an activity log grows past anything a facet\n * should carry. So each of those is a collection of its own under\n * `orgs/{orgId}/`, pointing back at the contact by id.\n *\n * ## One scope model, not a second one\n *\n * Every collection here carries `visibleTo`, the same array `contacts`\n * carries, and is stamped the same way — {@link crmScopeTokens} is the\n * contact create path's expression, exported so that no creator computes its\n * own. The rules gate all six on the contacts predicate (`canReadScopedPeople`,\n * `data.manage`), because a deal or a call log is a fact about a person and\n * discloses exactly what the contact row would.\n *\n * What these collections do NOT have is the facet map. A contact is shared\n * across holders because one human is one row; a company, deal or task is\n * one holder's record from the moment it is created, so `visibleTo` alone\n * says who may see it and there is nothing to split per holder.\n *\n * Pure data module: types, constants and the small helpers every surface\n * would otherwise write for itself. No Firestore, no React.\n */\n\nimport {\n type AglynPostalAddress,\n normalizeAddress,\n normalizePhone,\n type OrgCrmAssignmentRule,\n} from '../foundation'\nimport { type ConsentGroup, consentGroupScope } from './consent-groups'\nimport {\n CONTACT_LIFECYCLE_STAGES,\n type ContactLifecycleStage,\n type CrmActivityKind,\n CRM_TASK_KINDS,\n type CrmTaskKind,\n isContactLifecycleStage,\n} from './crm-kinds'\nimport {\n CONTACT_ALTERNATE_EMAILS_FIELD,\n CONTACT_FACETS_FIELD,\n CONTACT_SOURCE_LABELS,\n type ContactInteraction,\n type ContactSegment,\n type ContactSource,\n composeContactName,\n normalizeContactEmail,\n readContactFacet,\n} from './contacts'\nimport { type EmailState, isEmailStateStatus } from './email-state'\nimport {\n NAME_TOKEN_MAX_PREFIX,\n nameSearchKey,\n nameSearchTokens,\n SCOPED_SEARCH_JOIN,\n scopedSearchTokens,\n} from './name-search'\nimport {\n effectivePicklistValueSet,\n isStandardPicklistValueId,\n judgePicklistValue,\n mintPicklistValueId,\n normalizePicklistLabel,\n normalizePicklistValueSet,\n PICKLIST_VALUES_MAX,\n picklistActiveValues,\n picklistDefaultLabel,\n picklistFromLabels,\n type PicklistJudgement,\n picklistOptions,\n type PicklistOption,\n picklistRank,\n picklistRefusalSentence,\n type PicklistSpec,\n type PicklistValue,\n picklistValueByLabel,\n type PicklistValueSet,\n} from './picklists'\nimport { MAX_SCOPE_HOSTS, ORG_SCOPE_TOKEN, type ScopeToken } from './scope-tokens'\n\n// The fixed vocabularies and their guards live in a leaf module; every name\n// stays importable from here.\nexport * from './crm-kinds'\n\n/**\n * The CRM's collections, every one under `orgs/{orgId}/`.\n *\n * Named here rather than spelled at each call site because the rules, the\n * indexes and the console must agree on the string, and the three prefixed\n * ones are prefixed on purpose: `tasks`, `activities` and `views` are words\n * the org document will want for something else one day, and a collection\n * name is persisted in every document path that uses it.\n */\nexport const CRM_COLLECTIONS = {\n companies: 'companies',\n pipelines: 'pipelines',\n deals: 'deals',\n tasks: 'crmTasks',\n activities: 'crmActivities',\n contactFields: 'contactFields',\n views: 'crmViews',\n emailTemplates: 'crmEmailTemplates',\n /** An org's value sets for its standard picklist fields (AGL-3298). */\n picklists: 'crmPicklists',\n} as const\n\nexport type CrmCollection = (typeof CRM_COLLECTIONS)[keyof typeof CRM_COLLECTIONS]\n\n/**\n * The three collections the CRM RECORDS band counts (AGL-2611), in the order\n * the billing caption lists them. `contacts` is not in `CRM_COLLECTIONS`\n * because it predates the hub and is addressed through `orgDataCollection`,\n * so the band's own list has to name all three itself. Tasks, pipelines,\n * activities and field definitions are deliberately absent: a band that\n * counted what a rep does every hour would price the team's effort, not the\n * audience it holds.\n */\nexport const CRM_RECORD_COLLECTIONS = [\n 'contacts',\n CRM_COLLECTIONS.companies,\n CRM_COLLECTIONS.deals,\n] as const\n\n/**\n * What a create says when the records band refused it, on every surface —\n * the contacts list's alert, the company and deal drawers, the plugin's\n * create routes and the lead conversion (AGL-2596, widened in AGL-2611).\n *\n * One sentence in one place, because a reader who is refused in the drawer\n * and then reads the list must be told the same thing, and the remedy is the\n * same wherever the refusal lands: a band refuses only on a plan that puts\n * no overage rate past it, so \"upgrade\" is the whole of the answer.\n */\nexport const CRM_RECORDS_BAND_FULL_MESSAGE =\n 'CRM records limit reached — this record was not added. Upgrade in ' +\n 'Billing to keep collecting.'\n\n/**\n * The most logged activities ONE record may carry (AGL-2611) — a contact's,\n * a company's or a deal's own log, counted on the link the activity was\n * filed under.\n *\n * A platform ceiling and not a plan dimension, in the family of\n * `WEBHOOK_MAX_PER_HOST` and `NON_PAGE_SCREEN_MAX_PER_HOST`: activities are\n * not in the records band because they are bounded by human effort, and\n * this is the bound. Five thousand is a call a day for fourteen years on one\n * person, so nobody working a real relationship reaches it — what does is\n * an automation logging on every event, or an import replaying a history,\n * and either of those past this line is a document cost with no reader.\n *\n * Enforced where an activity is written: the console's log dialog, the\n * `logCrmActivity` automation step and `POST /v1/activities`, each with one\n * aggregate read on the record's link before the create. The timeline reads\n * a hundred at a time and is untouched by the number.\n */\nexport const CRM_ACTIVITIES_PER_RECORD_CEILING = 5_000\n\n/**\n * What every writer says when a record's log is full — the console's log\n * dialog, the automation step's run history and `POST /v1/activities`.\n */\nexport const CRM_ACTIVITY_LOG_FULL_MESSAGE =\n `This record already has ${CRM_ACTIVITIES_PER_RECORD_CEILING.toLocaleString(\n 'en-US',\n )} activities, which is the most one record can carry.`\n\n/** The record an activity is filed under: its links, of which one leads. */\nexport interface CrmActivityLink {\n contactId?: string | null\n companyId?: string | null\n dealId?: string | null\n /**\n * `hosts/{hostId}/leads/{leadId}` — a person not yet converted (AGL-2615).\n * A lead is host-scoped by path and carries no `visibleTo` of its own, so\n * an activity filed under one is stamped with the site's scope like any\n * other record created from that site.\n */\n leadId?: string | null\n}\n\n/**\n * The field the per-record activity ceiling is counted on, or `null` for an\n * activity that names no record at all.\n *\n * The contact leads, then the company, then the deal: an activity logged\n * from a contact's page carries the company beside it (the automation step\n * copies the facet's `companyId` onto every record it creates), and a\n * ceiling counted on the company would let one busy account exhaust every\n * contact filed under it. The record whose PAGE the log is read on is the\n * one whose log has the limit. Shared by the client dialog and the two\n * server writers so they count the same thing.\n */\nexport function crmActivityCeilingLink(\n link: CrmActivityLink,\n): { field: 'contactId' | 'companyId' | 'dealId' | 'leadId'; id: string } | null {\n if (link.contactId) return { field: 'contactId', id: String(link.contactId) }\n if (link.companyId) return { field: 'companyId', id: String(link.companyId) }\n if (link.dealId) return { field: 'dealId', id: String(link.dealId) }\n // Last, because a converted lead's activities carry the contact beside it\n // and the contact is the record whose page they are read on.\n if (link.leadId) return { field: 'leadId', id: String(link.leadId) }\n return null\n}\n\n/**\n * Whether ONE MORE activity fits under `CRM_ACTIVITIES_PER_RECORD_CEILING`.\n * Both halves of the boundary live here so no writer re-derives `>=`.\n */\nexport function crmActivityLogHasRoom(existing: number): boolean {\n const count = Number(existing)\n return !(Number.isFinite(count) && count >= CRM_ACTIVITIES_PER_RECORD_CEILING)\n}\n\n/** How a lifecycle stage reads on screen — typed so a stage cannot ship unlabeled. */\nexport const CONTACT_LIFECYCLE_STAGE_LABELS: Record<ContactLifecycleStage, string> = {\n subscriber: 'Subscriber',\n lead: 'Lead',\n 'marketing-qualified': 'Marketing qualified',\n 'sales-qualified': 'Sales qualified',\n opportunity: 'Opportunity',\n customer: 'Customer',\n evangelist: 'Evangelist',\n other: 'Other',\n}\n\n/**\n * The stage a person is in once a capture has happened that implies at least\n * `floor` (AGL-2612).\n *\n * The one ordering rule every capture door shares: a door names the EARLIEST\n * stage that describes what just happened — a form submission is a lead, a\n * newsletter opt-in is a subscriber, a purchase is a customer — and the\n * result is that stage for a person who had none or an earlier one, and the\n * stage they already had otherwise. \"Never downgrades\" is the whole contract:\n * a customer who fills in a contact form is still a customer, and `other` —\n * the deliberate stage a business picked for a funnel step none of the names\n * fit — sits after `customer` in the list precisely so no capture can\n * overwrite it. A stored value that is not a stage at all reads as absent,\n * because a capture door is not the place to preserve a typo.\n *\n * With no floor the answer is the current stage as it stands, or `undefined`\n * for one that is absent or unusable — so a writer can apply this\n * unconditionally and write only what comes back.\n */\nexport function advanceContactLifecycleStage(\n current: unknown,\n floor: ContactLifecycleStage | undefined,\n): ContactLifecycleStage | undefined {\n const held = isContactLifecycleStage(current) ? current : undefined\n if (!floor) return held\n if (!held) return floor\n const order: readonly string[] = CONTACT_LIFECYCLE_STAGES\n return order.indexOf(held) < order.indexOf(floor) ? floor : held\n}\n\n/**\n * The stage a person is in once they have BOUGHT something (AGL-2596).\n *\n * `customer` when they had no stage or an earlier one; whatever they already\n * had otherwise — {@link advanceContactLifecycleStage} with `customer` as the\n * floor, kept under its own name because \"after a purchase\" is the question\n * the order paths and the reports ask.\n */\nexport function contactLifecycleStageAfterPurchase(\n current: unknown,\n): ContactLifecycleStage {\n return advanceContactLifecycleStage(current, 'customer') ?? 'customer'\n}\n\n/**\n * What a custom contact field may hold.\n *\n * Scalars only. A field is something a merchant filters and exports on, and\n * a nested value is neither; `null` is the explicit \"cleared\" that a form\n * writes so the key stays present for a `where` clause to find.\n */\nexport type ContactCustomValue = string | number | boolean | null\n\n/**\n * The same scalar, under the name the other objects use (AGL-2661).\n *\n * A company's and a deal's `custom` map hold exactly what a contact's\n * does — one definition, one coercion rule, one stored shape — so the\n * value type is one type with two names: the contact name it was born\n * with, and this one for code that is not about contacts.\n */\nexport type CrmCustomValue = ContactCustomValue\n\n/**\n * The fields every CRM document carries.\n *\n * `visibleTo` is the scope both enforcement layers evaluate — see\n * `scope-tokens.ts` for why an absent one is seen by NOBODY. `hostId` is\n * provenance: the site whose console created the record, never rewritten,\n * so a report can say where a deal came from after the scope has widened.\n * The timestamps are `unknown` because a client write carries a `Date` and a\n * read hands back a Firestore `Timestamp`; callers narrow at the edge.\n */\nexport interface CrmScoped {\n visibleTo: string[]\n /** The site that created it. */\n hostId: string\n createdAt?: unknown\n updatedAt?: unknown\n}\n\n/** `orgs/{orgId}/companies/{companyId}`. */\nexport interface CrmCompany extends CrmScoped {\n name: string\n /** `nameSearchFields` twins, for the prefix search the list runs. */\n nameLower?: string\n nameTokens?: string[]\n /** Lowercase hostname, no protocol — see {@link normalizeCompanyDomain}. */\n domain?: string\n website?: string\n /** E.164 — `normalizePhone` before writing. */\n phone?: string\n /** The billing address — Salesforce's Billing Address, under its original name. */\n address?: AglynPostalAddress | null\n /**\n * Salesforce's Industry: a label of the `industry` picklist (AGL-3514).\n * A record written while it was free text keeps its text.\n */\n industry?: string\n ownerUid?: string\n notes?: string\n /*\n * SALESFORCE'S ACCOUNT FIELDS (AGL-3514) — see the company account block.\n * The picklist fields hold the value's LABEL, with the list's key beside\n * the ones the Companies list filters by.\n */\n /** Account Type: a label of the `accountType` picklist. */\n type?: string | null\n /** Rating: a label of the `rating` picklist. */\n rating?: string | null\n /** Ownership: a label of the `ownership` picklist. */\n ownership?: string | null\n /** Account Source: a label of the `leadSource` picklist. */\n accountSource?: string | null\n /** Annual revenue in the minor unit of {@link CrmCompany.currency}. */\n annualRevenueCents?: number | null\n /** Lowercase ISO 4217, the deals' convention; `'usd'` when absent. */\n currency?: string\n numberOfEmployees?: number | null\n /** E.164 — `normalizePhone`, like `phone`. */\n fax?: string | null\n accountNumber?: string | null\n /** Salesforce's Account Site: which of the company's locations this record is. */\n site?: string | null\n tickerSymbol?: string | null\n sicCode?: string | null\n shippingAddress?: AglynPostalAddress | null\n /** Another company of the same scope this one sits under; never itself or a descendant. */\n parentCompanyId?: string | null\n /**\n * Lowercased, deduplicated, capped at twenty — the same shape a contact's\n * tags take, so a bulk \"Add tag\" over companies and one over contacts\n * write the same kind of value (AGL-2621).\n */\n tags?: string[]\n createdByUid?: string\n /**\n * How many contacts name this company in their {@link CONTACT_COMPANY_IDS_FIELD}\n * mirror — see {@link COMPANY_CONTACTS_COUNT_FIELD}. Absent on a company\n * nobody has linked since the counter existed, which reads as zero.\n */\n contactsCount?: number\n /**\n * Custom field values, keyed by the key of a definition whose `object`\n * is `company` — see {@link fieldDefinitionObject} (AGL-2661).\n */\n custom?: Record<string, CrmCustomValue>\n /**\n * When the earliest OPEN task filed against this company is due, epoch\n * ms, or `null` when none is — see {@link CrmDeal.nextTaskAtMs}.\n */\n nextTaskAtMs?: number | null\n /** Org-library files attached to the company (AGL-2662) — see {@link CRM_MEDIA_IDS_MAX}. */\n mediaIds?: string[]\n}\n\n/** One step of a pipeline. */\nexport interface CrmDealStage {\n id: string\n name: string\n /** Position in the pipeline, ascending. */\n order: number\n /** Chance of closing from here, 0–100 — what a weighted forecast multiplies by. */\n probability: number\n /**\n * Whether landing in this stage closes the deal. A pipeline has exactly one\n * `won` and one `lost` stage in the default set; `open` is everything in\n * between.\n */\n kind: 'open' | 'won' | 'lost'\n /**\n * The forecast category a deal takes on landing here (AGL-3516) — see\n * {@link CrmForecastCategory}. Absent on a stage written before the field\n * existed, which reads as its kind's: Closed for won, Omitted for lost,\n * Pipeline for open ({@link dealStageForecastCategory}).\n */\n forecastCategory?: CrmForecastCategory\n}\n\n/*==========================================\n * FORECAST CATEGORIES (AGL-3516).\n *\n * Salesforce's Opportunity forecast categories, a FIXED set — not a\n * picklist an org edits, as in Salesforce, because a forecast rolls up by\n * them and a renamed or added category would be one no forecast knows. A\n * stage names the category a deal takes on landing in it; the deal stores\n * its own copy, stamped at every stage move, which the deal page may then\n * change for that deal alone.\n *=========================================*/\n\nexport const CRM_FORECAST_CATEGORIES = [\n 'omitted',\n 'pipeline',\n 'bestCase',\n 'commit',\n 'closed',\n] as const\n\nexport type CrmForecastCategory = (typeof CRM_FORECAST_CATEGORIES)[number]\n\nexport const CRM_FORECAST_CATEGORY_LABELS: Record<CrmForecastCategory, string> = {\n omitted: 'Omitted',\n pipeline: 'Pipeline',\n bestCase: 'Best Case',\n commit: 'Commit',\n closed: 'Closed',\n}\n\nexport function isCrmForecastCategory(value: unknown): value is CrmForecastCategory {\n return typeof value === 'string' && (CRM_FORECAST_CATEGORIES as readonly string[]).includes(value)\n}\n\n/**\n * A category as typed — the stored key or its label in any case and\n * spacing (\"Best Case\", \"best case\", `bestCase`) — or `null`.\n */\nexport function readCrmForecastCategory(value: unknown): CrmForecastCategory | null {\n if (isCrmForecastCategory(value)) return value\n const key = String(value ?? '')\n .trim()\n .replace(/\\s+/g, ' ')\n .toLowerCase()\n if (!key) return null\n return (\n CRM_FORECAST_CATEGORIES.find(\n (category) =>\n category.toLowerCase() === key ||\n CRM_FORECAST_CATEGORY_LABELS[category].toLowerCase() === key,\n ) ?? null\n )\n}\n\n/** The category a stage stamps: its own, else its kind's. */\nexport function dealStageForecastCategory(\n stage: Pick<CrmDealStage, 'kind' | 'forecastCategory'>,\n): CrmForecastCategory {\n if (isCrmForecastCategory(stage.forecastCategory)) return stage.forecastCategory\n return stage.kind === 'won' ? 'closed' : stage.kind === 'lost' ? 'omitted' : 'pipeline'\n}\n\n/**\n * A deal's forecast category as every reader takes it: the deal's own,\n * else its stage's, else its status's — a deal written before the field\n * existed forecasts where its stage puts it.\n */\nexport function dealForecastCategory(\n deal: Partial<Pick<CrmDeal, 'forecastCategory' | 'status'>>,\n stage: Pick<CrmDealStage, 'kind' | 'forecastCategory'> | null | undefined,\n): CrmForecastCategory {\n if (isCrmForecastCategory(deal.forecastCategory)) return deal.forecastCategory\n if (stage) return dealStageForecastCategory(stage)\n return deal.status === 'won' ? 'closed' : deal.status === 'lost' ? 'omitted' : 'pipeline'\n}\n\n/** The longest Next step a deal keeps — Salesforce's own 255. */\nexport const DEAL_NEXT_STEP_MAX = 255\n\n/**\n * A probability override as stored: a whole number 0–100, or `null` for\n * none (the stage's applies). Anything that is not a number in range is\n * `undefined` — unreadable, for the writer to refuse.\n */\nexport function readDealProbability(value: unknown): number | null | undefined {\n if (value === null || value === undefined) return null\n if (typeof value === 'string' && !value.trim()) return null\n const number = typeof value === 'number' ? value : Number(String(value).trim().replace(/%$/, ''))\n if (!Number.isFinite(number) || !Number.isInteger(number) || number < 0 || number > 100) {\n return undefined\n }\n return number\n}\n\n/**\n * A deal's probability: its own override when it holds one, else its\n * stage's, else `null` for a stage the pipeline no longer has.\n */\nexport function dealProbability(\n deal: Partial<Pick<CrmDeal, 'probability'>>,\n stage: Pick<CrmDealStage, 'probability'> | null | undefined,\n): number | null {\n const own = readDealProbability(deal.probability)\n if (typeof own === 'number') return own\n if (!stage) return null\n return Math.min(100, Math.max(0, Number(stage.probability) || 0))\n}\n\n/**\n * What a stage move writes besides the stage, the status and the clocks:\n * the new stage's forecast category, and the probability override CLEARED\n * — Salesforce re-defaults a probability when the stage changes, so an\n * override typed for the last stage does not follow the deal into the next.\n * A write that sets either field itself, in the same request, wins.\n */\nexport function dealStageMoveFields(\n stage: Pick<CrmDealStage, 'kind' | 'forecastCategory'>,\n): { forecastCategory: CrmForecastCategory; probability: null } {\n return { forecastCategory: dealStageForecastCategory(stage), probability: null }\n}\n\n/** `orgs/{orgId}/pipelines/{pipelineId}`. */\nexport interface CrmPipeline extends CrmScoped {\n name: string\n stages: CrmDealStage[]\n /** The pipeline a new deal lands in when nobody picks one. */\n isDefault?: boolean\n /**\n * When the pipeline was retired, epoch ms. An archived pipeline takes no\n * new deal and is offered by no picker, but it is never deleted: the deals\n * it closed still name it, and a report that could not resolve their\n * stages would forecast them as orphans. Absent or `null` while active.\n */\n archivedAt?: number | null\n}\n\n/** Whether a pipeline has been retired — see {@link CrmPipeline.archivedAt}. */\nexport function isPipelineArchived(\n pipeline: Pick<CrmPipeline, 'archivedAt'> | null | undefined,\n): boolean {\n return typeof pipeline?.archivedAt === 'number' && pipeline.archivedAt > 0\n}\n\n/**\n * The stages a fresh pipeline starts with: Salesforce's standard\n * Opportunity stages, with its probabilities and forecast categories\n * (AGL-3516). A pipeline seeded before them keeps the stages it was seeded\n * with — nothing migrates a stored pipeline.\n *\n * The closing stages keep the ids `won` and `lost` every pipeline has had,\n * so an automation filter naming them reads the same on old pipelines and\n * new ones.\n *\n * Readonly on purpose: a pipeline document stores its own COPY (`[...]`), so\n * a merchant editing their stages must not be editing the module's default,\n * and a second pipeline seeded later must start from the original set.\n */\nexport const DEFAULT_DEAL_STAGES: readonly CrmDealStage[] = [\n { id: 'prospecting', name: 'Prospecting', order: 0, probability: 10, kind: 'open', forecastCategory: 'pipeline' },\n { id: 'qualification', name: 'Qualification', order: 1, probability: 10, kind: 'open', forecastCategory: 'pipeline' },\n { id: 'needs-analysis', name: 'Needs Analysis', order: 2, probability: 20, kind: 'open', forecastCategory: 'pipeline' },\n { id: 'value-proposition', name: 'Value Proposition', order: 3, probability: 50, kind: 'open', forecastCategory: 'pipeline' },\n { id: 'id-decision-makers', name: 'Id. Decision Makers', order: 4, probability: 60, kind: 'open', forecastCategory: 'pipeline' },\n { id: 'perception-analysis', name: 'Perception Analysis', order: 5, probability: 70, kind: 'open', forecastCategory: 'pipeline' },\n { id: 'proposal-price-quote', name: 'Proposal/Price Quote', order: 6, probability: 75, kind: 'open', forecastCategory: 'bestCase' },\n { id: 'negotiation-review', name: 'Negotiation/Review', order: 7, probability: 90, kind: 'open', forecastCategory: 'commit' },\n { id: 'won', name: 'Closed Won', order: 8, probability: 100, kind: 'won', forecastCategory: 'closed' },\n { id: 'lost', name: 'Closed Lost', order: 9, probability: 0, kind: 'lost', forecastCategory: 'omitted' },\n]\n\nexport type CrmDealStatus = 'open' | 'won' | 'lost'\n\n/**\n * One product on a deal — what is being sold, how many, at what.\n *\n * `productId` names a catalog product when the line came from one; a line\n * typed by hand has none. The name is copied rather than joined, the way a\n * deal copies its contact's name: a catalog product can be renamed or\n * deleted after the deal was priced, and the deal has to keep saying what\n * it was for. `currency` is the deal's — every line on a deal is in one\n * currency, because their sum is the deal's amount and a sum across\n * currencies is a number with no unit.\n */\nexport interface CrmDealLineItem {\n productId?: string\n name: string\n /** A whole number of units, one or more. */\n quantity: number\n /** Per unit, in the currency's minor unit, zero or more. */\n unitAmountCents: number\n /** Lowercase ISO 4217. */\n currency: string\n}\n\n/** The most lines one deal carries — a quote, not a catalog. */\nexport const DEAL_LINE_ITEMS_MAX = 50\nexport const DEAL_LINE_ITEM_NAME_MAX = 120\n/** Units per line; past this the number is a data-entry slip. */\nexport const DEAL_LINE_ITEM_QUANTITY_MAX = 1_000_000\n\n/**\n * `orgs/{orgId}/deals/{dealId}`.\n *\n * A WON DEAL MAKES ITS CONTACT A CUSTOMER (AGL-2641). A win is the same\n * fact as a purchase — the business has decided this person bought — and an\n * order already floors a contact's lifecycle stage at `customer` on\n * capture. So every writer of `status` — the console's stage route at both\n * levels and the REST resource — applies the same floor to the contact\n * `contactId` names on the transition into `won`, in the facet of the site\n * the deal was made on (`hostId`), with `advanceContactLifecycleStage`'s\n * rule: an empty stage is filled, an earlier one advanced, and nobody is\n * ever moved back. Behind no setting, because a won deal is a customer by\n * definition; an automation on `dealWon` is for what happens NEXT.\n */\nexport interface CrmDeal extends CrmScoped {\n title: string\n titleLower?: string\n pipelineId: string\n stageId: string\n /**\n * Denormalized from the stage's `kind` at every stage move, because it is\n * what the list filters and the indexes sort on — a query cannot join the\n * pipeline to ask what the stage means.\n */\n status: CrmDealStatus\n /**\n * What the deal is worth. Typed by hand on a deal with no line items;\n * on a deal WITH them it is their sum, stored beside them by every\n * writer (`lineItemsTotalCents`), because the board, the reports and the\n * REST list all read this one field and none of them can afford to add\n * up fifty lines per row. A deal with line items refuses a typed amount.\n */\n amountCents?: number\n /** Lowercase ISO 4217; `'usd'` when absent. */\n currency?: string\n /** The products behind the amount — see {@link CrmDealLineItem}. */\n lineItems?: CrmDealLineItem[]\n expectedCloseAtMs?: number | null\n closedAtMs?: number | null\n /** When the deal last moved — what \"stuck in stage\" reports read. */\n stageChangedAtMs?: number\n ownerUid?: string\n contactId?: string\n companyId?: string\n lostReason?: string\n notes?: string\n createdByUid?: string\n /*\n * SALESFORCE'S OPPORTUNITY FIELDS (AGL-3516).\n */\n /**\n * Salesforce's Type — the LABEL of one of the org's `opportunityType`\n * values (New Business, Existing Business, …); `typeKey` beside it is\n * what the Deals list filters by.\n */\n type?: string\n /**\n * Salesforce's Lead Source — the LABEL of one of the org's `leadSource`\n * values, the picklist a lead's own lead source is; stamped from the\n * lead a conversion opens the deal for. `leadSourceKey` beside it.\n */\n leadSource?: string\n /** What happens next, at most {@link DEAL_NEXT_STEP_MAX} characters. */\n nextStep?: string\n /**\n * This deal's own chance of closing, a whole number 0–100, overriding\n * its stage's; `null` or absent means the stage's applies. Cleared by\n * every stage move ({@link dealStageMoveFields}).\n */\n probability?: number | null\n /**\n * Where the deal is forecast — stamped from the stage at every stage move\n * and changeable for this deal afterwards. See {@link dealForecastCategory}.\n */\n forecastCategory?: CrmForecastCategory\n /**\n * Salesforce's Primary Campaign Source: ONE of the org's campaigns (the\n * Marketing plugin's containers, which a lead's `campaignIds` name).\n */\n campaignId?: string\n /**\n * Salesforce's Opportunity Contact Roles (AGL-3521): the people on the\n * deal and the part each plays, at most one of them Primary — the one\n * `contactId` names. See {@link dealContactRolesOf}.\n */\n contactRoles?: CrmDealContactRole[]\n /**\n * Custom field values, keyed by the key of a definition whose `object`\n * is `deal` — see {@link fieldDefinitionObject} (AGL-2661).\n */\n custom?: Record<string, CrmCustomValue>\n /**\n * When the earliest OPEN task filed against this deal is due, epoch ms,\n * or `null` when no open task names it (AGL-2661).\n *\n * DENORMALIZED from `crmTasks`, because the question \"which deals have\n * nothing scheduled\" is asked of a LIST — a column, a filter, a report\n * tile — and a list cannot afford a task query per row. Every server\n * writer of a task recomputes it for the records the task names\n * (`recomputeCrmNextTaskAt` in the CRM's server), and a client-direct\n * task write asks the console route to do the same; the Fields section\n * offers a one-off recompute for the records written before the field\n * existed. Absent on such a record, which every reader treats as `null`.\n */\n nextTaskAtMs?: number | null\n /** Org-library files attached to the deal (AGL-2662) — see {@link CRM_MEDIA_IDS_MAX}. */\n mediaIds?: string[]\n}\n\n/*------------------------------------------\n * OPPORTUNITY CONTACT ROLES (AGL-3521).\n *\n * A deal names any number of contacts, each with the part they play — a\n * label of the org's `opportunityContactRole` picklist, or none — and at\n * most one of them Primary. The Primary is `contactId`, which every reader\n * that existed before roles keeps reading: the won-deal customer floor, the\n * send-email button, the CSV's Contact column, the REST filter. So the two\n * are written together by every door — setting a Primary sets `contactId`,\n * and a door that sets `contactId` the old way makes that contact Primary,\n * adding them without a role when the deal did not name them.\n *\n * A deal written before roles holds `contactId` alone, which reads as one\n * Primary row with no role; its first write through any door stores it.\n * Where the two disagree — a write from a door that knows only\n * `contactId` — `contactId` wins, because it is what every older reader\n * already acted on.\n *-----------------------------------------*/\n\n/** The most contacts one deal names — a buying committee, not a mailing list. */\nexport const DEAL_CONTACT_ROLES_MAX = 50\n\n/** One contact on a deal. */\nexport interface CrmDealContactRole {\n contactId: string\n /** The `opportunityContactRole` label, absent for a contact with no role yet. */\n role?: string\n primary: boolean\n}\n\n/**\n * Stored or sent roles, read defensively: a row without a usable contact\n * id is dropped, a contact named twice keeps its first row, only the first\n * Primary stays Primary, and the list stops at {@link DEAL_CONTACT_ROLES_MAX}.\n */\nexport function readDealContactRoles(raw: unknown): CrmDealContactRole[] {\n if (!Array.isArray(raw)) return []\n const roles: CrmDealContactRole[] = []\n const seen = new Set<string>()\n let primaryTaken = false\n for (const entry of raw) {\n if (!entry || typeof entry !== 'object') continue\n const row = entry as Record<string, unknown>\n const contactId = typeof row['contactId'] === 'string' ? row['contactId'].trim() : ''\n if (!contactId || contactId.length > 200 || contactId.includes('/') || seen.has(contactId)) {\n continue\n }\n seen.add(contactId)\n const role = typeof row['role'] === 'string' ? normalizePicklistLabel(row['role']) : ''\n const primary = row['primary'] === true && !primaryTaken\n if (primary) primaryTaken = true\n roles.push({ contactId, ...(role ? { role } : {}), primary })\n if (roles.length >= DEAL_CONTACT_ROLES_MAX) break\n }\n return roles\n}\n\n/**\n * `roles` with `contactId` as the one Primary — added first, with no role,\n * when the list does not name them — or, for `null`, with no Primary.\n */\nexport function dealContactRolesWithPrimary(\n roles: readonly CrmDealContactRole[],\n contactId: string | null | undefined,\n): CrmDealContactRole[] {\n const id = typeof contactId === 'string' ? contactId.trim() : ''\n const next = roles.map((row) => ({ ...row, primary: Boolean(id) && row.contactId === id }))\n if (id && !next.some((row) => row.contactId === id)) next.unshift({ contactId: id, primary: true })\n return next.slice(0, DEAL_CONTACT_ROLES_MAX)\n}\n\n/**\n * The roles a deal holds, in step with its `contactId`: the stored list\n * with the contact `contactId` names as its Primary — so a deal written\n * before roles reads as its one contact, Primary, with no role.\n */\nexport function dealContactRolesOf(deal: {\n contactId?: unknown\n contactRoles?: unknown\n}): CrmDealContactRole[] {\n const contactId = typeof deal.contactId === 'string' ? deal.contactId : null\n return dealContactRolesWithPrimary(readDealContactRoles(deal.contactRoles), contactId)\n}\n\n/** The Primary's contact id, or `null` for a deal with none. */\nexport function dealPrimaryContactId(roles: readonly CrmDealContactRole[]): string | null {\n return roles.find((row) => row.primary)?.contactId ?? null\n}\n\n/** `roles` without one contact — a delete or an erasure of that person. */\nexport function dealContactRolesWithout(\n roles: readonly CrmDealContactRole[],\n contactId: string,\n): CrmDealContactRole[] {\n return roles.filter((row) => row.contactId !== contactId)\n}\n\n/**\n * `roles` with every row naming `from` moved to `to` — a contact merge.\n * When both are on the deal the survivor keeps its own row, taking the\n * merged row's role where it has none and its Primary where it was.\n */\nexport function dealContactRolesRepointed(\n roles: readonly CrmDealContactRole[],\n from: string,\n to: string,\n): CrmDealContactRole[] {\n const merged = roles.find((row) => row.contactId === from)\n if (!merged || from === to) return [...roles]\n if (!roles.some((row) => row.contactId === to)) {\n return roles.map((row) => (row.contactId === from ? { ...row, contactId: to } : row))\n }\n return roles\n .filter((row) => row.contactId !== from)\n .map((row) =>\n row.contactId === to\n ? {\n ...row,\n ...(!row.role && merged.role ? { role: merged.role } : {}),\n primary: row.primary || merged.primary,\n }\n : row,\n )\n}\n\n/**\n * What a deal stores for a list of roles: the list, and the `contactId`\n * its Primary names — `null` for none, which a writer turns into a delete.\n */\nexport function dealContactRoleFields(roles: readonly CrmDealContactRole[]): {\n contactRoles: CrmDealContactRole[]\n contactId: string | null\n} {\n const contactRoles = readDealContactRoles(roles)\n return { contactRoles, contactId: dealPrimaryContactId(contactRoles) }\n}\n\nexport const CRM_TASK_KIND_LABELS: Record<CrmTaskKind, string> = {\n call: 'Call',\n email: 'Email',\n meeting: 'Meeting',\n todo: 'To-do',\n}\n\nexport type CrmTaskPriority = 'low' | 'normal' | 'high'\nexport type CrmTaskStatus = 'open' | 'done'\n\n/**\n * `orgs/{orgId}/crmTasks/{taskId}`.\n *\n * The one CRM record that may belong to NO site (AGL-2637): a task filed\n * from the organization's own hub carries `hostId: null` and the org scope\n * token alone, because a to-do owed by the organization — renew the\n * insurance, chase the agency's own invoice — is not captured by any brand.\n * Every other record is a fact about a person some site met, so\n * `CrmScoped` keeps its site required.\n */\nexport interface CrmTask extends Omit<CrmScoped, 'hostId'> {\n /** The site that created it, or `null` for the organization's own task. */\n hostId: string | null\n title: string\n notes?: string\n kind: CrmTaskKind\n priority: CrmTaskPriority\n status: CrmTaskStatus\n /**\n * The org's labels for `status`, `priority` and `kind` (AGL-3517) — a\n * value of the `taskStatus`, `taskPriority` and `taskType` picklists,\n * whose meaning is the field beside it. Absent or `null` shows the first\n * active value of that meaning; see `crmTaskPicklistLabels`.\n */\n statusLabel?: string | null\n priorityLabel?: string | null\n typeLabel?: string | null\n dueAtMs?: number | null\n completedAtMs?: number | null\n /**\n * Who ticked it off, which is not always the assignee: a manager closing\n * out a departed teammate's list completes tasks that were never theirs.\n * Stamped by the `crm/task-complete` route beside `completedAtMs`.\n */\n completedByUid?: string\n assigneeUid?: string\n /**\n * The person who made it, or `''` when no person did.\n *\n * An automation has no uid, and inventing one — the action's id, a\n * sentinel — would put a value into a field every reader resolves as a\n * member. The empty string says \"nobody\", and {@link sourceActionId}\n * beside it says what.\n */\n createdByUid: string\n /** The automation that created it (AGL-2605), when a person did not. */\n sourceActionId?: string\n /**\n * The plugin that filed it on the record-timeline seam (AGL-2981) — a\n * sequence's call step, a reply to answer — when no person did.\n */\n sourcePluginId?: string\n contactId?: string\n companyId?: string\n dealId?: string\n /**\n * `hosts/{hostId}/leads/{leadId}` — a task filed on a lead not yet\n * converted (AGL-3233): a sequence's call step or a reply to answer. The\n * conversion stamps the contact beside it, so the task is the contact's\n * from then on and the lead's page still lists it.\n */\n leadId?: string\n /**\n * When the assignee is reminded (AGL-2659): the due time unless a person\n * moved it, `null` for no reminder. The hourly `/api/crm/task-reminders`\n * runner reads every open task whose reminder has come due, so a task\n * that should never remind carries `null` rather than no field — a\n * missing key and a null both fall outside the range, but the null says\n * it was decided.\n */\n remindAtMs?: number | null\n /**\n * When the runner handled the reminder — sent it, or found nobody to\n * send it to. Absent while the reminder is still owed, which is how a\n * rerun over the same hour sends nothing twice; cleared again when the\n * reminder moves, because a moved reminder is a new one.\n */\n reminderSentAtMs?: number\n}\n\n/** How an activity kind reads on screen — typed so a kind cannot ship unlabeled. */\nexport const CRM_ACTIVITY_KIND_LABELS: Record<CrmActivityKind, string> = {\n call: 'Call',\n email: 'Email',\n meeting: 'Meeting',\n note: 'Note',\n other: 'Other',\n}\n\n/**\n * Whether a kind takes an outcome and a duration.\n *\n * A call and a meeting are conversations: they end somewhere (\"left a\n * voicemail\", \"agreed to a trial\") and they take a measurable amount of\n * time, and both are what a manager reading the log wants to know. An email\n * has neither in any useful sense, a note is not an event at all, and\n * `other` is unknowable — so the dialog hides the two fields for those\n * rather than offering boxes that mean nothing.\n */\nexport function activityKindHasOutcome(kind: CrmActivityKind): boolean {\n return kind === 'call' || kind === 'meeting'\n}\n\n/**\n * Where a one-to-one email got to (AGL-2615), in the order it normally\n * happens — the vocabulary the delivery webhook maps its events onto an\n * `email` activity in, and the chip the timeline shows beside the entry.\n *\n * The first four are a progression: a message is sent, then delivered, then\n * opened, then clicked, and a later state implies the earlier ones. The last\n * two are terminal failures. A message the mailbox provider bounced or that\n * the recipient reported is never \"opened\" in any sense the timeline should\n * report, whatever a tracking pixel says afterwards.\n */\nexport const CRM_EMAIL_DELIVERY_STATES = [\n 'sent',\n 'delivered',\n 'opened',\n 'clicked',\n 'bounced',\n 'complained',\n] as const\n\nexport type CrmEmailDeliveryState = (typeof CRM_EMAIL_DELIVERY_STATES)[number]\n\n/** How a delivery state reads on the chip — typed so a state cannot ship unlabeled. */\nexport const CRM_EMAIL_DELIVERY_STATE_LABELS: Record<CrmEmailDeliveryState, string> = {\n sent: 'Sent',\n delivered: 'Delivered',\n opened: 'Opened',\n clicked: 'Clicked',\n bounced: 'Bounced',\n complained: 'Marked as spam',\n}\n\nexport function isCrmEmailDeliveryState(\n value: unknown,\n): value is CrmEmailDeliveryState {\n return (\n typeof value === 'string' &&\n (CRM_EMAIL_DELIVERY_STATES as readonly string[]).includes(value)\n )\n}\n\n/** The two states that mean the message did not land. */\nexport function isCrmEmailDeliveryFailure(state: unknown): boolean {\n return state === 'bounced' || state === 'complained'\n}\n\n/**\n * The rank the webhook advances by. Higher wins; a failure outranks every\n * progression state, and a complaint outranks a bounce because it is the\n * one a sender is scored on.\n */\nconst CRM_EMAIL_DELIVERY_RANK: Record<CrmEmailDeliveryState, number> = {\n sent: 1,\n delivered: 2,\n opened: 3,\n clicked: 4,\n bounced: 5,\n complained: 6,\n}\n\n/**\n * The state an activity holds after one more delivery event, from the state\n * it held before.\n *\n * MONOTONIC. Provider events arrive at least once and in no promised order\n * — an `opened` can reach the webhook before the `delivered` it implies, and\n * a replay can hand back yesterday's `delivered` after today's `clicked` —\n * so the row keeps whichever state is further along, and an event that says\n * less than the row already knows changes nothing. A stored value the\n * vocabulary does not name reads as nothing, so the incoming event stands.\n */\nexport function nextCrmEmailDeliveryState(\n current: unknown,\n incoming: CrmEmailDeliveryState,\n): CrmEmailDeliveryState {\n if (!isCrmEmailDeliveryState(current)) return incoming\n return CRM_EMAIL_DELIVERY_RANK[incoming] > CRM_EMAIL_DELIVERY_RANK[current]\n ? incoming\n : current\n}\n\n/** The most a one-to-one email's subject may hold. */\nexport const CRM_EMAIL_SUBJECT_MAX = 200\n/** The most a one-to-one email's body may hold — a letter, not a document. */\nexport const CRM_EMAIL_BODY_MAX = 10_000\n\n/**\n * The `context` a one-to-one email is sent under, which `sendEmail` stamps\n * as a provider tag on the message and the delivery log files it by.\n */\nexport const CRM_EMAIL_CONTEXT = 'crm'\n/** The provider tag naming the activity row a one-to-one email belongs to. */\nexport const CRM_EMAIL_ACTIVITY_TAG = 'activityId'\n/** The provider tag naming the org whose `crmActivities` holds that row. */\nexport const CRM_EMAIL_ORG_TAG = 'orgId'\n\n/** What a provider tag value may be — anything else fails the whole send. */\nconst PROVIDER_TAG_VALUE = /^[A-Za-z0-9_-]{1,256}$/\n\n/**\n * The tags a one-to-one email carries so the delivery webhook can find its\n * activity row (AGL-2615): the org, the activity, and the site for the\n * per-site suppression list a bounce lands on.\n *\n * Every value is checked against the provider's alphabet rather than\n * trusted, for the reason `contextTag` gives — a value the provider rejects\n * fails the send, and a failed send is worse than an untracked one. The\n * org and the activity are the pair the webhook needs, so an unusable\n * value for EITHER yields no tags at all: a tag set that named a row it\n * could not locate would be a promise the timeline cannot keep. The site\n * is stamped when it can be and dropped alone when it cannot.\n */\nexport function crmEmailDeliveryTags(input: {\n orgId: string\n hostId: string\n activityId: string\n}): { name: string; value: string }[] {\n const orgId = String(input.orgId ?? '')\n const activityId = String(input.activityId ?? '')\n const hostId = String(input.hostId ?? '')\n if (!PROVIDER_TAG_VALUE.test(orgId) || !PROVIDER_TAG_VALUE.test(activityId)) {\n return []\n }\n return [\n { name: CRM_EMAIL_ORG_TAG, value: orgId },\n { name: CRM_EMAIL_ACTIVITY_TAG, value: activityId },\n ...(PROVIDER_TAG_VALUE.test(hostId) ? [{ name: 'hostId', value: hostId }] : []),\n ]\n}\n\n/**\n * Which way an `email` activity's message traveled. `outbound` is every\n * message the workspace wrote — sent by the platform, or copied to the\n * capture address from a mailbox; `inbound` is one a correspondent wrote,\n * which only the capture route files (AGL-2657).\n */\nexport type CrmEmailDirection = 'outbound' | 'inbound'\n\n/**\n * Which way a call went (AGL-3517) — Salesforce's Call Type: `inbound` for\n * one the contact placed, `outbound` for one the team placed, `internal`\n * for one between teammates about the record.\n */\nexport type CrmCallDirection = CrmEmailDirection | 'internal'\n\n/** An activity's direction: a call's three, of which an email takes the first two. */\nexport type CrmActivityDirection = CrmCallDirection\n\n/** The directions each kind takes, in the order a select lists them; none for the rest. */\nexport const CRM_ACTIVITY_DIRECTIONS: Readonly<Partial<Record<CrmActivityKind, readonly CrmActivityDirection[]>>> = {\n call: ['outbound', 'inbound', 'internal'],\n email: ['outbound', 'inbound'],\n}\n\nexport const CRM_ACTIVITY_DIRECTION_LABELS: Record<CrmActivityDirection, string> = {\n outbound: 'Outbound',\n inbound: 'Inbound',\n internal: 'Internal',\n}\n\n/** `value` when `kind` takes it as a direction, else `null`. */\nexport function crmActivityDirection(\n kind: unknown,\n value: unknown,\n): CrmActivityDirection | null {\n const allowed = CRM_ACTIVITY_DIRECTIONS[kind as CrmActivityKind] ?? []\n const text = String(value ?? '').trim().toLowerCase()\n return (allowed as readonly string[]).includes(text) ? (text as CrmActivityDirection) : null\n}\n\n/** \"Inbound call\", \"Outbound email\", \"Internal call\" — or the kind alone with no direction. */\nexport function crmActivityKindTitle(kind: CrmActivityKind, direction?: unknown): string {\n const known = crmActivityDirection(kind, direction)\n const noun = CRM_ACTIVITY_KIND_LABELS[kind] ?? String(kind)\n return known ? `${CRM_ACTIVITY_DIRECTION_LABELS[known]} ${noun.toLowerCase()}` : noun\n}\n\n/**\n * `orgs/{orgId}/crmActivities/{activityId}` — one thing that happened.\n *\n * Distinct from a contact's `interactions`: those are what the PLATFORM\n * recorded (a form, an order, a booking) and live capped on the row. An\n * activity is what a PERSON logged — a call made, a meeting held — and can\n * hang off a company or a deal with no contact at all.\n */\nexport interface CrmActivity extends CrmScoped {\n kind: CrmActivityKind\n body: string\n /** When it happened, which is not when it was logged. */\n atMs: number\n /** Who logged it, or `''` for an automation — see `CrmTask.createdByUid`. */\n byUid: string\n /**\n * The author's display name as it read when the activity was logged.\n *\n * Denormalized because there is no lookup that could answer it later: a\n * member document is readable by its own subject and by org-wide members\n * only, so a scoped editor reading a colleague's call log could not\n * resolve the `byUid` beside it into a name. Stamped from the signed-in\n * user's resolved name at log time and never rewritten, so it can drift\n * from a later rename — the way a signed letter keeps the name it was\n * signed with.\n */\n byName?: string\n /** The automation that logged it (AGL-2605), when a person did not. */\n sourceActionId?: string\n /**\n * The plugin that filed it on the record-timeline seam (AGL-2981) — an\n * email a sequence sent, a reply it read — or the CRM's own id on an\n * entry it wrote about a filing (AGL-3274), so a reader can tell the\n * bookkeeping entries from what a person logged.\n */\n sourcePluginId?: string\n /**\n * The campaign a filing entry is about (AGL-3274): \"Filed under\" and\n * \"Removed from\" name it in the body and carry the container's id here,\n * so a campaign's own page can one day list what happened under it\n * without parsing a sentence.\n */\n campaignId?: string\n contactId?: string\n companyId?: string\n dealId?: string\n /** The lead it was filed under (AGL-2615) — see {@link CrmActivityLink.leadId}. */\n leadId?: string\n outcome?: string\n durationMinutes?: number\n /**\n * An `email` the platform SENT (AGL-2615), as against one a person logged\n * by hand: the subject line, who it went to, and where delivery got to.\n * Absent on a hand-logged email, which has a body and nothing else.\n */\n subject?: string\n /** The address the message left for. */\n to?: string\n /**\n * On an email: `outbound` for a message the platform sent or a teammate\n * copied to the capture address; `inbound` for one a correspondent wrote\n * (AGL-2657). On a call: inbound, outbound or internal (AGL-3517). See\n * {@link CRM_ACTIVITY_DIRECTIONS}.\n */\n direction?: CrmActivityDirection\n /** See {@link CrmEmailDeliveryState}; advanced by the delivery webhook. */\n deliveryState?: CrmEmailDeliveryState\n /** When the delivery state last moved, epoch ms. */\n deliveryAtMs?: number\n /**\n * What the receiving server said when the state moved to a failure\n * (AGL-3245): a bounce's diagnostic, scrubbed of the address. Absent on a\n * progression state.\n */\n deliveryDetail?: string\n /**\n * `hosts/{hostId}/bookings/{bookingId}` — the booking a `meeting` was\n * filed from (AGL-2660), on the rows filed before a booking reached the\n * record through the record-timeline seam. A booking's meeting is now an\n * entry keyed by the booking, and `sourcePluginId` names who filed it.\n */\n bookingId?: string\n /**\n * An email CAPTURED from a mailbox (AGL-2657) — forwarded or copied to\n * the workspace's capture address — as against one the platform sent:\n * who wrote it, the provider's `Message-ID` the row is deduplicated by,\n * the message it answered, and the subject with its reply and forward\n * prefixes removed, which is what groups a thread. Absent on every other\n * email row.\n */\n from?: string\n messageId?: string\n inReplyTo?: string\n threadSubject?: string\n}\n\n/** An activity as a listener hands it back: the document plus its id. */\nexport type CrmActivityRow = CrmActivity & { $id: string }\n\n/** What a sent one-to-one email is logged from — see {@link buildCrmEmailActivity}. */\nexport interface CrmEmailActivityInput {\n subject: string\n body: string\n /** The recipient, as the message was addressed. */\n to: string\n /** When it was sent. */\n atMs: number\n /** Who sent it, or `''` for an automation — see `CrmActivity.byUid`. */\n byUid: string\n byName?: string\n sourceActionId?: string\n link: CrmActivityLink\n hostId: string\n visibleTo: string[]\n}\n\n/**\n * The activity row a sent one-to-one email is logged as (AGL-2615).\n *\n * ONE builder for the two writers — the console's send route and the\n * `sendEmail` automation step — so a message a rep wrote and a message a\n * flow sent are the same kind of entry on the timeline: `kind: 'email'`,\n * outbound, starting at `sent` for the delivery webhook to advance. A\n * writer that assembled its own would be the one whose rows the chip did\n * not know how to read. Timestamps are the writer's: a server stamps\n * `serverTimestamp()` and this module has no Firestore.\n */\nexport function buildCrmEmailActivity(input: CrmEmailActivityInput): CrmActivity {\n const { link } = input\n return {\n kind: 'email',\n subject: String(input.subject ?? '').slice(0, CRM_EMAIL_SUBJECT_MAX),\n body: String(input.body ?? '').slice(0, CRM_EMAIL_BODY_MAX),\n to: input.to,\n direction: 'outbound',\n deliveryState: 'sent',\n deliveryAtMs: input.atMs,\n atMs: input.atMs,\n byUid: input.byUid,\n ...(input.byName ? { byName: input.byName } : {}),\n ...(input.sourceActionId ? { sourceActionId: input.sourceActionId } : {}),\n // Only the links the caller fixed: a key with no value is `undefined`,\n // which Firestore refuses.\n ...(link.contactId ? { contactId: String(link.contactId) } : {}),\n ...(link.companyId ? { companyId: String(link.companyId) } : {}),\n ...(link.dealId ? { dealId: String(link.dealId) } : {}),\n ...(link.leadId ? { leadId: String(link.leadId) } : {}),\n hostId: input.hostId,\n visibleTo: [...input.visibleTo],\n }\n}\n\n/**\n * One campaign email this person was sent, and what became of it (AGL-2616).\n *\n * The per-recipient delivery log — `emailDeliveries/{key}/messages` — keeps\n * one document per MESSAGE with a timestamp per lifecycle state and a count\n * of opens and clicks, and this is that document as a timeline reads it:\n * which email, when it went out, and how far it got. A message that bounced\n * or was complained about carries that state too, because \"we mailed them\n * and it bounced\" is part of the history of a relationship and a timeline\n * that showed only the successes would read as if nothing had been tried.\n *\n * The server answers with this shape, never the log record itself: the log\n * carries the recipient's address, the provider and the links they followed,\n * and none of that is the timeline's to show.\n */\nexport interface ContactCampaignEmail {\n /** The provider's message id — distinct per send, and the entry's key. */\n messageId: string\n /** The site the campaign went out from — the send's `hostId`. */\n hostId: string\n /** `orgs/{orgId}/campaigns/{campaignId}` — the email, whose report the entry links to. */\n campaignId: string\n /**\n * The email as the team named it in the Emails console — its display name,\n * or its subject when it has none. `null` when the email has since been\n * deleted, in which case the entry still has the subject it was sent with.\n */\n campaignName: string | null\n /** The subject line the person received. */\n subject: string | null\n /** When it went out — the provider's `sent` instant, or the first event seen. */\n sentAtMs: number\n deliveredAtMs?: number\n openedAtMs?: number\n clickedAtMs?: number\n bouncedAtMs?: number\n complainedAtMs?: number\n openCount: number\n clickCount: number\n}\n\n/**\n * One entry of a contact's timeline (AGL-2600): something the platform\n * CAPTURED on the contact's facet, something a person LOGGED beside it, or\n * — since AGL-2616 — a CAMPAIGN email the person was sent.\n *\n * A tagged union rather than a flattened row, because the three are\n * different facts with different affordances — a captured interaction is\n * read-only and names the door it came through, a logged activity has an\n * author who may edit it, a campaign email is read-only and links to the\n * campaign's report — and a surface drawing the stream has to say which is\n * which. A logged activity of kind `email` and a campaign entry are two\n * different things on purpose: the first is a message a person on the team\n * sent or recorded, the second is a mailing the platform delivered, and the\n * two must never share a kind id or a glyph.\n */\nexport type ContactTimelineEntry =\n | {\n kind: 'captured'\n /** Distinct across the merged list, for a React key. */\n key: string\n atMs: number\n interaction: ContactInteraction\n }\n | {\n kind: 'logged'\n key: string\n atMs: number\n activity: CrmActivityRow\n }\n | {\n kind: 'campaign'\n key: string\n atMs: number\n email: ContactCampaignEmail\n }\n\n/**\n * What became of one campaign email, as the words a timeline row prints\n * after the email's name: `sent · delivered · opened ×2 · clicked`.\n *\n * In lifecycle order, and only the states that happened. `sent` is always\n * first because the row exists; the counts appear only past one, because\n * \"opened ×1\" says nothing \"opened\" does not. A bounce or a complaint is\n * printed where it falls, after whatever succeeded before it — a message\n * delivered and then complained about reads as both, which is what\n * happened.\n */\nexport function campaignEmailSummary(email: ContactCampaignEmail): string[] {\n const counted = (word: string, count: number): string =>\n count > 1 ? `${word} ×${count}` : word\n const parts = ['sent']\n if (email.deliveredAtMs) parts.push('delivered')\n if (email.openedAtMs) parts.push(counted('opened', email.openCount))\n if (email.clickedAtMs) parts.push(counted('clicked', email.clickCount))\n if (email.bouncedAtMs) parts.push('bounced')\n if (email.complainedAtMs) parts.push('marked as spam')\n return parts\n}\n\n/** A time that can be sorted on; anything that is not one sinks to the bottom. */\nconst sortableMs = (value: unknown): number =>\n typeof value === 'number' && Number.isFinite(value)\n ? value\n : Number.NEGATIVE_INFINITY\n\n/**\n * ONE newest-first stream from a contact's two histories.\n *\n * The captured interactions live on the contact's facet and the logged\n * activities in their own collection, and a page showing them as two lists\n * asks the reader to do the interleaving in their head — \"did the call come\n * before or after the order?\" is the question a timeline exists to answer.\n *\n * STABLE on purpose. The sort is on `atMs` alone, and at a tie the captured\n * entries keep their place ahead of the logged ones, each in the order it\n * arrived: a facet's interactions are already newest-first and a listener's\n * rows are already ordered, so the merge must not reshuffle what its inputs\n * settled. An entry with no usable time goes LAST, never first — a row that\n * cannot say when it happened must not read as the most recent thing.\n *\n * The key is what a list renders by. A captured interaction has no id of its\n * own, so its key is built from what it does carry; a logged activity's is\n * its document id; a campaign email's is the provider's message id.\n *\n * A campaign email is placed at the instant it was SENT, not at its latest\n * open. The row tells the whole story of that message in one line, and a\n * message that moved up the stream every time its reader re-opened it would\n * be a timeline that reorders itself under the reader.\n */\nexport function mergeContactTimeline(\n interactions: readonly ContactInteraction[] | null | undefined,\n activities: readonly CrmActivityRow[] | null | undefined,\n campaignEmails?: readonly ContactCampaignEmail[] | null,\n): ContactTimelineEntry[] {\n const entries: ContactTimelineEntry[] = [\n ...(interactions ?? []).map(\n (interaction, index): ContactTimelineEntry => ({\n kind: 'captured',\n key: `captured:${interaction.type}:${interaction.refId ?? index}:${interaction.atMs}:${index}`,\n atMs: interaction.atMs,\n interaction,\n }),\n ),\n ...(activities ?? []).map(\n (activity): ContactTimelineEntry => ({\n kind: 'logged',\n key: `logged:${activity.$id}`,\n atMs: activity.atMs,\n activity,\n }),\n ),\n ...(campaignEmails ?? []).map(\n (email): ContactTimelineEntry => ({\n kind: 'campaign',\n key: `campaign:${email.messageId}`,\n atMs: email.sentAtMs,\n email,\n }),\n ),\n ]\n // `Array.prototype.sort` is stable, which is what keeps the tie rule above\n // true without a secondary comparison on the entry's kind or position.\n return entries.sort((a, b) => sortableMs(b.atMs) - sortableMs(a.atMs))\n}\n\n/**\n * Which record an activity is ABOUT, for a surface that links to it, or\n * `null` when it was filed against nothing.\n *\n * A contact outranks a deal outranks a company. An activity can name all\n * three — a call with a person about a deal at their company — and the\n * contact is the one a reader means when they ask \"who was this with\"; the\n * deal is next because it is the thing with a clock on it; the company is\n * the widest and so the last resort.\n */\nexport function crmActivityRecordLink(\n activity: Pick<CrmActivity, 'contactId' | 'companyId' | 'dealId' | 'leadId'>,\n): { record: 'contact' | 'deal' | 'company' | 'lead'; id: string } | null {\n if (activity.contactId) return { record: 'contact', id: activity.contactId }\n if (activity.dealId) return { record: 'deal', id: activity.dealId }\n if (activity.companyId) return { record: 'company', id: activity.companyId }\n // A lead is the narrowest: once converted, the contact it became leads.\n if (activity.leadId) return { record: 'lead', id: activity.leadId }\n return null\n}\n\nconst MINUTE_MS = 60_000\nconst HOUR_MS = 60 * MINUTE_MS\nconst DAY_MS = 24 * HOUR_MS\n\n/**\n * How long ago something happened, in the words a list row uses.\n *\n * Coarse on purpose. A row reads \"3 days ago\" and carries the full timestamp\n * in its tooltip; the sentence exists so that a reader scanning a log can\n * tell this week from last month without parsing dates. Past a week the\n * relative form stops helping — \"412 days ago\" is a date the reader has to\n * compute — so it becomes one. A time AHEAD of now is a date too: a call\n * logged for tomorrow is a scheduling mistake the page should show plainly\n * rather than dress as \"in 14 hours\". An unusable time reads as nothing.\n *\n * English rather than `Intl.RelativeTimeFormat` because the console is\n * English-only, and the thresholds are what the spec pins.\n */\nexport function activityTimeLabel(atMs: number, nowMs: number): string {\n if (!Number.isFinite(atMs) || !Number.isFinite(nowMs)) return ''\n const elapsed = nowMs - atMs\n if (elapsed < -MINUTE_MS || elapsed >= 7 * DAY_MS) {\n return new Date(atMs).toLocaleDateString()\n }\n if (elapsed < MINUTE_MS) return 'just now'\n if (elapsed < HOUR_MS) return `${Math.floor(elapsed / MINUTE_MS)} min ago`\n if (elapsed < DAY_MS) return `${Math.floor(elapsed / HOUR_MS)} h ago`\n if (elapsed < 2 * DAY_MS) return 'yesterday'\n return `${Math.floor(elapsed / DAY_MS)} days ago`\n}\n\nexport type ContactFieldType =\n | 'text'\n | 'number'\n | 'date'\n | 'select'\n | 'checkbox'\n | 'url'\n\n/**\n * `orgs/{orgId}/contactFields/{fieldId}` — a custom field a holder defined.\n *\n * The `key` is the map key under a facet's `custom`, so it is IMMUTABLE once\n * a value has been written under it; a rename is a new field and a retire.\n * `retiredAt` rather than a delete for the same reason: values written under\n * the key survive, and a retired field still has to be able to read them\n * back on an export.\n */\nexport interface ContactFieldDefinition extends CrmScoped {\n /** `^[a-z][a-z0-9_]{0,39}$` — see {@link normalizeContactFieldKey}. */\n key: string\n label: string\n type: ContactFieldType\n /** The choices, for `select`. */\n options?: string[]\n required?: boolean\n /** Position in the form and the export, ascending. */\n order: number\n retiredAt?: number | null\n /**\n * Which record the field describes (AGL-2661). ABSENT means `contact`,\n * because every definition written before companies and deals could carry\n * custom fields described a contact, and a backfill that stamped them\n * would touch every org for a fact the reader can infer. Read it through\n * {@link fieldDefinitionObject}, never directly, so that inference lives\n * in one place. Keys are unique PER OBJECT: a company field and a contact\n * field may both be called `region`.\n */\n object?: CrmFieldObject\n}\n\n/**\n * The records a custom field may describe, in the order the Fields\n * section tabs them (AGL-2661), with `lead` since AGL-3272.\n *\n * A lead is last because it is the record a person leaves: the three\n * before it are what a lead becomes, and the tabs read in that order.\n */\nexport const CRM_FIELD_OBJECTS = ['contact', 'company', 'deal', 'lead'] as const\n\nexport type CrmFieldObject = (typeof CRM_FIELD_OBJECTS)[number]\n\n/** How each object reads on the Fields section's tabs and in a refusal. */\nexport const CRM_FIELD_OBJECT_LABELS: Record<CrmFieldObject, string> = {\n contact: 'Contacts',\n company: 'Companies',\n deal: 'Deals',\n lead: 'Leads',\n}\n\nexport function isCrmFieldObject(value: unknown): value is CrmFieldObject {\n return (\n typeof value === 'string' &&\n (CRM_FIELD_OBJECTS as readonly string[]).includes(value)\n )\n}\n\n/**\n * Which record a definition describes — `contact` when the document says\n * nothing, or says something no reader understands.\n *\n * The one reader of {@link ContactFieldDefinition.object}. A stored value\n * outside the list is read as `contact` rather than refused, for the\n * reason an absent one is: the definition predates the field, or was\n * written by something that never learned it, and either way the values\n * under its key sit on contacts.\n */\nexport function fieldDefinitionObject(\n definition: Pick<ContactFieldDefinition, 'object'> | null | undefined,\n): CrmFieldObject {\n return isCrmFieldObject(definition?.object) ? definition.object : 'contact'\n}\n\n/**\n * What the CRM › Fields table's query reads on a definition (AGL-3335):\n *\n * object which tab it belongs to, stored even for a contact's, because\n * a query cannot find a field's absence;\n * required a boolean on every definition, for the Required filter;\n * searchTokens every word prefix of its name and of its key, the key read\n * whole and split at `_`, `-` and `.`, so \"plan\" finds\n * `plan_interest` and \"interest\" does too.\n *\n * Its script-side twin is `crmFieldListFields` in\n * `tools/scripts/lib/org-record-list-fields.mjs`; both answer the\n * `fieldDefinitions` of `org-record-list-fields.fixtures.json`.\n */\nexport function crmFieldListFields(definition: Record<string, unknown> | null | undefined): {\n object: CrmFieldObject\n required: boolean\n searchTokens: string[]\n} {\n const label = typeof definition?.['label'] === 'string' ? definition['label'] : ''\n const key = typeof definition?.['key'] === 'string' ? definition['key'] : ''\n return {\n object: fieldDefinitionObject(definition as Pick<ContactFieldDefinition, 'object'>),\n required: definition?.['required'] === true,\n searchTokens: nameSearchTokens([label, key, key.replace(/[_.-]+/g, ' ')].join(' ')),\n }\n}\n\n/**\n * The definition type under the name new code uses (AGL-2661). The type,\n * the collection (`contactFields`) and the rules match keep the contact\n * name: renaming them would ripple through the rules, the REST resources\n * and the docs for no change in what is stored.\n */\nexport type CrmFieldDefinition = ContactFieldDefinition\n\n/** What a stored field key must look like — a letter, then up to 39 of `[a-z0-9_]`. */\nexport const CONTACT_FIELD_KEY_PATTERN = /^[a-z][a-z0-9_]{0,39}$/\n\n/**\n * A typed label, as the key it would be stored under, or `null` when nothing\n * usable survives.\n *\n * Lowercased and snake-cased rather than refused, because the key is derived\n * from the label a merchant typed (\"Annual revenue\" → `annual_revenue`) and\n * they should not have to learn the grammar. Anything outside `[a-z0-9_]` is\n * dropped after separators become underscores, runs collapse, and a leading\n * run of digits or underscores goes — a key has to start with a letter so it\n * can never collide with an array index or read as a number in a filter.\n */\nexport function normalizeContactFieldKey(input: unknown): string | null {\n const key = String(input ?? '')\n .trim()\n .toLowerCase()\n .replace(/[\\s-]+/g, '_')\n .replace(/[^a-z0-9_]/g, '')\n .replace(/_+/g, '_')\n .replace(/^[^a-z]+/, '')\n .replace(/_+$/, '')\n .slice(0, 40)\n return CONTACT_FIELD_KEY_PATTERN.test(key) ? key : null\n}\n\n/** The stage a deal is in, or `null` when the pipeline no longer has it. */\nexport function dealStageById(\n pipeline: Pick<CrmPipeline, 'stages'> | null | undefined,\n stageId: string,\n): CrmDealStage | null {\n return pipeline?.stages?.find((stage) => stage.id === stageId) ?? null\n}\n\n/**\n * What a deal is worth to a forecast, in cents.\n *\n * STATUS wins over the stage. A deal marked won is worth its full amount\n * whatever stage it happens to sit in, and a lost one is worth nothing —\n * the stage's probability is the odds of an OPEN deal, and applying it to a\n * closed one would forecast a sale that has already happened as a fraction\n * of itself. An open deal with no resolvable stage is worth nothing rather\n * than everything: the pipeline lost the stage, and a forecast that filled\n * the gap with 100% would be the most optimistic number available.\n *\n * An open deal's own probability override (AGL-3516) replaces its stage's\n * — see {@link dealProbability}.\n */\nexport function weightedDealAmountCents(\n deal: Pick<CrmDeal, 'amountCents' | 'status'> & Partial<Pick<CrmDeal, 'probability'>>,\n stage: Pick<CrmDealStage, 'probability'> | null | undefined,\n): number {\n const amount = Math.max(0, Math.round(Number(deal.amountCents ?? 0) || 0))\n if (deal.status === 'won') return amount\n if (deal.status === 'lost' || !stage) return 0\n const probability = dealProbability(deal, stage) ?? 0\n return Math.round((amount * probability) / 100)\n}\n\n/** Whether a deal's amount is derived — it has at least one line item. */\nexport function dealHasLineItems(\n deal: Pick<CrmDeal, 'lineItems'> | null | undefined,\n): boolean {\n return Array.isArray(deal?.lineItems) && deal.lineItems.length > 0\n}\n\n/**\n * What a set of line items adds up to, in cents — the amount a deal with\n * line items stores. Whole cents, never negative: a line's quantity and\n * unit amount have both been through `readDealLineItems`, but a stored\n * document from an older writer is trusted no further than that.\n */\nexport function lineItemsTotalCents(\n items: readonly Pick<CrmDealLineItem, 'quantity' | 'unitAmountCents'>[] | null | undefined,\n): number {\n let total = 0\n for (const item of items ?? []) {\n const quantity = Math.max(0, Math.round(Number(item.quantity) || 0))\n const unit = Math.max(0, Math.round(Number(item.unitAmountCents) || 0))\n total += quantity * unit\n }\n return total\n}\n\n/**\n * Line items as a client typed or sent them, validated into the stored\n * shape, or the one reason they were refused.\n *\n * The one reader for both writers — the products card on a deal's page\n * and `POST`/`PATCH /v1/deals` — so a line the console accepts is a line\n * the API accepts and the other way round. The rules: at most\n * {@link DEAL_LINE_ITEMS_MAX} lines; every line a name, a whole quantity\n * of one or more, a whole unit amount of zero or more; every line in the\n * DEAL's currency (`currency`), which a line may omit and may not\n * contradict. An empty list is valid and means \"no line items\" — the\n * amount goes back to being typed.\n */\nexport function readDealLineItems(\n input: unknown,\n currency: string,\n): { items: CrmDealLineItem[] } | { error: string } {\n if (input === undefined || input === null) return { items: [] }\n if (!Array.isArray(input)) return { error: 'Line items must be a list' }\n if (input.length > DEAL_LINE_ITEMS_MAX) {\n return { error: `A deal carries at most ${DEAL_LINE_ITEMS_MAX} line items` }\n }\n const dealCurrency = String(currency || 'usd').trim().toLowerCase()\n const items: CrmDealLineItem[] = []\n for (const [index, raw] of input.entries()) {\n const at = `Line ${index + 1}`\n if (!raw || typeof raw !== 'object' || Array.isArray(raw)) {\n return { error: `${at} must be an object` }\n }\n const line = raw as Record<string, unknown>\n const name = String(line.name ?? '')\n .trim()\n .slice(0, DEAL_LINE_ITEM_NAME_MAX)\n if (!name) return { error: `${at} needs a name` }\n const quantity = Number(line.quantity)\n if (!Number.isInteger(quantity) || quantity < 1 || quantity > DEAL_LINE_ITEM_QUANTITY_MAX) {\n return {\n error: `${at}: the quantity must be a whole number from 1 to ${DEAL_LINE_ITEM_QUANTITY_MAX.toLocaleString()}`,\n }\n }\n const unitAmountCents = Number(line.unitAmountCents)\n if (!Number.isInteger(unitAmountCents) || unitAmountCents < 0) {\n return { error: `${at}: the unit amount must be a whole number of cents, 0 or more` }\n }\n const lineCurrency =\n line.currency === undefined || line.currency === null || line.currency === ''\n ? dealCurrency\n : String(line.currency).trim().toLowerCase()\n if (lineCurrency !== dealCurrency) {\n return { error: `${at} is in ${lineCurrency.toUpperCase()}; every line must be in the deal's currency, ${dealCurrency.toUpperCase()}` }\n }\n const productId =\n typeof line.productId === 'string' && line.productId.trim()\n ? line.productId.trim().slice(0, 200)\n : undefined\n items.push({\n ...(productId ? { productId } : {}),\n name,\n quantity,\n unitAmountCents,\n currency: dealCurrency,\n })\n }\n return { items }\n}\n\nexport type TaskDueState = 'overdue' | 'today' | 'upcoming' | 'none' | 'done'\n\n/**\n * Where a task stands against the clock.\n *\n * `today` is decided on the LOCAL calendar day and beats `overdue`: a task\n * due at nine this morning is today's work until midnight, not something the\n * list should paint red at 9:01. Overdue is yesterday or earlier. `done`\n * comes first because a completed task's due date is history whichever side\n * of now it fell on.\n */\nexport function taskDueState(\n task: Pick<CrmTask, 'status' | 'dueAtMs'>,\n nowMs: number,\n): TaskDueState {\n if (task.status === 'done') return 'done'\n const dueAtMs = task.dueAtMs\n if (typeof dueAtMs !== 'number' || !Number.isFinite(dueAtMs)) return 'none'\n const due = new Date(dueAtMs)\n const now = new Date(nowMs)\n const sameDay =\n due.getFullYear() === now.getFullYear() &&\n due.getMonth() === now.getMonth() &&\n due.getDate() === now.getDate()\n if (sameDay) return 'today'\n return dueAtMs < nowMs ? 'overdue' : 'upcoming'\n}\n\n/**\n * A hostname label: `[a-z0-9-]`, not starting or ending with a hyphen, at\n * most 63 characters — RFC 1123 as far as a company domain needs it.\n */\nconst DOMAIN_LABEL = /^[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?$/\n\n/**\n * A typed or pasted domain, reduced to the bare lowercase hostname a company\n * is keyed by, or `null` when what is left is not one.\n *\n * Strips what people paste along with a domain — the protocol, a `www.`, a\n * path, a query, a port — because the value is a KEY: two contacts at\n * `https://www.acme.com/about` and `acme.com` work for one company, and the\n * match is only findable if both reduce to the same string. The last label\n * has to be letters because a company's domain has a TLD; a bare IP address\n * or a single word is not one and answers `null` rather than being stored as\n * something a later match will never hit.\n */\nexport function normalizeCompanyDomain(input: unknown): string | null {\n let value = String(input ?? '')\n .trim()\n .toLowerCase()\n if (!value) return null\n value = value.replace(/^[a-z][a-z0-9+.-]*:\\/\\//, '')\n value = value.replace(/^www\\./, '')\n value = value.split(/[/?#]/, 1)[0]\n value = value.split(':', 1)[0]\n value = value.replace(/\\.+$/, '')\n if (!value || value.length > 253) return null\n const labels = value.split('.')\n if (labels.length < 2) return null\n if (!labels.every((label) => DOMAIN_LABEL.test(label))) return null\n if (!/^[a-z]{2,}$/.test(labels[labels.length - 1])) return null\n return value\n}\n\n/** The longest website URL a company stores. */\nconst COMPANY_WEBSITE_MAX = 500\n\n/**\n * The typed website, as a URL; `''` for a blank; `null` when it cannot be\n * one.\n *\n * People type `acme.com` where a URL is asked for, and refusing that is\n * pedantry — it becomes `https://acme.com`. What IS refused is anything the\n * URL parser cannot read or a scheme other than http(s): a `javascript:` link\n * on a record that renders as an anchor is not a website. One function for\n * the company drawer and the companies import, so a website typed and a\n * website imported are stored the same way.\n */\nexport function normalizeCompanyWebsite(input: unknown): string | null {\n const raw = String(input ?? '').trim()\n if (!raw) return ''\n const candidate = /^[a-z][a-z0-9+.-]*:\\/\\//i.test(raw) ? raw : `https://${raw}`\n try {\n const url = new URL(candidate)\n if (url.protocol !== 'http:' && url.protocol !== 'https:') return null\n return url.href.length > COMPANY_WEBSITE_MAX ? null : url.href\n } catch {\n return null\n }\n}\n\n/*==========================================\n * A COMPANY'S ACCOUNT FIELDS (AGL-3514).\n *\n * Salesforce's Account carries more than a name and a domain: a Type, an\n * Industry, a Rating, an Ownership and an Account Source, each a picklist;\n * an annual revenue and a head count; a fax, an account number, a site, a\n * ticker symbol and an SIC code; a shipping address beside the billing one;\n * and a parent account. Every door that writes a company — the drawer, the\n * REST resource, the import, a lead's conversion — reads them through this\n * block, so a value one door refuses is refused by all of them.\n *\n * ## Each field's three answers\n *\n * A door reads what a request said about a field: not named (`undefined`)\n * leaves the record alone, `null` or a blank clears it, and anything else\n * is normalized or refused by name. A picklist field stores the value's\n * LABEL, judged against the org's list with the record's current value\n * kept (see the picklist block); the Companies list filters by the key of\n * Type, Industry and Rating, which `crmCompanyListFields` writes beside\n * the label.\n *=========================================*/\n\n/** Each company picklist field: the list it holds a value of, and the key the list filters by. */\nexport const CRM_COMPANY_PICKLIST_FIELDS = [\n { field: 'type', picklistId: 'accountType', keyField: 'typeKey' },\n { field: 'industry', picklistId: 'industry', keyField: 'industryKey' },\n { field: 'rating', picklistId: 'rating', keyField: 'ratingKey' },\n { field: 'ownership', picklistId: 'ownership' },\n { field: 'accountSource', picklistId: 'leadSource', keyField: 'accountSourceKey' },\n] as const\n\nexport type CrmCompanyPicklistField = (typeof CRM_COMPANY_PICKLIST_FIELDS)[number]['field']\n\n/** The short text fields, each capped at Salesforce's own length. */\nexport const CRM_COMPANY_TEXT_MAX = {\n accountNumber: 40,\n site: 80,\n tickerSymbol: 20,\n sicCode: 20,\n} as const\n\nexport type CrmCompanyTextField = keyof typeof CRM_COMPANY_TEXT_MAX\n\n/** The most employees a company records — Salesforce's eight digits. */\nexport const CRM_COMPANY_EMPLOYEES_MAX = 99_999_999\n\n/** The account fields that are not picklists, as every door names them. */\nexport const CRM_COMPANY_ACCOUNT_FIELDS = [\n 'annualRevenueCents',\n 'currency',\n 'numberOfEmployees',\n 'fax',\n 'accountNumber',\n 'site',\n 'tickerSymbol',\n 'sicCode',\n 'shippingAddress',\n] as const\n\nexport type CrmCompanyAccountField = (typeof CRM_COMPANY_ACCOUNT_FIELDS)[number]\n\n/** A short text field as stored: trimmed, single-spaced. */\nexport function normalizeCompanyText(value: unknown): string {\n return String(value ?? '')\n .trim()\n .replace(/\\s+/g, ' ')\n}\n\nconst blank = (value: unknown): boolean =>\n value === null || (typeof value === 'string' && value.trim() === '')\n\n/**\n * The account fields a request names, normalized, or the refusal of each\n * one that cannot be stored — see the block above. A value the door has\n * already parsed from text (the drawer's revenue, a file's head count)\n * arrives here as the number it parsed.\n */\nexport function readCrmCompanyAccountFields(input: Readonly<Record<string, unknown>>): {\n values: Partial<Pick<CrmCompany, CrmCompanyAccountField>>\n errors: Record<string, string>\n} {\n const values: Record<string, unknown> = {}\n const errors: Record<string, string> = {}\n for (const field of CRM_COMPANY_ACCOUNT_FIELDS) {\n const raw = input[field]\n if (raw === undefined) continue\n if (blank(raw)) {\n values[field] = null\n continue\n }\n switch (field) {\n case 'annualRevenueCents':\n if (typeof raw === 'number' && Number.isSafeInteger(raw) && raw >= 0) values[field] = raw\n else errors[field] = 'Must be a whole number of cents, 0 or more'\n break\n case 'currency': {\n const code = typeof raw === 'string' ? raw.trim().toLowerCase() : ''\n if (/^[a-z]{3}$/.test(code)) values[field] = code\n else errors[field] = 'Must be a three-letter ISO 4217 code, like usd'\n break\n }\n case 'numberOfEmployees':\n if (typeof raw === 'number' && Number.isInteger(raw) && raw >= 0 && raw <= CRM_COMPANY_EMPLOYEES_MAX) {\n values[field] = raw\n } else {\n errors[field] = `Must be a whole number from 0 to ${CRM_COMPANY_EMPLOYEES_MAX}`\n }\n break\n case 'fax': {\n const fax = typeof raw === 'string' ? normalizePhone(raw) : null\n if (fax) values[field] = fax\n else errors[field] = 'Must be a phone number with a country code, like +15125550123'\n break\n }\n case 'shippingAddress':\n if (typeof raw === 'object' && !Array.isArray(raw)) {\n values[field] = normalizeAddress(raw as AglynPostalAddress)\n } else {\n errors[field] = 'Must be an address object'\n }\n break\n default: {\n const max = CRM_COMPANY_TEXT_MAX[field]\n if (typeof raw !== 'string' && typeof raw !== 'number') {\n errors[field] = 'Must be text'\n break\n }\n const text = normalizeCompanyText(raw)\n if (text.length > max) errors[field] = `Must be at most ${max} characters`\n else values[field] = text\n }\n }\n }\n return { values: values as Partial<Pick<CrmCompany, CrmCompanyAccountField>>, errors }\n}\n\n/**\n * The picklist fields a request names, judged against the org's lists —\n * `lists` holds each list the caller read, and a list it did not read is\n * judged as the standard values alone. `current` is the record as stored:\n * its value is kept even when the list no longer offers it, which is how\n * a company whose industry was typed before Industry became a picklist\n * keeps it. On a create (`created`), a field not named starts from its\n * list's default when the org set one.\n */\nexport function judgeCrmCompanyPicklists(\n lists: Readonly<Partial<Record<CrmPicklistId, CrmPicklist>>>,\n requested: Readonly<Partial<Record<string, unknown>>>,\n options: { current?: Readonly<Partial<Record<string, unknown>>>; created?: boolean } = {},\n): {\n values: Partial<Record<CrmCompanyPicklistField, string | null>>\n errors: Record<string, string>\n} {\n const values: Partial<Record<CrmCompanyPicklistField, string | null>> = {}\n const errors: Record<string, string> = {}\n for (const { field, picklistId } of CRM_COMPANY_PICKLIST_FIELDS) {\n const list = lists[picklistId] ?? effectiveCrmPicklist(picklistId, undefined)\n const raw = requested[field]\n if (raw === undefined) {\n const fallback = options.created ? picklistDefaultLabel(list) : null\n if (fallback) values[field] = fallback\n continue\n }\n if (blank(raw)) {\n values[field] = null\n continue\n }\n if (typeof raw !== 'string') {\n errors[field] = 'Must be text'\n continue\n }\n const judged = judgeCrmPicklistValue(picklistId, list, raw, options.current?.[field])\n if (judged.ok === false) errors[field] = judged.error\n else values[field] = judged.value\n }\n return { values, errors }\n}\n\n/** How deep a chain of parent companies may run. */\nexport const CRM_COMPANY_PARENT_DEPTH_MAX = 25\n\n/**\n * What a parent lookup answers for one company id: its own parent, or\n * `null` when there is no such company the writer may see.\n */\nexport type CrmCompanyParentReader = (\n companyId: string,\n) => Promise<{ parentCompanyId: string | null } | null>\n\n/**\n * Why `parentId` cannot be the parent of `companyId` (`null` for a company\n * not yet created), or `null` when it can: a company is never its own\n * parent, the parent must be a company the writer can see, and a company\n * already UNDER this one cannot be put above it — the cycle Salesforce\n * refuses too. Walks up from the parent through `read`, at most\n * {@link CRM_COMPANY_PARENT_DEPTH_MAX} steps.\n */\nexport async function crmCompanyParentRefusal(\n companyId: string | null,\n parentId: string,\n read: CrmCompanyParentReader,\n): Promise<string | null> {\n if (companyId && parentId === companyId) return 'A company cannot be its own parent.'\n const parent = await read(parentId)\n if (!parent) return 'There is no such parent company.'\n const seen = new Set<string>([parentId])\n let cursor = parent.parentCompanyId\n for (let depth = 1; cursor; depth += 1) {\n if (companyId && cursor === companyId) {\n return 'That company sits under this one, so it cannot be its parent.'\n }\n // A loop above that does not pass through this company is not this write's.\n if (seen.has(cursor)) return null\n if (depth >= CRM_COMPANY_PARENT_DEPTH_MAX) {\n return `A company can sit at most ${CRM_COMPANY_PARENT_DEPTH_MAX} levels under another.`\n }\n seen.add(cursor)\n cursor = (await read(cursor))?.parentCompanyId ?? null\n }\n return null\n}\n\n/** A stored parent id as a reader takes it, `null` for none. */\nexport function crmCompanyParentId(value: unknown): string | null {\n return typeof value === 'string' && value.trim() !== '' ? value : null\n}\n\n/**\n * Mailbox domains that belong to people rather than to a business: the\n * providers anybody can sign up with, and the ones an internet provider\n * hands its subscribers.\n *\n * Auto-associating a contact with a company by email domain is the whole\n * reason {@link companyDomainForEmail} exists, and it is wrong for every\n * address at a public mailbox: `gmail.com` is not a company, and a rule\n * that treated it as one would file half of a consumer list under a single\n * phantom account. It is also the fact a cold email has to know — an\n * address here is a person's own, not their work address — which is why the\n * list is exported with {@link isPublicMailboxDomain}: a second list kept\n * for that question would drift from this one.\n *\n * Exact domains, not suffixes: a subscriber's regional host (`nc.rr.com`)\n * is not listed, and neither is a company that happens to end in `mail.com`.\n */\nexport const PUBLIC_MAILBOX_DOMAINS: ReadonlySet<string> = new Set([\n // Google, Yahoo, Microsoft, Apple, AOL.\n 'gmail.com',\n 'googlemail.com',\n 'yahoo.com',\n 'yahoo.co.uk',\n 'yahoo.ca',\n 'yahoo.com.au',\n 'yahoo.co.in',\n 'yahoo.fr',\n 'yahoo.de',\n 'ymail.com',\n 'rocketmail.com',\n 'hotmail.com',\n 'hotmail.co.uk',\n 'hotmail.fr',\n 'hotmail.de',\n 'hotmail.it',\n 'outlook.com',\n 'live.com',\n 'live.co.uk',\n 'live.ca',\n 'msn.com',\n 'icloud.com',\n 'me.com',\n 'mac.com',\n 'aol.com',\n 'aim.com',\n // Independent and privacy-first providers.\n 'proton.me',\n 'protonmail.com',\n 'protonmail.ch',\n 'pm.me',\n 'tutanota.com',\n 'tuta.io',\n 'hushmail.com',\n 'gmx.com',\n 'gmx.net',\n 'gmx.de',\n 'web.de',\n 'mail.com',\n 'yandex.com',\n 'yandex.ru',\n 'zoho.com',\n 'zohomail.com',\n 'fastmail.com',\n 'hey.com',\n // The largest consumer providers outside the US and Europe.\n 'qq.com',\n '163.com',\n '126.com',\n 'sina.com',\n 'naver.com',\n 'hanmail.net',\n 'rediffmail.com',\n // US internet providers' subscriber mailboxes.\n 'comcast.net',\n 'att.net',\n 'sbcglobal.net',\n 'bellsouth.net',\n 'pacbell.net',\n 'verizon.net',\n 'cox.net',\n 'charter.net',\n 'earthlink.net',\n 'optonline.net',\n 'frontier.com',\n 'windstream.net',\n 'centurylink.net',\n 'juno.com',\n 'netzero.net',\n // Subscriber mailboxes at the largest providers in Canada, the UK, France,\n // Germany, Italy and Australia.\n 'shaw.ca',\n 'sympatico.ca',\n 'btinternet.com',\n 'sky.com',\n 'virginmedia.com',\n 'orange.fr',\n 'free.fr',\n 'laposte.net',\n 't-online.de',\n 'libero.it',\n 'bigpond.com',\n])\n\n/**\n * Whether a domain — or the domain of an address — is one of the\n * {@link PUBLIC_MAILBOX_DOMAINS}. Read the way a company domain is, so case,\n * a trailing dot and a pasted `www.` do not hide one.\n */\nexport function isPublicMailboxDomain(domainOrEmail: unknown): boolean {\n const value = String(domainOrEmail ?? '').trim()\n const domain = normalizeCompanyDomain(value.slice(value.lastIndexOf('@') + 1))\n return domain !== null && PUBLIC_MAILBOX_DOMAINS.has(domain)\n}\n\n/**\n * The company domain an email address implies, or `null` when it implies\n * none — a malformed address, or one at a public mailbox provider.\n */\nexport function companyDomainForEmail(email: unknown): string | null {\n const value = String(email ?? '')\n .trim()\n .toLowerCase()\n const at = value.lastIndexOf('@')\n if (at < 1 || at === value.length - 1) return null\n if (/[\\s@]/.test(value.slice(0, at))) return null\n const domain = normalizeCompanyDomain(value.slice(at + 1))\n if (!domain || PUBLIC_MAILBOX_DOMAINS.has(domain)) return null\n return domain\n}\n\n/**\n * The name a company minted from a domain starts with: the first label with\n * a capital — `acme.com` → `Acme`, `initech.co.uk` → `Initech`.\n *\n * A starting point and not a claim about what the business is called: the\n * lead-convert dialog offers it for editing, and a company the capture door\n * creates on its own carries it until somebody renames the record. The\n * domain itself would be an honest name too, but a list of companies that\n * reads `acme.com`, `globex.example` looks like a list of websites, and the\n * domain is on the row beside the name anyway.\n */\nexport function companyNameForDomain(domain: string): string {\n const label = domain.split('.')[0] || domain\n return label.charAt(0).toUpperCase() + label.slice(1)\n}\n\n/*==========================================\n * THE CONTACT–COMPANY LINK (AGL-2597, AGL-2613).\n *\n * The association proper is `facets.{groupId}.companyId` — one company per\n * holder, inside that holder's facet like the notes and the tags, and for\n * the same reason: which account a person belongs to is one business's\n * knowledge of them. But a facet path is per group, and Firestore cannot\n * answer \"every contact whose facet, whichever group's, names company X\" —\n * a `where` needs one field path, and the group id is part of the path. So\n * the facet is MIRRORED into a top-level array an `array-contains` can\n * query, and the company carries a COUNT of the contacts whose mirror names\n * it, so a list of companies can say how many people are at each without\n * reading a person.\n *\n * Three fields, kept in step by ONE planner. Every writer — the picker on a\n * contact's page, the company page's link control, the bulk bar, the server\n * doors that link on capture — asks {@link planContactCompanyLink} what\n * changes and applies the answer with its own SDK's sentinels. None of them\n * decides for itself whether the old id leaves the mirror, which is the rule\n * a second copy would get wrong: another business filing the same person\n * under the same account is THEIR link, and this holder letting go must not\n * take it away.\n *=========================================*/\n\n/**\n * The top-level field on a CONTACT naming every company it is linked to.\n *\n * It carries the union of every holder's link, so removing an id from it is\n * only correct when no other holder's facet still names that id — the rule\n * the planner enforces, and the reason nothing writes this field without\n * going through it. The facet is the truth and this is its index: a reader\n * answering \"which company is this person at\" reads the facet; only a QUERY\n * reads this.\n */\nexport const CONTACT_COMPANY_IDS_FIELD = 'companyIds'\n\n/**\n * The field on a COMPANY counting the contacts whose mirror names it.\n *\n * Denormalized because the honest figure is an aggregate over every contact\n * in the org, which a list of two hundred companies cannot afford to take\n * per row. Moved by `increment` in the same batch as the link that changes\n * it, so the two cannot disagree by a failed second write; a company's own\n * page still takes the live aggregate, which is what corrects a count that\n * predates the counter.\n */\nexport const COMPANY_CONTACTS_COUNT_FIELD = 'contactsCount'\n\n/** What the planner needs to know about a contact's links, and nothing else. */\nexport interface ContactCompanyLinkState {\n /** This holder's link, or `null` when the facet names no company. */\n companyId: string | null\n /** The mirror as stored, in no order. */\n companyIds: string[]\n /**\n * Ids named by OTHER holders' facets — what keeps an id in the mirror\n * when this holder lets go of it.\n */\n heldElsewhere: string[]\n}\n\n/** The link state of a stored contact, read for one holder. */\nexport function readContactCompanyLink(\n contact: Record<string, unknown> | null | undefined,\n groupId: string,\n): ContactCompanyLinkState {\n const document = contact ?? {}\n const facets = document[CONTACT_FACETS_FIELD]\n const heldElsewhere = new Set<string>()\n if (facets && typeof facets === 'object' && !Array.isArray(facets)) {\n for (const [holder, facet] of Object.entries(\n facets as Record<string, unknown>,\n )) {\n if (holder === groupId) continue\n const named =\n facet && typeof facet === 'object' && !Array.isArray(facet)\n ? (facet as Record<string, unknown>)['companyId']\n : undefined\n if (typeof named === 'string' && named) heldElsewhere.add(named)\n }\n }\n const mirror = document[CONTACT_COMPANY_IDS_FIELD]\n return {\n companyId: readContactFacet(document, groupId).companyId ?? null,\n companyIds: Array.isArray(mirror)\n ? mirror.filter((id): id is string => typeof id === 'string' && !!id)\n : [],\n heldElsewhere: [...heldElsewhere],\n }\n}\n\n/**\n * Every holder whose facet names this company, in no order.\n *\n * What a company's DELETION clears. Unlike a link change, which is one\n * holder's, a deletion takes the record away from every holder at once: a\n * facet still naming it would be a link to nothing, on a surface its holder\n * has no reason to revisit. Nothing else about another holder's facet is\n * read.\n */\nexport function contactGroupsNamingCompany(\n contact: Record<string, unknown> | null | undefined,\n companyId: string,\n): string[] {\n const facets = (contact ?? {})[CONTACT_FACETS_FIELD]\n if (!companyId || !facets || typeof facets !== 'object' || Array.isArray(facets)) {\n return []\n }\n return Object.entries(facets as Record<string, unknown>)\n .filter(([, facet]) =>\n Boolean(\n facet &&\n typeof facet === 'object' &&\n !Array.isArray(facet) &&\n (facet as Record<string, unknown>)['companyId'] === companyId,\n ),\n )\n .map(([groupId]) => groupId)\n}\n\n/**\n * How the mirror changes. Three shapes because Firestore takes ONE transform\n * per field per write: an `arrayUnion` and an `arrayRemove` on the same\n * field cannot share an update, so a move rewrites the array whole.\n */\nexport type ContactCompanyMirrorChange =\n | { op: 'union'; companyId: string }\n | { op: 'remove'; companyId: string }\n | { op: 'set'; companyIds: string[] }\n\nexport interface ContactCompanyLinkPlan {\n /** The facet's new value: the id, or `null` meaning delete the field. */\n companyId: string | null\n /** What happens to the mirror, or `null` when it already carries the right ids. */\n mirror: ContactCompanyMirrorChange | null\n /**\n * Per company, how its contacts count moves. A company enters the list\n * only when the mirror actually gains or loses it, so a link some other\n * holder already made is not counted twice and a mirror that never carried\n * an id is not decremented for it.\n */\n counts: Array<{ companyId: string; delta: 1 | -1 }>\n}\n\n/**\n * What linking a contact to a company FOR ONE HOLDER changes — or unlinking\n * them, with `null`. `null` when the document already says what was asked.\n *\n * Three cases, and the mirror is handled differently in each because it is\n * shared across holders while the facet is not:\n *\n * - A first link `union`s the id in, which is safe against a concurrent\n * writer adding another holder's id.\n * - A MOVE from one company to another rewrites the mirror as a whole. The\n * old id is dropped only if no other holder's facet still names it.\n * - An unlink `remove`s the old id, on the same condition, and leaves the\n * mirror alone when another holder still needs it there.\n *\n * The counts follow the mirror, not the facet: the company's figure is \"how\n * many contacts name me in the mirror\", and that is the quantity the\n * company page's live aggregate measures.\n */\nexport function planContactCompanyLink(\n state: ContactCompanyLinkState,\n companyId: string | null,\n): ContactCompanyLinkPlan | null {\n const previous = state.companyId\n if (previous === companyId) return null\n const mirror = new Set(state.companyIds)\n const previousLeaves =\n previous !== null && !state.heldElsewhere.includes(previous)\n const counts: ContactCompanyLinkPlan['counts'] = []\n let change: ContactCompanyMirrorChange | null = null\n if (companyId && !previous) {\n change = { op: 'union', companyId }\n if (!mirror.has(companyId)) counts.push({ companyId, delta: 1 })\n } else if (companyId && previous) {\n const kept = state.companyIds.filter(\n (id) => id !== previous || !previousLeaves,\n )\n change = { op: 'set', companyIds: [...new Set([...kept, companyId])] }\n if (previousLeaves && mirror.has(previous)) {\n counts.push({ companyId: previous, delta: -1 })\n }\n if (!mirror.has(companyId)) counts.push({ companyId, delta: 1 })\n } else if (previous && previousLeaves) {\n change = { op: 'remove', companyId: previous }\n if (mirror.has(previous)) counts.push({ companyId: previous, delta: -1 })\n }\n return { companyId, mirror: change, counts }\n}\n\n/*==========================================\n * THE CRM's ORGANIZATION SETTINGS (AGL-2613).\n *\n * One map under `crm` on the org document, so the CRM → Settings section\n * can grow a key per setting without a rules change each time: the org\n * document's client branch is a deny-list, and `crm` is declared\n * client-writable in `ORG_CLIENT_WRITABLE_FIELDS` with its reason.\n *=========================================*/\n\n/** The org-document key the CRM's settings live under. */\nexport const ORG_CRM_SETTINGS_FIELD = 'crm'\n\n/** The dotted path an `update()` writes the auto-create switch by. */\nexport const CRM_AUTO_CREATE_COMPANIES_PATH = `${ORG_CRM_SETTINGS_FIELD}.autoCreateCompanies`\n\n/**\n * Whether a capture from a work email domain no visible company carries\n * should CREATE the company. Off unless the org document says `true`: a\n * company minted from every domain that ever submitted a form is a list\n * nobody asked for, so the default is the quiet one.\n *\n * Read off the raw document rather than a typed field, because the capture\n * door holds a `Partial<AglynOrganization>` and the console a\n * `Partial<AglynOrgBilling>`, and one reader has to answer both.\n */\nexport function orgAutoCreatesCompanies(\n orgDocument: Record<string, unknown> | null | undefined,\n): boolean {\n const settings = (orgDocument ?? {})[ORG_CRM_SETTINGS_FIELD]\n return Boolean(\n settings &&\n typeof settings === 'object' &&\n !Array.isArray(settings) &&\n (settings as Record<string, unknown>)['autoCreateCompanies'] === true,\n )\n}\n\n/*==========================================\n * WHO A NEW RECORD BELONGS TO (AGL-2618).\n *\n * Salesforce calls these assignment rules and HubSpot rotates to an owner;\n * either way they are the first thing a sales team configures, because a\n * record with no owner is one nobody follows up. The shape here is what the\n * CRM → Settings section writes onto the org document under `crm`, what the\n * capture door's assignment pass reads, and what the `assignContactOwner`\n * automation step's round-robin mode reads — one vocabulary, three readers.\n *\n * ## The order of decision\n *\n * The rules are tried in their stored order and the FIRST match assigns; a\n * rule that names a member the roster no longer has, or a round-robin rule\n * over an empty pool, is passed over rather than stopping the pass. When no\n * rule claims the capture, the capturing site's default owner does; when\n * the site has none, the record stays unassigned, which is what the product\n * did before any of this existed and is the honest answer to \"nobody has\n * said\".\n *\n * ## Only a record with no owner\n *\n * The capture pass runs when a record is CREATED and never overwrites an\n * owner: a door that named one (the console's drawer, an import column, a\n * conversion with a picked owner) has expressed a person's choice, and a\n * returning visitor's contact already belongs to somebody. The automation\n * step is the deliberate exception — an author who put \"assign an owner\"\n * on a stage change means to reassign.\n *\n * ## Why the pointer is a uid, and why the server moves it\n *\n * See `OrgCrmRoundRobin`. The pool is edited in the console and the pointer\n * is advanced by the Admin SDK inside the transaction that writes the owner,\n * so concurrent captures take distinct turns and an edit to the pool never\n * skips or repeats a member.\n *=========================================*/\n\n/** Where the ordered rules live on the org document. */\nexport const CRM_ASSIGNMENT_RULES_PATH = `${ORG_CRM_SETTINGS_FIELD}.assignmentRules`\n\n/** The round-robin pool's member list, in rotation order. */\nexport const CRM_ROUND_ROBIN_POOL_PATH = `${ORG_CRM_SETTINGS_FIELD}.roundRobin.memberUids`\n\n/** The member handed the most recent round-robin record. */\nexport const CRM_ROUND_ROBIN_LAST_ASSIGNED_PATH = `${ORG_CRM_SETTINGS_FIELD}.roundRobin.lastAssignedUid`\n\n/**\n * The most rules an org may keep. A first-match list longer than this is\n * one nobody can reason about, and the section refuses the fifty-first\n * rather than letting the document grow unbounded.\n */\nexport const CRM_ASSIGNMENT_RULES_MAX = 50\n\n/** The most members a round-robin pool may hold. */\nexport const CRM_ROUND_ROBIN_POOL_MAX = 50\n\n/**\n * The map of per-site CRM settings under `crm` on the ORG document, keyed\n * by host id. A field on the org, not a subcollection of the host: the\n * host-subcollection guards read a quoted `'hosts'` beside a site id as a\n * client path under `hosts/{hostId}`, which this is not, so the key is\n * named here and spelled nowhere else.\n */\nconst CRM_HOST_SETTINGS_KEY = 'hosts'\n\n/**\n * The field-path SEGMENTS of a site's default owner on the org document.\n *\n * Segments rather than a dotted string, because the host id is a document\n * id and a document id may contain a dot; joined with dots it would be read\n * as two path elements and the write would land beside the setting rather\n * than in it. A caller builds a `FieldPath` from these on either SDK.\n */\nexport function crmHostDefaultOwnerSegments(hostId: string): string[] {\n if (!hostId) throw new Error('a site default owner must name a site')\n return [ORG_CRM_SETTINGS_FIELD, CRM_HOST_SETTINGS_KEY, hostId, 'defaultOwnerUid']\n}\n\n/** The conditions a rule may name — every one present must hold. */\nexport interface CrmAssignmentRuleWhen {\n /** The capture door: `form`, `booking`, `order`, `manual`… */\n source?: ContactSource\n /** The form the capture came through, by its document id. */\n formId?: string\n /** The captured address's domain, lowercased, without a leading `@`. */\n emailDomain?: string\n /** A tag the capture carries or the contact already wears. */\n tag?: string\n}\n\n/** How a matching rule assigns: one member, or the next in the pool. */\nexport type CrmAssignmentTarget = { memberUid: string } | { roundRobin: true }\n\n/**\n * One rule as the CRM reads it — `OrgCrmAssignmentRule` with the source\n * narrowed to the capture vocabulary.\n */\nexport interface CrmAssignmentRule extends OrgCrmAssignmentRule {\n when: CrmAssignmentRuleWhen\n assign: CrmAssignmentTarget\n}\n\n/** The pool as the CRM reads it: never absent, possibly empty. */\nexport interface CrmRoundRobinPool {\n memberUids: string[]\n lastAssignedUid: string | null\n}\n\n/**\n * The org's assignment settings, read tolerantly off the raw document.\n *\n * Every field is optional on the document and the section writes each on\n * its own, so a reader that trusted the shape would throw on the first org\n * that has set the pool but never a rule. Malformed entries are dropped\n * rather than refused wholesale: one hand-edited rule must not stop the\n * others from assigning.\n */\nexport interface CrmAssignmentSettings {\n rules: CrmAssignmentRule[]\n pool: CrmRoundRobinPool\n /** Site id → default owner uid, for the sites that set one. */\n hostDefaultOwners: Record<string, string>\n}\n\nfunction isContactSourceValue(value: unknown): value is ContactSource {\n return typeof value === 'string' && value in CONTACT_SOURCE_LABELS\n}\n\nfunction cleanText(value: unknown, max: number): string | undefined {\n const text = typeof value === 'string' ? value.trim().slice(0, max) : ''\n return text || undefined\n}\n\n/** A rule as the document holds it, or `null` for one that cannot assign. */\nexport function readCrmAssignmentRule(raw: unknown): CrmAssignmentRule | null {\n if (!raw || typeof raw !== 'object' || Array.isArray(raw)) return null\n const entry = raw as Record<string, unknown>\n const id = cleanText(entry['id'], 64)\n if (!id) return null\n const whenRaw =\n entry['when'] && typeof entry['when'] === 'object' && !Array.isArray(entry['when'])\n ? (entry['when'] as Record<string, unknown>)\n : {}\n const when: CrmAssignmentRuleWhen = {}\n if (isContactSourceValue(whenRaw['source'])) when.source = whenRaw['source']\n const formId = cleanText(whenRaw['formId'], 128)\n if (formId) when.formId = formId\n // A person types a domain the way they see one in an address — `@acme.com`\n // — so the `@` is what the field means, not part of the domain.\n const emailDomain = normalizeCompanyDomain(\n String(whenRaw['emailDomain'] ?? '')\n .trim()\n .replace(/^@/, ''),\n )\n if (emailDomain) when.emailDomain = emailDomain\n const tag = cleanText(whenRaw['tag'], 60)?.toLowerCase()\n if (tag) when.tag = tag\n const assignRaw =\n entry['assign'] &&\n typeof entry['assign'] === 'object' &&\n !Array.isArray(entry['assign'])\n ? (entry['assign'] as Record<string, unknown>)\n : null\n if (!assignRaw) return null\n if (assignRaw['roundRobin'] === true) {\n return { id, when, assign: { roundRobin: true } }\n }\n const memberUid = cleanText(assignRaw['memberUid'], 128)\n return memberUid ? { id, when, assign: { memberUid } } : null\n}\n\nexport function readCrmAssignmentSettings(\n orgDocument: Record<string, unknown> | null | undefined,\n): CrmAssignmentSettings {\n const settings = (orgDocument ?? {})[ORG_CRM_SETTINGS_FIELD]\n const crm =\n settings && typeof settings === 'object' && !Array.isArray(settings)\n ? (settings as Record<string, unknown>)\n : {}\n const rules = (Array.isArray(crm['assignmentRules']) ? crm['assignmentRules'] : [])\n .map(readCrmAssignmentRule)\n .filter((rule): rule is CrmAssignmentRule => rule !== null)\n .slice(0, CRM_ASSIGNMENT_RULES_MAX)\n const roundRobin =\n crm['roundRobin'] && typeof crm['roundRobin'] === 'object'\n ? (crm['roundRobin'] as Record<string, unknown>)\n : {}\n const memberUids = [\n ...new Set(\n (Array.isArray(roundRobin['memberUids']) ? roundRobin['memberUids'] : [])\n .map((uid) => cleanText(uid, 128))\n .filter((uid): uid is string => Boolean(uid)),\n ),\n ].slice(0, CRM_ROUND_ROBIN_POOL_MAX)\n const hostSettings = crm[CRM_HOST_SETTINGS_KEY]\n const hosts =\n hostSettings && typeof hostSettings === 'object' && !Array.isArray(hostSettings)\n ? (hostSettings as Record<string, unknown>)\n : {}\n const hostDefaultOwners: Record<string, string> = {}\n for (const [hostId, value] of Object.entries(hosts)) {\n const owner =\n value && typeof value === 'object'\n ? cleanText((value as Record<string, unknown>)['defaultOwnerUid'], 128)\n : undefined\n if (owner) hostDefaultOwners[hostId] = owner\n }\n return {\n rules,\n pool: {\n memberUids,\n lastAssignedUid: cleanText(roundRobin['lastAssignedUid'], 128) ?? null,\n },\n hostDefaultOwners,\n }\n}\n\n/** The member a site hands unclaimed captures to, or `null` for nobody. */\nexport function crmHostDefaultOwner(\n orgDocument: Record<string, unknown> | null | undefined,\n hostId: string,\n): string | null {\n return readCrmAssignmentSettings(orgDocument).hostDefaultOwners[hostId] ?? null\n}\n\n/** What a rule is matched against: the capture, as its door described it. */\nexport interface CrmAssignmentCapture {\n source: ContactSource\n email: string\n formId?: string | null\n /** The capture's own tags and whatever the contact already wears. */\n tags?: readonly string[]\n}\n\n/**\n * The domain a rule's `emailDomain` is compared with — the address's own,\n * lowercased. Not `companyDomainForEmail`, which answers `null` for a public\n * mailbox: a team may well route every `gmail.com` sign-up to one rep, and\n * a rule that could not name a consumer domain could not say so.\n */\nexport function assignmentEmailDomain(email: string): string | null {\n const at = email.lastIndexOf('@')\n if (at < 0) return null\n return normalizeCompanyDomain(email.slice(at + 1))\n}\n\n/**\n * Whether every condition the rule names holds for this capture. A rule\n * with no condition matches every capture — the catch-all.\n */\nexport function assignmentRuleMatches(\n when: CrmAssignmentRuleWhen,\n capture: CrmAssignmentCapture,\n): boolean {\n if (when.source && when.source !== capture.source) return false\n if (when.formId && when.formId !== (capture.formId ?? '')) return false\n if (when.emailDomain && when.emailDomain !== assignmentEmailDomain(capture.email)) {\n return false\n }\n if (when.tag) {\n const worn = (capture.tags ?? []).map((tag) => String(tag).trim().toLowerCase())\n if (!worn.includes(when.tag)) return false\n }\n return true\n}\n\n/**\n * The pool in the order the next record should try it: the member after\n * the last recipient first, wrapping round, and the last recipient\n * themselves at the end — so a pool of one still assigns, and a pointer\n * naming somebody no longer in the pool starts from the top.\n */\nexport function roundRobinOrder(\n memberUids: readonly string[],\n lastAssignedUid: string | null | undefined,\n): string[] {\n if (!memberUids.length) return []\n const at = lastAssignedUid ? memberUids.indexOf(lastAssignedUid) : -1\n if (at < 0) return [...memberUids]\n return [...memberUids.slice(at + 1), ...memberUids.slice(0, at + 1)]\n}\n\n/**\n * How a rule reads in the settings list — its conditions in words and\n * where it sends the record — so a reader can tell two rules apart without\n * opening either. `memberLabel` turns a uid into a name; the section passes\n * the roster's.\n */\nexport function describeAssignmentRule(\n rule: CrmAssignmentRule,\n memberLabel: (uid: string) => string,\n): { when: string; assign: string } {\n const parts: string[] = []\n if (rule.when.source) parts.push(`source is ${CONTACT_SOURCE_LABELS[rule.when.source]}`)\n if (rule.when.formId) parts.push(`form is ${rule.when.formId}`)\n if (rule.when.emailDomain) parts.push(`email domain is ${rule.when.emailDomain}`)\n if (rule.when.tag) parts.push(`tagged ${rule.when.tag}`)\n return {\n when: parts.length ? parts.join(' and ') : 'Every capture',\n assign:\n 'roundRobin' in rule.assign ? 'Round robin' : memberLabel(rule.assign.memberUid),\n }\n}\n\n/**\n * A fresh rule id, unique among the ones the org already holds. Time and\n * entropy rather than a counter, so two admins adding a rule in two tabs\n * do not mint the same id and have one reorder clobber the other's rule.\n */\nexport function newAssignmentRuleId(existing: readonly string[]): string {\n for (;;) {\n const id = `rule-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}`\n if (!existing.includes(id)) return id\n }\n}\n\n/**\n * The `visibleTo` every CRM creator stamps — client or server, whichever\n * door the record comes in through.\n *\n * The contact create path's own expression (`upsertHostContact`), lifted\n * here so that a company, a deal or a task created from a site's console\n * lands in exactly the scope a contact captured on that site would: the\n * whole org when the org has chosen `defaultResourceScope: 'org'`, and\n * otherwise the sites that present as one sender — which, undeclared, is\n * this site alone. That is the agency's isolation, arrived at with nothing\n * configured, and the reason this is a function rather than a convention\n * eight creators are asked to remember.\n *\n * Widening past the group is an ACT — an org-wide member editing the scope\n * on the record — never a default. The rules refuse a scoped member creating\n * a record outside their own tokens, so a creator that bypassed this could\n * only make its record invisible to itself, never wider.\n */\nexport function crmScopeTokens(\n org: Record<string, unknown> | null | undefined,\n group: ConsentGroup,\n): ScopeToken[] {\n return (org ?? {})['defaultResourceScope'] === 'org'\n ? [ORG_SCOPE_TOKEN]\n : consentGroupScope(group)\n}\n\n/**\n * The `visibleTo` tokens a reader of this group may LIST, for the\n * `array-contains-any` every CRM listener filters by.\n *\n * The other half of {@link crmScopeTokens}: that is what a creator stamps,\n * this is what a reader asks for, and the two differ by exactly one token.\n * `'org'` leads because an org-wide record is visible to every site — an org\n * that widened its default deliberately still sees its own rows — and the\n * group's sites follow. The contacts list computes this inline; every other\n * CRM reader takes it from here so that none of them can drop the org token\n * and lose the org-wide rows, or forget the cap and have Firestore refuse\n * the query outright.\n *\n * Capped at {@link MAX_SCOPE_HOSTS}: a group wider than the operator's limit\n * is listed as far as the limit reaches, which is the contacts list's\n * behavior and the only one short of an error.\n */\nexport function crmReadTokens(group: ConsentGroup): ScopeToken[] {\n return [ORG_SCOPE_TOKEN, ...consentGroupScope(group)].slice(0, MAX_SCOPE_HOSTS)\n}\n\n/*==========================================\n * THE TEAM, AS A RECORD NAMES THEM (AGL-2614).\n *\n * An owner and an assignee are stored by uid — `orgs/{orgId}/members/{uid}`\n * is keyed by it, and a uid outlives every address change. But the people\n * who NAME an owner do not think in uids: an automation step is typed, a\n * CSV column is an address, and a picker lists names. So a reference\n * arrives as one of two things, and every reader that turns a reference\n * into a person has to accept both or the roster splits into the members\n * who can be named and the members who cannot.\n *\n * The member documents make the second case real. Two production paths\n * create a member document WITHOUT its `email` — a host-access re-grant,\n * and an add whose auth record carried none — and such a member was\n * unnameable everywhere a surface asked for an address: an automation\n * could not assign to them, and a roster mapper that dropped a row with no\n * address could not even list them. A name they still have (the roster's\n * `displayName`), and a uid they always have, so a label falls through to\n * the uid rather than to nothing, and a reference is resolved by uid first\n * and by address second.\n *\n * Roster-only, deliberately. The project's Auth records could resolve an\n * address the roster cannot, and would resolve people who are not on this\n * organization at all (AGL-1122); a reference that names nobody on the\n * roster names nobody.\n *=========================================*/\n\n/** One person on the team, as a picker lists them and a reference resolves to them. */\nexport interface CrmMemberOption {\n /** The account uid — what `ownerUid` and `assigneeUid` store. */\n uid: string\n /** The name a colleague recognizes: display name, else address, else the uid. */\n label: string\n /** The roster's address, when the member document carries one. */\n email?: string\n}\n\n/**\n * How a step or a column names somebody on the team: an address when the\n * text is one, otherwise a uid. `null` for a blank, which every caller\n * treats as \"nobody named\" rather than as a member called \"\".\n */\nexport type CrmMemberRef =\n | { kind: 'uid'; uid: string }\n | { kind: 'email'; email: string }\n\nexport function parseCrmMemberRef(value: unknown): CrmMemberRef | null {\n const text = String(value ?? '').trim()\n if (!text) return null\n if (text.includes('@')) {\n const email = normalizeContactEmail(text)\n return email ? { kind: 'email', email } : null\n }\n return { kind: 'uid', uid: text }\n}\n\n/**\n * A roster row as every CRM picker and column shows it.\n *\n * One mapping rather than four, because four is what the CRM had — and one\n * of them dropped a member whose document had neither a display name nor an\n * address, which is precisely the member the uid fallback exists for.\n */\nexport function crmMemberOption(\n member: Record<string, unknown>,\n): CrmMemberOption | null {\n const uid = String(member['$id'] ?? member['uid'] ?? '').trim()\n if (!uid) return null\n const displayName = String(member['displayName'] ?? '').trim()\n const email = String(member['email'] ?? '').trim()\n return {\n uid,\n label: displayName || email || uid,\n ...(email ? { email } : {}),\n }\n}\n\n/**\n * A roster row as a PICKER lists it: the name, and the address in\n * parentheses when the roster holds one that the name does not already\n * spell. Two teammates can share a display name — a workspace with two\n * \"Zach Gover\" rows cannot tell them apart by name alone, and the address is\n * the one thing the roster guarantees is unique. A column keeps `label`,\n * because a column is read at a glance and a picker is chosen from.\n */\nexport function crmMemberPickerLabel(\n member: Pick<CrmMemberOption, 'label' | 'email'>,\n): string {\n const label = String(member.label ?? '').trim()\n const email = String(member.email ?? '').trim()\n if (!email || email.toLowerCase() === label.toLowerCase()) return label || email\n return `${label} (${email})`\n}\n\n/**\n * The member a stored reference names: by uid first — the stored shape —\n * and by address second, so a record that carries an address where a uid\n * belongs (an older import, a hand edit) still names the person the roster\n * has under it. Undefined for nobody, which the caller renders honestly as\n * \"former member\" or as the reference itself rather than as unassigned.\n */\nexport function findOrgMember<T extends { uid: string; email?: string | null }>(\n members: readonly T[],\n ref: string | null | undefined,\n): T | undefined {\n const parsed = parseCrmMemberRef(ref)\n if (!parsed) return undefined\n if (parsed.kind === 'uid') {\n return members.find((member) => member.uid === parsed.uid)\n }\n return members.find(\n (member) => normalizeContactEmail(member.email) === parsed.email,\n )\n}\n\n/*==========================================\n * LEADS (AGL-2608).\n *\n * A lead is NOT one of the six collections above. It lives at\n * `hosts/{hostId}/leads/{personKey}`, written by `addHostLead` when a\n * visitor signs up, books or submits a form, and it is host-scoped by PATH:\n * no `visibleTo`, no facet map, private to the site that captured it. What\n * the CRM adds is the working state a sales team keeps on such a capture —\n * a status, an owner, notes — and the record of its conversion into the\n * contact, company and deal that live in the org collections.\n *\n * These fields are typed here rather than beside `addHostLead` because the\n * writer of the capture and the reader of the working state are different\n * programs: the capture door stamps none of them, and a lead that predates\n * this block carries none of them, which is why every field is optional and\n * {@link crmLeadStatus} reads an absent status as `new`.\n *=========================================*/\n\n/**\n * Where a lead stands, in the order a person works one.\n *\n * `nurturing` is the stage automated email holds a lead in: a sequence step\n * or a campaign email has reached it and no person has engaged yet. It is\n * open — the lead still needs somebody — but it is not untouched, so the\n * digest's unworked list leaves it out. Automation writes it only over\n * `new` (a person may also set it by hand), and a reply moves it on to\n * `working`.\n *\n * `qualified` is the CONVERTED state — a lead becomes a contact by being\n * qualified, and the conversion stamps `convertedContactId` beside it — and\n * `unqualified` is the closed-without-conversion state with its reason. A\n * fixed list rather than free text for the reason the lifecycle stages are:\n * the section filters on it and a report counts by it.\n */\nexport const CRM_LEAD_STATUSES = [\n 'new',\n 'nurturing',\n 'working',\n 'qualified',\n 'unqualified',\n] as const\n\nexport type CrmLeadStatus = (typeof CRM_LEAD_STATUSES)[number]\n\n/** How a lead status reads on screen — typed so a status cannot ship unlabeled. */\nexport const CRM_LEAD_STATUS_LABELS: Record<CrmLeadStatus, string> = {\n new: 'New',\n nurturing: 'Nurturing',\n working: 'Working',\n qualified: 'Qualified',\n unqualified: 'Unqualified',\n}\n\n/**\n * The statuses a lead still needs somebody's attention in — what the Leads\n * section shows by default, so the list opens on the work rather than on\n * the history.\n */\nexport const CRM_LEAD_OPEN_STATUSES: readonly CrmLeadStatus[] = [\n 'new',\n 'nurturing',\n 'working',\n]\n\n/**\n * The statuses that mean nobody needs to work the lead any more — the\n * operand of the `in` clause an open-lead figure subtracts with. Subtracts,\n * because an untouched lead carries no status field at all and Firestore\n * cannot select on a field's absence; the closed statuses are always\n * written, so they can be counted, and what remains is open. See\n * `openLeadsFromCounts`.\n */\nexport const CRM_LEAD_CLOSED_STATUSES: readonly CrmLeadStatus[] =\n CRM_LEAD_STATUSES.filter((status) => !CRM_LEAD_OPEN_STATUSES.includes(status))\n\n/**\n * The leads still needing somebody, from two server counts: every lead,\n * less the ones closed one way or the other.\n *\n * Subtraction rather than a count of the open statuses because a lead\n * nobody has touched carries NO status field — see `crmLeadStatus` — and\n * Firestore cannot select a document by a field's absence. The closed\n * statuses are always written, so they can be counted; what remains is\n * open. Clamped at zero for the moment between the two counts in which a\n * lead was closed.\n */\nexport function openLeadsFromCounts(total: number, closed: number): number {\n return Math.max(0, Math.round(Number(total) || 0) - Math.round(Number(closed) || 0))\n}\n\nexport function isCrmLeadStatus(value: unknown): value is CrmLeadStatus {\n return (\n typeof value === 'string' &&\n (CRM_LEAD_STATUSES as readonly string[]).includes(value)\n )\n}\n\n/**\n * The working state the CRM writes onto a lead document, beside what the\n * capture door wrote (`email`, `name`, `sources`, `submissionCount`,\n * `firstSeenAtMs`, `lastSeenAtMs`, the consent map).\n *\n * The four `converted*`/`dealId`/`companyId` fields are stamped ONLY by the\n * `crm/lead-convert` server route, in one write after the contact exists, so\n * a lead that carries `convertedContactId` names a contact that was really\n * created and a lead without it was never converted, whatever its status\n * says. `unqualifiedReason` travels with `status: 'unqualified'` and is the\n * one free-text field a report will want to read back.\n */\nexport interface CrmLeadFields extends CrmLeadProfile {\n status?: CrmLeadStatus\n /**\n * Which of the org's Lead status values of `status`'s meaning the lead\n * holds (AGL-3512); absent, the lead shows its meaning's default label —\n * see `crmLeadStatusLabel`.\n */\n statusLabel?: string\n /** The team member working the lead. */\n ownerUid?: string\n notes?: string\n unqualifiedReason?: string\n /** `orgs/{orgId}/contacts/{contactId}` — the person this lead became. */\n convertedContactId?: string\n convertedAtMs?: number\n /** The deal the conversion opened, when the converter asked for one. */\n dealId?: string\n /** The company the conversion created or linked, when it named one. */\n companyId?: string\n /**\n * The last verdict on the lead's address (AGL-3245) — bounced, blocked,\n * unsubscribed, do-not-contact — written by the platform's senders only,\n * never by a form or the lead's editor. Absent means nothing is known.\n */\n emailState?: EmailState\n /**\n * The campaigns the lead is filed under (AGL-3254): container ids from\n * `orgs/{orgId}/emailCampaigns`, at the top of the document the way a\n * form carries them (`container-membership.ts`), never names. Written by\n * the New lead drawer, the import, the bulk bar and a sequence's enroll;\n * handed to the contact's facet when the lead converts.\n */\n campaignIds?: string[]\n /**\n * The org's custom LEAD fields, keyed by each definition's `key`\n * (AGL-3272) — the same map a contact facet, a company and a deal keep,\n * judged against the definitions whose `object` is `lead`.\n *\n * It does NOT travel to the contact on convert. Keys are unique per\n * object, so a lead's `budget` and a contact's `budget` are two\n * definitions that may hold two types, and copying one map into the\n * other would write a value the receiving definition never agreed to.\n * Salesforce answers this with an explicit lead field mapping; until\n * there is one here the values stay on the lead, which a converted lead\n * is still read back for.\n */\n custom?: Record<string, CrmCustomValue>\n}\n\n/*==========================================\n * THE LEAD'S OWN PROFILE (AGL-3231).\n *\n * A Salesforce lead is a record of its own: it carries the person AND\n * their company as text — company, title, phone, website, address, the\n * source that produced it — and none of it is a contact until the lead\n * converts, when the convert step hands every field to the contact and the\n * account it creates. A lead that held only an address and a name could\n * not be worked without a contact beside it, which is how one person came\n * to sit in both lists; these fields are what let the lead stand alone.\n *\n * ## Text, not links\n *\n * `company` is the company's NAME, never a company id. A lead has not been\n * qualified, so the account it names may not deserve a record yet — a\n * thousand imported leads must not mint a thousand companies — and the\n * conversion is where the text becomes a link: it finds the company by\n * that name or by the address's domain, or creates it. The `companyId` on\n * `CrmLeadFields` is the conversion's stamp, written once, and never what\n * a lead carries while it is open.\n *\n * ## The same shapes the contact keeps\n *\n * The phone is E.164 through `normalizePhone`, the address is\n * `AglynPostalAddress` through `normalizeAddress`, tags are lower-cased and\n * capped as the contact's are, and the website is normalized as a\n * company's is — so the conversion copies values rather than translating\n * them, and a lead typed in the drawer and a contact typed in its drawer\n * refuse the same phone number in the same sentence.\n *\n * ## `leadSource` beside `sources`\n *\n * `sources` is the capture door's record of which SURFACES met the person\n * — `signup`, `booking`, `form:{id}`, `import` — and no file may rewrite\n * it. `leadSource` is what Salesforce calls Lead Source: one of the org's\n * own picklist values (\"Trade show\", \"Webinar\" — see THE CRM'S PICKLISTS\n * below), reported on and filtered by, and never derived. Two\n * fields because they answer two questions, and a file that could write\n * the first would be rewriting the site's own history.\n *=========================================*/\n\n/** The longest text one profile field holds — the contact's own cap. */\nexport const CRM_LEAD_TEXT_MAX = 120\n/** The most tags one lead keeps — the contact's own cap. */\nexport const CRM_LEAD_TAGS_MAX = 20\n/** The longest note a lead keeps — the lead page's own cap. */\nexport const CRM_LEAD_NOTES_MAX = 4000\n\nexport interface CrmLeadProfile {\n /** The company's name, as text — see the block header. */\n company?: string\n jobTitle?: string\n /** E.164 — `normalizePhone` before writing. */\n phone?: string\n /** An http(s) URL — `normalizeCompanyWebsite` before writing. */\n website?: string\n address?: AglynPostalAddress | null\n /** Lower-cased, deduplicated, at most {@link CRM_LEAD_TAGS_MAX}. */\n tags?: string[]\n /**\n * Where the lead came from — Salesforce's Lead Source: the LABEL of one\n * of the org's lead source values (see {@link CrmPicklist}).\n */\n leadSource?: string\n /*\n * SALESFORCE'S STANDARD LEAD FIELDS (AGL-3513), in the shapes the contact\n * and the company keep them, so a conversion copies values rather than\n * translating them.\n */\n /** One of the org's salutation values, by label (`Mr.`, `Dr.` …) — the contact's list. */\n salutation?: string\n /**\n * The person's given and family names. While either is set, the lead's\n * `name` is their composition ({@link crmLeadComposedName}), so the name\n * the list shows and searches is the person the structured fields name. A\n * lead holding only `name` keeps it as captured: nothing splits a stored\n * name into parts after the fact.\n */\n firstName?: string\n lastName?: string\n /** E.164, like {@link phone}. */\n mobilePhone?: string\n /** E.164. */\n fax?: string\n /** A label of the `industry` picklist — the list companies keep (AGL-3514). */\n industry?: string\n /** A label of the `rating` picklist — the list companies keep. */\n rating?: string\n /** Annual revenue in the minor unit of {@link currency}, the company's convention. */\n annualRevenueCents?: number\n /** Lowercase ISO 4217 of the revenue; `'usd'` when absent. */\n currency?: string\n numberOfEmployees?: number\n /**\n * The person asked not to be phoned. Stored only as `true`; absent is\n * \"may be called\". A hint beside every number and on the Call button,\n * never a block — the contact's rule.\n */\n doNotCall?: boolean\n}\n\n/** The profile's keys, in the order a card lists them. */\nexport const CRM_LEAD_PROFILE_KEYS = [\n 'salutation',\n 'firstName',\n 'lastName',\n 'company',\n 'jobTitle',\n 'phone',\n 'mobilePhone',\n 'fax',\n 'doNotCall',\n 'website',\n 'address',\n 'tags',\n 'leadSource',\n 'industry',\n 'rating',\n 'annualRevenueCents',\n 'currency',\n 'numberOfEmployees',\n] as const satisfies readonly (keyof CrmLeadProfile)[]\n\nexport type CrmLeadProfileKey = (typeof CRM_LEAD_PROFILE_KEYS)[number]\n\n/**\n * A profile as a writer receives it: a key present is a value to store, a\n * key present and `null` is a field to clear, a key absent is left alone.\n * The PATCH semantics every CRM write has.\n */\nexport type CrmLeadProfilePatch = {\n [K in CrmLeadProfileKey]?: NonNullable<CrmLeadProfile[K]> | null\n}\n\n/** Why one field of a profile could not be read, under the field. */\nexport type CrmLeadProfileErrors = Partial<Record<CrmLeadProfileKey, string>>\n\n/** The sentence an unreadable phone number is refused with, under the field. */\nexport const CRM_LEAD_PHONE_REFUSAL =\n 'That phone number could not be read. Enter it with its country code, like ' +\n '+1 512 555 0107.'\n\n/** The sentence an unreadable website is refused with, under the field. */\nexport const CRM_LEAD_WEBSITE_REFUSAL = 'Enter a web address, like acme.com'\n\n/**\n * Tags as a lead stores them: lower-cased, trimmed, deduplicated, capped —\n * from an array or a comma-separated string, so a file cell and a form\n * field are one input.\n */\nexport function normalizeCrmLeadTags(value: unknown): string[] {\n const items = Array.isArray(value)\n ? value\n : typeof value === 'string'\n ? value.split(',')\n : []\n return [\n ...new Set(\n items\n .map((tag) =>\n String(tag ?? '')\n .trim()\n .slice(0, 40)\n .toLowerCase(),\n )\n .filter(Boolean),\n ),\n ].slice(0, CRM_LEAD_TAGS_MAX)\n}\n\n/**\n * Read a profile off an untrusted body, one field at a time.\n *\n * Every key the body names is answered: as a normalized value, as `null`\n * when the body cleared it (an empty string, `null`, an empty list, a blank\n * address), or as an error under the field when the value cannot be\n * stored as typed. Keys the body does not name are not in the answer, so\n * a caller can write exactly what was asked. A `website` is normalized the\n * way a company's is; a phone the way a contact's is; both refuse rather\n * than store a value the record would then render as a link to nowhere.\n */\nexport function normalizeCrmLeadProfile(\n input: Record<string, unknown> | null | undefined,\n): { patch: CrmLeadProfilePatch; errors: CrmLeadProfileErrors } {\n const body = input ?? {}\n const patch: CrmLeadProfilePatch = {}\n const errors: CrmLeadProfileErrors = {}\n const text = (\n key: 'company' | 'jobTitle' | 'leadSource' | 'salutation' | 'industry' | 'rating',\n ) => {\n if (!(key in body)) return\n const value = String(body[key] ?? '')\n .trim()\n .replace(/\\s+/g, ' ')\n .slice(0, CRM_LEAD_TEXT_MAX)\n patch[key] = value || null\n }\n text('company')\n text('jobTitle')\n text('leadSource')\n text('salutation')\n text('industry')\n text('rating')\n // A name part as the contact's facet keeps one — see `composeContactName`.\n for (const key of ['firstName', 'lastName'] as const) {\n if (!(key in body)) continue\n const value = composeContactName(body[key], '')\n patch[key] = value || null\n }\n for (const key of ['phone', 'mobilePhone', 'fax'] as const) {\n if (!(key in body)) continue\n const raw = String(body[key] ?? '').trim()\n if (!raw) patch[key] = null\n else {\n const phone = normalizePhone(raw)\n if (phone) patch[key] = phone\n else errors[key] = CRM_LEAD_PHONE_REFUSAL\n }\n }\n if ('doNotCall' in body) {\n // Stored only as `true`: anything else is \"may be called\", a clear.\n patch.doNotCall = body['doNotCall'] === true ? true : null\n }\n /*\n * The revenue, its currency and the head count, read by the company's\n * own reader so a value one record refuses the other refuses in the same\n * words. A head count typed as digits is read as the number it spells.\n */\n const account: Record<string, unknown> = {}\n if ('annualRevenueCents' in body) account['annualRevenueCents'] = body['annualRevenueCents'] ?? null\n if ('currency' in body) account['currency'] = body['currency'] ?? null\n if ('numberOfEmployees' in body) {\n const raw = body['numberOfEmployees']\n const digits = typeof raw === 'string' ? raw.replace(/[\\s,]/g, '') : null\n account['numberOfEmployees'] =\n digits === null ? (raw ?? null) : digits === '' ? null : /^\\d+$/.test(digits) ? Number(digits) : raw\n }\n if (Object.keys(account).length) {\n const read = readCrmCompanyAccountFields(account)\n const into = patch as Record<string, unknown>\n for (const key of ['annualRevenueCents', 'currency', 'numberOfEmployees'] as const) {\n if (read.errors[key]) errors[key] = read.errors[key]\n else if (key in read.values) into[key] = read.values[key] ?? null\n }\n }\n if ('website' in body) {\n const raw = String(body['website'] ?? '').trim()\n if (!raw) patch.website = null\n else {\n const website = normalizeCompanyWebsite(raw)\n if (website) patch.website = website\n else errors.website = CRM_LEAD_WEBSITE_REFUSAL\n }\n }\n if ('address' in body) {\n const raw = body['address']\n patch.address =\n raw && typeof raw === 'object' && !Array.isArray(raw)\n ? normalizeAddress(raw as AglynPostalAddress)\n : null\n }\n if ('tags' in body) {\n const tags = normalizeCrmLeadTags(body['tags'])\n patch.tags = tags.length ? tags : null\n }\n return { patch, errors }\n}\n\n/**\n * The `name` a write must store beside a lead's first and last names\n * (AGL-3513), the contact's rule: while either part is set the name is\n * their composition, written whenever either moves. `undefined` when the\n * write names neither part, or when it leaves both blank — a name-only\n * lead keeps its name, and clearing both parts leaves the name as it\n * stands.\n */\nexport function crmLeadComposedName(\n stored: Readonly<Record<string, unknown>> | null | undefined,\n patch: Readonly<Pick<CrmLeadProfilePatch, 'firstName' | 'lastName'>>,\n): string | undefined {\n if (patch.firstName === undefined && patch.lastName === undefined) return undefined\n const part = (key: 'firstName' | 'lastName') =>\n patch[key] === undefined ? stored?.[key] : (patch[key] ?? '')\n return composeContactName(part('firstName'), part('lastName')) || undefined\n}\n\n/**\n * Each lead picklist field beside the lead source (AGL-3513): the list it\n * holds a value of, and the key the Leads list filters by. The lists are\n * the contact's and the company's own — one Salutation, one Industry, one\n * Rating per org — so a lead converts into the same values.\n */\nexport const CRM_LEAD_PICKLIST_FIELDS = [\n { field: 'salutation', picklistId: 'salutation' },\n { field: 'industry', picklistId: 'industry', keyField: 'industryKey' },\n { field: 'rating', picklistId: 'rating', keyField: 'ratingKey' },\n] as const\n\nexport type CrmLeadPicklistField = (typeof CRM_LEAD_PICKLIST_FIELDS)[number]['field']\n\n/**\n * The lead picklist fields a write names, judged against the org's lists\n * — the company's {@link judgeCrmCompanyPicklists} over the lead's fields:\n * `lists` holds each list the caller read (a list it did not read is\n * judged as the standard values alone), the lead's `current` value is kept\n * even when the list no longer offers it, and on a create a field not\n * named starts from its list's default.\n */\nexport function judgeCrmLeadPicklists(\n lists: Readonly<Partial<Record<CrmPicklistId, CrmPicklist>>>,\n requested: Readonly<Partial<Record<string, unknown>>>,\n options: { current?: Readonly<Partial<Record<string, unknown>>> | null; created?: boolean } = {},\n): {\n values: Partial<Record<CrmLeadPicklistField, string | null>>\n errors: Partial<Record<CrmLeadPicklistField, string>>\n} {\n const values: Partial<Record<CrmLeadPicklistField, string | null>> = {}\n const errors: Partial<Record<CrmLeadPicklistField, string>> = {}\n for (const { field, picklistId } of CRM_LEAD_PICKLIST_FIELDS) {\n const list = lists[picklistId] ?? effectiveCrmPicklist(picklistId, undefined)\n const raw = requested[field]\n if (raw === undefined) {\n const fallback = options.created ? picklistDefaultLabel(list) : null\n if (fallback) values[field] = fallback\n continue\n }\n if (raw === null || (typeof raw === 'string' && !raw.trim())) {\n values[field] = null\n continue\n }\n if (typeof raw !== 'string') {\n errors[field] = 'Must be text'\n continue\n }\n const judged = judgeCrmPicklistValue(picklistId, list, raw, options.current?.[field])\n if (judged.ok === false) errors[field] = judged.error\n else values[field] = judged.value\n }\n return { values, errors }\n}\n\n/*==========================================\n * THE CRM'S PICKLISTS (AGL-3298, AGL-3510).\n *\n * Salesforce gives every object standard picklist fields — Lead Source,\n * Lead Status, Industry, Stage — each shipping a STANDARD set of values\n * every org has, to which an admin adds the org's own. Here each such field\n * is one {@link CrmPicklistDefinition} in {@link CRM_PICKLIST_DEFINITIONS},\n * and the org's list for it is one document,\n * `orgs/{orgId}/crmPicklists/{picklistId}`, holding the values in order as\n * `{ id, label, active, group?, meaning? }` and an optional default for new\n * records. The engine under it — the merge with the standard values, the\n * judge, the options — is the platform's `picklists` module; this block is\n * the CRM's registry and the names every CRM reader already uses.\n *\n * ## Records store the LABEL\n *\n * A lead carries `leadSource: \"Trade show\"`, not an id. Every reader that\n * existed before the picklist — the CSV export, the REST resource, the\n * record facts, a saved view's filter, a report grouped by the field —\n * reads the text and keeps working, and a file exported from here\n * re-imports as is. What an id would have bought is a free rename; here a\n * rename (and a delete) REPLACES the old label on every record the\n * definition's `targets` name in the same operation, the way Salesforce's\n * own Replace does, so a report grouped by the field follows the rename\n * rather than splitting in two. The value's `id` is what the list itself is\n * keyed by — the default, a reorder, the manage page's rows — and it never\n * changes.\n *\n * ## Standard values, and the org's own\n *\n * A standard value is in every org's effective list whether or not the org\n * stored one; the org may relabel, reorder, regroup, deactivate or default\n * it — stored under the standard id — but not delete it. A stored value\n * whose id is a standard id is that standard value overridden, which is why\n * a list written down from the earlier starter set (whose slugs `web`,\n * `phone-inquiry`, `purchased-list`, `trade-show` and `other` are standard\n * ids) needs no record rewritten. Its `referral` and `partner` are not\n * standard, and stay as the org's own values. An org with NO document reads\n * the standard values only, so a record such an org holds as \"Referral\" or\n * \"Partner\" shows as not in the list until that value is written down as\n * the org's own.\n *\n * ## Inactive, not gone\n *\n * A deactivated value leaves every picker but stays on the records that\n * hold it, shown with an \"(inactive)\" hint, and a write that keeps a\n * record's current value is never refused for it. An added value is\n * removed only by Delete, which names the value its records move to — of\n * the same meaning, on a definition with meanings.\n *=========================================*/\n\n/** The CRM objects a picklist's values are kept on. */\nexport type CrmPicklistObject = CrmFieldObject | 'task'\n\n/**\n * The Fields tabs, in order: every object with custom fields, then Tasks,\n * which has standard picklists and no custom fields (AGL-3517).\n */\nexport const CRM_PICKLIST_OBJECTS: readonly CrmPicklistObject[] = [...CRM_FIELD_OBJECTS, 'task']\n\n/** How each object reads on the Fields tabs and in a refusal. */\nexport const CRM_PICKLIST_OBJECT_LABELS: Record<CrmPicklistObject, string> = {\n ...CRM_FIELD_OBJECT_LABELS,\n task: 'Tasks',\n}\n\nexport function isCrmPicklistObject(value: unknown): value is CrmPicklistObject {\n return typeof value === 'string' && (CRM_PICKLIST_OBJECTS as readonly string[]).includes(value)\n}\n\n/**\n * One place records hold a picklist's label. `field` is the record's own\n * field; `keyField`, when set, is the query key written beside it\n * ({@link crmPicklistKey}); `facet` means the label lives in each holder's\n * facet on a contact rather than on the record itself.\n *\n * `arrayKey` (AGL-3521) means `field` is a LIST of objects and the label is\n * each entry's `arrayKey` — a deal's contact roles, `{ contactId, role }`.\n * Such a target always names a `keyField`, which is then the list of the\n * keys its entries hold: the one array a query can find the records by.\n */\nexport interface CrmPicklistTarget {\n object: CrmPicklistObject\n field: string\n keyField?: string\n facet?: true\n arrayKey?: string\n /**\n * The field holding the GROUP of the value the record holds (AGL-3577) —\n * a lead's `leadSourceDirection` — which every move that can change a\n * label's group (a regroup, an add, a rename, a delete) rewrites.\n */\n groupField?: string\n}\n\n/** A standard picklist field of the CRM. */\nexport interface CrmPicklistDefinition extends PicklistSpec {\n /** The document id under `crmPicklists`, and the field's name in code. */\n id: string\n /** The field's name in a sentence's subject: \"Lead source must be one of: …\". */\n label: string\n /** The values in a sentence: \"no active lead sources\". */\n plural: string\n /** The Fields tab its values are managed on. */\n object: CrmPicklistObject\n /** Every place records hold its label — what a rename and a delete rewrite. */\n targets: readonly CrmPicklistTarget[]\n /** How each of `meanings` reads on screen — the Fields page's Means column (AGL-3512). */\n meaningLabels?: Readonly<Record<string, string>>\n /**\n * Meanings only the platform sets, which an org-added value may not take\n * (AGL-3512): a lead is Qualified by its conversion alone.\n */\n reservedMeanings?: readonly string[]\n}\n\n/**\n * Lead Source, with Salesforce's standard values and Aglyn's own (AGL-3519).\n * Each carries the group a report splits pipeline by: Inbound for a lead\n * who came to the org, Outbound for one the org went to, and Other in\n * neither. Aglyn's are the doors and the outreach the platform runs itself\n * — a website form, a booking, a sequence — stamped on a record the door\n * met first ({@link CRM_LEAD_SOURCE_ORIGINS}); a vendor the platform does\n * not run (a data provider, a sending tool) is an org's own value.\n */\nconst LEAD_SOURCE_DEFINITION = {\n id: 'leadSource',\n label: 'Lead source',\n plural: 'lead sources',\n object: 'lead',\n restricted: true,\n groups: [\n { id: 'inbound', label: 'Inbound' },\n { id: 'outbound', label: 'Outbound' },\n ],\n standardValues: [\n { id: 'web', label: 'Web', group: 'inbound' },\n { id: 'phone-inquiry', label: 'Phone inquiry', group: 'inbound' },\n { id: 'email-inquiry', label: 'Email inquiry', group: 'inbound' },\n { id: 'partner-referral', label: 'Partner referral', group: 'inbound' },\n { id: 'employee-referral', label: 'Employee referral', group: 'inbound' },\n { id: 'external-referral', label: 'External referral', group: 'inbound' },\n { id: 'advertisement', label: 'Advertisement', group: 'inbound' },\n { id: 'trade-show', label: 'Trade show', group: 'inbound' },\n { id: 'webinar', label: 'Webinar', group: 'inbound' },\n { id: 'word-of-mouth', label: 'Word of mouth', group: 'inbound' },\n // Aglyn's own doors (AGL-3519), each stamped by the door that met the person.\n { id: 'website-form', label: 'Website form', group: 'inbound' },\n { id: 'booking', label: 'Booking', group: 'inbound' },\n { id: 'newsletter-sign-up', label: 'Newsletter sign-up', group: 'inbound' },\n { id: 'site-member-sign-up', label: 'Site member sign-up', group: 'inbound' },\n { id: 'online-purchase', label: 'Online purchase', group: 'inbound' },\n { id: 'account-sign-up', label: 'Account sign-up', group: 'inbound' },\n { id: 'purchased-list', label: 'Purchased list', group: 'outbound' },\n // Aglyn's own outreach (AGL-3519).\n { id: 'sequence', label: 'Sequence', group: 'outbound' },\n { id: 'email-campaign', label: 'Email campaign', group: 'outbound' },\n { id: 'other', label: 'Other' },\n ],\n targets: [\n // The key the Leads list filters by moves with the label (AGL-3321).\n // So is its direction, the value's group (AGL-3577).\n {\n object: 'lead',\n field: 'leadSource',\n keyField: 'leadSourceKey',\n groupField: 'leadSourceDirection',\n },\n // A contact's lead source is each holder's own, like the rest of its profile.\n { object: 'contact', field: 'leadSource', facet: true },\n // A company's Account Source is a lead source value (AGL-3514).\n { object: 'company', field: 'accountSource', keyField: 'accountSourceKey' },\n // A deal's, stamped by the conversion that opened it (AGL-3516).\n { object: 'deal', field: 'leadSource', keyField: 'leadSourceKey' },\n ],\n} as const satisfies CrmPicklistDefinition\n\n/*------------------------------------------\n * LEAD STATUS (AGL-3512) — a SEMANTIC picklist.\n *\n * Every value MEANS one of {@link CRM_LEAD_STATUSES}, and the meaning stays\n * on the lead as `status`, which every query, count, index, automation and\n * rule reads exactly as before. The value's label sits beside it as\n * `statusLabel` — see {@link crmLeadStatusLabel}. Salesforce ships one\n * standard value per meaning; an org adds its own (\"Contacted\", \"Meeting\n * set\") under a meaning, never under Qualified, which only a conversion\n * sets.\n *-----------------------------------------*/\nconst LEAD_STATUS_DEFINITION = {\n id: 'leadStatus',\n label: 'Lead status',\n plural: 'lead statuses',\n object: 'lead',\n restricted: true,\n meanings: CRM_LEAD_STATUSES,\n meaningLabels: CRM_LEAD_STATUS_LABELS,\n reservedMeanings: ['qualified'],\n standardValues: [\n { id: 'new', label: CRM_LEAD_STATUS_LABELS.new, meaning: 'new' },\n { id: 'nurturing', label: CRM_LEAD_STATUS_LABELS.nurturing, meaning: 'nurturing' },\n { id: 'working', label: CRM_LEAD_STATUS_LABELS.working, meaning: 'working' },\n { id: 'qualified', label: CRM_LEAD_STATUS_LABELS.qualified, meaning: 'qualified' },\n { id: 'unqualified', label: CRM_LEAD_STATUS_LABELS.unqualified, meaning: 'unqualified' },\n ],\n targets: [{ object: 'lead', field: 'statusLabel' }],\n} as const satisfies CrmPicklistDefinition\n\n/*------------------------------------------\n * THE COMPANY PICKLISTS (AGL-3514).\n *\n * Salesforce's Account Type, Industry, Rating and Ownership, each kept on\n * the Companies tab. Industry and Rating are shared with leads, whose\n * targets join these definitions rather than repeating them. Account\n * Source is not a list of its own: it holds a lead source value, so it is\n * a target of the lead source definition above.\n *-----------------------------------------*/\n\n/** Salesforce's Account Type. */\nconst ACCOUNT_TYPE_DEFINITION = {\n id: 'accountType',\n label: 'Type',\n plural: 'account types',\n object: 'company',\n restricted: true,\n standardValues: [\n { id: 'analyst', label: 'Analyst' },\n { id: 'press', label: 'Press' },\n { id: 'competitor', label: 'Competitor' },\n { id: 'prospect', label: 'Prospect' },\n { id: 'customer', label: 'Customer' },\n { id: 'reseller', label: 'Reseller' },\n { id: 'integrator', label: 'Integrator' },\n { id: 'investor', label: 'Investor' },\n { id: 'partner', label: 'Partner' },\n { id: 'consulting', label: 'Consulting' },\n { id: 'other', label: 'Other' },\n ],\n targets: [{ object: 'company', field: 'type', keyField: 'typeKey' }],\n} as const satisfies CrmPicklistDefinition\n\n/**\n * Salesforce's Industry. A company written while the field was free text\n * keeps its text; a write that keeps it is never refused.\n */\nconst INDUSTRY_DEFINITION = {\n id: 'industry',\n label: 'Industry',\n plural: 'industries',\n object: 'company',\n restricted: true,\n standardValues: [\n { id: 'agriculture', label: 'Agriculture' },\n { id: 'apparel', label: 'Apparel' },\n { id: 'banking', label: 'Banking' },\n { id: 'biotechnology', label: 'Biotechnology' },\n { id: 'chemicals', label: 'Chemicals' },\n { id: 'communications', label: 'Communications' },\n { id: 'construction', label: 'Construction' },\n { id: 'consulting', label: 'Consulting' },\n { id: 'education', label: 'Education' },\n { id: 'electronics', label: 'Electronics' },\n { id: 'energy', label: 'Energy' },\n { id: 'engineering', label: 'Engineering' },\n { id: 'entertainment', label: 'Entertainment' },\n { id: 'environmental', label: 'Environmental' },\n { id: 'finance', label: 'Finance' },\n { id: 'food-and-beverage', label: 'Food & Beverage' },\n { id: 'government', label: 'Government' },\n { id: 'healthcare', label: 'Healthcare' },\n { id: 'hospitality', label: 'Hospitality' },\n { id: 'insurance', label: 'Insurance' },\n { id: 'machinery', label: 'Machinery' },\n { id: 'manufacturing', label: 'Manufacturing' },\n { id: 'media', label: 'Media' },\n { id: 'not-for-profit', label: 'Not For Profit' },\n { id: 'recreation', label: 'Recreation' },\n { id: 'retail', label: 'Retail' },\n { id: 'shipping', label: 'Shipping' },\n { id: 'technology', label: 'Technology' },\n { id: 'telecommunications', label: 'Telecommunications' },\n { id: 'transportation', label: 'Transportation' },\n { id: 'utilities', label: 'Utilities' },\n { id: 'other', label: 'Other' },\n ],\n targets: [\n { object: 'company', field: 'industry', keyField: 'industryKey' },\n // Leads (AGL-3513): the same list, so a lead converts into its value.\n { object: 'lead', field: 'industry', keyField: 'industryKey' },\n ],\n} as const satisfies CrmPicklistDefinition\n\n/** Salesforce's Rating. */\nconst RATING_DEFINITION = {\n id: 'rating',\n label: 'Rating',\n plural: 'ratings',\n object: 'company',\n restricted: true,\n standardValues: [\n { id: 'hot', label: 'Hot' },\n { id: 'warm', label: 'Warm' },\n { id: 'cold', label: 'Cold' },\n ],\n targets: [\n { object: 'company', field: 'rating', keyField: 'ratingKey' },\n // Leads (AGL-3513).\n { object: 'lead', field: 'rating', keyField: 'ratingKey' },\n ],\n} as const satisfies CrmPicklistDefinition\n\n/** Salesforce's Ownership. */\nconst OWNERSHIP_DEFINITION = {\n id: 'ownership',\n label: 'Ownership',\n plural: 'ownership values',\n object: 'company',\n restricted: true,\n standardValues: [\n { id: 'public', label: 'Public' },\n { id: 'private', label: 'Private' },\n { id: 'subsidiary', label: 'Subsidiary' },\n { id: 'other', label: 'Other' },\n ],\n targets: [{ object: 'company', field: 'ownership' }],\n} as const satisfies CrmPicklistDefinition\n\n/*------------------------------------------\n * CONTACT picklists (AGL-3515).\n *-----------------------------------------*/\n\n/**\n * Salutation, with Salesforce's standard values. Shared by every object\n * that addresses a person: defined here with the contact target, where the\n * label lives in each holder's facet like the rest of the profile.\n */\nconst SALUTATION_DEFINITION = {\n id: 'salutation',\n label: 'Salutation',\n plural: 'salutations',\n object: 'contact',\n restricted: true,\n standardValues: [\n { id: 'mr', label: 'Mr.' },\n { id: 'ms', label: 'Ms.' },\n { id: 'mrs', label: 'Mrs.' },\n { id: 'dr', label: 'Dr.' },\n { id: 'prof', label: 'Prof.' },\n ],\n targets: [\n { object: 'contact', field: 'salutation', facet: true },\n // Leads (AGL-3513): on the lead itself, carried to the facet on convert.\n { object: 'lead', field: 'salutation' },\n ],\n} as const satisfies CrmPicklistDefinition\n\n/*------------------------------------------\n * DEALS (AGL-3516).\n *-----------------------------------------*/\n\n/**\n * Salesforce's Opportunity Type. Plain: the deal stores the label, and\n * `typeKey` beside it for the Deals list's filter.\n */\nconst OPPORTUNITY_TYPE_DEFINITION = {\n id: 'opportunityType',\n label: 'Type',\n plural: 'deal types',\n object: 'deal',\n restricted: true,\n standardValues: [\n { id: 'existing-business', label: 'Existing Business' },\n { id: 'new-business', label: 'New Business' },\n ],\n targets: [{ object: 'deal', field: 'type', keyField: 'typeKey' }],\n} as const satisfies CrmPicklistDefinition\n\n/**\n * Salesforce's Opportunity Contact Role (AGL-3521): the part a contact\n * plays on a deal. Plain; the label sits in each entry of the deal's\n * `contactRoles`, and `contactRoleKeys` lists their keys, which is how a\n * rename or a delete finds the deals to rewrite.\n */\nconst OPPORTUNITY_CONTACT_ROLE_DEFINITION = {\n id: 'opportunityContactRole',\n label: 'Contact role',\n plural: 'contact roles',\n object: 'deal',\n restricted: true,\n standardValues: [\n { id: 'business-user', label: 'Business User' },\n { id: 'decision-maker', label: 'Decision Maker' },\n { id: 'economic-buyer', label: 'Economic Buyer' },\n { id: 'economic-decision-maker', label: 'Economic Decision Maker' },\n { id: 'evaluator', label: 'Evaluator' },\n { id: 'executive-sponsor', label: 'Executive Sponsor' },\n { id: 'influencer', label: 'Influencer' },\n { id: 'technical-buyer', label: 'Technical Buyer' },\n { id: 'other', label: 'Other' },\n ],\n targets: [{ object: 'deal', field: 'contactRoles', arrayKey: 'role', keyField: 'contactRoleKeys' }],\n} as const satisfies CrmPicklistDefinition\n\n/*\n * TASKS (AGL-3517) — Salesforce's Task Status, Priority, Type and Subject.\n *\n * Status, Priority and Type are SEMANTIC: each value means one of the\n * task's existing `status`, `priority` or `kind` values, which stay on the\n * document untouched for every query, reminder, due state, digest and\n * automation. The value's LABEL sits beside it in `statusLabel`,\n * `priorityLabel` or `typeLabel`; a task with no label shows the first\n * active value of its meaning (see `crmTaskPicklistLabel`). Subject is an\n * unrestricted combobox over the title: its values are suggestions, the\n * title stays free text, and no record holds a subject \"value\", so a\n * rename or a delete rewrites nothing.\n */\nconst TASK_STATUS_DEFINITION = {\n id: 'taskStatus',\n label: 'Status',\n plural: 'task statuses',\n object: 'task',\n restricted: true,\n meanings: ['open', 'done'],\n standardValues: [\n { id: 'not-started', label: 'Not Started', meaning: 'open' },\n { id: 'in-progress', label: 'In Progress', meaning: 'open' },\n { id: 'waiting', label: 'Waiting on someone else', meaning: 'open' },\n { id: 'deferred', label: 'Deferred', meaning: 'open' },\n { id: 'completed', label: 'Completed', meaning: 'done' },\n ],\n defaultValueId: 'not-started',\n targets: [{ object: 'task', field: 'statusLabel' }],\n} as const satisfies CrmPicklistDefinition\n\nconst TASK_PRIORITY_DEFINITION = {\n id: 'taskPriority',\n label: 'Priority',\n plural: 'task priorities',\n object: 'task',\n restricted: true,\n meanings: ['low', 'normal', 'high'],\n standardValues: [\n { id: 'high', label: 'High', meaning: 'high' },\n { id: 'normal', label: 'Normal', meaning: 'normal' },\n { id: 'low', label: 'Low', meaning: 'low' },\n ],\n defaultValueId: 'normal',\n targets: [{ object: 'task', field: 'priorityLabel' }],\n} as const satisfies CrmPicklistDefinition\n\nconst TASK_TYPE_DEFINITION = {\n id: 'taskType',\n label: 'Type',\n plural: 'task types',\n object: 'task',\n restricted: true,\n meanings: CRM_TASK_KINDS,\n standardValues: [\n { id: 'call', label: 'Call', meaning: 'call' },\n { id: 'email', label: 'Email', meaning: 'email' },\n { id: 'meeting', label: 'Meeting', meaning: 'meeting' },\n // Salesforce's \"Other\": a task that is none of the three conversations.\n { id: 'todo', label: 'To-do', meaning: 'todo' },\n ],\n defaultValueId: 'todo',\n targets: [{ object: 'task', field: 'typeLabel' }],\n} as const satisfies CrmPicklistDefinition\n\nconst TASK_SUBJECT_DEFINITION = {\n id: 'taskSubject',\n label: 'Subject',\n plural: 'task subjects',\n object: 'task',\n restricted: false,\n standardValues: [\n { id: 'call', label: 'Call' },\n { id: 'send-letter', label: 'Send Letter' },\n { id: 'send-quote', label: 'Send Quote' },\n { id: 'other', label: 'Other' },\n ],\n // Suggestions for a free-text title: nothing holds them, nothing moves.\n targets: [],\n} as const satisfies CrmPicklistDefinition\n\n/** Every standard picklist field the CRM keeps, one document each. */\nexport const CRM_PICKLIST_DEFINITIONS = [\n LEAD_SOURCE_DEFINITION,\n LEAD_STATUS_DEFINITION,\n // Companies (AGL-3514).\n ACCOUNT_TYPE_DEFINITION,\n INDUSTRY_DEFINITION,\n RATING_DEFINITION,\n OWNERSHIP_DEFINITION,\n // Contacts (AGL-3515).\n SALUTATION_DEFINITION,\n // Deals (AGL-3516).\n OPPORTUNITY_TYPE_DEFINITION,\n // Deal contact roles (AGL-3521).\n OPPORTUNITY_CONTACT_ROLE_DEFINITION,\n // Tasks (AGL-3517).\n TASK_STATUS_DEFINITION,\n TASK_PRIORITY_DEFINITION,\n TASK_TYPE_DEFINITION,\n TASK_SUBJECT_DEFINITION,\n] as const satisfies readonly CrmPicklistDefinition[]\n\nexport type CrmPicklistId = (typeof CRM_PICKLIST_DEFINITIONS)[number]['id']\n\n/** The picklists an org keeps, by document id. */\nexport const CRM_PICKLIST_IDS: readonly CrmPicklistId[] = CRM_PICKLIST_DEFINITIONS.map(\n (definition) => definition.id,\n)\n\n/** The lead source value set's document id. */\nexport const CRM_LEAD_SOURCE_PICKLIST: CrmPicklistId = 'leadSource'\n\n/** The salutation value set's document id (AGL-3515). */\nexport const CRM_SALUTATION_PICKLIST: CrmPicklistId = 'salutation'\n/** A deal's Type value set's document id (AGL-3516). */\nexport const CRM_OPPORTUNITY_TYPE_PICKLIST: CrmPicklistId = 'opportunityType'\n/** A deal contact's role value set's document id (AGL-3521). */\nexport const CRM_OPPORTUNITY_CONTACT_ROLE_PICKLIST: CrmPicklistId = 'opportunityContactRole'\n\n/**\n * The built-in Lead source value each first-party door stamps (AGL-3519), by\n * the door's word for how it met a person — a capture's source, or the\n * outreach that reached them. `api`, `import` and `manual` are ways a record\n * was ADDED, not where a person came from, and stamp nothing.\n */\nexport const CRM_LEAD_SOURCE_ORIGINS: Readonly<Record<string, string>> = {\n form: 'website-form',\n booking: 'booking',\n newsletter: 'newsletter-sign-up',\n member: 'site-member-sign-up',\n order: 'online-purchase',\n account: 'account-sign-up',\n sequence: 'sequence',\n emailCampaign: 'email-campaign',\n}\n\nexport function isCrmPicklistId(value: unknown): value is CrmPicklistId {\n return typeof value === 'string' && (CRM_PICKLIST_IDS as readonly string[]).includes(value)\n}\n\n/** The definition an id names, or `null` for one the registry does not hold. */\nexport function crmPicklistDefinition(id: unknown): CrmPicklistDefinition | null {\n return CRM_PICKLIST_DEFINITIONS.find((definition) => definition.id === id) ?? null\n}\n\n/** The definitions whose values are managed on one Fields tab, in registry order. */\nexport function crmPicklistDefinitionsFor(object: CrmPicklistObject): CrmPicklistDefinition[] {\n return CRM_PICKLIST_DEFINITIONS.filter((definition) => definition.object === object)\n}\n\n/** The definition behind a registered id — total, because the id is typed. */\nfunction definitionOf(id: CrmPicklistId): CrmPicklistDefinition {\n return crmPicklistDefinition(id) as CrmPicklistDefinition\n}\n\n/** The most values one picklist holds — a menu, not a table. */\nexport const CRM_PICKLIST_VALUES_MAX = PICKLIST_VALUES_MAX\n\n/** One value of a picklist. `id` never changes; `label` is what records store. */\nexport type CrmPicklistValue = PicklistValue\n\n/** A picklist as stored and as every reader takes it. */\nexport type CrmPicklist = PicklistValueSet\n\n/** One option of a picklist select. */\nexport type CrmPicklistOption = PicklistOption\n\n/**\n * The values an org without a lead source document offered before the\n * standard set was built in. \"Referral\" and \"Partner\" are not standard: a\n * record holding one, in an org that never stored its list, reads as not in\n * the list until the value is written down as the org's own.\n */\nexport const CRM_LEAD_SOURCE_STARTER_LABELS: readonly string[] = [\n 'Web',\n 'Phone inquiry',\n 'Referral',\n 'Partner',\n 'Purchased list',\n 'Trade show',\n 'Other',\n]\n\n/** A label as a picklist stores it: trimmed, single-spaced, capped. */\nexport const normalizeCrmPicklistLabel = normalizePicklistLabel\n\n/** A new value's id — see `mintPicklistValueId`. */\nexport const crmPicklistValueId = mintPicklistValueId\n\n/** A picklist of active values from labels, in order, with no default. */\nexport const crmPicklistFromLabels = picklistFromLabels\n\n/**\n * A stored document as a picklist, or `null` for one that holds none —\n * tolerant, as `normalizePicklistValueSet` reads it, and without the\n * standard values merged in. {@link effectiveCrmPicklist} is what a reader\n * judges against.\n */\nexport function normalizeCrmPicklist(raw: unknown): CrmPicklist | null {\n return normalizePicklistValueSet(raw)\n}\n\n/** An org's list for one picklist as every reader should take it — see the block header. */\nexport function effectiveCrmPicklist(id: CrmPicklistId, raw: unknown): CrmPicklist {\n return effectivePicklistValueSet(definitionOf(id), raw)\n}\n\n/** The org's lead source list as every reader should take it. */\nexport function effectiveCrmLeadSourcePicklist(raw: unknown): CrmPicklist {\n return effectiveCrmPicklist(CRM_LEAD_SOURCE_PICKLIST, raw)\n}\n\n/**\n * The label a door stamps for `origin` (AGL-3519): its built-in value as the\n * org spells it, while that value is ACTIVE — an org that deactivated it has\n * said not to file people under it — else `null`, as for a word with none.\n */\nexport function crmLeadSourceForOrigin(picklist: CrmPicklist, origin: unknown): string | null {\n const id = typeof origin === 'string' ? CRM_LEAD_SOURCE_ORIGINS[origin] : undefined\n if (!id || !Object.hasOwn(CRM_LEAD_SOURCE_ORIGINS, origin as string)) return null\n const value = picklist.values.find((entry) => entry.id === id)\n return value?.active ? value.label : null\n}\n\n/** Whether a value is one of a picklist's standard values — computed from its id, never stored. */\nexport function isStandardCrmPicklistValue(id: CrmPicklistId, valueId: string): boolean {\n return isStandardPicklistValueId(definitionOf(id), valueId)\n}\n\n/** The value a label names, in any case or spacing — `null` for none. */\nexport const crmPicklistValueByLabel = picklistValueByLabel\n\n/** The values a picker offers. */\nexport const crmPicklistActiveValues = picklistActiveValues\n\n/** The label a new record starts with — only while the default value is active. */\nexport const crmPicklistDefaultLabel = picklistDefaultLabel\n\n/**\n * The sentence a value outside a restricted picklist is refused with,\n * naming what the list allows, and where to add one when it allows none.\n */\nexport function crmPicklistRefusal(id: CrmPicklistId, picklist: CrmPicklist): string {\n const definition = definitionOf(id)\n return picklistRefusalSentence(picklist, {\n field: definition.label,\n empty:\n `This organization has no active ${definition.plural}. Add one under ` +\n `CRM › Fields › ${CRM_PICKLIST_OBJECT_LABELS[definition.object]}.`,\n })\n}\n\n/** The sentence a lead source outside the list is refused with. */\nexport function crmLeadSourceRefusal(picklist: CrmPicklist): string {\n return crmPicklistRefusal(CRM_LEAD_SOURCE_PICKLIST, picklist)\n}\n\n/**\n * Whether a write may store `value` as a record's value of picklist `id`,\n * and the label it stores — `judgePicklistValue` under the definition's\n * restriction: blank clears, an active value stores the list's spelling,\n * the record's `current` value is always kept, and on a restricted\n * picklist anything else is refused with {@link crmPicklistRefusal}.\n */\nexport function judgeCrmPicklistValue(\n id: CrmPicklistId,\n picklist: CrmPicklist,\n value: unknown,\n current?: unknown,\n): PicklistJudgement {\n return judgePicklistValue(picklist, value, {\n restricted: definitionOf(id).restricted,\n current,\n refusal: () => crmPicklistRefusal(id, picklist),\n })\n}\n\n/**\n * A deal's contact roles with each role judged against the org's\n * `opportunityContactRole` list (AGL-3521): an active value stored as the\n * list spells it, a blank cleared, and the role a contact already holds on\n * the deal (`current`) kept — or the first refusal, naming what the list\n * allows.\n */\nexport function judgeDealContactRoles(\n picklist: CrmPicklist,\n roles: readonly CrmDealContactRole[],\n current: readonly CrmDealContactRole[] = [],\n): { ok: true; roles: CrmDealContactRole[] } | { ok: false; error: string } {\n const judged: CrmDealContactRole[] = []\n for (const row of roles) {\n const held = current.find((entry) => entry.contactId === row.contactId)?.role\n const verdict = judgeCrmPicklistValue(\n 'opportunityContactRole',\n picklist,\n row.role ?? '',\n held,\n )\n if (verdict.ok === false) return verdict\n judged.push({\n contactId: row.contactId,\n ...(verdict.value ? { role: verdict.value } : {}),\n primary: row.primary,\n })\n }\n return { ok: true, roles: judged }\n}\n\n/** {@link judgeCrmPicklistValue} for the lead source. */\nexport function judgeCrmLeadSource(\n picklist: CrmPicklist,\n value: unknown,\n current?: unknown,\n): PicklistJudgement {\n return judgeCrmPicklistValue(CRM_LEAD_SOURCE_PICKLIST, picklist, value, current)\n}\n\n/**\n * What a select offers for a record holding `current`: every active value\n * in order, then the record's own value when the list would not otherwise\n * show it, so the select keeps what the record holds until it is changed.\n */\nexport const crmPicklistOptions = picklistOptions\n\n/**\n * Where a label sorts: its value's position in the list, unlisted values\n * after every listed one, and a record with none last. The order the admin\n * chose is the order a sort by the field reads in, as Salesforce's does.\n */\nexport const crmPicklistRank = picklistRank\n\n/*------------------------------------------\n * A LEAD'S STATUS LABEL (AGL-3512).\n *\n * `status` is the meaning and stays the truth: a writer that knows only the\n * meaning — an automation, an inbox reply, a rule — sets `status` alone and\n * the lead still reads right. `statusLabel` names which of the org's values\n * of that meaning the lead holds, and is believed only while it IS one of\n * that meaning; otherwise, or when absent, the lead shows the default value\n * when it is active and of that meaning, else the first active value of\n * that meaning, else the standard label.\n *-----------------------------------------*/\n\n/** The lead status value set's document id. */\nexport const CRM_LEAD_STATUS_PICKLIST: CrmPicklistId = 'leadStatus'\n\n/** The field a lead's status label is stored in, beside `status`. */\nexport const CRM_LEAD_STATUS_LABEL_FIELD = 'statusLabel'\n\n/** The org's lead status list as every reader should take it. */\nexport function effectiveCrmLeadStatusPicklist(raw: unknown): CrmPicklist {\n return effectiveCrmPicklist(CRM_LEAD_STATUS_PICKLIST, raw)\n}\n\n/** The standard lead statuses alone — what a reader answers before the org's list is read. */\nexport const STANDARD_CRM_LEAD_STATUS_PICKLIST: CrmPicklist = effectiveCrmLeadStatusPicklist(null)\n\n/**\n * The value a lead with `status` shows when it names none of its own: the\n * default when active and of that meaning, else the first active value of\n * that meaning, else the first of that meaning at all.\n */\nexport function crmLeadStatusValueFor(\n picklist: CrmPicklist,\n status: CrmLeadStatus,\n): CrmPicklistValue | null {\n const ofMeaning = picklist.values.filter((value) => value.meaning === status)\n const preferred = ofMeaning.find((value) => value.id === picklist.defaultValueId && value.active)\n return preferred ?? ofMeaning.find((value) => value.active) ?? ofMeaning[0] ?? null\n}\n\n/** The label a writer stamps beside `status` when it sets a meaning and names no value. */\nexport function crmLeadStatusLabelFor(picklist: CrmPicklist, status: CrmLeadStatus): string {\n return crmLeadStatusValueFor(picklist, status)?.label ?? CRM_LEAD_STATUS_LABELS[status]\n}\n\n/**\n * How a lead's status reads: its own `statusLabel` while that is a value of\n * its `status`'s meaning (as the list spells it), else the meaning's label\n * by {@link crmLeadStatusLabelFor}. A label the list no longer holds is shown\n * as stored while nothing contradicts it — a value deleted without moving\n * every lead off it reads as what the lead was given.\n */\nexport function crmLeadStatusLabel(\n lead: Pick<CrmLeadFields, 'status' | 'statusLabel'> | null | undefined,\n picklist: CrmPicklist = STANDARD_CRM_LEAD_STATUS_PICKLIST,\n): string {\n const status = crmLeadStatus(lead)\n const held = normalizeCrmPicklistLabel(lead?.statusLabel)\n if (held) {\n const value = picklistValueByLabel(picklist, held)\n if (value?.meaning === status) return value.label\n if (!value) return held\n }\n return crmLeadStatusLabelFor(picklist, status)\n}\n\n/** A status write, as both fields a lead stores — or why it is refused. */\nexport type CrmLeadStatusWrite =\n | { ok: true; status: CrmLeadStatus; statusLabel: string }\n | { ok: false; error: string }\n\n/**\n * What a write naming a lead status stores (AGL-3512): an ACTIVE value's\n * label, in any case or spacing, stores its meaning and its label; one of\n * the meanings themselves (`working`) stores that meaning and the label\n * {@link crmLeadStatusLabelFor} gives it; the lead's `current` label is\n * kept even when inactive. `allowed` narrows the meanings this door may\n * set — every door but a conversion leaves Qualified out — and anything\n * else is refused naming what the door accepts.\n */\nexport function resolveCrmLeadStatusWrite(\n picklist: CrmPicklist,\n requested: unknown,\n options: {\n allowed?: readonly CrmLeadStatus[]\n current?: Pick<CrmLeadFields, 'status' | 'statusLabel'> | null\n } = {},\n): CrmLeadStatusWrite {\n const allowed = options.allowed ?? CRM_LEAD_STATUSES\n const text = normalizeCrmPicklistLabel(requested)\n const refuse = (): CrmLeadStatusWrite => {\n const labels = picklist.values\n .filter((value) => value.active && allowed.includes(value.meaning as CrmLeadStatus))\n .map((value) => value.label)\n return { ok: false, error: `Lead status must be one of: ${labels.join(', ')}.` }\n }\n if (!text) return refuse()\n const value = picklistValueByLabel(picklist, text)\n const current = options.current ? normalizeCrmPicklistLabel(options.current.statusLabel) : ''\n const keeps = Boolean(value && current && picklistLabelKeyOf(current) === picklistLabelKeyOf(value.label))\n if (value && isCrmLeadStatus(value.meaning) && (value.active || keeps)) {\n return allowed.includes(value.meaning)\n ? { ok: true, status: value.meaning, statusLabel: value.label }\n : refuse()\n }\n const meaning = text.toLowerCase()\n if (isCrmLeadStatus(meaning) && allowed.includes(meaning)) {\n return { ok: true, status: meaning, statusLabel: crmLeadStatusLabelFor(picklist, meaning) }\n }\n return refuse()\n}\n\nconst picklistLabelKeyOf = (label: string): string => normalizeCrmPicklistLabel(label).toLowerCase()\n\n/**\n * The values a lead status select offers for a lead holding `current`:\n * every active value whose meaning `allowed` admits, in the list's order,\n * then the lead's own value when the list would not otherwise show it.\n */\nexport function crmLeadStatusOptions(\n picklist: CrmPicklist,\n allowed: readonly CrmLeadStatus[] = CRM_LEAD_STATUSES,\n current?: Pick<CrmLeadFields, 'status' | 'statusLabel'> | null,\n): Array<{ label: string; status: CrmLeadStatus; inactive: boolean }> {\n const options = picklist.values\n .filter((value) => value.active && allowed.includes(value.meaning as CrmLeadStatus))\n .map((value) => ({ label: value.label, status: value.meaning as CrmLeadStatus, inactive: false }))\n if (current) {\n const label = crmLeadStatusLabel(current, picklist)\n if (!options.some((option) => option.label === label)) {\n options.push({ label, status: crmLeadStatus(current), inactive: true })\n }\n }\n return options\n}\n\n/*==========================================\n * SEMANTIC PICKLISTS ON A TASK (AGL-3517).\n *\n * Status, Priority and Type keep the task's own `status`, `priority` and\n * `kind` as the MEANING every query, reminder and automation reads, and\n * store the org's label beside it. These helpers are the two directions:\n * what a task shows for its meaning and label, and what a write stores for\n * a label or a meaning a person, a file or an API caller named.\n *=========================================*/\n\n/** The label a value of `meaning` reads as: the first active one, else the first at all. */\nexport function crmPicklistMeaningLabel(picklist: CrmPicklist, meaning: unknown): string | null {\n if (typeof meaning !== 'string' || !meaning) return null\n const held = picklist.values.filter((value) => value.meaning === meaning)\n return (held.find((value) => value.active) ?? held[0])?.label ?? null\n}\n\n/**\n * What a record shows for a semantic field: its own label when it holds\n * one, else the first active value of its meaning, else the meaning itself.\n */\nexport function crmPicklistShownLabel(\n picklist: CrmPicklist,\n meaning: unknown,\n label: unknown,\n): string {\n const own = normalizeCrmPicklistLabel(label)\n if (own) return own\n return crmPicklistMeaningLabel(picklist, meaning) ?? String(meaning ?? '')\n}\n\n/**\n * The label a NEW record of `meaning` starts with: the list's default when\n * it means that, else the first active value that does.\n */\nexport function crmPicklistLabelForNew(picklist: CrmPicklist, meaning: unknown): string | null {\n const fallback = crmPicklistDefaultLabel(picklist)\n const value = fallback ? crmPicklistValueByLabel(picklist, fallback) : null\n return value && value.meaning === meaning ? value.label : crmPicklistMeaningLabel(picklist, meaning)\n}\n\n/** What a semantic write stores: the meaning and the label beside it. */\nexport type CrmSemanticPicklistWrite =\n | { ok: true; meaning: string; label: string | null }\n | { ok: false; error: string }\n\n/**\n * A label OR a meaning, as a write names it, resolved against picklist `id`:\n *\n * - an active value's label (any case or spacing) stores that value's\n * label and its meaning;\n * - the record's `current` label is kept, with its value's meaning, even\n * when the value has since been deactivated;\n * - one of the definition's meanings (`high`, `done`, `call`) stores that\n * meaning with the first active value's label — or the record's current\n * label when that already means it;\n * - anything else is refused naming the values the list allows.\n *\n * `null` answers a blank input: the caller keeps its own default.\n */\nexport function resolveCrmSemanticPicklistWrite(\n id: CrmPicklistId,\n picklist: CrmPicklist,\n input: unknown,\n current?: unknown,\n): CrmSemanticPicklistWrite | null {\n const text = normalizeCrmPicklistLabel(input)\n if (!text) return null\n const definition = definitionOf(id)\n const meanings = (definition.meanings ?? []) as readonly string[]\n const held = normalizeCrmPicklistLabel(current)\n const byLabel = crmPicklistValueByLabel(picklist, text)\n const keeps = held && held.toLowerCase() === text.toLowerCase()\n if (byLabel?.meaning && (byLabel.active || keeps)) {\n return { ok: true, meaning: byLabel.meaning, label: keeps ? held : byLabel.label }\n }\n const meaning = meanings.find((entry) => entry === text.toLowerCase())\n if (meaning) {\n const heldValue = held ? crmPicklistValueByLabel(picklist, held) : null\n return {\n ok: true,\n meaning,\n label: heldValue?.meaning === meaning ? held : crmPicklistMeaningLabel(picklist, meaning),\n }\n }\n return { ok: false, error: crmPicklistRefusal(id, picklist) }\n}\n\n/** A task's three semantic picklists, as every task surface reads them. */\nexport interface CrmTaskPicklists {\n status: CrmPicklist\n priority: CrmPicklist\n type: CrmPicklist\n}\n\n/** The picklist id behind each of a task's semantic fields. */\nexport const CRM_TASK_PICKLIST_IDS = {\n status: 'taskStatus',\n priority: 'taskPriority',\n type: 'taskType',\n} as const satisfies Record<keyof CrmTaskPicklists, CrmPicklistId>\n\n/** The org's three task lists from their stored documents — standard values alone for none. */\nexport function effectiveCrmTaskPicklists(\n raw: Partial<Record<keyof CrmTaskPicklists, unknown>> = {},\n): CrmTaskPicklists {\n return {\n status: effectiveCrmPicklist(CRM_TASK_PICKLIST_IDS.status, raw.status),\n priority: effectiveCrmPicklist(CRM_TASK_PICKLIST_IDS.priority, raw.priority),\n type: effectiveCrmPicklist(CRM_TASK_PICKLIST_IDS.type, raw.type),\n }\n}\n\n/** What a task shows for its status, priority and type — see `crmPicklistShownLabel`. */\nexport function crmTaskPicklistLabels(\n task: Partial<Pick<CrmTask, 'status' | 'statusLabel' | 'priority' | 'priorityLabel' | 'kind' | 'typeLabel'>>,\n picklists: CrmTaskPicklists,\n): { status: string; priority: string; type: string } {\n return {\n status: crmPicklistShownLabel(picklists.status, task.status ?? 'open', task.statusLabel),\n priority: crmPicklistShownLabel(picklists.priority, task.priority ?? 'normal', task.priorityLabel),\n type: crmPicklistShownLabel(picklists.type, task.kind ?? 'todo', task.typeLabel),\n }\n}\n\n/**\n * The status a tick or an untick writes: done with the first active done\n * value's label (\"Completed\"), or open with the label a new task starts\n * with (\"Not Started\").\n */\nexport function crmTaskStatusWrite(\n picklist: CrmPicklist,\n done: boolean,\n): { status: CrmTaskStatus; statusLabel: string | null } {\n return done\n ? { status: 'done', statusLabel: crmPicklistMeaningLabel(picklist, 'done') }\n : { status: 'open', statusLabel: crmPicklistLabelForNew(picklist, 'open') }\n}\n\n/** The labels a new task of these meanings starts with. */\nexport function crmTaskLabelsForNew(\n picklists: CrmTaskPicklists,\n meanings: Pick<CrmTask, 'kind' | 'priority'> & { status?: CrmTaskStatus },\n): { statusLabel: string | null; priorityLabel: string | null; typeLabel: string | null } {\n return {\n statusLabel: crmPicklistLabelForNew(picklists.status, meanings.status ?? 'open'),\n priorityLabel: crmPicklistLabelForNew(picklists.priority, meanings.priority),\n typeLabel: crmPicklistLabelForNew(picklists.type, meanings.kind),\n }\n}\n\n/** The name a lead is listed under: the name it carries, else its address. */\nexport function crmLeadDisplayName(\n lead: Record<string, unknown> | null | undefined,\n): string {\n const name = String(lead?.['name'] ?? '').trim()\n return name || String(lead?.['email'] ?? '').trim()\n}\n\n/**\n * A lead's status as the list and the filter should read it.\n *\n * `new` for a lead that carries no status at all — every lead captured\n * before the CRM existed, and every lead the capture door writes today,\n * because `addHostLead` stamps none. Reading the absence as `new` is what\n * lets the section open on the leads a site already holds rather than on an\n * empty list until each one has been touched once. A stored value the union\n * does not name also reads as `new`: it is a document some other writer\n * produced, and refusing to list it would hide a person.\n */\nexport function crmLeadStatus(\n lead: Pick<CrmLeadFields, 'status'> | null | undefined,\n): CrmLeadStatus {\n const status = lead?.status\n return isCrmLeadStatus(status) ? status : 'new'\n}\n\n/** Whether a lead still needs working — see {@link CRM_LEAD_OPEN_STATUSES}. */\nexport function isCrmLeadOpen(\n lead: Pick<CrmLeadFields, 'status'> | null | undefined,\n): boolean {\n return CRM_LEAD_OPEN_STATUSES.includes(crmLeadStatus(lead))\n}\n\n/*==========================================\n * SAVED VIEWS (AGL-2617) — a list the way one person works it.\n *\n * \"My open leads in Texas\" is a list a rep opens every morning, and it is\n * three things at once: the filters that pick the people, the columns they\n * want to see about them, and the order they want them in. A `ContactSegment`\n * kept the first of those for two dimensions — tags and capture sources —\n * and nothing kept the rest, so every reload put the reader back at the\n * whole list. A view keeps all three, for every list in the CRM, under a\n * name, and is addressable: `?view=<id>` on the section's own URL opens it.\n *\n * ## The filters are the LIST'S grammar, not a second one\n *\n * A clause here is `{ field, op, value }` — structurally the\n * `ListFilterRequest` the shared list-filter grammar already speaks, the\n * request a grid's filter panel produces and the one the query translator\n * and the in-memory matcher both read. A view stores a list of them and\n * nothing more, so a view can only ask what the list can already answer,\n * and a list that gains a filterable field gains it for its views at no\n * cost. The type is restated here rather than imported because this module\n * is pure data and the grammar's home is a UI library; the shape is the\n * contract, and `normalizeCrmViewFilters` is what holds a stored document\n * to it.\n *\n * ## Mine, and shared\n *\n * A view is one person's working arrangement until they say otherwise, so\n * it lists only for its owner; `shared` puts it in front of everyone who\n * can read the section. Both are stamped `visibleTo` like every CRM row,\n * because a view is a description of the people it selects and answers to\n * the same authority they do — a private view is hidden by the LISTING, not\n * by the rules, and the rules let the creator or an org-wide member change\n * or remove it.\n *\n * ## The default is the reader's, not the view's\n *\n * Which view a section opens on is a preference of one person for one\n * organization, so it lives on the reader's own profile document beside\n * their notification mutes rather than on the view — a shared view somebody\n * else made their default must not become everybody's.\n *=========================================*/\n\n/** The lists a view can be saved for — every CRM section that has one. */\nexport const CRM_VIEW_SECTIONS = [\n 'contacts',\n 'companies',\n 'deals',\n 'tasks',\n 'leads',\n] as const\n\nexport type CrmViewSection = (typeof CRM_VIEW_SECTIONS)[number]\n\nexport function isCrmViewSection(value: unknown): value is CrmViewSection {\n return (\n typeof value === 'string' &&\n (CRM_VIEW_SECTIONS as readonly string[]).includes(value)\n )\n}\n\n/**\n * One filter a view carries: a field, an operator and a value, exactly as a\n * list's own filter panel would send them to its query.\n *\n * `label` is the human reading of an OPAQUE value — a team member's name\n * beside their uid, a company's name beside its id — kept on the clause so\n * the chip that shows it can say \"Owner is Dana\" without a read. Display\n * only: nothing matches on it, and a clause without one shows its value.\n */\nexport interface CrmViewFilterClause {\n field: string\n op: string\n value: string\n label?: string\n}\n\n/** The column a view orders by, and which way. */\nexport interface CrmViewSort {\n field: string\n direction: 'asc' | 'desc'\n}\n\n/**\n * What a view holds about a list — the part a section reads and writes,\n * without the name and the ownership around it.\n *\n * `columns` is the VISIBLE set, by column field, or empty for the list's\n * own default; `sort` is one column or none, because a table sorts by one.\n */\nexport interface CrmViewState {\n filters: CrmViewFilterClause[]\n columns: string[]\n sort: CrmViewSort | null\n}\n\n/** `orgs/{orgId}/crmViews/{viewId}`. */\nexport interface CrmSavedView extends CrmScoped, CrmViewState {\n section: CrmViewSection\n name: string\n /** Whose working arrangement this is — the creator, and the one member the rules let change it. */\n ownerUid: string\n createdByUid: string\n /** Listed for everybody who can read the section, rather than the owner alone. */\n shared: boolean\n}\n\nexport const CRM_VIEW_NAME_MAX = 60\n/**\n * The most clauses one view carries. A bound on the predicate, not the\n * audience: the list matches most of them in memory over a bounded window,\n * and a stored array has no natural end.\n */\nexport const CRM_VIEW_MAX_FILTERS = 20\nexport const CRM_VIEW_MAX_COLUMNS = 60\n/** Filter operators that carry no value, so an empty `value` is not a dropped clause. */\nconst VALUELESS_VIEW_OPS: ReadonlySet<string> = new Set(['isEmpty', 'isNotEmpty'])\n\nexport const EMPTY_CRM_VIEW_STATE: CrmViewState = Object.freeze({\n filters: [],\n columns: [],\n sort: null,\n}) as CrmViewState\n\nconst asViewText = (value: unknown, max: number): string =>\n typeof value === 'string' ? value.trim().slice(0, max) : ''\n\n/**\n * A stored view's filters, held to the clause shape.\n *\n * Strict about what it keeps: a clause with no field or no operator is not\n * a filter, and a valued operator with no value would read as a filter\n * that matches nothing — so both are dropped rather than kept, for the same\n * reason the dynamic-list rule drops an empty branch: a view must not\n * quietly select a different population than the one it reads as.\n */\nexport function normalizeCrmViewFilters(value: unknown): CrmViewFilterClause[] {\n if (!Array.isArray(value)) return []\n const clauses: CrmViewFilterClause[] = []\n for (const raw of value) {\n const entry = (raw ?? {}) as Record<string, unknown>\n const field = asViewText(entry['field'], 80)\n const op = asViewText(entry['op'], 40)\n const text = asViewText(entry['value'], 500)\n if (!field || !op) continue\n if (!text && !VALUELESS_VIEW_OPS.has(op)) continue\n const label = asViewText(entry['label'], 120)\n clauses.push({ field, op, value: text, ...(label ? { label } : {}) })\n if (clauses.length >= CRM_VIEW_MAX_FILTERS) break\n }\n return clauses\n}\n\nexport function normalizeCrmViewColumns(value: unknown): string[] {\n if (!Array.isArray(value)) return []\n const seen = new Set<string>()\n for (const raw of value) {\n const column = asViewText(raw, 80)\n if (column) seen.add(column)\n if (seen.size >= CRM_VIEW_MAX_COLUMNS) break\n }\n return [...seen]\n}\n\nexport function normalizeCrmViewSort(value: unknown): CrmViewSort | null {\n const entry = (value ?? null) as Record<string, unknown> | null\n const field = entry ? asViewText(entry['field'], 80) : ''\n if (!field) return null\n return { field, direction: entry?.['direction'] === 'desc' ? 'desc' : 'asc' }\n}\n\n/** A stored document's three list-facing fields, held to their shapes. */\nexport function normalizeCrmViewState(value: unknown): CrmViewState {\n const entry = (value ?? {}) as Record<string, unknown>\n return {\n filters: normalizeCrmViewFilters(entry['filters']),\n columns: normalizeCrmViewColumns(entry['columns']),\n sort: normalizeCrmViewSort(entry['sort']),\n }\n}\n\n/**\n * Whether two states describe the same list — what \"unsaved changes\" means.\n *\n * Clause order matters (a list applies them in order, and the first one the\n * query can serve is the one it serves); the label on a clause does not,\n * because nothing matches on it.\n */\nexport function crmViewStateEquals(a: CrmViewState, b: CrmViewState): boolean {\n if (a.filters.length !== b.filters.length) return false\n for (let index = 0; index < a.filters.length; index += 1) {\n const left = a.filters[index]\n const right = b.filters[index]\n if (\n left.field !== right.field ||\n left.op !== right.op ||\n left.value !== right.value\n ) {\n return false\n }\n }\n if (a.columns.length !== b.columns.length) return false\n if (a.columns.some((column, index) => column !== b.columns[index])) {\n return false\n }\n if (!a.sort || !b.sort) return a.sort === b.sort\n return a.sort.field === b.sort.field && a.sort.direction === b.sort.direction\n}\n\n/**\n * Whether a view belongs in this reader's menu: their own, or one somebody\n * shared. The rules admit the read either way — see the module note — so\n * this is what keeps one person's private arrangement out of another's\n * list.\n */\nexport function crmViewIsListed(\n view: Pick<CrmSavedView, 'shared' | 'ownerUid'>,\n uid: string | null | undefined,\n): boolean {\n return view.shared === true || (Boolean(uid) && view.ownerUid === uid)\n}\n\n/**\n * The Contacts list's filterable fields, by the names a view clause carries.\n *\n * A contract between three readers that cannot import one another: the\n * list's own filter grammar (which declares these as its columns), a saved\n * view (which stores them), and the dynamic-list translator below in\n * `dynamic-list-rule.ts` (which turns them into audience dimensions). Named\n * once so a rename on the list cannot silently stop an audience matching.\n * `custom` is a PREFIX: a custom field's column is the prefix and its key.\n */\nexport const CRM_CONTACT_VIEW_FIELDS = {\n tags: 'tags',\n source: 'source',\n owner: 'ownerUid',\n stage: 'lifecycleStage',\n company: 'companyId',\n createdAt: 'createdAt',\n updatedAt: 'updatedAt',\n orders: 'ordersCount',\n ltv: 'ltvCents',\n custom: 'custom_',\n} as const\n\n/** The column a custom contact field filters and shows as. */\nexport const crmContactCustomColumn = (key: string): string =>\n `${CRM_CONTACT_VIEW_FIELDS.custom}${key}`\n\n/** The field key a custom column stands for, or `null` for any other column. */\nexport function crmContactCustomKey(column: string): string | null {\n const prefix = CRM_CONTACT_VIEW_FIELDS.custom\n return column.startsWith(prefix) && column.length > prefix.length\n ? column.slice(prefix.length)\n : null\n}\n\n/**\n * A stored phone number as the `href` of a click-to-call link, or `null`\n * when the value is not one (AGL-2661).\n *\n * The console stores E.164 (`normalizePhone` runs before every write), and\n * a `tel:` URL wants exactly that: digits with a leading `+`, nothing else.\n * Spaces, dots, dashes and parentheses are dropped rather than refused\n * because a value written before normalization existed may carry them;\n * anything that is not a run of digits after that — a word, an extension\n * typed as `x123` — is refused, because a dialer handed it would ring\n * nothing and the link would be a lie.\n */\nexport function crmTelHref(phone: unknown): string | null {\n const text = String(phone ?? '').trim()\n if (!text) return null\n const compact = text.replace(/[\\s().-]/g, '')\n return /^\\+?\\d{3,20}$/.test(compact) ? `tel:${compact}` : null\n}\n\nconst CRM_VIEW_TAGS_FIELD = CRM_CONTACT_VIEW_FIELDS.tags\nconst CRM_VIEW_SOURCE_FIELD = CRM_CONTACT_VIEW_FIELDS.source\n\n/**\n * A saved segment, as the filters of a contacts view.\n *\n * A segment is \"any of these tags AND any of these sources\", so each\n * becomes one `isAnyOf` clause — OR within, AND across, which is the\n * reading `contactMatchesSegment` gives it. A single tag is emitted as\n * `contains` instead: that operator is the one the contacts query can serve\n * from the index, so a one-tag segment opened as a view reaches the whole\n * collection rather than the loaded window. The values are the segment's\n * own, joined the way the grammar's `isAnyOf` splits them.\n */\nexport function crmViewFiltersFromSegment(\n segment: Pick<ContactSegment, 'tags' | 'sources'>,\n): CrmViewFilterClause[] {\n const tags = (segment.tags ?? []).map((tag) => tag.trim()).filter(Boolean)\n const sources = (segment.sources ?? []).filter(\n (source) => source in CONTACT_SOURCE_LABELS,\n )\n return [\n ...(tags.length === 1\n ? [{ field: CRM_VIEW_TAGS_FIELD, op: 'contains', value: tags[0] }]\n : tags.length\n ? [{ field: CRM_VIEW_TAGS_FIELD, op: 'isAnyOf', value: tags.join(',') }]\n : []),\n ...(sources.length === 1\n ? [{ field: CRM_VIEW_SOURCE_FIELD, op: 'equals', value: sources[0] }]\n : sources.length\n ? [\n {\n field: CRM_VIEW_SOURCE_FIELD,\n op: 'isAnyOf',\n value: sources.join(','),\n },\n ]\n : []),\n ]\n}\n\n/**\n * The other direction: the segment a view's filters describe, or `null`\n * when the view carries no tag or source clause a segment could hold.\n *\n * Only the two dimensions a segment has are read, and only through the\n * operators that mean \"has one of these\"; a `name startsWith` or an owner\n * clause is not a segment's to keep. What comes back is what \"Save as\n * segment\" writes, so a campaign audience built from it selects exactly the\n * tags and sources the reader could see on the chips.\n */\nexport function crmViewSegmentFilters(\n filters: readonly CrmViewFilterClause[],\n): Pick<ContactSegment, 'tags' | 'sources'> | null {\n const tags = new Set<string>()\n const sources = new Set<ContactSource>()\n const split = (value: string) =>\n value\n .split(',')\n .map((entry) => entry.trim())\n .filter(Boolean)\n for (const clause of filters) {\n if (clause.field === CRM_VIEW_TAGS_FIELD) {\n if (clause.op === 'contains') tags.add(clause.value.trim().toLowerCase())\n else if (clause.op === 'isAnyOf') {\n for (const tag of split(clause.value)) tags.add(tag.toLowerCase())\n }\n } else if (clause.field === CRM_VIEW_SOURCE_FIELD) {\n const values = clause.op === 'equals' ? [clause.value.trim()] : clause.op === 'isAnyOf' ? split(clause.value) : []\n for (const source of values) {\n if (source in CONTACT_SOURCE_LABELS) sources.add(source as ContactSource)\n }\n }\n }\n tags.delete('')\n if (!tags.size && !sources.size) return null\n return {\n ...(tags.size ? { tags: [...tags] } : {}),\n ...(sources.size ? { sources: [...sources] } : {}),\n }\n}\n\n/**\n * `users/{uid}.crmDefaultViews` — `{ [orgId]: { [section]: viewId } }`.\n *\n * On the reader's own profile document, beside `notificationPrefs`, because\n * it is the same kind of fact: how one person wants their console to\n * behave, owned and written by them alone. Keyed by org first because one\n * account sits on several organizations and a view id is only meaningful\n * inside the one that holds it.\n */\nexport const CRM_DEFAULT_VIEWS_FIELD = 'crmDefaultViews'\n\n/** The view a section opens on for this reader in this org, or none. */\nexport function crmDefaultViewId(\n profile: Record<string, unknown> | null | undefined,\n orgId: string | null | undefined,\n section: CrmViewSection,\n): string | null {\n if (!profile || !orgId) return null\n const byOrg = profile[CRM_DEFAULT_VIEWS_FIELD]\n if (!byOrg || typeof byOrg !== 'object' || Array.isArray(byOrg)) return null\n const bySection = (byOrg as Record<string, unknown>)[orgId]\n if (!bySection || typeof bySection !== 'object' || Array.isArray(bySection)) {\n return null\n }\n const viewId = (bySection as Record<string, unknown>)[section]\n return typeof viewId === 'string' && viewId.trim() ? viewId : null\n}\n\n/**\n * The merge patch that sets — or, with `null`, clears — one default.\n *\n * Shaped for a merged write so the other organizations' and sections'\n * defaults on the same document survive it; `null` rather than a delete\n * sentinel because this module carries no Firestore, and the reader above\n * treats a null as no default.\n */\nexport function crmDefaultViewPatch(\n orgId: string,\n section: CrmViewSection,\n viewId: string | null,\n): Record<string, unknown> {\n return { [CRM_DEFAULT_VIEWS_FIELD]: { [orgId]: { [section]: viewId } } }\n}\n\n/*==========================================\n * FILES ON A RECORD (AGL-2662)\n *=========================================*/\n\n/** The field a record's attached files are stored under, on all three. */\nexport const CRM_MEDIA_IDS_FIELD = 'mediaIds'\n\n/**\n * The most files one record may carry.\n *\n * A platform ceiling in the family of {@link CRM_ACTIVITIES_PER_RECORD_CEILING}\n * rather than a plan dimension: attachments are bounded by human effort, and\n * this is the bound. It is also what the Firestore rules can actually check\n * — a rule can assert a list and its size, and cannot walk one — so the\n * number is enforced rather than merely documented.\n */\nexport const CRM_MEDIA_IDS_MAX = 20\n\n/**\n * A stored or submitted attachment list, cleaned.\n *\n * Ids rather than URLs, deduplicated, trimmed, non-empty and capped. Written\n * through by every path that sets the field — the console card, the REST\n * write — so a record cannot hold a list one of them would refuse.\n *\n * An id is a media DOCUMENT id, resolved against the organization's library\n * (`orgs/{orgId}/media/{mediaId}`) at read time. Storing the id and not the\n * URL is what lets a file move between folders, and what keeps a private\n * asset behind the signed CDN door rather than pinned to a raw storage URL\n * that names its current location.\n */\nexport function normalizeCrmMediaIds(value: unknown): string[] {\n if (!Array.isArray(value)) return []\n return [\n ...new Set(\n value\n .map((id) => String(id ?? '').trim())\n .filter((id) => id.length > 0 && id.length <= 200),\n ),\n ].slice(0, CRM_MEDIA_IDS_MAX)\n}\n\n/*==========================================\n * WHAT A CRM LIST QUERIES BY (AGL-3321).\n *\n * Every filter and every search word on the Leads, Contacts, Companies,\n * Deals and Tasks lists is a predicate on the list's Firestore query, and a\n * query can only ask about a field a WRITER stored in the shape the query\n * asks for. Three kinds of field exist for that and for nothing else:\n *\n * `searchTokens` word prefixes of what the list's search box reads\n * (a lead's name, address, company, title and tags;\n * a deal's title), for `array-contains` on one typed\n * word — see `nameSearchTokens`.\n * `scopedSearchTokens` the same tokens behind each of the record's\n * `visibleTo` tokens (`host:abc~acme`). A list under\n * a site already spends its one array clause on\n * `visibleTo array-contains-any`, so its search asks\n * `array-contains-any` over the site's tokens joined\n * to the word — one clause answering both.\n * keys a normalized scalar where the stored value cannot\n * be asked as it is: a lead's status (absent on a\n * capture), its lead source (compared the way the\n * picklist compares labels), a verdict on the\n * address (`none` when there is none) and a contact's\n * per-holder facet values (`facetKeys`).\n *\n * All of them are DERIVED: a pure function of the document — save a lead's\n * `leadSourceDirection`, which is the document and the org's lead source\n * list (see `crmLeadSourceDirection`) — computed here and nowhere else,\n * and written by every path that writes one of its inputs — see\n * `CRM_LIST_FIELD_INPUTS` — so a record a list cannot find is a writer\n * that forgot, never a second formula that disagrees. A server writer that\n * holds the whole document spreads `crmListFields`; one that patched a field\n * restamps the record from what was stored (`restampCrmListFields` in the\n * admin library); the one-time backfill (`backfill-crm-list-fields.mjs`)\n * restates them for a plain Node script and is held to the same worked\n * examples (`tools/scripts/lib/org-record-list-fields.fixtures.json`), which this\n * library's spec asserts too.\n *=========================================*/\n\n/** The search box's word-prefix tokens, on every CRM record a list searches. */\nexport const CRM_SEARCH_TOKENS_FIELD = 'searchTokens'\n/** The same tokens behind each `visibleTo` token — see the block header. */\nexport const CRM_SCOPED_SEARCH_TOKENS_FIELD = 'scopedSearchTokens'\n/** What joins a scope token to a search token — the platform's one join (`SCOPED_SEARCH_JOIN`). */\nexport const CRM_SCOPED_SEARCH_JOIN = SCOPED_SEARCH_JOIN\n/**\n * The most search tokens one record keeps. A name, an address, a company\n * and a title are a few dozen prefixes; the cap is what keeps a record with\n * twenty long tags from spending hundreds of index entries on them.\n */\nexport const CRM_SEARCH_TOKENS_MAX = 200\n\n/** The collections whose lists query the fields below. */\nexport type CrmListCollection = 'leads' | 'contacts' | 'companies' | 'deals' | 'crmTasks'\n\nconst SEARCH_KEY = nameSearchKey\n\n/**\n * The word-prefix tokens of several values, merged: every prefix (up to\n * twelve characters) of every whitespace-separated word, lower-cased —\n * exactly `nameSearchTokens` per value — plus, for an email address, the\n * words it is made of, so `acme` finds `dana@acme.com` and `dana` does too.\n */\nexport function crmSearchTokens(values: readonly unknown[]): string[] {\n const tokens = new Set<string>()\n const addWord = (word: string) => {\n const capped = word.slice(0, NAME_TOKEN_MAX_PREFIX)\n for (let end = 1; end <= capped.length; end += 1) {\n if (tokens.size >= CRM_SEARCH_TOKENS_MAX) return\n tokens.add(capped.slice(0, end))\n }\n }\n const addText = (text: string) => {\n const key = SEARCH_KEY(text)\n if (!key) return\n for (const word of key.split(' ')) {\n if (!word) continue\n addWord(word)\n // An address, a domain or a hyphenated name is also its parts.\n if (/[@.+_-]/.test(word)) {\n for (const part of word.split(/[@.+_-]+/)) if (part) addWord(part)\n }\n }\n }\n for (const value of values) {\n if (Array.isArray(value)) {\n for (const entry of value) if (typeof entry === 'string') addText(entry)\n } else if (typeof value === 'string') {\n addText(value)\n }\n }\n return [...tokens]\n}\n\n/**\n * The two search fields for a record whose search reads `values`: the\n * tokens, and the same tokens behind each of its scope tokens through the\n * platform's one scoped writer (`scopedSearchTokens`).\n */\nexport function crmSearchFields(\n visibleTo: unknown,\n values: readonly unknown[],\n): { searchTokens: string[]; scopedSearchTokens: string[] } {\n const searchTokens = crmSearchTokens(values)\n return { searchTokens, scopedSearchTokens: scopedSearchTokens(visibleTo, searchTokens) }\n}\n\n/**\n * The address verdict as a list asks it (AGL-3245): the state's status, or\n * `none` when nothing is known — stored rather than left absent, because a\n * query cannot find a field's absence and \"Nothing known\" is a filter.\n */\nexport const CRM_EMAIL_STATUS_FIELD = 'emailStatus'\nexport const CRM_EMAIL_STATUS_NONE = 'none'\n\nexport function crmEmailStatusKey(record: Record<string, unknown> | null | undefined): string {\n const raw = record?.['emailState']\n const status =\n raw && typeof raw === 'object' ? (raw as Record<string, unknown>)['status'] : undefined\n // A status outside the vocabulary reads as nothing known, as `readEmailState` reads it.\n return isEmailStateStatus(status) ? status : CRM_EMAIL_STATUS_NONE\n}\n\n/**\n * A picklist label as a list query compares it — the key a target's\n * `keyField` holds beside the label — `null` for none.\n */\nexport function crmPicklistKey(value: unknown): string | null {\n const key = SEARCH_KEY(normalizeCrmPicklistLabel(value))\n return key || null\n}\n\n/** A lead's lead source as the list compares it: the picklist's key, `null` for none. */\nexport const CRM_LEAD_SOURCE_KEY_FIELD = 'leadSourceKey'\n\nexport function crmLeadSourceKey(value: unknown): string | null {\n return crmPicklistKey(value)\n}\n\n/*------------------------------------------\n * A LEAD'S LEAD SOURCE DIRECTION (AGL-3577).\n *\n * The group of the Lead source picklist its value sits in — Inbound for a\n * person who came to the organization, Outbound for one it went to — or\n * `null` for no lead source, a value in no group, or a label the list does\n * not hold. STORED on the lead as `leadSourceDirection`, so the Leads list\n * asks `leadSourceDirection == inbound` — one equality, however many values\n * the group holds. (It was asked as `leadSourceKey in [...]` over the\n * group's values, and Inbound holds more than one `in` may.)\n *\n * The one list field that is not a function of the record alone: it is\n * read off the org's list. So a writer passes that list\n * ({@link CrmListFieldsContext}) or, without it, writes the direction only\n * where it needs no list — `null` for a lead with no lead source — and\n * leaves a held value's direction as stored. Every move of the list that\n * can change a label's group rewrites the leads holding it (the\n * `groupField` of the picklist's lead target).\n *-----------------------------------------*/\n\nexport const CRM_LEAD_SOURCE_DIRECTION_FIELD = 'leadSourceDirection'\n\n/** The directions a lead source can have: the Lead source picklist's groups. */\nexport const CRM_LEAD_SOURCE_DIRECTIONS = ['inbound', 'outbound'] as const\n\nexport type CrmLeadSourceDirection = (typeof CRM_LEAD_SOURCE_DIRECTIONS)[number]\n\nexport function isCrmLeadSourceDirection(value: unknown): value is CrmLeadSourceDirection {\n return (CRM_LEAD_SOURCE_DIRECTIONS as readonly unknown[]).includes(value)\n}\n\n/** The direction `label` has in the org's lead source list — `null` for none. */\nexport function crmLeadSourceDirection(\n leadSources: PicklistValueSet,\n label: unknown,\n): CrmLeadSourceDirection | null {\n const group = picklistValueByLabel(leadSources, label)?.group\n return isCrmLeadSourceDirection(group) ? group : null\n}\n\n/** What a list-field writer knows beyond the record. */\nexport interface CrmListFieldsContext {\n /**\n * The org's Lead source list as every reader takes it\n * (`effectiveCrmLeadSourcePicklist`), which a lead's direction is read\n * off. Without it a lead holding a lead source keeps the direction it has.\n */\n leadSources?: PicklistValueSet | null\n}\n\n/** A lead's direction field — see the block above. */\nfunction leadSourceDirectionField(\n lead: Record<string, unknown>,\n context: CrmListFieldsContext,\n): { leadSourceDirection?: CrmLeadSourceDirection | null } {\n if (!crmLeadSourceKey(lead['leadSource'])) return { leadSourceDirection: null }\n return context.leadSources\n ? { leadSourceDirection: crmLeadSourceDirection(context.leadSources, lead['leadSource']) }\n : {}\n}\n\n/** What a lead's search box reads (AGL-3246). */\nexport const CRM_LEAD_SEARCH_SOURCES = ['name', 'email', 'company', 'jobTitle', 'tags'] as const\n\n/**\n * A lead's campaigns behind each of its `visibleTo` tokens\n * (`host:abc~{campaignId}`), for the Leads list's Campaign filter under a\n * site: there the scope clause is the query's one array clause, and a\n * campaign asked of these keys answers both at once — the way the search\n * folds into the scope through `scopedSearchTokens`.\n */\nexport const CRM_LEAD_SCOPED_CAMPAIGNS_FIELD = 'scopedCampaignIds'\n\nfunction leadCampaignIds(lead: Record<string, unknown>): string[] {\n const raw = lead['campaignIds']\n return Array.isArray(raw)\n ? raw.filter((entry): entry is string => typeof entry === 'string' && entry.length > 0)\n : []\n}\n\n/**\n * Every field the Leads list queries by, from the lead as stored (or as it\n * will be once written): the search tokens, its campaigns behind its scope,\n * the status — `new` for a lead nobody has touched, written so `status in\n * [new, working]` finds it — the lead source key and the address verdict.\n */\nexport function crmLeadListFields(\n record: object,\n context: CrmListFieldsContext = {},\n): {\n searchTokens: string[]\n scopedSearchTokens: string[]\n scopedCampaignIds: string[]\n status: CrmLeadStatus\n leadSourceKey: string | null\n /** Absent when the lead holds a lead source and no list was given. */\n leadSourceDirection?: CrmLeadSourceDirection | null\n emailStatus: string\n industryKey: string | null\n ratingKey: string | null\n} {\n const lead = record as Record<string, unknown>\n return {\n ...crmSearchFields(\n lead['visibleTo'],\n CRM_LEAD_SEARCH_SOURCES.map((field) => lead[field]),\n ),\n scopedCampaignIds: scopedSearchTokens(lead['visibleTo'], leadCampaignIds(lead)),\n status: crmLeadStatus(lead as Pick<CrmLeadFields, 'status'>),\n leadSourceKey: crmLeadSourceKey(lead['leadSource']),\n // Its group in the org's list (AGL-3577).\n ...leadSourceDirectionField(lead, context),\n emailStatus: crmEmailStatusKey(lead),\n // The Industry and Rating the Leads list filters by (AGL-3513).\n industryKey: crmPicklistKey(lead['industry']),\n ratingKey: crmPicklistKey(lead['rating']),\n }\n}\n\n/** What a company's search box reads: its name and its domain. */\nexport const CRM_COMPANY_SEARCH_SOURCES = ['name', 'domain'] as const\n\n/**\n * Every field the Companies list queries by: the search tokens, and the\n * key of each picklist field that names one (AGL-3514) — `null` for none,\n * so \"no value\" is a value a query can ask for.\n */\nexport function crmCompanyListFields(record: object): {\n searchTokens: string[]\n scopedSearchTokens: string[]\n typeKey: string | null\n industryKey: string | null\n ratingKey: string | null\n accountSourceKey: string | null\n} {\n const company = record as Record<string, unknown>\n return {\n ...crmSearchFields(\n company['visibleTo'],\n CRM_COMPANY_SEARCH_SOURCES.map((field) => company[field]),\n ),\n typeKey: crmPicklistKey(company['type']),\n industryKey: crmPicklistKey(company['industry']),\n ratingKey: crmPicklistKey(company['rating']),\n accountSourceKey: crmPicklistKey(company['accountSource']),\n }\n}\n\n/**\n * What a deal's search box reads: its title (AGL-3315) — as word prefixes,\n * and as `titleLower`, the whole title's key, which a reader whose access\n * is some sites searches by its start. And the keys the Deals list filters\n * its Type and Lead source by (AGL-3516): each label as the picklist\n * compares it, `null` for none.\n */\nexport function crmDealListFields(record: object): {\n searchTokens: string[]\n scopedSearchTokens: string[]\n titleLower: string\n typeKey: string | null\n leadSourceKey: string | null\n contactRoleContactIds: string[]\n scopedContactRoleContactIds: string[]\n contactRoleKeys: string[]\n} {\n const deal = record as Record<string, unknown>\n return {\n ...crmSearchFields(deal['visibleTo'], [deal['title']]),\n titleLower: SEARCH_KEY(typeof deal['title'] === 'string' ? deal['title'] : ''),\n typeKey: crmPicklistKey(deal['type']),\n leadSourceKey: crmPicklistKey(deal['leadSource']),\n ...crmDealContactRoleListFields(deal),\n }\n}\n\n/**\n * The arrays a deal's contact roles are found by (AGL-3521): every contact\n * on it — `contactRoleContactIds`, which a contact's page asks\n * `array-contains` at the organization level, and the same ids behind each\n * scope token (`scopedContactRoleContactIds`, `host:x~contactId`), which it\n * asks under a site in the one array clause a query has — and the keys of\n * the roles they hold, which a rename of a role finds the deals by.\n */\nexport function crmDealContactRoleListFields(deal: Record<string, unknown>): {\n contactRoleContactIds: string[]\n scopedContactRoleContactIds: string[]\n contactRoleKeys: string[]\n} {\n const roles = dealContactRolesOf(deal)\n const contactRoleContactIds = roles.map((row) => row.contactId)\n const keys = new Set<string>()\n for (const row of roles) {\n const key = crmPicklistKey(row.role)\n if (key) keys.add(key)\n }\n return {\n contactRoleContactIds,\n scopedContactRoleContactIds: scopedSearchTokens(deal['visibleTo'], contactRoleContactIds),\n contactRoleKeys: [...keys],\n }\n}\n\n/** What a task's search box reads: its title. */\nexport function crmTaskListFields(record: object): {\n searchTokens: string[]\n scopedSearchTokens: string[]\n} {\n const task = record as Record<string, unknown>\n return crmSearchFields(task['visibleTo'], [task['title']])\n}\n\n/*------------------------------------------\n * A CONTACT'S FACET KEYS.\n *\n * An owner, a stage, a source, a company, a tag and a custom value live on\n * a holder's facet — `facets.{groupId}` — so a `where` on one would be a path\n * per holder and an index per holder. `facetKeys` flattens them into one\n * array a query can ask `array-contains` of:\n *\n * `{groupId}:{field}={value}` this holder's value\n * `{groupId}:{field}` this holder has one (for \"is set\")\n * `*:{field}={value}`, `*:{field}` any holder's, for the organization level\n *\n * `field` is `owner`, `stage`, `source`, `company`, `tag`, `custom.{key}` or\n * `leadSource` (AGL-3511), plus two presence-only fields: `orders` for a holder the person has bought\n * from, and `ltv` for one they are worth something to — the per-holder\n * figures a range cannot reach, asked only whether there are any.\n * A text value is keyed lower-cased and single-spaced; a number or a flag\n * as its string. Under a site the viewing group's keys are asked; at the\n * organization level, where no group is viewing, the `*` keys are.\n *-----------------------------------------*/\n\nexport const CRM_CONTACT_FACET_KEYS_FIELD = 'facetKeys'\n/** The group a key names at the organization level: any holder. */\nexport const CRM_FACET_KEY_ANY_GROUP = '*'\n/** The most facet keys one contact keeps. */\nexport const CRM_FACET_KEYS_MAX = 600\n\n/** The facet fields a contact list filters by, as their keys name them. */\nexport type CrmFacetKeyField =\n | 'owner'\n | 'stage'\n | 'source'\n | 'company'\n | 'tag'\n | 'orders'\n | 'ltv'\n | `custom.${string}`\n // A holder's lead source, by its label (AGL-3511).\n | 'leadSource'\n\n/** A facet value as its key spells it, or `null` for a value a key cannot hold. */\nexport function crmFacetKeyValue(value: unknown): string | null {\n if (typeof value === 'boolean') return value ? 'true' : 'false'\n if (typeof value === 'number') return Number.isFinite(value) ? String(value) : null\n if (typeof value !== 'string') return null\n const key = SEARCH_KEY(value).slice(0, 120)\n return key || null\n}\n\n/** One facet key: `{group}:{field}` for presence, `{group}:{field}={value}` for a value. */\nexport function crmFacetKey(group: string, field: CrmFacetKeyField, value?: string): string {\n return value === undefined ? `${group}:${field}` : `${group}:${field}=${value}`\n}\n\n/** The keys one holder's facet contributes, under `group`. */\nfunction facetKeysOf(group: string, facet: Record<string, unknown>, into: Set<string>): void {\n const add = (field: CrmFacetKeyField, raw: unknown) => {\n const value = crmFacetKeyValue(raw)\n if (value === null) return\n into.add(crmFacetKey(group, field))\n into.add(crmFacetKey(group, field, value))\n }\n add('owner', facet['ownerUid'])\n if (isContactLifecycleStage(facet['lifecycleStage'])) add('stage', facet['lifecycleStage'])\n add('company', facet['companyId'])\n const sources = facet['sources']\n if (sources && typeof sources === 'object' && !Array.isArray(sources)) {\n for (const [source, on] of Object.entries(sources as Record<string, unknown>)) {\n if (on) add('source', source)\n }\n }\n if (Array.isArray(facet['tags'])) {\n for (const tag of facet['tags']) add('tag', tag)\n }\n // Presence only: a holder's commercial figures are asked \"any?\", never ranged.\n if (typeof facet['ordersCount'] === 'number' && facet['ordersCount'] > 0) {\n into.add(crmFacetKey(group, 'orders'))\n }\n if (typeof facet['ltvCents'] === 'number' && facet['ltvCents'] > 0) {\n into.add(crmFacetKey(group, 'ltv'))\n }\n const custom = facet['custom']\n if (custom && typeof custom === 'object' && !Array.isArray(custom)) {\n for (const [key, value] of Object.entries(custom as Record<string, unknown>)) {\n if (/^[A-Za-z0-9_-]{1,64}$/.test(key)) add(`custom.${key}`, value)\n }\n }\n // Keyed as `crmPicklistKey` compares a label, so a rename's rewrite restamps it.\n add('leadSource', facet['leadSource'])\n}\n\n/** Every facet key a contact carries — see the block above. */\nexport function crmContactFacetKeys(record: object): string[] {\n const facets = (record as Record<string, unknown>)[CONTACT_FACETS_FIELD]\n if (!facets || typeof facets !== 'object' || Array.isArray(facets)) return []\n const groups = new Set<string>()\n const any = new Set<string>()\n for (const [groupId, facet] of Object.entries(facets as Record<string, unknown>)) {\n if (!groupId || !facet || typeof facet !== 'object' || Array.isArray(facet)) continue\n facetKeysOf(groupId, facet as Record<string, unknown>, groups)\n facetKeysOf(CRM_FACET_KEY_ANY_GROUP, facet as Record<string, unknown>, any)\n }\n return [...any, ...groups].slice(0, CRM_FACET_KEYS_MAX)\n}\n\n/**\n * What a contact's search box reads: the shared identity — the canonical\n * name and every address — and the two profile values a person is looked\n * up by, through their top-level search echoes (`HostContact.phone`,\n * `HostContact.companyName`): the company's words, and the phone's digits.\n */\nexport const CRM_CONTACT_SEARCH_SOURCES = [\n 'name',\n 'email',\n CONTACT_ALTERNATE_EMAILS_FIELD,\n 'companyName',\n] as const\n\n/**\n * A phone number as the words a search box finds it by: its digits whole,\n * without a leading country code of one to three digits, and its last seven\n * and last four — so `5551234`, `555123` and `4567` all find +1 555 123 4567.\n * Typed as digits: a search reads one word, and `(555)` is not one of these.\n */\nexport function crmPhoneSearchWords(phone: unknown): string[] {\n const digits = typeof phone === 'string' ? phone.replace(/\\D/g, '') : ''\n if (digits.length < 4) return []\n const words = new Set<string>([digits])\n for (let strip = 1; strip <= 3; strip += 1) {\n if (digits.length - strip >= 7) words.add(digits.slice(strip))\n }\n if (digits.length > 7) words.add(digits.slice(-7))\n words.add(digits.slice(-4))\n return [...words]\n}\n\nexport function crmContactListFields(record: object): {\n searchTokens: string[]\n scopedSearchTokens: string[]\n facetKeys: string[]\n emailStatus: string\n} {\n const contact = record as Record<string, unknown>\n return {\n ...crmSearchFields(contact['visibleTo'], [\n ...CRM_CONTACT_SEARCH_SOURCES.map((field) => contact[field]),\n crmPhoneSearchWords(contact['phone']),\n ]),\n facetKeys: crmContactFacetKeys(contact),\n emailStatus: crmEmailStatusKey(contact),\n }\n}\n\n/**\n * The stored fields each collection's list fields are computed from. A\n * write that sets one of them must restamp the record — or spread\n * {@link crmListFields} over the document it wrote.\n */\nexport const CRM_LIST_FIELD_INPUTS: Readonly<Record<CrmListCollection, readonly string[]>> = {\n leads: [\n 'visibleTo',\n ...CRM_LEAD_SEARCH_SOURCES,\n 'status',\n 'leadSource',\n 'emailState',\n 'campaignIds',\n // AGL-3513.\n 'industry',\n 'rating',\n ],\n contacts: ['visibleTo', ...CRM_CONTACT_SEARCH_SOURCES, 'phone', CONTACT_FACETS_FIELD, 'emailState'],\n companies: ['visibleTo', ...CRM_COMPANY_SEARCH_SOURCES, 'type', 'industry', 'rating', 'accountSource'],\n deals: ['visibleTo', 'title', 'type', 'leadSource', 'contactRoles', 'contactId'],\n crmTasks: ['visibleTo', 'title'],\n}\n\n/**\n * Every list field of one record, from the record as stored — and, for a\n * lead's direction, the org's lead source list when `context` holds it.\n */\nexport function crmListFields(\n collection: CrmListCollection,\n record: object,\n context: CrmListFieldsContext = {},\n): Record<string, unknown> {\n switch (collection) {\n case 'leads':\n return crmLeadListFields(record, context)\n case 'contacts':\n return crmContactListFields(record)\n case 'companies':\n return crmCompanyListFields(record)\n case 'deals':\n return crmDealListFields(record)\n case 'crmTasks':\n return crmTaskListFields(record)\n }\n}\n\nconst sameValue = (a: unknown, b: unknown): boolean => {\n if (Array.isArray(a) && Array.isArray(b)) {\n return a.length === b.length && a.every((entry, at) => entry === b[at])\n }\n return a === b\n}\n\n/**\n * The list fields a stored record carries WRONGLY — the patch that brings\n * it level, `{}` when it already is. What a restamp and a backfill write.\n */\nexport function crmListFieldsPatch(\n collection: CrmListCollection,\n record: object,\n context: CrmListFieldsContext = {},\n): Record<string, unknown> {\n const fields = crmListFields(collection, record, context)\n const stored = record as Record<string, unknown>\n const patch: Record<string, unknown> = {}\n for (const [field, value] of Object.entries(fields)) {\n if (!sameValue(stored[field], value)) patch[field] = value\n }\n return patch\n}\n\n/**\n * The field each collection's list ORDERS or asks `null` of, which a record\n * must therefore carry even when it has no value: a contact, a company and a\n * deal carry `nextTaskAtMs` (\"No next activity\" asks `== null`), and a task\n * carries `dueAtMs` (every task view orders by it, and Firestore leaves a\n * document without the ordered field out of the answer). An absent field is\n * neither `null` nor anything else a query can ask for.\n */\nexport const CRM_LIST_NULLABLE_FIELD: Readonly<Record<CrmListCollection, string | null>> = {\n leads: null,\n contacts: 'nextTaskAtMs',\n companies: 'nextTaskAtMs',\n deals: 'nextTaskAtMs',\n crmTasks: 'dueAtMs',\n}\n\n/**\n * The list fields a NEW record is written with: {@link crmListFields}, and\n * the collection's {@link CRM_LIST_NULLABLE_FIELD} as `null` when the record\n * does not set it — nothing is scheduled against a record that did not\n * exist, and a task created undated is still one its views must reach.\n */\nexport function crmNewRecordListFields(\n collection: CrmListCollection,\n record: object,\n context: CrmListFieldsContext = {},\n): Record<string, unknown> {\n const fields = crmListFields(collection, record, context)\n const nullable = CRM_LIST_NULLABLE_FIELD[collection]\n const stored = (record as Record<string, unknown>)[nullable ?? '']\n if (!nullable || (nullable in record && stored !== undefined)) return fields\n return { ...fields, [nullable]: null }\n}\n\n/** Whether a patch touches a field the collection's list fields are computed from. */\nexport function crmListFieldsTouched(\n collection: CrmListCollection,\n patch: Record<string, unknown>,\n): boolean {\n const inputs = CRM_LIST_FIELD_INPUTS[collection]\n return Object.keys(patch).some((key) =>\n inputs.some((input) => key === input || key.startsWith(`${input}.`)),\n )\n}\n"],"names":["normalizeAddress","normalizePhone","consentGroupScope","CONTACT_LIFECYCLE_STAGES","CRM_TASK_KINDS","isContactLifecycleStage","CONTACT_ALTERNATE_EMAILS_FIELD","CONTACT_FACETS_FIELD","CONTACT_SOURCE_LABELS","composeContactName","normalizeContactEmail","readContactFacet","isEmailStateStatus","NAME_TOKEN_MAX_PREFIX","nameSearchKey","nameSearchTokens","SCOPED_SEARCH_JOIN","scopedSearchTokens","effectivePicklistValueSet","isStandardPicklistValueId","judgePicklistValue","mintPicklistValueId","normalizePicklistLabel","normalizePicklistValueSet","PICKLIST_VALUES_MAX","picklistActiveValues","picklistDefaultLabel","picklistFromLabels","picklistOptions","picklistRank","picklistRefusalSentence","picklistValueByLabel","MAX_SCOPE_HOSTS","ORG_SCOPE_TOKEN","CRM_COLLECTIONS","companies","pipelines","deals","tasks","activities","contactFields","views","emailTemplates","picklists","CRM_RECORD_COLLECTIONS","CRM_RECORDS_BAND_FULL_MESSAGE","CRM_ACTIVITIES_PER_RECORD_CEILING","CRM_ACTIVITY_LOG_FULL_MESSAGE","toLocaleString","crmActivityCeilingLink","link","contactId","field","id","String","companyId","dealId","leadId","crmActivityLogHasRoom","existing","count","Number","isFinite","CONTACT_LIFECYCLE_STAGE_LABELS","subscriber","lead","opportunity","customer","evangelist","other","advanceContactLifecycleStage","current","floor","held","undefined","order","indexOf","contactLifecycleStageAfterPurchase","CRM_FORECAST_CATEGORIES","CRM_FORECAST_CATEGORY_LABELS","omitted","pipeline","bestCase","commit","closed","isCrmForecastCategory","value","includes","readCrmForecastCategory","key","trim","replace","toLowerCase","find","category","dealStageForecastCategory","stage","forecastCategory","kind","dealForecastCategory","deal","status","DEAL_NEXT_STEP_MAX","readDealProbability","number","isInteger","dealProbability","own","probability","Math","min","max","dealStageMoveFields","isPipelineArchived","archivedAt","DEFAULT_DEAL_STAGES","name","DEAL_LINE_ITEMS_MAX","DEAL_LINE_ITEM_NAME_MAX","DEAL_LINE_ITEM_QUANTITY_MAX","DEAL_CONTACT_ROLES_MAX","readDealContactRoles","raw","Array","isArray","roles","seen","Set","primaryTaken","entry","row","length","has","add","role","primary","push","dealContactRolesWithPrimary","next","map","Boolean","some","unshift","slice","dealContactRolesOf","contactRoles","dealPrimaryContactId","dealContactRolesWithout","filter","dealContactRolesRepointed","from","to","merged","dealContactRoleFields","CRM_TASK_KIND_LABELS","call","email","meeting","todo","CRM_ACTIVITY_KIND_LABELS","note","activityKindHasOutcome","CRM_EMAIL_DELIVERY_STATES","CRM_EMAIL_DELIVERY_STATE_LABELS","sent","delivered","opened","clicked","bounced","complained","isCrmEmailDeliveryState","isCrmEmailDeliveryFailure","state","CRM_EMAIL_DELIVERY_RANK","nextCrmEmailDeliveryState","incoming","CRM_EMAIL_SUBJECT_MAX","CRM_EMAIL_BODY_MAX","CRM_EMAIL_CONTEXT","CRM_EMAIL_ACTIVITY_TAG","CRM_EMAIL_ORG_TAG","PROVIDER_TAG_VALUE","crmEmailDeliveryTags","input","orgId","activityId","hostId","test","CRM_ACTIVITY_DIRECTIONS","CRM_ACTIVITY_DIRECTION_LABELS","outbound","inbound","internal","crmActivityDirection","allowed","text","crmActivityKindTitle","direction","known","noun","buildCrmEmailActivity","subject","body","deliveryState","deliveryAtMs","atMs","byUid","byName","sourceActionId","visibleTo","campaignEmailSummary","counted","word","parts","deliveredAtMs","openedAtMs","openCount","clickedAtMs","clickCount","bouncedAtMs","complainedAtMs","sortableMs","NEGATIVE_INFINITY","mergeContactTimeline","interactions","campaignEmails","entries","interaction","index","type","refId","activity","$id","messageId","sentAtMs","sort","a","b","crmActivityRecordLink","record","MINUTE_MS","HOUR_MS","DAY_MS","activityTimeLabel","nowMs","elapsed","Date","toLocaleDateString","CRM_FIELD_OBJECTS","CRM_FIELD_OBJECT_LABELS","contact","company","isCrmFieldObject","fieldDefinitionObject","definition","object","crmFieldListFields","label","required","searchTokens","join","CONTACT_FIELD_KEY_PATTERN","normalizeContactFieldKey","dealStageById","stageId","stages","weightedDealAmountCents","amount","round","amountCents","dealHasLineItems","lineItems","lineItemsTotalCents","items","total","item","quantity","unit","unitAmountCents","readDealLineItems","currency","error","dealCurrency","line","at","lineCurrency","toUpperCase","productId","taskDueState","task","dueAtMs","due","now","sameDay","getFullYear","getMonth","getDate","DOMAIN_LABEL","normalizeCompanyDomain","split","labels","every","COMPANY_WEBSITE_MAX","normalizeCompanyWebsite","candidate","url","URL","protocol","href","CRM_COMPANY_PICKLIST_FIELDS","picklistId","keyField","CRM_COMPANY_TEXT_MAX","accountNumber","site","tickerSymbol","sicCode","CRM_COMPANY_EMPLOYEES_MAX","CRM_COMPANY_ACCOUNT_FIELDS","normalizeCompanyText","blank","readCrmCompanyAccountFields","values","errors","isSafeInteger","code","fax","judgeCrmCompanyPicklists","lists","requested","options","list","effectiveCrmPicklist","fallback","created","judged","judgeCrmPicklistValue","ok","CRM_COMPANY_PARENT_DEPTH_MAX","crmCompanyParentRefusal","parentId","read","parent","cursor","parentCompanyId","depth","crmCompanyParentId","PUBLIC_MAILBOX_DOMAINS","isPublicMailboxDomain","domainOrEmail","domain","lastIndexOf","companyDomainForEmail","companyNameForDomain","charAt","CONTACT_COMPANY_IDS_FIELD","COMPANY_CONTACTS_COUNT_FIELD","readContactCompanyLink","groupId","document","facets","heldElsewhere","holder","facet","Object","named","mirror","companyIds","contactGroupsNamingCompany","planContactCompanyLink","previous","previousLeaves","counts","change","op","delta","kept","ORG_CRM_SETTINGS_FIELD","CRM_AUTO_CREATE_COMPANIES_PATH","orgAutoCreatesCompanies","orgDocument","settings","CRM_ASSIGNMENT_RULES_PATH","CRM_ROUND_ROBIN_POOL_PATH","CRM_ROUND_ROBIN_LAST_ASSIGNED_PATH","CRM_ASSIGNMENT_RULES_MAX","CRM_ROUND_ROBIN_POOL_MAX","CRM_HOST_SETTINGS_KEY","crmHostDefaultOwnerSegments","Error","isContactSourceValue","cleanText","readCrmAssignmentRule","whenRaw","when","source","formId","emailDomain","tag","assignRaw","assign","roundRobin","memberUid","readCrmAssignmentSettings","crm","rules","rule","memberUids","uid","hostSettings","hosts","hostDefaultOwners","owner","pool","lastAssignedUid","crmHostDefaultOwner","assignmentEmailDomain","assignmentRuleMatches","capture","worn","tags","roundRobinOrder","describeAssignmentRule","memberLabel","newAssignmentRuleId","toString","random","crmScopeTokens","org","group","crmReadTokens","parseCrmMemberRef","crmMemberOption","member","displayName","crmMemberPickerLabel","findOrgMember","members","ref","parsed","CRM_LEAD_STATUSES","CRM_LEAD_STATUS_LABELS","new","nurturing","working","qualified","unqualified","CRM_LEAD_OPEN_STATUSES","CRM_LEAD_CLOSED_STATUSES","openLeadsFromCounts","isCrmLeadStatus","CRM_LEAD_TEXT_MAX","CRM_LEAD_TAGS_MAX","CRM_LEAD_NOTES_MAX","CRM_LEAD_PROFILE_KEYS","CRM_LEAD_PHONE_REFUSAL","CRM_LEAD_WEBSITE_REFUSAL","normalizeCrmLeadTags","normalizeCrmLeadProfile","patch","phone","doNotCall","account","digits","keys","into","website","address","crmLeadComposedName","stored","firstName","lastName","part","CRM_LEAD_PICKLIST_FIELDS","judgeCrmLeadPicklists","CRM_PICKLIST_OBJECTS","CRM_PICKLIST_OBJECT_LABELS","isCrmPicklistObject","LEAD_SOURCE_DEFINITION","plural","restricted","groups","standardValues","targets","groupField","LEAD_STATUS_DEFINITION","meanings","meaningLabels","reservedMeanings","meaning","ACCOUNT_TYPE_DEFINITION","INDUSTRY_DEFINITION","RATING_DEFINITION","OWNERSHIP_DEFINITION","SALUTATION_DEFINITION","OPPORTUNITY_TYPE_DEFINITION","OPPORTUNITY_CONTACT_ROLE_DEFINITION","arrayKey","TASK_STATUS_DEFINITION","defaultValueId","TASK_PRIORITY_DEFINITION","TASK_TYPE_DEFINITION","TASK_SUBJECT_DEFINITION","CRM_PICKLIST_DEFINITIONS","CRM_PICKLIST_IDS","CRM_LEAD_SOURCE_PICKLIST","CRM_SALUTATION_PICKLIST","CRM_OPPORTUNITY_TYPE_PICKLIST","CRM_OPPORTUNITY_CONTACT_ROLE_PICKLIST","CRM_LEAD_SOURCE_ORIGINS","form","booking","newsletter","sequence","emailCampaign","isCrmPicklistId","crmPicklistDefinition","crmPicklistDefinitionsFor","definitionOf","CRM_PICKLIST_VALUES_MAX","CRM_LEAD_SOURCE_STARTER_LABELS","normalizeCrmPicklistLabel","crmPicklistValueId","crmPicklistFromLabels","normalizeCrmPicklist","effectiveCrmLeadSourcePicklist","crmLeadSourceForOrigin","picklist","origin","hasOwn","active","isStandardCrmPicklistValue","valueId","crmPicklistValueByLabel","crmPicklistActiveValues","crmPicklistDefaultLabel","crmPicklistRefusal","empty","crmLeadSourceRefusal","refusal","judgeDealContactRoles","verdict","judgeCrmLeadSource","crmPicklistOptions","crmPicklistRank","CRM_LEAD_STATUS_PICKLIST","CRM_LEAD_STATUS_LABEL_FIELD","effectiveCrmLeadStatusPicklist","STANDARD_CRM_LEAD_STATUS_PICKLIST","crmLeadStatusValueFor","preferred","ofMeaning","crmLeadStatusLabelFor","crmLeadStatusLabel","crmLeadStatus","statusLabel","resolveCrmLeadStatusWrite","refuse","keeps","picklistLabelKeyOf","crmLeadStatusOptions","inactive","option","crmPicklistMeaningLabel","crmPicklistShownLabel","crmPicklistLabelForNew","resolveCrmSemanticPicklistWrite","byLabel","heldValue","CRM_TASK_PICKLIST_IDS","priority","effectiveCrmTaskPicklists","crmTaskPicklistLabels","priorityLabel","typeLabel","crmTaskStatusWrite","done","crmTaskLabelsForNew","crmLeadDisplayName","isCrmLeadOpen","CRM_VIEW_SECTIONS","isCrmViewSection","CRM_VIEW_NAME_MAX","CRM_VIEW_MAX_FILTERS","CRM_VIEW_MAX_COLUMNS","VALUELESS_VIEW_OPS","EMPTY_CRM_VIEW_STATE","freeze","filters","columns","asViewText","normalizeCrmViewFilters","clauses","normalizeCrmViewColumns","column","size","normalizeCrmViewSort","normalizeCrmViewState","crmViewStateEquals","left","right","crmViewIsListed","view","shared","ownerUid","CRM_CONTACT_VIEW_FIELDS","createdAt","updatedAt","orders","ltv","custom","crmContactCustomColumn","crmContactCustomKey","prefix","startsWith","crmTelHref","compact","CRM_VIEW_TAGS_FIELD","CRM_VIEW_SOURCE_FIELD","crmViewFiltersFromSegment","segment","sources","crmViewSegmentFilters","clause","delete","CRM_DEFAULT_VIEWS_FIELD","crmDefaultViewId","profile","section","byOrg","bySection","viewId","crmDefaultViewPatch","CRM_MEDIA_IDS_FIELD","CRM_MEDIA_IDS_MAX","normalizeCrmMediaIds","CRM_SEARCH_TOKENS_FIELD","CRM_SCOPED_SEARCH_TOKENS_FIELD","CRM_SCOPED_SEARCH_JOIN","CRM_SEARCH_TOKENS_MAX","SEARCH_KEY","crmSearchTokens","tokens","addWord","capped","end","addText","crmSearchFields","CRM_EMAIL_STATUS_FIELD","CRM_EMAIL_STATUS_NONE","crmEmailStatusKey","crmPicklistKey","CRM_LEAD_SOURCE_KEY_FIELD","crmLeadSourceKey","CRM_LEAD_SOURCE_DIRECTION_FIELD","CRM_LEAD_SOURCE_DIRECTIONS","isCrmLeadSourceDirection","crmLeadSourceDirection","leadSources","leadSourceDirectionField","context","leadSourceDirection","CRM_LEAD_SEARCH_SOURCES","CRM_LEAD_SCOPED_CAMPAIGNS_FIELD","leadCampaignIds","crmLeadListFields","scopedCampaignIds","leadSourceKey","emailStatus","industryKey","ratingKey","CRM_COMPANY_SEARCH_SOURCES","crmCompanyListFields","typeKey","accountSourceKey","crmDealListFields","titleLower","crmDealContactRoleListFields","contactRoleContactIds","scopedContactRoleContactIds","contactRoleKeys","crmTaskListFields","CRM_CONTACT_FACET_KEYS_FIELD","CRM_FACET_KEY_ANY_GROUP","CRM_FACET_KEYS_MAX","crmFacetKeyValue","crmFacetKey","facetKeysOf","on","crmContactFacetKeys","any","CRM_CONTACT_SEARCH_SOURCES","crmPhoneSearchWords","words","strip","crmContactListFields","facetKeys","CRM_LIST_FIELD_INPUTS","leads","contacts","crmTasks","crmListFields","collection","sameValue","crmListFieldsPatch","fields","CRM_LIST_NULLABLE_FIELD","crmNewRecordListFields","nullable","crmListFieldsTouched","inputs"],"mappings":"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"}
1
+ {"version":3,"sources":["../../../../../../libs/aglyn/src/lib/app-utils/crm.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n/**\n * Contacts CRM v2 (AGL-2595): the records that sit BESIDE a contact.\n *\n * v1 is one collection — `contacts` — and everything a holder knows about a\n * person lives in that holder's facet on the shared row. That shape is right\n * for the person and wrong for everything a sales team keeps AROUND the\n * person: a company is known by several contacts, a deal moves through\n * stages on its own clock, a task is due whether or not anybody opens the\n * contact it hangs off, and an activity log grows past anything a facet\n * should carry. So each of those is a collection of its own under\n * `orgs/{orgId}/`, pointing back at the contact by id.\n *\n * ## One scope model, not a second one\n *\n * Every collection here carries `visibleTo`, the same array `contacts`\n * carries, and is stamped the same way — {@link crmScopeTokens} is the\n * contact create path's expression, exported so that no creator computes its\n * own. The rules gate all six on the contacts predicate (`canReadScopedPeople`,\n * `data.manage`), because a deal or a call log is a fact about a person and\n * discloses exactly what the contact row would.\n *\n * What these collections do NOT have is the facet map. A contact is shared\n * across holders because one human is one row; a company, deal or task is\n * one holder's record from the moment it is created, so `visibleTo` alone\n * says who may see it and there is nothing to split per holder.\n *\n * Pure data module: types, constants and the small helpers every surface\n * would otherwise write for itself. No Firestore, no React.\n */\n\nimport {\n type AglynPostalAddress,\n normalizeAddress,\n normalizePhone,\n type OrgCrmAssignmentRule,\n} from '../foundation'\nimport { type ConsentGroup, consentGroupScope } from './consent-groups'\nimport {\n CONTACT_LIFECYCLE_STAGES,\n type ContactLifecycleStage,\n type CrmActivityKind,\n CRM_TASK_KINDS,\n type CrmTaskKind,\n isContactLifecycleStage,\n} from './crm-kinds'\nimport {\n CONTACT_ALTERNATE_EMAILS_FIELD,\n CONTACT_FACETS_FIELD,\n CONTACT_SOURCE_LABELS,\n type ContactInteraction,\n type ContactSegment,\n type ContactSource,\n composeContactName,\n normalizeContactEmail,\n readContactFacet,\n} from './contacts'\nimport { type EmailState, isEmailStateStatus } from './email-state'\nimport {\n NAME_TOKEN_MAX_PREFIX,\n nameSearchKey,\n nameSearchTokens,\n SCOPED_SEARCH_JOIN,\n scopedSearchTokens,\n} from './name-search'\nimport {\n effectivePicklistValueSet,\n isStandardPicklistValueId,\n judgePicklistValue,\n mintPicklistValueId,\n normalizePicklistLabel,\n normalizePicklistValueSet,\n PICKLIST_VALUES_MAX,\n picklistActiveValues,\n picklistDefaultLabel,\n picklistFromLabels,\n type PicklistJudgement,\n picklistOptions,\n type PicklistOption,\n picklistRank,\n picklistRefusalSentence,\n type PicklistSpec,\n type PicklistValue,\n picklistValueByLabel,\n type PicklistValueSet,\n} from './picklists'\nimport { MAX_SCOPE_HOSTS, ORG_SCOPE_TOKEN, type ScopeToken } from './scope-tokens'\n\n// The fixed vocabularies and their guards live in a leaf module; every name\n// stays importable from here.\nexport * from './crm-kinds'\n\n/**\n * The CRM's collections, every one under `orgs/{orgId}/`.\n *\n * Named here rather than spelled at each call site because the rules, the\n * indexes and the console must agree on the string, and the three prefixed\n * ones are prefixed on purpose: `tasks`, `activities` and `views` are words\n * the org document will want for something else one day, and a collection\n * name is persisted in every document path that uses it.\n */\nexport const CRM_COLLECTIONS = {\n companies: 'companies',\n pipelines: 'pipelines',\n deals: 'deals',\n tasks: 'crmTasks',\n activities: 'crmActivities',\n contactFields: 'contactFields',\n views: 'crmViews',\n emailTemplates: 'crmEmailTemplates',\n /** An org's value sets for its standard picklist fields (AGL-3298). */\n picklists: 'crmPicklists',\n} as const\n\nexport type CrmCollection = (typeof CRM_COLLECTIONS)[keyof typeof CRM_COLLECTIONS]\n\n/**\n * The three collections the CRM RECORDS band counts (AGL-2611), in the order\n * the billing caption lists them. `contacts` is not in `CRM_COLLECTIONS`\n * because it predates the hub and is addressed through `orgDataCollection`,\n * so the band's own list has to name all three itself. Tasks, pipelines,\n * activities and field definitions are deliberately absent: a band that\n * counted what a rep does every hour would price the team's effort, not the\n * audience it holds.\n */\nexport const CRM_RECORD_COLLECTIONS = [\n 'contacts',\n CRM_COLLECTIONS.companies,\n CRM_COLLECTIONS.deals,\n] as const\n\n/**\n * What a create says when the records band refused it, on every surface —\n * the contacts list's alert, the company and deal drawers, the plugin's\n * create routes and the lead conversion (AGL-2596, widened in AGL-2611).\n *\n * One sentence in one place, because a reader who is refused in the drawer\n * and then reads the list must be told the same thing, and the remedy is the\n * same wherever the refusal lands: a band refuses only on a plan that puts\n * no overage rate past it, so \"upgrade\" is the whole of the answer.\n */\nexport const CRM_RECORDS_BAND_FULL_MESSAGE =\n 'CRM records limit reached — this record was not added. Upgrade in ' +\n 'Billing to keep collecting.'\n\n/**\n * The most logged activities ONE record may carry (AGL-2611) — a contact's,\n * a company's or a deal's own log, counted on the link the activity was\n * filed under.\n *\n * A platform ceiling and not a plan dimension, in the family of\n * `WEBHOOK_MAX_PER_HOST` and `NON_PAGE_SCREEN_MAX_PER_HOST`: activities are\n * not in the records band because they are bounded by human effort, and\n * this is the bound. Five thousand is a call a day for fourteen years on one\n * person, so nobody working a real relationship reaches it — what does is\n * an automation logging on every event, or an import replaying a history,\n * and either of those past this line is a document cost with no reader.\n *\n * Enforced where an activity is written: the console's log dialog, the\n * `logCrmActivity` automation step and `POST /v1/activities`, each with one\n * aggregate read on the record's link before the create. The timeline reads\n * a hundred at a time and is untouched by the number.\n */\nexport const CRM_ACTIVITIES_PER_RECORD_CEILING = 5_000\n\n/**\n * What every writer says when a record's log is full — the console's log\n * dialog, the automation step's run history and `POST /v1/activities`.\n */\nexport const CRM_ACTIVITY_LOG_FULL_MESSAGE =\n `This record already has ${CRM_ACTIVITIES_PER_RECORD_CEILING.toLocaleString(\n 'en-US',\n )} activities, which is the most one record can carry.`\n\n/** The record an activity is filed under: its links, of which one leads. */\nexport interface CrmActivityLink {\n contactId?: string | null\n companyId?: string | null\n dealId?: string | null\n /**\n * `hosts/{hostId}/leads/{leadId}` — a person not yet converted (AGL-2615).\n * A lead is host-scoped by path and carries no `visibleTo` of its own, so\n * an activity filed under one is stamped with the site's scope like any\n * other record created from that site.\n */\n leadId?: string | null\n}\n\n/**\n * The field the per-record activity ceiling is counted on, or `null` for an\n * activity that names no record at all.\n *\n * The contact leads, then the company, then the deal: an activity logged\n * from a contact's page carries the company beside it (the automation step\n * copies the facet's `companyId` onto every record it creates), and a\n * ceiling counted on the company would let one busy account exhaust every\n * contact filed under it. The record whose PAGE the log is read on is the\n * one whose log has the limit. Shared by the client dialog and the two\n * server writers so they count the same thing.\n */\nexport function crmActivityCeilingLink(\n link: CrmActivityLink,\n): { field: 'contactId' | 'companyId' | 'dealId' | 'leadId'; id: string } | null {\n if (link.contactId) return { field: 'contactId', id: String(link.contactId) }\n if (link.companyId) return { field: 'companyId', id: String(link.companyId) }\n if (link.dealId) return { field: 'dealId', id: String(link.dealId) }\n // Last, because a converted lead's activities carry the contact beside it\n // and the contact is the record whose page they are read on.\n if (link.leadId) return { field: 'leadId', id: String(link.leadId) }\n return null\n}\n\n/**\n * Whether ONE MORE activity fits under `CRM_ACTIVITIES_PER_RECORD_CEILING`.\n * Both halves of the boundary live here so no writer re-derives `>=`.\n */\nexport function crmActivityLogHasRoom(existing: number): boolean {\n const count = Number(existing)\n return !(Number.isFinite(count) && count >= CRM_ACTIVITIES_PER_RECORD_CEILING)\n}\n\n/** How a lifecycle stage reads on screen — typed so a stage cannot ship unlabeled. */\nexport const CONTACT_LIFECYCLE_STAGE_LABELS: Record<ContactLifecycleStage, string> = {\n subscriber: 'Subscriber',\n lead: 'Lead',\n 'marketing-qualified': 'Marketing qualified',\n 'sales-qualified': 'Sales qualified',\n opportunity: 'Opportunity',\n customer: 'Customer',\n evangelist: 'Evangelist',\n other: 'Other',\n}\n\n/**\n * The stage a person is in once a capture has happened that implies at least\n * `floor` (AGL-2612).\n *\n * The one ordering rule every capture door shares: a door names the EARLIEST\n * stage that describes what just happened — a form submission is a lead, a\n * newsletter opt-in is a subscriber, a purchase is a customer — and the\n * result is that stage for a person who had none or an earlier one, and the\n * stage they already had otherwise. \"Never downgrades\" is the whole contract:\n * a customer who fills in a contact form is still a customer, and `other` —\n * the deliberate stage a business picked for a funnel step none of the names\n * fit — sits after `customer` in the list precisely so no capture can\n * overwrite it. A stored value that is not a stage at all reads as absent,\n * because a capture door is not the place to preserve a typo.\n *\n * With no floor the answer is the current stage as it stands, or `undefined`\n * for one that is absent or unusable — so a writer can apply this\n * unconditionally and write only what comes back.\n */\nexport function advanceContactLifecycleStage(\n current: unknown,\n floor: ContactLifecycleStage | undefined,\n): ContactLifecycleStage | undefined {\n const held = isContactLifecycleStage(current) ? current : undefined\n if (!floor) return held\n if (!held) return floor\n const order: readonly string[] = CONTACT_LIFECYCLE_STAGES\n return order.indexOf(held) < order.indexOf(floor) ? floor : held\n}\n\n/**\n * The stage a person is in once they have BOUGHT something (AGL-2596).\n *\n * `customer` when they had no stage or an earlier one; whatever they already\n * had otherwise — {@link advanceContactLifecycleStage} with `customer` as the\n * floor, kept under its own name because \"after a purchase\" is the question\n * the order paths and the reports ask.\n */\nexport function contactLifecycleStageAfterPurchase(\n current: unknown,\n): ContactLifecycleStage {\n return advanceContactLifecycleStage(current, 'customer') ?? 'customer'\n}\n\n/**\n * What a custom contact field may hold.\n *\n * Scalars only. A field is something a merchant filters and exports on, and\n * a nested value is neither; `null` is the explicit \"cleared\" that a form\n * writes so the key stays present for a `where` clause to find.\n */\nexport type ContactCustomValue = string | number | boolean | null\n\n/**\n * The same scalar, under the name the other objects use (AGL-2661).\n *\n * A company's and a deal's `custom` map hold exactly what a contact's\n * does — one definition, one coercion rule, one stored shape — so the\n * value type is one type with two names: the contact name it was born\n * with, and this one for code that is not about contacts.\n */\nexport type CrmCustomValue = ContactCustomValue\n\n/**\n * The fields every CRM document carries.\n *\n * `visibleTo` is the scope both enforcement layers evaluate — see\n * `scope-tokens.ts` for why an absent one is seen by NOBODY. `hostId` is\n * provenance: the site whose console created the record, never rewritten,\n * so a report can say where a deal came from after the scope has widened.\n * The timestamps are `unknown` because a client write carries a `Date` and a\n * read hands back a Firestore `Timestamp`; callers narrow at the edge.\n */\nexport interface CrmScoped {\n visibleTo: string[]\n /** The site that created it. */\n hostId: string\n createdAt?: unknown\n updatedAt?: unknown\n}\n\n/** `orgs/{orgId}/companies/{companyId}`. */\nexport interface CrmCompany extends CrmScoped {\n name: string\n /** `nameSearchFields` twins, for the prefix search the list runs. */\n nameLower?: string\n nameTokens?: string[]\n /** Lowercase hostname, no protocol — see {@link normalizeCompanyDomain}. */\n domain?: string\n website?: string\n /** E.164 — `normalizePhone` before writing. */\n phone?: string\n /** The billing address — Salesforce's Billing Address, under its original name. */\n address?: AglynPostalAddress | null\n /**\n * Salesforce's Industry: a label of the `industry` picklist (AGL-3514).\n * A record written while it was free text keeps its text.\n */\n industry?: string\n ownerUid?: string\n notes?: string\n /*\n * SALESFORCE'S ACCOUNT FIELDS (AGL-3514) — see the company account block.\n * The picklist fields hold the value's LABEL, with the list's key beside\n * the ones the Companies list filters by.\n */\n /** Account Type: a label of the `accountType` picklist. */\n type?: string | null\n /** Rating: a label of the `rating` picklist. */\n rating?: string | null\n /** Ownership: a label of the `ownership` picklist. */\n ownership?: string | null\n /** Account Source: a label of the `leadSource` picklist. */\n accountSource?: string | null\n /** Annual revenue in the minor unit of {@link CrmCompany.currency}. */\n annualRevenueCents?: number | null\n /** Lowercase ISO 4217, the deals' convention; `'usd'` when absent. */\n currency?: string\n numberOfEmployees?: number | null\n /** E.164 — `normalizePhone`, like `phone`. */\n fax?: string | null\n accountNumber?: string | null\n /** Salesforce's Account Site: which of the company's locations this record is. */\n site?: string | null\n tickerSymbol?: string | null\n sicCode?: string | null\n shippingAddress?: AglynPostalAddress | null\n /** Another company of the same scope this one sits under; never itself or a descendant. */\n parentCompanyId?: string | null\n /**\n * Lowercased, deduplicated, capped at twenty — the same shape a contact's\n * tags take, so a bulk \"Add tag\" over companies and one over contacts\n * write the same kind of value (AGL-2621).\n */\n tags?: string[]\n createdByUid?: string\n /**\n * How many contacts name this company in their {@link CONTACT_COMPANY_IDS_FIELD}\n * mirror — see {@link COMPANY_CONTACTS_COUNT_FIELD}. Absent on a company\n * nobody has linked since the counter existed, which reads as zero.\n */\n contactsCount?: number\n /**\n * Custom field values, keyed by the key of a definition whose `object`\n * is `company` — see {@link fieldDefinitionObject} (AGL-2661).\n */\n custom?: Record<string, CrmCustomValue>\n /**\n * When the earliest OPEN task filed against this company is due, epoch\n * ms, or `null` when none is — see {@link CrmDeal.nextTaskAtMs}.\n */\n nextTaskAtMs?: number | null\n /** Org-library files attached to the company (AGL-2662) — see {@link CRM_MEDIA_IDS_MAX}. */\n mediaIds?: string[]\n}\n\n/** One step of a pipeline. */\nexport interface CrmDealStage {\n id: string\n name: string\n /** Position in the pipeline, ascending. */\n order: number\n /** Chance of closing from here, 0–100 — what a weighted forecast multiplies by. */\n probability: number\n /**\n * Whether landing in this stage closes the deal. A pipeline has exactly one\n * `won` and one `lost` stage in the default set; `open` is everything in\n * between.\n */\n kind: 'open' | 'won' | 'lost'\n /**\n * The forecast category a deal takes on landing here (AGL-3516) — see\n * {@link CrmForecastCategory}. Absent on a stage written before the field\n * existed, which reads as its kind's: Closed for won, Omitted for lost,\n * Pipeline for open ({@link dealStageForecastCategory}).\n */\n forecastCategory?: CrmForecastCategory\n}\n\n/*==========================================\n * FORECAST CATEGORIES (AGL-3516).\n *\n * Salesforce's Opportunity forecast categories, a FIXED set — not a\n * picklist an org edits, as in Salesforce, because a forecast rolls up by\n * them and a renamed or added category would be one no forecast knows. A\n * stage names the category a deal takes on landing in it; the deal stores\n * its own copy, stamped at every stage move, which the deal page may then\n * change for that deal alone.\n *=========================================*/\n\nexport const CRM_FORECAST_CATEGORIES = [\n 'omitted',\n 'pipeline',\n 'bestCase',\n 'commit',\n 'closed',\n] as const\n\nexport type CrmForecastCategory = (typeof CRM_FORECAST_CATEGORIES)[number]\n\nexport const CRM_FORECAST_CATEGORY_LABELS: Record<CrmForecastCategory, string> = {\n omitted: 'Omitted',\n pipeline: 'Pipeline',\n bestCase: 'Best Case',\n commit: 'Commit',\n closed: 'Closed',\n}\n\nexport function isCrmForecastCategory(value: unknown): value is CrmForecastCategory {\n return typeof value === 'string' && (CRM_FORECAST_CATEGORIES as readonly string[]).includes(value)\n}\n\n/**\n * A category as typed — the stored key or its label in any case and\n * spacing (\"Best Case\", \"best case\", `bestCase`) — or `null`.\n */\nexport function readCrmForecastCategory(value: unknown): CrmForecastCategory | null {\n if (isCrmForecastCategory(value)) return value\n const key = String(value ?? '')\n .trim()\n .replace(/\\s+/g, ' ')\n .toLowerCase()\n if (!key) return null\n return (\n CRM_FORECAST_CATEGORIES.find(\n (category) =>\n category.toLowerCase() === key ||\n CRM_FORECAST_CATEGORY_LABELS[category].toLowerCase() === key,\n ) ?? null\n )\n}\n\n/** The category a stage stamps: its own, else its kind's. */\nexport function dealStageForecastCategory(\n stage: Pick<CrmDealStage, 'kind' | 'forecastCategory'>,\n): CrmForecastCategory {\n if (isCrmForecastCategory(stage.forecastCategory)) return stage.forecastCategory\n return stage.kind === 'won' ? 'closed' : stage.kind === 'lost' ? 'omitted' : 'pipeline'\n}\n\n/**\n * A deal's forecast category as every reader takes it: the deal's own,\n * else its stage's, else its status's — a deal written before the field\n * existed forecasts where its stage puts it.\n */\nexport function dealForecastCategory(\n deal: Partial<Pick<CrmDeal, 'forecastCategory' | 'status'>>,\n stage: Pick<CrmDealStage, 'kind' | 'forecastCategory'> | null | undefined,\n): CrmForecastCategory {\n if (isCrmForecastCategory(deal.forecastCategory)) return deal.forecastCategory\n if (stage) return dealStageForecastCategory(stage)\n return deal.status === 'won' ? 'closed' : deal.status === 'lost' ? 'omitted' : 'pipeline'\n}\n\n/** The longest Next step a deal keeps — Salesforce's own 255. */\nexport const DEAL_NEXT_STEP_MAX = 255\n\n/**\n * A probability override as stored: a whole number 0–100, or `null` for\n * none (the stage's applies). Anything that is not a number in range is\n * `undefined` — unreadable, for the writer to refuse.\n */\nexport function readDealProbability(value: unknown): number | null | undefined {\n if (value === null || value === undefined) return null\n if (typeof value === 'string' && !value.trim()) return null\n const number = typeof value === 'number' ? value : Number(String(value).trim().replace(/%$/, ''))\n if (!Number.isFinite(number) || !Number.isInteger(number) || number < 0 || number > 100) {\n return undefined\n }\n return number\n}\n\n/**\n * A deal's probability: its own override when it holds one, else its\n * stage's, else `null` for a stage the pipeline no longer has.\n */\nexport function dealProbability(\n deal: Partial<Pick<CrmDeal, 'probability'>>,\n stage: Pick<CrmDealStage, 'probability'> | null | undefined,\n): number | null {\n const own = readDealProbability(deal.probability)\n if (typeof own === 'number') return own\n if (!stage) return null\n return Math.min(100, Math.max(0, Number(stage.probability) || 0))\n}\n\n/**\n * What a stage move writes besides the stage, the status and the clocks:\n * the new stage's forecast category, and the probability override CLEARED\n * — Salesforce re-defaults a probability when the stage changes, so an\n * override typed for the last stage does not follow the deal into the next.\n * A write that sets either field itself, in the same request, wins.\n */\nexport function dealStageMoveFields(\n stage: Pick<CrmDealStage, 'kind' | 'forecastCategory'>,\n): { forecastCategory: CrmForecastCategory; probability: null } {\n return { forecastCategory: dealStageForecastCategory(stage), probability: null }\n}\n\n/** `orgs/{orgId}/pipelines/{pipelineId}`. */\nexport interface CrmPipeline extends CrmScoped {\n name: string\n stages: CrmDealStage[]\n /** The pipeline a new deal lands in when nobody picks one. */\n isDefault?: boolean\n /**\n * When the pipeline was retired, epoch ms. An archived pipeline takes no\n * new deal and is offered by no picker, but it is never deleted: the deals\n * it closed still name it, and a report that could not resolve their\n * stages would forecast them as orphans. Absent or `null` while active.\n */\n archivedAt?: number | null\n}\n\n/** Whether a pipeline has been retired — see {@link CrmPipeline.archivedAt}. */\nexport function isPipelineArchived(\n pipeline: Pick<CrmPipeline, 'archivedAt'> | null | undefined,\n): boolean {\n return typeof pipeline?.archivedAt === 'number' && pipeline.archivedAt > 0\n}\n\n/**\n * The stages a fresh pipeline starts with: Salesforce's standard\n * Opportunity stages, with its probabilities and forecast categories\n * (AGL-3516). A pipeline seeded before them keeps the stages it was seeded\n * with — nothing migrates a stored pipeline.\n *\n * The closing stages keep the ids `won` and `lost` every pipeline has had,\n * so an automation filter naming them reads the same on old pipelines and\n * new ones.\n *\n * Readonly on purpose: a pipeline document stores its own COPY (`[...]`), so\n * a merchant editing their stages must not be editing the module's default,\n * and a second pipeline seeded later must start from the original set.\n */\nexport const DEFAULT_DEAL_STAGES: readonly CrmDealStage[] = [\n { id: 'prospecting', name: 'Prospecting', order: 0, probability: 10, kind: 'open', forecastCategory: 'pipeline' },\n { id: 'qualification', name: 'Qualification', order: 1, probability: 10, kind: 'open', forecastCategory: 'pipeline' },\n { id: 'needs-analysis', name: 'Needs Analysis', order: 2, probability: 20, kind: 'open', forecastCategory: 'pipeline' },\n { id: 'value-proposition', name: 'Value Proposition', order: 3, probability: 50, kind: 'open', forecastCategory: 'pipeline' },\n { id: 'id-decision-makers', name: 'Id. Decision Makers', order: 4, probability: 60, kind: 'open', forecastCategory: 'pipeline' },\n { id: 'perception-analysis', name: 'Perception Analysis', order: 5, probability: 70, kind: 'open', forecastCategory: 'pipeline' },\n { id: 'proposal-price-quote', name: 'Proposal/Price Quote', order: 6, probability: 75, kind: 'open', forecastCategory: 'bestCase' },\n { id: 'negotiation-review', name: 'Negotiation/Review', order: 7, probability: 90, kind: 'open', forecastCategory: 'commit' },\n { id: 'won', name: 'Closed Won', order: 8, probability: 100, kind: 'won', forecastCategory: 'closed' },\n { id: 'lost', name: 'Closed Lost', order: 9, probability: 0, kind: 'lost', forecastCategory: 'omitted' },\n]\n\nexport type CrmDealStatus = 'open' | 'won' | 'lost'\n\n/**\n * One product on a deal — what is being sold, how many, at what.\n *\n * `productId` names a catalog product when the line came from one; a line\n * typed by hand has none. The name is copied rather than joined, the way a\n * deal copies its contact's name: a catalog product can be renamed or\n * deleted after the deal was priced, and the deal has to keep saying what\n * it was for. `currency` is the deal's — every line on a deal is in one\n * currency, because their sum is the deal's amount and a sum across\n * currencies is a number with no unit.\n */\nexport interface CrmDealLineItem {\n productId?: string\n name: string\n /** A whole number of units, one or more. */\n quantity: number\n /** Per unit, in the currency's minor unit, zero or more. */\n unitAmountCents: number\n /** Lowercase ISO 4217. */\n currency: string\n}\n\n/** The most lines one deal carries — a quote, not a catalog. */\nexport const DEAL_LINE_ITEMS_MAX = 50\nexport const DEAL_LINE_ITEM_NAME_MAX = 120\n/** Units per line; past this the number is a data-entry slip. */\nexport const DEAL_LINE_ITEM_QUANTITY_MAX = 1_000_000\n\n/**\n * `orgs/{orgId}/deals/{dealId}`.\n *\n * A WON DEAL MAKES ITS CONTACT A CUSTOMER (AGL-2641). A win is the same\n * fact as a purchase — the business has decided this person bought — and an\n * order already floors a contact's lifecycle stage at `customer` on\n * capture. So every writer of `status` — the console's stage route at both\n * levels and the REST resource — applies the same floor to the contact\n * `contactId` names on the transition into `won`, in the facet of the site\n * the deal was made on (`hostId`), with `advanceContactLifecycleStage`'s\n * rule: an empty stage is filled, an earlier one advanced, and nobody is\n * ever moved back. Behind no setting, because a won deal is a customer by\n * definition; an automation on `dealWon` is for what happens NEXT.\n */\nexport interface CrmDeal extends CrmScoped {\n title: string\n titleLower?: string\n pipelineId: string\n stageId: string\n /**\n * Denormalized from the stage's `kind` at every stage move, because it is\n * what the list filters and the indexes sort on — a query cannot join the\n * pipeline to ask what the stage means.\n */\n status: CrmDealStatus\n /**\n * What the deal is worth. Typed by hand on a deal with no line items;\n * on a deal WITH them it is their sum, stored beside them by every\n * writer (`lineItemsTotalCents`), because the board, the reports and the\n * REST list all read this one field and none of them can afford to add\n * up fifty lines per row. A deal with line items refuses a typed amount.\n */\n amountCents?: number\n /** Lowercase ISO 4217; `'usd'` when absent. */\n currency?: string\n /** The products behind the amount — see {@link CrmDealLineItem}. */\n lineItems?: CrmDealLineItem[]\n expectedCloseAtMs?: number | null\n closedAtMs?: number | null\n /** When the deal last moved — what \"stuck in stage\" reports read. */\n stageChangedAtMs?: number\n ownerUid?: string\n contactId?: string\n companyId?: string\n lostReason?: string\n notes?: string\n createdByUid?: string\n /*\n * SALESFORCE'S OPPORTUNITY FIELDS (AGL-3516).\n */\n /**\n * Salesforce's Type — the LABEL of one of the org's `opportunityType`\n * values (New Business, Existing Business, …); `typeKey` beside it is\n * what the Deals list filters by.\n */\n type?: string\n /**\n * Salesforce's Lead Source — the LABEL of one of the org's `leadSource`\n * values, the picklist a lead's own lead source is; stamped from the\n * lead a conversion opens the deal for. `leadSourceKey` beside it.\n */\n leadSource?: string\n /** What happens next, at most {@link DEAL_NEXT_STEP_MAX} characters. */\n nextStep?: string\n /**\n * This deal's own chance of closing, a whole number 0–100, overriding\n * its stage's; `null` or absent means the stage's applies. Cleared by\n * every stage move ({@link dealStageMoveFields}).\n */\n probability?: number | null\n /**\n * Where the deal is forecast — stamped from the stage at every stage move\n * and changeable for this deal afterwards. See {@link dealForecastCategory}.\n */\n forecastCategory?: CrmForecastCategory\n /**\n * Salesforce's Primary Campaign Source: ONE of the org's campaigns (the\n * Marketing plugin's containers, which a lead's `campaignIds` name).\n */\n campaignId?: string\n /**\n * Salesforce's Opportunity Contact Roles (AGL-3521): the people on the\n * deal and the part each plays, at most one of them Primary — the one\n * `contactId` names. See {@link dealContactRolesOf}.\n */\n contactRoles?: CrmDealContactRole[]\n /**\n * Custom field values, keyed by the key of a definition whose `object`\n * is `deal` — see {@link fieldDefinitionObject} (AGL-2661).\n */\n custom?: Record<string, CrmCustomValue>\n /**\n * When the earliest OPEN task filed against this deal is due, epoch ms,\n * or `null` when no open task names it (AGL-2661).\n *\n * DENORMALIZED from `crmTasks`, because the question \"which deals have\n * nothing scheduled\" is asked of a LIST — a column, a filter, a report\n * tile — and a list cannot afford a task query per row. Every server\n * writer of a task recomputes it for the records the task names\n * (`recomputeCrmNextTaskAt` in the CRM's server), and a client-direct\n * task write asks the console route to do the same; the Fields section\n * offers a one-off recompute for the records written before the field\n * existed. Absent on such a record, which every reader treats as `null`.\n */\n nextTaskAtMs?: number | null\n /** Org-library files attached to the deal (AGL-2662) — see {@link CRM_MEDIA_IDS_MAX}. */\n mediaIds?: string[]\n}\n\n/*------------------------------------------\n * OPPORTUNITY CONTACT ROLES (AGL-3521).\n *\n * A deal names any number of contacts, each with the part they play — a\n * label of the org's `opportunityContactRole` picklist, or none — and at\n * most one of them Primary. The Primary is `contactId`, which every reader\n * that existed before roles keeps reading: the won-deal customer floor, the\n * send-email button, the CSV's Contact column, the REST filter. So the two\n * are written together by every door — setting a Primary sets `contactId`,\n * and a door that sets `contactId` the old way makes that contact Primary,\n * adding them without a role when the deal did not name them.\n *\n * A deal written before roles holds `contactId` alone, which reads as one\n * Primary row with no role; its first write through any door stores it.\n * Where the two disagree — a write from a door that knows only\n * `contactId` — `contactId` wins, because it is what every older reader\n * already acted on.\n *-----------------------------------------*/\n\n/** The most contacts one deal names — a buying committee, not a mailing list. */\nexport const DEAL_CONTACT_ROLES_MAX = 50\n\n/** One contact on a deal. */\nexport interface CrmDealContactRole {\n contactId: string\n /** The `opportunityContactRole` label, absent for a contact with no role yet. */\n role?: string\n primary: boolean\n}\n\n/**\n * Stored or sent roles, read defensively: a row without a usable contact\n * id is dropped, a contact named twice keeps its first row, only the first\n * Primary stays Primary, and the list stops at {@link DEAL_CONTACT_ROLES_MAX}.\n */\nexport function readDealContactRoles(raw: unknown): CrmDealContactRole[] {\n if (!Array.isArray(raw)) return []\n const roles: CrmDealContactRole[] = []\n const seen = new Set<string>()\n let primaryTaken = false\n for (const entry of raw) {\n if (!entry || typeof entry !== 'object') continue\n const row = entry as Record<string, unknown>\n const contactId = typeof row['contactId'] === 'string' ? row['contactId'].trim() : ''\n if (!contactId || contactId.length > 200 || contactId.includes('/') || seen.has(contactId)) {\n continue\n }\n seen.add(contactId)\n const role = typeof row['role'] === 'string' ? normalizePicklistLabel(row['role']) : ''\n const primary = row['primary'] === true && !primaryTaken\n if (primary) primaryTaken = true\n roles.push({ contactId, ...(role ? { role } : {}), primary })\n if (roles.length >= DEAL_CONTACT_ROLES_MAX) break\n }\n return roles\n}\n\n/**\n * `roles` with `contactId` as the one Primary — added first, with no role,\n * when the list does not name them — or, for `null`, with no Primary.\n */\nexport function dealContactRolesWithPrimary(\n roles: readonly CrmDealContactRole[],\n contactId: string | null | undefined,\n): CrmDealContactRole[] {\n const id = typeof contactId === 'string' ? contactId.trim() : ''\n const next = roles.map((row) => ({ ...row, primary: Boolean(id) && row.contactId === id }))\n if (id && !next.some((row) => row.contactId === id)) next.unshift({ contactId: id, primary: true })\n return next.slice(0, DEAL_CONTACT_ROLES_MAX)\n}\n\n/**\n * The roles a deal holds, in step with its `contactId`: the stored list\n * with the contact `contactId` names as its Primary — so a deal written\n * before roles reads as its one contact, Primary, with no role.\n */\nexport function dealContactRolesOf(deal: {\n contactId?: unknown\n contactRoles?: unknown\n}): CrmDealContactRole[] {\n const contactId = typeof deal.contactId === 'string' ? deal.contactId : null\n return dealContactRolesWithPrimary(readDealContactRoles(deal.contactRoles), contactId)\n}\n\n/** The Primary's contact id, or `null` for a deal with none. */\nexport function dealPrimaryContactId(roles: readonly CrmDealContactRole[]): string | null {\n return roles.find((row) => row.primary)?.contactId ?? null\n}\n\n/** `roles` without one contact — a delete or an erasure of that person. */\nexport function dealContactRolesWithout(\n roles: readonly CrmDealContactRole[],\n contactId: string,\n): CrmDealContactRole[] {\n return roles.filter((row) => row.contactId !== contactId)\n}\n\n/**\n * `roles` with every row naming `from` moved to `to` — a contact merge.\n * When both are on the deal the survivor keeps its own row, taking the\n * merged row's role where it has none and its Primary where it was.\n */\nexport function dealContactRolesRepointed(\n roles: readonly CrmDealContactRole[],\n from: string,\n to: string,\n): CrmDealContactRole[] {\n const merged = roles.find((row) => row.contactId === from)\n if (!merged || from === to) return [...roles]\n if (!roles.some((row) => row.contactId === to)) {\n return roles.map((row) => (row.contactId === from ? { ...row, contactId: to } : row))\n }\n return roles\n .filter((row) => row.contactId !== from)\n .map((row) =>\n row.contactId === to\n ? {\n ...row,\n ...(!row.role && merged.role ? { role: merged.role } : {}),\n primary: row.primary || merged.primary,\n }\n : row,\n )\n}\n\n/**\n * What a deal stores for a list of roles: the list, and the `contactId`\n * its Primary names — `null` for none, which a writer turns into a delete.\n */\nexport function dealContactRoleFields(roles: readonly CrmDealContactRole[]): {\n contactRoles: CrmDealContactRole[]\n contactId: string | null\n} {\n const contactRoles = readDealContactRoles(roles)\n return { contactRoles, contactId: dealPrimaryContactId(contactRoles) }\n}\n\nexport const CRM_TASK_KIND_LABELS: Record<CrmTaskKind, string> = {\n call: 'Call',\n email: 'Email',\n meeting: 'Meeting',\n todo: 'To-do',\n}\n\nexport type CrmTaskPriority = 'low' | 'normal' | 'high'\nexport type CrmTaskStatus = 'open' | 'done'\n\n/**\n * `orgs/{orgId}/crmTasks/{taskId}`.\n *\n * The one CRM record that may belong to NO site (AGL-2637): a task filed\n * from the organization's own hub carries `hostId: null` and the org scope\n * token alone, because a to-do owed by the organization — renew the\n * insurance, chase the agency's own invoice — is not captured by any brand.\n * Every other record is a fact about a person some site met, so\n * `CrmScoped` keeps its site required.\n */\nexport interface CrmTask extends Omit<CrmScoped, 'hostId'> {\n /** The site that created it, or `null` for the organization's own task. */\n hostId: string | null\n title: string\n notes?: string\n kind: CrmTaskKind\n priority: CrmTaskPriority\n status: CrmTaskStatus\n /**\n * The org's labels for `status`, `priority` and `kind` (AGL-3517) — a\n * value of the `taskStatus`, `taskPriority` and `taskType` picklists,\n * whose meaning is the field beside it. Absent or `null` shows the first\n * active value of that meaning; see `crmTaskPicklistLabels`.\n */\n statusLabel?: string | null\n priorityLabel?: string | null\n typeLabel?: string | null\n dueAtMs?: number | null\n completedAtMs?: number | null\n /**\n * Who ticked it off, which is not always the assignee: a manager closing\n * out a departed teammate's list completes tasks that were never theirs.\n * Stamped by the `crm/task-complete` route beside `completedAtMs`.\n */\n completedByUid?: string\n assigneeUid?: string\n /**\n * The person who made it, or `''` when no person did.\n *\n * An automation has no uid, and inventing one — the action's id, a\n * sentinel — would put a value into a field every reader resolves as a\n * member. The empty string says \"nobody\", and {@link sourceActionId}\n * beside it says what.\n */\n createdByUid: string\n /** The automation that created it (AGL-2605), when a person did not. */\n sourceActionId?: string\n /**\n * The plugin that filed it on the record-timeline seam (AGL-2981) — a\n * sequence's call step, a reply to answer — when no person did.\n */\n sourcePluginId?: string\n contactId?: string\n companyId?: string\n dealId?: string\n /**\n * `hosts/{hostId}/leads/{leadId}` — a task filed on a lead not yet\n * converted (AGL-3233): a sequence's call step or a reply to answer. The\n * conversion stamps the contact beside it, so the task is the contact's\n * from then on and the lead's page still lists it.\n */\n leadId?: string\n /**\n * When the assignee is reminded (AGL-2659): the due time unless a person\n * moved it, `null` for no reminder. The hourly `/api/crm/task-reminders`\n * runner reads every open task whose reminder has come due, so a task\n * that should never remind carries `null` rather than no field — a\n * missing key and a null both fall outside the range, but the null says\n * it was decided.\n */\n remindAtMs?: number | null\n /**\n * When the runner handled the reminder — sent it, or found nobody to\n * send it to. Absent while the reminder is still owed, which is how a\n * rerun over the same hour sends nothing twice; cleared again when the\n * reminder moves, because a moved reminder is a new one.\n */\n reminderSentAtMs?: number\n}\n\n/** How an activity kind reads on screen — typed so a kind cannot ship unlabeled. */\nexport const CRM_ACTIVITY_KIND_LABELS: Record<CrmActivityKind, string> = {\n call: 'Call',\n email: 'Email',\n meeting: 'Meeting',\n note: 'Note',\n other: 'Other',\n}\n\n/**\n * Whether a kind takes an outcome and a duration.\n *\n * A call and a meeting are conversations: they end somewhere (\"left a\n * voicemail\", \"agreed to a trial\") and they take a measurable amount of\n * time, and both are what a manager reading the log wants to know. An email\n * has neither in any useful sense, a note is not an event at all, and\n * `other` is unknowable — so the dialog hides the two fields for those\n * rather than offering boxes that mean nothing.\n */\nexport function activityKindHasOutcome(kind: CrmActivityKind): boolean {\n return kind === 'call' || kind === 'meeting'\n}\n\n/**\n * Where a one-to-one email got to (AGL-2615), in the order it normally\n * happens — the vocabulary the delivery webhook maps its events onto an\n * `email` activity in, and the chip the timeline shows beside the entry.\n *\n * The first four are a progression: a message is sent, then delivered, then\n * opened, then clicked, and a later state implies the earlier ones. The last\n * two are terminal failures. A message the mailbox provider bounced or that\n * the recipient reported is never \"opened\" in any sense the timeline should\n * report, whatever a tracking pixel says afterwards.\n */\nexport const CRM_EMAIL_DELIVERY_STATES = [\n 'sent',\n 'delivered',\n 'opened',\n 'clicked',\n 'bounced',\n 'complained',\n] as const\n\nexport type CrmEmailDeliveryState = (typeof CRM_EMAIL_DELIVERY_STATES)[number]\n\n/** How a delivery state reads on the chip — typed so a state cannot ship unlabeled. */\nexport const CRM_EMAIL_DELIVERY_STATE_LABELS: Record<CrmEmailDeliveryState, string> = {\n sent: 'Sent',\n delivered: 'Delivered',\n opened: 'Opened',\n clicked: 'Clicked',\n bounced: 'Bounced',\n complained: 'Marked as spam',\n}\n\nexport function isCrmEmailDeliveryState(\n value: unknown,\n): value is CrmEmailDeliveryState {\n return (\n typeof value === 'string' &&\n (CRM_EMAIL_DELIVERY_STATES as readonly string[]).includes(value)\n )\n}\n\n/** The two states that mean the message did not land. */\nexport function isCrmEmailDeliveryFailure(state: unknown): boolean {\n return state === 'bounced' || state === 'complained'\n}\n\n/**\n * The rank the webhook advances by. Higher wins; a failure outranks every\n * progression state, and a complaint outranks a bounce because it is the\n * one a sender is scored on.\n */\nconst CRM_EMAIL_DELIVERY_RANK: Record<CrmEmailDeliveryState, number> = {\n sent: 1,\n delivered: 2,\n opened: 3,\n clicked: 4,\n bounced: 5,\n complained: 6,\n}\n\n/**\n * The state an activity holds after one more delivery event, from the state\n * it held before.\n *\n * MONOTONIC. Provider events arrive at least once and in no promised order\n * — an `opened` can reach the webhook before the `delivered` it implies, and\n * a replay can hand back yesterday's `delivered` after today's `clicked` —\n * so the row keeps whichever state is further along, and an event that says\n * less than the row already knows changes nothing. A stored value the\n * vocabulary does not name reads as nothing, so the incoming event stands.\n */\nexport function nextCrmEmailDeliveryState(\n current: unknown,\n incoming: CrmEmailDeliveryState,\n): CrmEmailDeliveryState {\n if (!isCrmEmailDeliveryState(current)) return incoming\n return CRM_EMAIL_DELIVERY_RANK[incoming] > CRM_EMAIL_DELIVERY_RANK[current]\n ? incoming\n : current\n}\n\n/** The most a one-to-one email's subject may hold. */\nexport const CRM_EMAIL_SUBJECT_MAX = 200\n/** The most a one-to-one email's body may hold — a letter, not a document. */\nexport const CRM_EMAIL_BODY_MAX = 10_000\n\n/**\n * The `context` a one-to-one email is sent under, which `sendEmail` stamps\n * as a provider tag on the message and the delivery log files it by.\n */\nexport const CRM_EMAIL_CONTEXT = 'crm'\n/** The provider tag naming the activity row a one-to-one email belongs to. */\nexport const CRM_EMAIL_ACTIVITY_TAG = 'activityId'\n/** The provider tag naming the org whose `crmActivities` holds that row. */\nexport const CRM_EMAIL_ORG_TAG = 'orgId'\n\n/** What a provider tag value may be — anything else fails the whole send. */\nconst PROVIDER_TAG_VALUE = /^[A-Za-z0-9_-]{1,256}$/\n\n/**\n * The tags a one-to-one email carries so the delivery webhook can find its\n * activity row (AGL-2615): the org, the activity, and the site for the\n * per-site suppression list a bounce lands on.\n *\n * Every value is checked against the provider's alphabet rather than\n * trusted, for the reason `contextTag` gives — a value the provider rejects\n * fails the send, and a failed send is worse than an untracked one. The\n * org and the activity are the pair the webhook needs, so an unusable\n * value for EITHER yields no tags at all: a tag set that named a row it\n * could not locate would be a promise the timeline cannot keep. The site\n * is stamped when it can be and dropped alone when it cannot.\n */\nexport function crmEmailDeliveryTags(input: {\n orgId: string\n hostId: string\n activityId: string\n}): { name: string; value: string }[] {\n const orgId = String(input.orgId ?? '')\n const activityId = String(input.activityId ?? '')\n const hostId = String(input.hostId ?? '')\n if (!PROVIDER_TAG_VALUE.test(orgId) || !PROVIDER_TAG_VALUE.test(activityId)) {\n return []\n }\n return [\n { name: CRM_EMAIL_ORG_TAG, value: orgId },\n { name: CRM_EMAIL_ACTIVITY_TAG, value: activityId },\n ...(PROVIDER_TAG_VALUE.test(hostId) ? [{ name: 'hostId', value: hostId }] : []),\n ]\n}\n\n/**\n * Which way an `email` activity's message traveled. `outbound` is every\n * message the workspace wrote — sent by the platform, or copied to the\n * capture address from a mailbox; `inbound` is one a correspondent wrote,\n * which only the capture route files (AGL-2657).\n */\nexport type CrmEmailDirection = 'outbound' | 'inbound'\n\n/**\n * Which way a call went (AGL-3517) — Salesforce's Call Type: `inbound` for\n * one the contact placed, `outbound` for one the team placed, `internal`\n * for one between teammates about the record.\n */\nexport type CrmCallDirection = CrmEmailDirection | 'internal'\n\n/** An activity's direction: a call's three, of which an email takes the first two. */\nexport type CrmActivityDirection = CrmCallDirection\n\n/** The directions each kind takes, in the order a select lists them; none for the rest. */\nexport const CRM_ACTIVITY_DIRECTIONS: Readonly<Partial<Record<CrmActivityKind, readonly CrmActivityDirection[]>>> = {\n call: ['outbound', 'inbound', 'internal'],\n email: ['outbound', 'inbound'],\n}\n\nexport const CRM_ACTIVITY_DIRECTION_LABELS: Record<CrmActivityDirection, string> = {\n outbound: 'Outbound',\n inbound: 'Inbound',\n internal: 'Internal',\n}\n\n/** `value` when `kind` takes it as a direction, else `null`. */\nexport function crmActivityDirection(\n kind: unknown,\n value: unknown,\n): CrmActivityDirection | null {\n const allowed = CRM_ACTIVITY_DIRECTIONS[kind as CrmActivityKind] ?? []\n const text = String(value ?? '').trim().toLowerCase()\n return (allowed as readonly string[]).includes(text) ? (text as CrmActivityDirection) : null\n}\n\n/** \"Inbound call\", \"Outbound email\", \"Internal call\" — or the kind alone with no direction. */\nexport function crmActivityKindTitle(kind: CrmActivityKind, direction?: unknown): string {\n const known = crmActivityDirection(kind, direction)\n const noun = CRM_ACTIVITY_KIND_LABELS[kind] ?? String(kind)\n return known ? `${CRM_ACTIVITY_DIRECTION_LABELS[known]} ${noun.toLowerCase()}` : noun\n}\n\n/**\n * `orgs/{orgId}/crmActivities/{activityId}` — one thing that happened.\n *\n * Distinct from a contact's `interactions`: those are what the PLATFORM\n * recorded (a form, an order, a booking) and live capped on the row. An\n * activity is what a PERSON logged — a call made, a meeting held — and can\n * hang off a company or a deal with no contact at all.\n */\nexport interface CrmActivity extends CrmScoped {\n kind: CrmActivityKind\n body: string\n /** When it happened, which is not when it was logged. */\n atMs: number\n /** Who logged it, or `''` for an automation — see `CrmTask.createdByUid`. */\n byUid: string\n /**\n * The author's display name as it read when the activity was logged.\n *\n * Denormalized because there is no lookup that could answer it later: a\n * member document is readable by its own subject and by org-wide members\n * only, so a scoped editor reading a colleague's call log could not\n * resolve the `byUid` beside it into a name. Stamped from the signed-in\n * user's resolved name at log time and never rewritten, so it can drift\n * from a later rename — the way a signed letter keeps the name it was\n * signed with.\n */\n byName?: string\n /** The automation that logged it (AGL-2605), when a person did not. */\n sourceActionId?: string\n /**\n * The plugin that filed it on the record-timeline seam (AGL-2981) — an\n * email a sequence sent, a reply it read — or the CRM's own id on an\n * entry it wrote about a filing (AGL-3274), so a reader can tell the\n * bookkeeping entries from what a person logged.\n */\n sourcePluginId?: string\n /**\n * The campaign a filing entry is about (AGL-3274): \"Filed under\" and\n * \"Removed from\" name it in the body and carry the container's id here,\n * so a campaign's own page can one day list what happened under it\n * without parsing a sentence.\n */\n campaignId?: string\n contactId?: string\n companyId?: string\n dealId?: string\n /** The lead it was filed under (AGL-2615) — see {@link CrmActivityLink.leadId}. */\n leadId?: string\n outcome?: string\n durationMinutes?: number\n /**\n * An `email` the platform SENT (AGL-2615), as against one a person logged\n * by hand: the subject line, who it went to, and where delivery got to.\n * Absent on a hand-logged email, which has a body and nothing else.\n */\n subject?: string\n /** The address the message left for. */\n to?: string\n /**\n * On an email: `outbound` for a message the platform sent or a teammate\n * copied to the capture address; `inbound` for one a correspondent wrote\n * (AGL-2657). On a call: inbound, outbound or internal (AGL-3517). See\n * {@link CRM_ACTIVITY_DIRECTIONS}.\n */\n direction?: CrmActivityDirection\n /** See {@link CrmEmailDeliveryState}; advanced by the delivery webhook. */\n deliveryState?: CrmEmailDeliveryState\n /** When the delivery state last moved, epoch ms. */\n deliveryAtMs?: number\n /**\n * What the receiving server said when the state moved to a failure\n * (AGL-3245): a bounce's diagnostic, scrubbed of the address. Absent on a\n * progression state.\n */\n deliveryDetail?: string\n /**\n * `hosts/{hostId}/bookings/{bookingId}` — the booking a `meeting` was\n * filed from (AGL-2660), on the rows filed before a booking reached the\n * record through the record-timeline seam. A booking's meeting is now an\n * entry keyed by the booking, and `sourcePluginId` names who filed it.\n */\n bookingId?: string\n /**\n * An email CAPTURED from a mailbox (AGL-2657) — forwarded or copied to\n * the workspace's capture address — as against one the platform sent:\n * who wrote it, the provider's `Message-ID` the row is deduplicated by,\n * the message it answered, and the subject with its reply and forward\n * prefixes removed, which is what groups a thread. Absent on every other\n * email row.\n */\n from?: string\n messageId?: string\n inReplyTo?: string\n threadSubject?: string\n}\n\n/** An activity as a listener hands it back: the document plus its id. */\nexport type CrmActivityRow = CrmActivity & { $id: string }\n\n/** What a sent one-to-one email is logged from — see {@link buildCrmEmailActivity}. */\nexport interface CrmEmailActivityInput {\n subject: string\n body: string\n /** The recipient, as the message was addressed. */\n to: string\n /** When it was sent. */\n atMs: number\n /** Who sent it, or `''` for an automation — see `CrmActivity.byUid`. */\n byUid: string\n byName?: string\n sourceActionId?: string\n link: CrmActivityLink\n hostId: string\n visibleTo: string[]\n}\n\n/**\n * The activity row a sent one-to-one email is logged as (AGL-2615).\n *\n * ONE builder for the two writers — the console's send route and the\n * `sendEmail` automation step — so a message a rep wrote and a message a\n * flow sent are the same kind of entry on the timeline: `kind: 'email'`,\n * outbound, starting at `sent` for the delivery webhook to advance. A\n * writer that assembled its own would be the one whose rows the chip did\n * not know how to read. Timestamps are the writer's: a server stamps\n * `serverTimestamp()` and this module has no Firestore.\n */\nexport function buildCrmEmailActivity(input: CrmEmailActivityInput): CrmActivity {\n const { link } = input\n return {\n kind: 'email',\n subject: String(input.subject ?? '').slice(0, CRM_EMAIL_SUBJECT_MAX),\n body: String(input.body ?? '').slice(0, CRM_EMAIL_BODY_MAX),\n to: input.to,\n direction: 'outbound',\n deliveryState: 'sent',\n deliveryAtMs: input.atMs,\n atMs: input.atMs,\n byUid: input.byUid,\n ...(input.byName ? { byName: input.byName } : {}),\n ...(input.sourceActionId ? { sourceActionId: input.sourceActionId } : {}),\n // Only the links the caller fixed: a key with no value is `undefined`,\n // which Firestore refuses.\n ...(link.contactId ? { contactId: String(link.contactId) } : {}),\n ...(link.companyId ? { companyId: String(link.companyId) } : {}),\n ...(link.dealId ? { dealId: String(link.dealId) } : {}),\n ...(link.leadId ? { leadId: String(link.leadId) } : {}),\n hostId: input.hostId,\n visibleTo: [...input.visibleTo],\n }\n}\n\n/**\n * One campaign email this person was sent, and what became of it (AGL-2616).\n *\n * The per-recipient delivery log — `emailDeliveries/{key}/messages` — keeps\n * one document per MESSAGE with a timestamp per lifecycle state and a count\n * of opens and clicks, and this is that document as a timeline reads it:\n * which email, when it went out, and how far it got. A message that bounced\n * or was complained about carries that state too, because \"we mailed them\n * and it bounced\" is part of the history of a relationship and a timeline\n * that showed only the successes would read as if nothing had been tried.\n *\n * The server answers with this shape, never the log record itself: the log\n * carries the recipient's address, the provider and the links they followed,\n * and none of that is the timeline's to show.\n */\nexport interface ContactCampaignEmail {\n /** The provider's message id — distinct per send, and the entry's key. */\n messageId: string\n /** The site the campaign went out from — the send's `hostId`. */\n hostId: string\n /** `orgs/{orgId}/campaigns/{campaignId}` — the email, whose report the entry links to. */\n campaignId: string\n /**\n * The email as the team named it in the Emails console — its display name,\n * or its subject when it has none. `null` when the email has since been\n * deleted, in which case the entry still has the subject it was sent with.\n */\n campaignName: string | null\n /** The subject line the person received. */\n subject: string | null\n /** When it went out — the provider's `sent` instant, or the first event seen. */\n sentAtMs: number\n deliveredAtMs?: number\n openedAtMs?: number\n clickedAtMs?: number\n bouncedAtMs?: number\n complainedAtMs?: number\n openCount: number\n clickCount: number\n}\n\n/**\n * One entry of a contact's timeline (AGL-2600): something the platform\n * CAPTURED on the contact's facet, something a person LOGGED beside it, or\n * — since AGL-2616 — a CAMPAIGN email the person was sent.\n *\n * A tagged union rather than a flattened row, because the three are\n * different facts with different affordances — a captured interaction is\n * read-only and names the door it came through, a logged activity has an\n * author who may edit it, a campaign email is read-only and links to the\n * campaign's report — and a surface drawing the stream has to say which is\n * which. A logged activity of kind `email` and a campaign entry are two\n * different things on purpose: the first is a message a person on the team\n * sent or recorded, the second is a mailing the platform delivered, and the\n * two must never share a kind id or a glyph.\n */\nexport type ContactTimelineEntry =\n | {\n kind: 'captured'\n /** Distinct across the merged list, for a React key. */\n key: string\n atMs: number\n interaction: ContactInteraction\n }\n | {\n kind: 'logged'\n key: string\n atMs: number\n activity: CrmActivityRow\n }\n | {\n kind: 'campaign'\n key: string\n atMs: number\n email: ContactCampaignEmail\n }\n\n/**\n * What became of one campaign email, as the words a timeline row prints\n * after the email's name: `sent · delivered · opened ×2 · clicked`.\n *\n * In lifecycle order, and only the states that happened. `sent` is always\n * first because the row exists; the counts appear only past one, because\n * \"opened ×1\" says nothing \"opened\" does not. A bounce or a complaint is\n * printed where it falls, after whatever succeeded before it — a message\n * delivered and then complained about reads as both, which is what\n * happened.\n */\nexport function campaignEmailSummary(email: ContactCampaignEmail): string[] {\n const counted = (word: string, count: number): string =>\n count > 1 ? `${word} ×${count}` : word\n const parts = ['sent']\n if (email.deliveredAtMs) parts.push('delivered')\n if (email.openedAtMs) parts.push(counted('opened', email.openCount))\n if (email.clickedAtMs) parts.push(counted('clicked', email.clickCount))\n if (email.bouncedAtMs) parts.push('bounced')\n if (email.complainedAtMs) parts.push('marked as spam')\n return parts\n}\n\n/** A time that can be sorted on; anything that is not one sinks to the bottom. */\nconst sortableMs = (value: unknown): number =>\n typeof value === 'number' && Number.isFinite(value)\n ? value\n : Number.NEGATIVE_INFINITY\n\n/**\n * ONE newest-first stream from a contact's two histories.\n *\n * The captured interactions live on the contact's facet and the logged\n * activities in their own collection, and a page showing them as two lists\n * asks the reader to do the interleaving in their head — \"did the call come\n * before or after the order?\" is the question a timeline exists to answer.\n *\n * STABLE on purpose. The sort is on `atMs` alone, and at a tie the captured\n * entries keep their place ahead of the logged ones, each in the order it\n * arrived: a facet's interactions are already newest-first and a listener's\n * rows are already ordered, so the merge must not reshuffle what its inputs\n * settled. An entry with no usable time goes LAST, never first — a row that\n * cannot say when it happened must not read as the most recent thing.\n *\n * The key is what a list renders by. A captured interaction has no id of its\n * own, so its key is built from what it does carry; a logged activity's is\n * its document id; a campaign email's is the provider's message id.\n *\n * A campaign email is placed at the instant it was SENT, not at its latest\n * open. The row tells the whole story of that message in one line, and a\n * message that moved up the stream every time its reader re-opened it would\n * be a timeline that reorders itself under the reader.\n */\nexport function mergeContactTimeline(\n interactions: readonly ContactInteraction[] | null | undefined,\n activities: readonly CrmActivityRow[] | null | undefined,\n campaignEmails?: readonly ContactCampaignEmail[] | null,\n): ContactTimelineEntry[] {\n const entries: ContactTimelineEntry[] = [\n ...(interactions ?? []).map(\n (interaction, index): ContactTimelineEntry => ({\n kind: 'captured',\n key: `captured:${interaction.type}:${interaction.refId ?? index}:${interaction.atMs}:${index}`,\n atMs: interaction.atMs,\n interaction,\n }),\n ),\n ...(activities ?? []).map(\n (activity): ContactTimelineEntry => ({\n kind: 'logged',\n key: `logged:${activity.$id}`,\n atMs: activity.atMs,\n activity,\n }),\n ),\n ...(campaignEmails ?? []).map(\n (email): ContactTimelineEntry => ({\n kind: 'campaign',\n key: `campaign:${email.messageId}`,\n atMs: email.sentAtMs,\n email,\n }),\n ),\n ]\n // `Array.prototype.sort` is stable, which is what keeps the tie rule above\n // true without a secondary comparison on the entry's kind or position.\n return entries.sort((a, b) => sortableMs(b.atMs) - sortableMs(a.atMs))\n}\n\n/**\n * Which record an activity is ABOUT, for a surface that links to it, or\n * `null` when it was filed against nothing.\n *\n * A contact outranks a deal outranks a company. An activity can name all\n * three — a call with a person about a deal at their company — and the\n * contact is the one a reader means when they ask \"who was this with\"; the\n * deal is next because it is the thing with a clock on it; the company is\n * the widest and so the last resort.\n */\nexport function crmActivityRecordLink(\n activity: Pick<CrmActivity, 'contactId' | 'companyId' | 'dealId' | 'leadId'>,\n): { record: 'contact' | 'deal' | 'company' | 'lead'; id: string } | null {\n if (activity.contactId) return { record: 'contact', id: activity.contactId }\n if (activity.dealId) return { record: 'deal', id: activity.dealId }\n if (activity.companyId) return { record: 'company', id: activity.companyId }\n // A lead is the narrowest: once converted, the contact it became leads.\n if (activity.leadId) return { record: 'lead', id: activity.leadId }\n return null\n}\n\nconst MINUTE_MS = 60_000\nconst HOUR_MS = 60 * MINUTE_MS\nconst DAY_MS = 24 * HOUR_MS\n\n/**\n * How long ago something happened, in the words a list row uses.\n *\n * Coarse on purpose. A row reads \"3 days ago\" and carries the full timestamp\n * in its tooltip; the sentence exists so that a reader scanning a log can\n * tell this week from last month without parsing dates. Past a week the\n * relative form stops helping — \"412 days ago\" is a date the reader has to\n * compute — so it becomes one. A time AHEAD of now is a date too: a call\n * logged for tomorrow is a scheduling mistake the page should show plainly\n * rather than dress as \"in 14 hours\". An unusable time reads as nothing.\n *\n * English rather than `Intl.RelativeTimeFormat` because the console is\n * English-only, and the thresholds are what the spec pins.\n */\nexport function activityTimeLabel(atMs: number, nowMs: number): string {\n if (!Number.isFinite(atMs) || !Number.isFinite(nowMs)) return ''\n const elapsed = nowMs - atMs\n if (elapsed < -MINUTE_MS || elapsed >= 7 * DAY_MS) {\n return new Date(atMs).toLocaleDateString()\n }\n if (elapsed < MINUTE_MS) return 'just now'\n if (elapsed < HOUR_MS) return `${Math.floor(elapsed / MINUTE_MS)} min ago`\n if (elapsed < DAY_MS) return `${Math.floor(elapsed / HOUR_MS)} h ago`\n if (elapsed < 2 * DAY_MS) return 'yesterday'\n return `${Math.floor(elapsed / DAY_MS)} days ago`\n}\n\nexport type ContactFieldType =\n | 'text'\n | 'number'\n | 'date'\n | 'select'\n | 'checkbox'\n | 'url'\n\n/**\n * `orgs/{orgId}/contactFields/{fieldId}` — a custom field a holder defined.\n *\n * The `key` is the map key under a facet's `custom`, so it is IMMUTABLE once\n * a value has been written under it; a rename is a new field and a retire.\n * `retiredAt` rather than a delete for the same reason: values written under\n * the key survive, and a retired field still has to be able to read them\n * back on an export.\n */\nexport interface ContactFieldDefinition extends CrmScoped {\n /** `^[a-z][a-z0-9_]{0,39}$` — see {@link normalizeContactFieldKey}. */\n key: string\n label: string\n type: ContactFieldType\n /** The choices, for `select`. */\n options?: string[]\n required?: boolean\n /** Position in the form and the export, ascending. */\n order: number\n retiredAt?: number | null\n /**\n * Which record the field describes (AGL-2661). ABSENT means `contact`,\n * because every definition written before companies and deals could carry\n * custom fields described a contact, and a backfill that stamped them\n * would touch every org for a fact the reader can infer. Read it through\n * {@link fieldDefinitionObject}, never directly, so that inference lives\n * in one place. Keys are unique PER OBJECT: a company field and a contact\n * field may both be called `region`.\n */\n object?: CrmFieldObject\n}\n\n/**\n * The records a custom field may describe, in the order the Fields\n * section tabs them (AGL-2661), with `lead` since AGL-3272.\n *\n * A lead is last because it is the record a person leaves: the three\n * before it are what a lead becomes, and the tabs read in that order.\n */\nexport const CRM_FIELD_OBJECTS = ['contact', 'company', 'deal', 'lead'] as const\n\nexport type CrmFieldObject = (typeof CRM_FIELD_OBJECTS)[number]\n\n/** How each object reads on the Fields section's tabs and in a refusal. */\nexport const CRM_FIELD_OBJECT_LABELS: Record<CrmFieldObject, string> = {\n contact: 'Contacts',\n company: 'Companies',\n deal: 'Deals',\n lead: 'Leads',\n}\n\nexport function isCrmFieldObject(value: unknown): value is CrmFieldObject {\n return (\n typeof value === 'string' &&\n (CRM_FIELD_OBJECTS as readonly string[]).includes(value)\n )\n}\n\n/**\n * Which record a definition describes — `contact` when the document says\n * nothing, or says something no reader understands.\n *\n * The one reader of {@link ContactFieldDefinition.object}. A stored value\n * outside the list is read as `contact` rather than refused, for the\n * reason an absent one is: the definition predates the field, or was\n * written by something that never learned it, and either way the values\n * under its key sit on contacts.\n */\nexport function fieldDefinitionObject(\n definition: Pick<ContactFieldDefinition, 'object'> | null | undefined,\n): CrmFieldObject {\n return isCrmFieldObject(definition?.object) ? definition.object : 'contact'\n}\n\n/**\n * What the CRM › Fields table's query reads on a definition (AGL-3335):\n *\n * object which tab it belongs to, stored even for a contact's, because\n * a query cannot find a field's absence;\n * required a boolean on every definition, for the Required filter;\n * searchTokens every word prefix of its name and of its key, the key read\n * whole and split at `_`, `-` and `.`, so \"plan\" finds\n * `plan_interest` and \"interest\" does too.\n *\n * Its script-side twin is `crmFieldListFields` in\n * `tools/scripts/lib/org-record-list-fields.mjs`; both answer the\n * `fieldDefinitions` of `org-record-list-fields.fixtures.json`.\n */\nexport function crmFieldListFields(definition: Record<string, unknown> | null | undefined): {\n object: CrmFieldObject\n required: boolean\n searchTokens: string[]\n} {\n const label = typeof definition?.['label'] === 'string' ? definition['label'] : ''\n const key = typeof definition?.['key'] === 'string' ? definition['key'] : ''\n return {\n object: fieldDefinitionObject(definition as Pick<ContactFieldDefinition, 'object'>),\n required: definition?.['required'] === true,\n searchTokens: nameSearchTokens([label, key, key.replace(/[_.-]+/g, ' ')].join(' ')),\n }\n}\n\n/**\n * The definition type under the name new code uses (AGL-2661). The type,\n * the collection (`contactFields`) and the rules match keep the contact\n * name: renaming them would ripple through the rules, the REST resources\n * and the docs for no change in what is stored.\n */\nexport type CrmFieldDefinition = ContactFieldDefinition\n\n/** What a stored field key must look like — a letter, then up to 39 of `[a-z0-9_]`. */\nexport const CONTACT_FIELD_KEY_PATTERN = /^[a-z][a-z0-9_]{0,39}$/\n\n/**\n * A typed label, as the key it would be stored under, or `null` when nothing\n * usable survives.\n *\n * Lowercased and snake-cased rather than refused, because the key is derived\n * from the label a merchant typed (\"Annual revenue\" → `annual_revenue`) and\n * they should not have to learn the grammar. Anything outside `[a-z0-9_]` is\n * dropped after separators become underscores, runs collapse, and a leading\n * run of digits or underscores goes — a key has to start with a letter so it\n * can never collide with an array index or read as a number in a filter.\n */\nexport function normalizeContactFieldKey(input: unknown): string | null {\n const key = String(input ?? '')\n .trim()\n .toLowerCase()\n .replace(/[\\s-]+/g, '_')\n .replace(/[^a-z0-9_]/g, '')\n .replace(/_+/g, '_')\n .replace(/^[^a-z]+/, '')\n .replace(/_+$/, '')\n .slice(0, 40)\n return CONTACT_FIELD_KEY_PATTERN.test(key) ? key : null\n}\n\n/** The stage a deal is in, or `null` when the pipeline no longer has it. */\nexport function dealStageById(\n pipeline: Pick<CrmPipeline, 'stages'> | null | undefined,\n stageId: string,\n): CrmDealStage | null {\n return pipeline?.stages?.find((stage) => stage.id === stageId) ?? null\n}\n\n/**\n * What a deal is worth to a forecast, in cents.\n *\n * STATUS wins over the stage. A deal marked won is worth its full amount\n * whatever stage it happens to sit in, and a lost one is worth nothing —\n * the stage's probability is the odds of an OPEN deal, and applying it to a\n * closed one would forecast a sale that has already happened as a fraction\n * of itself. An open deal with no resolvable stage is worth nothing rather\n * than everything: the pipeline lost the stage, and a forecast that filled\n * the gap with 100% would be the most optimistic number available.\n *\n * An open deal's own probability override (AGL-3516) replaces its stage's\n * — see {@link dealProbability}.\n */\nexport function weightedDealAmountCents(\n deal: Pick<CrmDeal, 'amountCents' | 'status'> & Partial<Pick<CrmDeal, 'probability'>>,\n stage: Pick<CrmDealStage, 'probability'> | null | undefined,\n): number {\n const amount = Math.max(0, Math.round(Number(deal.amountCents ?? 0) || 0))\n if (deal.status === 'won') return amount\n if (deal.status === 'lost' || !stage) return 0\n const probability = dealProbability(deal, stage) ?? 0\n return Math.round((amount * probability) / 100)\n}\n\n/** Whether a deal's amount is derived — it has at least one line item. */\nexport function dealHasLineItems(\n deal: Pick<CrmDeal, 'lineItems'> | null | undefined,\n): boolean {\n return Array.isArray(deal?.lineItems) && deal.lineItems.length > 0\n}\n\n/**\n * What a set of line items adds up to, in cents — the amount a deal with\n * line items stores. Whole cents, never negative: a line's quantity and\n * unit amount have both been through `readDealLineItems`, but a stored\n * document from an older writer is trusted no further than that.\n */\nexport function lineItemsTotalCents(\n items: readonly Pick<CrmDealLineItem, 'quantity' | 'unitAmountCents'>[] | null | undefined,\n): number {\n let total = 0\n for (const item of items ?? []) {\n const quantity = Math.max(0, Math.round(Number(item.quantity) || 0))\n const unit = Math.max(0, Math.round(Number(item.unitAmountCents) || 0))\n total += quantity * unit\n }\n return total\n}\n\n/**\n * Line items as a client typed or sent them, validated into the stored\n * shape, or the one reason they were refused.\n *\n * The one reader for both writers — the products card on a deal's page\n * and `POST`/`PATCH /v1/deals` — so a line the console accepts is a line\n * the API accepts and the other way round. The rules: at most\n * {@link DEAL_LINE_ITEMS_MAX} lines; every line a name, a whole quantity\n * of one or more, a whole unit amount of zero or more; every line in the\n * DEAL's currency (`currency`), which a line may omit and may not\n * contradict. An empty list is valid and means \"no line items\" — the\n * amount goes back to being typed.\n */\nexport function readDealLineItems(\n input: unknown,\n currency: string,\n): { items: CrmDealLineItem[] } | { error: string } {\n if (input === undefined || input === null) return { items: [] }\n if (!Array.isArray(input)) return { error: 'Line items must be a list' }\n if (input.length > DEAL_LINE_ITEMS_MAX) {\n return { error: `A deal carries at most ${DEAL_LINE_ITEMS_MAX} line items` }\n }\n const dealCurrency = String(currency || 'usd').trim().toLowerCase()\n const items: CrmDealLineItem[] = []\n for (const [index, raw] of input.entries()) {\n const at = `Line ${index + 1}`\n if (!raw || typeof raw !== 'object' || Array.isArray(raw)) {\n return { error: `${at} must be an object` }\n }\n const line = raw as Record<string, unknown>\n const name = String(line.name ?? '')\n .trim()\n .slice(0, DEAL_LINE_ITEM_NAME_MAX)\n if (!name) return { error: `${at} needs a name` }\n const quantity = Number(line.quantity)\n if (!Number.isInteger(quantity) || quantity < 1 || quantity > DEAL_LINE_ITEM_QUANTITY_MAX) {\n return {\n error: `${at}: the quantity must be a whole number from 1 to ${DEAL_LINE_ITEM_QUANTITY_MAX.toLocaleString()}`,\n }\n }\n const unitAmountCents = Number(line.unitAmountCents)\n if (!Number.isInteger(unitAmountCents) || unitAmountCents < 0) {\n return { error: `${at}: the unit amount must be a whole number of cents, 0 or more` }\n }\n const lineCurrency =\n line.currency === undefined || line.currency === null || line.currency === ''\n ? dealCurrency\n : String(line.currency).trim().toLowerCase()\n if (lineCurrency !== dealCurrency) {\n return { error: `${at} is in ${lineCurrency.toUpperCase()}; every line must be in the deal's currency, ${dealCurrency.toUpperCase()}` }\n }\n const productId =\n typeof line.productId === 'string' && line.productId.trim()\n ? line.productId.trim().slice(0, 200)\n : undefined\n items.push({\n ...(productId ? { productId } : {}),\n name,\n quantity,\n unitAmountCents,\n currency: dealCurrency,\n })\n }\n return { items }\n}\n\nexport type TaskDueState = 'overdue' | 'today' | 'upcoming' | 'none' | 'done'\n\n/**\n * Where a task stands against the clock.\n *\n * `today` is decided on the LOCAL calendar day and beats `overdue`: a task\n * due at nine this morning is today's work until midnight, not something the\n * list should paint red at 9:01. Overdue is yesterday or earlier. `done`\n * comes first because a completed task's due date is history whichever side\n * of now it fell on.\n */\nexport function taskDueState(\n task: Pick<CrmTask, 'status' | 'dueAtMs'>,\n nowMs: number,\n): TaskDueState {\n if (task.status === 'done') return 'done'\n const dueAtMs = task.dueAtMs\n if (typeof dueAtMs !== 'number' || !Number.isFinite(dueAtMs)) return 'none'\n const due = new Date(dueAtMs)\n const now = new Date(nowMs)\n const sameDay =\n due.getFullYear() === now.getFullYear() &&\n due.getMonth() === now.getMonth() &&\n due.getDate() === now.getDate()\n if (sameDay) return 'today'\n return dueAtMs < nowMs ? 'overdue' : 'upcoming'\n}\n\n/**\n * A hostname label: `[a-z0-9-]`, not starting or ending with a hyphen, at\n * most 63 characters — RFC 1123 as far as a company domain needs it.\n */\nconst DOMAIN_LABEL = /^[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?$/\n\n/**\n * A typed or pasted domain, reduced to the bare lowercase hostname a company\n * is keyed by, or `null` when what is left is not one.\n *\n * Strips what people paste along with a domain — the protocol, a `www.`, a\n * path, a query, a port — because the value is a KEY: two contacts at\n * `https://www.acme.com/about` and `acme.com` work for one company, and the\n * match is only findable if both reduce to the same string. The last label\n * has to be letters because a company's domain has a TLD; a bare IP address\n * or a single word is not one and answers `null` rather than being stored as\n * something a later match will never hit.\n */\nexport function normalizeCompanyDomain(input: unknown): string | null {\n let value = String(input ?? '')\n .trim()\n .toLowerCase()\n if (!value) return null\n value = value.replace(/^[a-z][a-z0-9+.-]*:\\/\\//, '')\n value = value.replace(/^www\\./, '')\n value = value.split(/[/?#]/, 1)[0]\n value = value.split(':', 1)[0]\n value = value.replace(/\\.+$/, '')\n if (!value || value.length > 253) return null\n const labels = value.split('.')\n if (labels.length < 2) return null\n if (!labels.every((label) => DOMAIN_LABEL.test(label))) return null\n if (!/^[a-z]{2,}$/.test(labels[labels.length - 1])) return null\n return value\n}\n\n/** The longest website URL a company stores. */\nconst COMPANY_WEBSITE_MAX = 500\n\n/**\n * The typed website, as a URL; `''` for a blank; `null` when it cannot be\n * one.\n *\n * People type `acme.com` where a URL is asked for, and refusing that is\n * pedantry — it becomes `https://acme.com`. What IS refused is anything the\n * URL parser cannot read or a scheme other than http(s): a `javascript:` link\n * on a record that renders as an anchor is not a website. One function for\n * the company drawer and the companies import, so a website typed and a\n * website imported are stored the same way.\n */\nexport function normalizeCompanyWebsite(input: unknown): string | null {\n const raw = String(input ?? '').trim()\n if (!raw) return ''\n const candidate = /^[a-z][a-z0-9+.-]*:\\/\\//i.test(raw) ? raw : `https://${raw}`\n try {\n const url = new URL(candidate)\n if (url.protocol !== 'http:' && url.protocol !== 'https:') return null\n return url.href.length > COMPANY_WEBSITE_MAX ? null : url.href\n } catch {\n return null\n }\n}\n\n/*==========================================\n * A COMPANY'S ACCOUNT FIELDS (AGL-3514).\n *\n * Salesforce's Account carries more than a name and a domain: a Type, an\n * Industry, a Rating, an Ownership and an Account Source, each a picklist;\n * an annual revenue and a head count; a fax, an account number, a site, a\n * ticker symbol and an SIC code; a shipping address beside the billing one;\n * and a parent account. Every door that writes a company — the drawer, the\n * REST resource, the import, a lead's conversion — reads them through this\n * block, so a value one door refuses is refused by all of them.\n *\n * ## Each field's three answers\n *\n * A door reads what a request said about a field: not named (`undefined`)\n * leaves the record alone, `null` or a blank clears it, and anything else\n * is normalized or refused by name. A picklist field stores the value's\n * LABEL, judged against the org's list with the record's current value\n * kept (see the picklist block); the Companies list filters by the key of\n * Type, Industry and Rating, which `crmCompanyListFields` writes beside\n * the label.\n *=========================================*/\n\n/** Each company picklist field: the list it holds a value of, and the key the list filters by. */\nexport const CRM_COMPANY_PICKLIST_FIELDS = [\n { field: 'type', picklistId: 'accountType', keyField: 'typeKey' },\n { field: 'industry', picklistId: 'industry', keyField: 'industryKey' },\n { field: 'rating', picklistId: 'rating', keyField: 'ratingKey' },\n { field: 'ownership', picklistId: 'ownership' },\n { field: 'accountSource', picklistId: 'leadSource', keyField: 'accountSourceKey' },\n] as const\n\nexport type CrmCompanyPicklistField = (typeof CRM_COMPANY_PICKLIST_FIELDS)[number]['field']\n\n/** The short text fields, each capped at Salesforce's own length. */\nexport const CRM_COMPANY_TEXT_MAX = {\n accountNumber: 40,\n site: 80,\n tickerSymbol: 20,\n sicCode: 20,\n} as const\n\nexport type CrmCompanyTextField = keyof typeof CRM_COMPANY_TEXT_MAX\n\n/** The most employees a company records — Salesforce's eight digits. */\nexport const CRM_COMPANY_EMPLOYEES_MAX = 99_999_999\n\n/** The account fields that are not picklists, as every door names them. */\nexport const CRM_COMPANY_ACCOUNT_FIELDS = [\n 'annualRevenueCents',\n 'currency',\n 'numberOfEmployees',\n 'fax',\n 'accountNumber',\n 'site',\n 'tickerSymbol',\n 'sicCode',\n 'shippingAddress',\n] as const\n\nexport type CrmCompanyAccountField = (typeof CRM_COMPANY_ACCOUNT_FIELDS)[number]\n\n/** A short text field as stored: trimmed, single-spaced. */\nexport function normalizeCompanyText(value: unknown): string {\n return String(value ?? '')\n .trim()\n .replace(/\\s+/g, ' ')\n}\n\nconst blank = (value: unknown): boolean =>\n value === null || (typeof value === 'string' && value.trim() === '')\n\n/**\n * The account fields a request names, normalized, or the refusal of each\n * one that cannot be stored — see the block above. A value the door has\n * already parsed from text (the drawer's revenue, a file's head count)\n * arrives here as the number it parsed.\n */\nexport function readCrmCompanyAccountFields(input: Readonly<Record<string, unknown>>): {\n values: Partial<Pick<CrmCompany, CrmCompanyAccountField>>\n errors: Record<string, string>\n} {\n const values: Record<string, unknown> = {}\n const errors: Record<string, string> = {}\n for (const field of CRM_COMPANY_ACCOUNT_FIELDS) {\n const raw = input[field]\n if (raw === undefined) continue\n if (blank(raw)) {\n values[field] = null\n continue\n }\n switch (field) {\n case 'annualRevenueCents':\n if (typeof raw === 'number' && Number.isSafeInteger(raw) && raw >= 0) values[field] = raw\n else errors[field] = 'Must be a whole number of cents, 0 or more'\n break\n case 'currency': {\n const code = typeof raw === 'string' ? raw.trim().toLowerCase() : ''\n if (/^[a-z]{3}$/.test(code)) values[field] = code\n else errors[field] = 'Must be a three-letter ISO 4217 code, like usd'\n break\n }\n case 'numberOfEmployees':\n if (typeof raw === 'number' && Number.isInteger(raw) && raw >= 0 && raw <= CRM_COMPANY_EMPLOYEES_MAX) {\n values[field] = raw\n } else {\n errors[field] = `Must be a whole number from 0 to ${CRM_COMPANY_EMPLOYEES_MAX}`\n }\n break\n case 'fax': {\n const fax = typeof raw === 'string' ? normalizePhone(raw) : null\n if (fax) values[field] = fax\n else errors[field] = 'Must be a phone number with a country code, like +15125550123'\n break\n }\n case 'shippingAddress':\n if (typeof raw === 'object' && !Array.isArray(raw)) {\n values[field] = normalizeAddress(raw as AglynPostalAddress)\n } else {\n errors[field] = 'Must be an address object'\n }\n break\n default: {\n const max = CRM_COMPANY_TEXT_MAX[field]\n if (typeof raw !== 'string' && typeof raw !== 'number') {\n errors[field] = 'Must be text'\n break\n }\n const text = normalizeCompanyText(raw)\n if (text.length > max) errors[field] = `Must be at most ${max} characters`\n else values[field] = text\n }\n }\n }\n return { values: values as Partial<Pick<CrmCompany, CrmCompanyAccountField>>, errors }\n}\n\n/**\n * The picklist fields a request names, judged against the org's lists —\n * `lists` holds each list the caller read, and a list it did not read is\n * judged as the standard values alone. `current` is the record as stored:\n * its value is kept even when the list no longer offers it, which is how\n * a company whose industry was typed before Industry became a picklist\n * keeps it. On a create (`created`), a field not named starts from its\n * list's default when the org set one.\n */\nexport function judgeCrmCompanyPicklists(\n lists: Readonly<Partial<Record<CrmPicklistId, CrmPicklist>>>,\n requested: Readonly<Partial<Record<string, unknown>>>,\n options: { current?: Readonly<Partial<Record<string, unknown>>>; created?: boolean } = {},\n): {\n values: Partial<Record<CrmCompanyPicklistField, string | null>>\n errors: Record<string, string>\n} {\n const values: Partial<Record<CrmCompanyPicklistField, string | null>> = {}\n const errors: Record<string, string> = {}\n for (const { field, picklistId } of CRM_COMPANY_PICKLIST_FIELDS) {\n const list = lists[picklistId] ?? effectiveCrmPicklist(picklistId, undefined)\n const raw = requested[field]\n if (raw === undefined) {\n const fallback = options.created ? picklistDefaultLabel(list) : null\n if (fallback) values[field] = fallback\n continue\n }\n if (blank(raw)) {\n values[field] = null\n continue\n }\n if (typeof raw !== 'string') {\n errors[field] = 'Must be text'\n continue\n }\n const judged = judgeCrmPicklistValue(picklistId, list, raw, options.current?.[field])\n if (judged.ok === false) errors[field] = judged.error\n else values[field] = judged.value\n }\n return { values, errors }\n}\n\n/** How deep a chain of parent companies may run. */\nexport const CRM_COMPANY_PARENT_DEPTH_MAX = 25\n\n/**\n * What a parent lookup answers for one company id: its own parent, or\n * `null` when there is no such company the writer may see.\n */\nexport type CrmCompanyParentReader = (\n companyId: string,\n) => Promise<{ parentCompanyId: string | null } | null>\n\n/**\n * Why `parentId` cannot be the parent of `companyId` (`null` for a company\n * not yet created), or `null` when it can: a company is never its own\n * parent, the parent must be a company the writer can see, and a company\n * already UNDER this one cannot be put above it — the cycle Salesforce\n * refuses too. Walks up from the parent through `read`, at most\n * {@link CRM_COMPANY_PARENT_DEPTH_MAX} steps.\n */\nexport async function crmCompanyParentRefusal(\n companyId: string | null,\n parentId: string,\n read: CrmCompanyParentReader,\n): Promise<string | null> {\n if (companyId && parentId === companyId) return 'A company cannot be its own parent.'\n const parent = await read(parentId)\n if (!parent) return 'There is no such parent company.'\n const seen = new Set<string>([parentId])\n let cursor = parent.parentCompanyId\n for (let depth = 1; cursor; depth += 1) {\n if (companyId && cursor === companyId) {\n return 'That company sits under this one, so it cannot be its parent.'\n }\n // A loop above that does not pass through this company is not this write's.\n if (seen.has(cursor)) return null\n if (depth >= CRM_COMPANY_PARENT_DEPTH_MAX) {\n return `A company can sit at most ${CRM_COMPANY_PARENT_DEPTH_MAX} levels under another.`\n }\n seen.add(cursor)\n cursor = (await read(cursor))?.parentCompanyId ?? null\n }\n return null\n}\n\n/** A stored parent id as a reader takes it, `null` for none. */\nexport function crmCompanyParentId(value: unknown): string | null {\n return typeof value === 'string' && value.trim() !== '' ? value : null\n}\n\n/**\n * Mailbox domains that belong to people rather than to a business: the\n * providers anybody can sign up with, and the ones an internet provider\n * hands its subscribers.\n *\n * Auto-associating a contact with a company by email domain is the whole\n * reason {@link companyDomainForEmail} exists, and it is wrong for every\n * address at a public mailbox: `gmail.com` is not a company, and a rule\n * that treated it as one would file half of a consumer list under a single\n * phantom account. It is also the fact a cold email has to know — an\n * address here is a person's own, not their work address — which is why the\n * list is exported with {@link isPublicMailboxDomain}: a second list kept\n * for that question would drift from this one.\n *\n * Exact domains, not suffixes: a subscriber's regional host (`nc.rr.com`)\n * is not listed, and neither is a company that happens to end in `mail.com`.\n */\nexport const PUBLIC_MAILBOX_DOMAINS: ReadonlySet<string> = new Set([\n // Google, Yahoo, Microsoft, Apple, AOL.\n 'gmail.com',\n 'googlemail.com',\n 'yahoo.com',\n 'yahoo.co.uk',\n 'yahoo.ca',\n 'yahoo.com.au',\n 'yahoo.co.in',\n 'yahoo.fr',\n 'yahoo.de',\n 'ymail.com',\n 'rocketmail.com',\n 'hotmail.com',\n 'hotmail.co.uk',\n 'hotmail.fr',\n 'hotmail.de',\n 'hotmail.it',\n 'outlook.com',\n 'live.com',\n 'live.co.uk',\n 'live.ca',\n 'msn.com',\n 'icloud.com',\n 'me.com',\n 'mac.com',\n 'aol.com',\n 'aim.com',\n // Independent and privacy-first providers.\n 'proton.me',\n 'protonmail.com',\n 'protonmail.ch',\n 'pm.me',\n 'tutanota.com',\n 'tuta.io',\n 'hushmail.com',\n 'gmx.com',\n 'gmx.net',\n 'gmx.de',\n 'web.de',\n 'mail.com',\n 'yandex.com',\n 'yandex.ru',\n 'zoho.com',\n 'zohomail.com',\n 'fastmail.com',\n 'hey.com',\n // The largest consumer providers outside the US and Europe.\n 'qq.com',\n '163.com',\n '126.com',\n 'sina.com',\n 'naver.com',\n 'hanmail.net',\n 'rediffmail.com',\n // US internet providers' subscriber mailboxes.\n 'comcast.net',\n 'att.net',\n 'sbcglobal.net',\n 'bellsouth.net',\n 'pacbell.net',\n 'verizon.net',\n 'cox.net',\n 'charter.net',\n 'earthlink.net',\n 'optonline.net',\n 'frontier.com',\n 'windstream.net',\n 'centurylink.net',\n 'juno.com',\n 'netzero.net',\n // Subscriber mailboxes at the largest providers in Canada, the UK, France,\n // Germany, Italy and Australia.\n 'shaw.ca',\n 'sympatico.ca',\n 'btinternet.com',\n 'sky.com',\n 'virginmedia.com',\n 'orange.fr',\n 'free.fr',\n 'laposte.net',\n 't-online.de',\n 'libero.it',\n 'bigpond.com',\n])\n\n/**\n * Whether a domain — or the domain of an address — is one of the\n * {@link PUBLIC_MAILBOX_DOMAINS}. Read the way a company domain is, so case,\n * a trailing dot and a pasted `www.` do not hide one.\n */\nexport function isPublicMailboxDomain(domainOrEmail: unknown): boolean {\n const value = String(domainOrEmail ?? '').trim()\n const domain = normalizeCompanyDomain(value.slice(value.lastIndexOf('@') + 1))\n return domain !== null && PUBLIC_MAILBOX_DOMAINS.has(domain)\n}\n\n/**\n * The company domain an email address implies, or `null` when it implies\n * none — a malformed address, or one at a public mailbox provider.\n */\nexport function companyDomainForEmail(email: unknown): string | null {\n const value = String(email ?? '')\n .trim()\n .toLowerCase()\n const at = value.lastIndexOf('@')\n if (at < 1 || at === value.length - 1) return null\n if (/[\\s@]/.test(value.slice(0, at))) return null\n const domain = normalizeCompanyDomain(value.slice(at + 1))\n if (!domain || PUBLIC_MAILBOX_DOMAINS.has(domain)) return null\n return domain\n}\n\n/**\n * The name a company minted from a domain starts with: the first label with\n * a capital — `acme.com` → `Acme`, `initech.co.uk` → `Initech`.\n *\n * A starting point and not a claim about what the business is called: the\n * lead-convert dialog offers it for editing, and a company the capture door\n * creates on its own carries it until somebody renames the record. The\n * domain itself would be an honest name too, but a list of companies that\n * reads `acme.com`, `globex.example` looks like a list of websites, and the\n * domain is on the row beside the name anyway.\n */\nexport function companyNameForDomain(domain: string): string {\n const label = domain.split('.')[0] || domain\n return label.charAt(0).toUpperCase() + label.slice(1)\n}\n\n/*==========================================\n * THE CONTACT–COMPANY LINK (AGL-2597, AGL-2613).\n *\n * The association proper is `facets.{groupId}.companyId` — one company per\n * holder, inside that holder's facet like the notes and the tags, and for\n * the same reason: which account a person belongs to is one business's\n * knowledge of them. But a facet path is per group, and Firestore cannot\n * answer \"every contact whose facet, whichever group's, names company X\" —\n * a `where` needs one field path, and the group id is part of the path. So\n * the facet is MIRRORED into a top-level array an `array-contains` can\n * query, and the company carries a COUNT of the contacts whose mirror names\n * it, so a list of companies can say how many people are at each without\n * reading a person.\n *\n * Three fields, kept in step by ONE planner. Every writer — the picker on a\n * contact's page, the company page's link control, the bulk bar, the server\n * doors that link on capture — asks {@link planContactCompanyLink} what\n * changes and applies the answer with its own SDK's sentinels. None of them\n * decides for itself whether the old id leaves the mirror, which is the rule\n * a second copy would get wrong: another business filing the same person\n * under the same account is THEIR link, and this holder letting go must not\n * take it away.\n *=========================================*/\n\n/**\n * The top-level field on a CONTACT naming every company it is linked to.\n *\n * It carries the union of every holder's link, so removing an id from it is\n * only correct when no other holder's facet still names that id — the rule\n * the planner enforces, and the reason nothing writes this field without\n * going through it. The facet is the truth and this is its index: a reader\n * answering \"which company is this person at\" reads the facet; only a QUERY\n * reads this.\n */\nexport const CONTACT_COMPANY_IDS_FIELD = 'companyIds'\n\n/**\n * The field on a COMPANY counting the contacts whose mirror names it.\n *\n * Denormalized because the honest figure is an aggregate over every contact\n * in the org, which a list of two hundred companies cannot afford to take\n * per row. Moved by `increment` in the same batch as the link that changes\n * it, so the two cannot disagree by a failed second write; a company's own\n * page still takes the live aggregate, which is what corrects a count that\n * predates the counter.\n */\nexport const COMPANY_CONTACTS_COUNT_FIELD = 'contactsCount'\n\n/** What the planner needs to know about a contact's links, and nothing else. */\nexport interface ContactCompanyLinkState {\n /** This holder's link, or `null` when the facet names no company. */\n companyId: string | null\n /** The mirror as stored, in no order. */\n companyIds: string[]\n /**\n * Ids named by OTHER holders' facets — what keeps an id in the mirror\n * when this holder lets go of it.\n */\n heldElsewhere: string[]\n}\n\n/** The link state of a stored contact, read for one holder. */\nexport function readContactCompanyLink(\n contact: Record<string, unknown> | null | undefined,\n groupId: string,\n): ContactCompanyLinkState {\n const document = contact ?? {}\n const facets = document[CONTACT_FACETS_FIELD]\n const heldElsewhere = new Set<string>()\n if (facets && typeof facets === 'object' && !Array.isArray(facets)) {\n for (const [holder, facet] of Object.entries(\n facets as Record<string, unknown>,\n )) {\n if (holder === groupId) continue\n const named =\n facet && typeof facet === 'object' && !Array.isArray(facet)\n ? (facet as Record<string, unknown>)['companyId']\n : undefined\n if (typeof named === 'string' && named) heldElsewhere.add(named)\n }\n }\n const mirror = document[CONTACT_COMPANY_IDS_FIELD]\n return {\n companyId: readContactFacet(document, groupId).companyId ?? null,\n companyIds: Array.isArray(mirror)\n ? mirror.filter((id): id is string => typeof id === 'string' && !!id)\n : [],\n heldElsewhere: [...heldElsewhere],\n }\n}\n\n/**\n * Every holder whose facet names this company, in no order.\n *\n * What a company's DELETION clears. Unlike a link change, which is one\n * holder's, a deletion takes the record away from every holder at once: a\n * facet still naming it would be a link to nothing, on a surface its holder\n * has no reason to revisit. Nothing else about another holder's facet is\n * read.\n */\nexport function contactGroupsNamingCompany(\n contact: Record<string, unknown> | null | undefined,\n companyId: string,\n): string[] {\n const facets = (contact ?? {})[CONTACT_FACETS_FIELD]\n if (!companyId || !facets || typeof facets !== 'object' || Array.isArray(facets)) {\n return []\n }\n return Object.entries(facets as Record<string, unknown>)\n .filter(([, facet]) =>\n Boolean(\n facet &&\n typeof facet === 'object' &&\n !Array.isArray(facet) &&\n (facet as Record<string, unknown>)['companyId'] === companyId,\n ),\n )\n .map(([groupId]) => groupId)\n}\n\n/**\n * How the mirror changes. Three shapes because Firestore takes ONE transform\n * per field per write: an `arrayUnion` and an `arrayRemove` on the same\n * field cannot share an update, so a move rewrites the array whole.\n */\nexport type ContactCompanyMirrorChange =\n | { op: 'union'; companyId: string }\n | { op: 'remove'; companyId: string }\n | { op: 'set'; companyIds: string[] }\n\nexport interface ContactCompanyLinkPlan {\n /** The facet's new value: the id, or `null` meaning delete the field. */\n companyId: string | null\n /** What happens to the mirror, or `null` when it already carries the right ids. */\n mirror: ContactCompanyMirrorChange | null\n /**\n * Per company, how its contacts count moves. A company enters the list\n * only when the mirror actually gains or loses it, so a link some other\n * holder already made is not counted twice and a mirror that never carried\n * an id is not decremented for it.\n */\n counts: Array<{ companyId: string; delta: 1 | -1 }>\n}\n\n/**\n * What linking a contact to a company FOR ONE HOLDER changes — or unlinking\n * them, with `null`. `null` when the document already says what was asked.\n *\n * Three cases, and the mirror is handled differently in each because it is\n * shared across holders while the facet is not:\n *\n * - A first link `union`s the id in, which is safe against a concurrent\n * writer adding another holder's id.\n * - A MOVE from one company to another rewrites the mirror as a whole. The\n * old id is dropped only if no other holder's facet still names it.\n * - An unlink `remove`s the old id, on the same condition, and leaves the\n * mirror alone when another holder still needs it there.\n *\n * The counts follow the mirror, not the facet: the company's figure is \"how\n * many contacts name me in the mirror\", and that is the quantity the\n * company page's live aggregate measures.\n */\nexport function planContactCompanyLink(\n state: ContactCompanyLinkState,\n companyId: string | null,\n): ContactCompanyLinkPlan | null {\n const previous = state.companyId\n if (previous === companyId) return null\n const mirror = new Set(state.companyIds)\n const previousLeaves =\n previous !== null && !state.heldElsewhere.includes(previous)\n const counts: ContactCompanyLinkPlan['counts'] = []\n let change: ContactCompanyMirrorChange | null = null\n if (companyId && !previous) {\n change = { op: 'union', companyId }\n if (!mirror.has(companyId)) counts.push({ companyId, delta: 1 })\n } else if (companyId && previous) {\n const kept = state.companyIds.filter(\n (id) => id !== previous || !previousLeaves,\n )\n change = { op: 'set', companyIds: [...new Set([...kept, companyId])] }\n if (previousLeaves && mirror.has(previous)) {\n counts.push({ companyId: previous, delta: -1 })\n }\n if (!mirror.has(companyId)) counts.push({ companyId, delta: 1 })\n } else if (previous && previousLeaves) {\n change = { op: 'remove', companyId: previous }\n if (mirror.has(previous)) counts.push({ companyId: previous, delta: -1 })\n }\n return { companyId, mirror: change, counts }\n}\n\n/*==========================================\n * THE CRM's ORGANIZATION SETTINGS (AGL-2613).\n *\n * One map under `crm` on the org document, so the CRM → Settings section\n * can grow a key per setting without a rules change each time: the org\n * document's client branch is a deny-list, and `crm` is declared\n * client-writable in `ORG_CLIENT_WRITABLE_FIELDS` with its reason.\n *=========================================*/\n\n/** The org-document key the CRM's settings live under. */\nexport const ORG_CRM_SETTINGS_FIELD = 'crm'\n\n/** The dotted path an `update()` writes the auto-create switch by. */\nexport const CRM_AUTO_CREATE_COMPANIES_PATH = `${ORG_CRM_SETTINGS_FIELD}.autoCreateCompanies`\n\n/** Where the CRM's default sharing for new records is stored (AGL-3662). */\nexport const CRM_DEFAULT_RECORD_SCOPE_PATH = `${ORG_CRM_SETTINGS_FIELD}.defaultRecordScope`\n\n/**\n * The org's default sharing for new CRM records (AGL-3662): `'org'` shares a\n * new contact, company, deal or task with every site, `'host'` keeps it to\n * the site it came in on and the sites that present as one sender with it.\n *\n * `crm.defaultRecordScope` when the org has set it. Before AGL-3662 the CRM\n * read the org's dataset default, `defaultResourceScope`, so an org that set\n * that to All sites keeps CRM records org-wide until it chooses separately.\n * Neither set reads `undefined`, which every caller treats as `'host'`.\n */\nexport function crmDefaultScopeOf(\n orgDocument: Record<string, unknown> | null | undefined,\n): 'org' | 'host' | undefined {\n const settings = (orgDocument ?? {})[ORG_CRM_SETTINGS_FIELD]\n const own =\n settings && typeof settings === 'object' && !Array.isArray(settings)\n ? (settings as Record<string, unknown>)['defaultRecordScope']\n : undefined\n if (own === 'org' || own === 'host') return own\n const legacy = (orgDocument ?? {})['defaultResourceScope']\n return legacy === 'org' || legacy === 'host' ? legacy : undefined\n}\n\n/**\n * Whether a capture from a work email domain no visible company carries\n * should CREATE the company. Off unless the org document says `true`: a\n * company minted from every domain that ever submitted a form is a list\n * nobody asked for, so the default is the quiet one.\n *\n * Read off the raw document rather than a typed field, because the capture\n * door holds a `Partial<AglynOrganization>` and the console a\n * `Partial<AglynOrgBilling>`, and one reader has to answer both.\n */\nexport function orgAutoCreatesCompanies(\n orgDocument: Record<string, unknown> | null | undefined,\n): boolean {\n const settings = (orgDocument ?? {})[ORG_CRM_SETTINGS_FIELD]\n return Boolean(\n settings &&\n typeof settings === 'object' &&\n !Array.isArray(settings) &&\n (settings as Record<string, unknown>)['autoCreateCompanies'] === true,\n )\n}\n\n/*==========================================\n * WHO A NEW RECORD BELONGS TO (AGL-2618).\n *\n * Salesforce calls these assignment rules and HubSpot rotates to an owner;\n * either way they are the first thing a sales team configures, because a\n * record with no owner is one nobody follows up. The shape here is what the\n * CRM → Settings section writes onto the org document under `crm`, what the\n * capture door's assignment pass reads, and what the `assignContactOwner`\n * automation step's round-robin mode reads — one vocabulary, three readers.\n *\n * ## The order of decision\n *\n * The rules are tried in their stored order and the FIRST match assigns; a\n * rule that names a member the roster no longer has, or a round-robin rule\n * over an empty pool, is passed over rather than stopping the pass. When no\n * rule claims the capture, the capturing site's default owner does; when\n * the site has none, the record stays unassigned, which is what the product\n * did before any of this existed and is the honest answer to \"nobody has\n * said\".\n *\n * ## Only a record with no owner\n *\n * The capture pass runs when a record is CREATED and never overwrites an\n * owner: a door that named one (the console's drawer, an import column, a\n * conversion with a picked owner) has expressed a person's choice, and a\n * returning visitor's contact already belongs to somebody. The automation\n * step is the deliberate exception — an author who put \"assign an owner\"\n * on a stage change means to reassign.\n *\n * ## Why the pointer is a uid, and why the server moves it\n *\n * See `OrgCrmRoundRobin`. The pool is edited in the console and the pointer\n * is advanced by the Admin SDK inside the transaction that writes the owner,\n * so concurrent captures take distinct turns and an edit to the pool never\n * skips or repeats a member.\n *=========================================*/\n\n/** Where the ordered rules live on the org document. */\nexport const CRM_ASSIGNMENT_RULES_PATH = `${ORG_CRM_SETTINGS_FIELD}.assignmentRules`\n\n/** The round-robin pool's member list, in rotation order. */\nexport const CRM_ROUND_ROBIN_POOL_PATH = `${ORG_CRM_SETTINGS_FIELD}.roundRobin.memberUids`\n\n/** The member handed the most recent round-robin record. */\nexport const CRM_ROUND_ROBIN_LAST_ASSIGNED_PATH = `${ORG_CRM_SETTINGS_FIELD}.roundRobin.lastAssignedUid`\n\n/**\n * The most rules an org may keep. A first-match list longer than this is\n * one nobody can reason about, and the section refuses the fifty-first\n * rather than letting the document grow unbounded.\n */\nexport const CRM_ASSIGNMENT_RULES_MAX = 50\n\n/** The most members a round-robin pool may hold. */\nexport const CRM_ROUND_ROBIN_POOL_MAX = 50\n\n/**\n * The map of per-site CRM settings under `crm` on the ORG document, keyed\n * by host id. A field on the org, not a subcollection of the host: the\n * host-subcollection guards read a quoted `'hosts'` beside a site id as a\n * client path under `hosts/{hostId}`, which this is not, so the key is\n * named here and spelled nowhere else.\n */\nconst CRM_HOST_SETTINGS_KEY = 'hosts'\n\n/**\n * The field-path SEGMENTS of a site's default owner on the org document.\n *\n * Segments rather than a dotted string, because the host id is a document\n * id and a document id may contain a dot; joined with dots it would be read\n * as two path elements and the write would land beside the setting rather\n * than in it. A caller builds a `FieldPath` from these on either SDK.\n */\nexport function crmHostDefaultOwnerSegments(hostId: string): string[] {\n if (!hostId) throw new Error('a site default owner must name a site')\n return [ORG_CRM_SETTINGS_FIELD, CRM_HOST_SETTINGS_KEY, hostId, 'defaultOwnerUid']\n}\n\n/** The conditions a rule may name — every one present must hold. */\nexport interface CrmAssignmentRuleWhen {\n /** The capture door: `form`, `booking`, `order`, `manual`… */\n source?: ContactSource\n /** The form the capture came through, by its document id. */\n formId?: string\n /** The captured address's domain, lowercased, without a leading `@`. */\n emailDomain?: string\n /** A tag the capture carries or the contact already wears. */\n tag?: string\n}\n\n/** How a matching rule assigns: one member, or the next in the pool. */\nexport type CrmAssignmentTarget = { memberUid: string } | { roundRobin: true }\n\n/**\n * One rule as the CRM reads it — `OrgCrmAssignmentRule` with the source\n * narrowed to the capture vocabulary.\n */\nexport interface CrmAssignmentRule extends OrgCrmAssignmentRule {\n when: CrmAssignmentRuleWhen\n assign: CrmAssignmentTarget\n}\n\n/** The pool as the CRM reads it: never absent, possibly empty. */\nexport interface CrmRoundRobinPool {\n memberUids: string[]\n lastAssignedUid: string | null\n}\n\n/**\n * The org's assignment settings, read tolerantly off the raw document.\n *\n * Every field is optional on the document and the section writes each on\n * its own, so a reader that trusted the shape would throw on the first org\n * that has set the pool but never a rule. Malformed entries are dropped\n * rather than refused wholesale: one hand-edited rule must not stop the\n * others from assigning.\n */\nexport interface CrmAssignmentSettings {\n rules: CrmAssignmentRule[]\n pool: CrmRoundRobinPool\n /** Site id → default owner uid, for the sites that set one. */\n hostDefaultOwners: Record<string, string>\n}\n\nfunction isContactSourceValue(value: unknown): value is ContactSource {\n return typeof value === 'string' && value in CONTACT_SOURCE_LABELS\n}\n\nfunction cleanText(value: unknown, max: number): string | undefined {\n const text = typeof value === 'string' ? value.trim().slice(0, max) : ''\n return text || undefined\n}\n\n/** A rule as the document holds it, or `null` for one that cannot assign. */\nexport function readCrmAssignmentRule(raw: unknown): CrmAssignmentRule | null {\n if (!raw || typeof raw !== 'object' || Array.isArray(raw)) return null\n const entry = raw as Record<string, unknown>\n const id = cleanText(entry['id'], 64)\n if (!id) return null\n const whenRaw =\n entry['when'] && typeof entry['when'] === 'object' && !Array.isArray(entry['when'])\n ? (entry['when'] as Record<string, unknown>)\n : {}\n const when: CrmAssignmentRuleWhen = {}\n if (isContactSourceValue(whenRaw['source'])) when.source = whenRaw['source']\n const formId = cleanText(whenRaw['formId'], 128)\n if (formId) when.formId = formId\n // A person types a domain the way they see one in an address — `@acme.com`\n // — so the `@` is what the field means, not part of the domain.\n const emailDomain = normalizeCompanyDomain(\n String(whenRaw['emailDomain'] ?? '')\n .trim()\n .replace(/^@/, ''),\n )\n if (emailDomain) when.emailDomain = emailDomain\n const tag = cleanText(whenRaw['tag'], 60)?.toLowerCase()\n if (tag) when.tag = tag\n const assignRaw =\n entry['assign'] &&\n typeof entry['assign'] === 'object' &&\n !Array.isArray(entry['assign'])\n ? (entry['assign'] as Record<string, unknown>)\n : null\n if (!assignRaw) return null\n if (assignRaw['roundRobin'] === true) {\n return { id, when, assign: { roundRobin: true } }\n }\n const memberUid = cleanText(assignRaw['memberUid'], 128)\n return memberUid ? { id, when, assign: { memberUid } } : null\n}\n\nexport function readCrmAssignmentSettings(\n orgDocument: Record<string, unknown> | null | undefined,\n): CrmAssignmentSettings {\n const settings = (orgDocument ?? {})[ORG_CRM_SETTINGS_FIELD]\n const crm =\n settings && typeof settings === 'object' && !Array.isArray(settings)\n ? (settings as Record<string, unknown>)\n : {}\n const rules = (Array.isArray(crm['assignmentRules']) ? crm['assignmentRules'] : [])\n .map(readCrmAssignmentRule)\n .filter((rule): rule is CrmAssignmentRule => rule !== null)\n .slice(0, CRM_ASSIGNMENT_RULES_MAX)\n const roundRobin =\n crm['roundRobin'] && typeof crm['roundRobin'] === 'object'\n ? (crm['roundRobin'] as Record<string, unknown>)\n : {}\n const memberUids = [\n ...new Set(\n (Array.isArray(roundRobin['memberUids']) ? roundRobin['memberUids'] : [])\n .map((uid) => cleanText(uid, 128))\n .filter((uid): uid is string => Boolean(uid)),\n ),\n ].slice(0, CRM_ROUND_ROBIN_POOL_MAX)\n const hostSettings = crm[CRM_HOST_SETTINGS_KEY]\n const hosts =\n hostSettings && typeof hostSettings === 'object' && !Array.isArray(hostSettings)\n ? (hostSettings as Record<string, unknown>)\n : {}\n const hostDefaultOwners: Record<string, string> = {}\n for (const [hostId, value] of Object.entries(hosts)) {\n const owner =\n value && typeof value === 'object'\n ? cleanText((value as Record<string, unknown>)['defaultOwnerUid'], 128)\n : undefined\n if (owner) hostDefaultOwners[hostId] = owner\n }\n return {\n rules,\n pool: {\n memberUids,\n lastAssignedUid: cleanText(roundRobin['lastAssignedUid'], 128) ?? null,\n },\n hostDefaultOwners,\n }\n}\n\n/** The member a site hands unclaimed captures to, or `null` for nobody. */\nexport function crmHostDefaultOwner(\n orgDocument: Record<string, unknown> | null | undefined,\n hostId: string,\n): string | null {\n return readCrmAssignmentSettings(orgDocument).hostDefaultOwners[hostId] ?? null\n}\n\n/** What a rule is matched against: the capture, as its door described it. */\nexport interface CrmAssignmentCapture {\n source: ContactSource\n email: string\n formId?: string | null\n /** The capture's own tags and whatever the contact already wears. */\n tags?: readonly string[]\n}\n\n/**\n * The domain a rule's `emailDomain` is compared with — the address's own,\n * lowercased. Not `companyDomainForEmail`, which answers `null` for a public\n * mailbox: a team may well route every `gmail.com` sign-up to one rep, and\n * a rule that could not name a consumer domain could not say so.\n */\nexport function assignmentEmailDomain(email: string): string | null {\n const at = email.lastIndexOf('@')\n if (at < 0) return null\n return normalizeCompanyDomain(email.slice(at + 1))\n}\n\n/**\n * Whether every condition the rule names holds for this capture. A rule\n * with no condition matches every capture — the catch-all.\n */\nexport function assignmentRuleMatches(\n when: CrmAssignmentRuleWhen,\n capture: CrmAssignmentCapture,\n): boolean {\n if (when.source && when.source !== capture.source) return false\n if (when.formId && when.formId !== (capture.formId ?? '')) return false\n if (when.emailDomain && when.emailDomain !== assignmentEmailDomain(capture.email)) {\n return false\n }\n if (when.tag) {\n const worn = (capture.tags ?? []).map((tag) => String(tag).trim().toLowerCase())\n if (!worn.includes(when.tag)) return false\n }\n return true\n}\n\n/**\n * The pool in the order the next record should try it: the member after\n * the last recipient first, wrapping round, and the last recipient\n * themselves at the end — so a pool of one still assigns, and a pointer\n * naming somebody no longer in the pool starts from the top.\n */\nexport function roundRobinOrder(\n memberUids: readonly string[],\n lastAssignedUid: string | null | undefined,\n): string[] {\n if (!memberUids.length) return []\n const at = lastAssignedUid ? memberUids.indexOf(lastAssignedUid) : -1\n if (at < 0) return [...memberUids]\n return [...memberUids.slice(at + 1), ...memberUids.slice(0, at + 1)]\n}\n\n/**\n * How a rule reads in the settings list — its conditions in words and\n * where it sends the record — so a reader can tell two rules apart without\n * opening either. `memberLabel` turns a uid into a name; the section passes\n * the roster's.\n */\nexport function describeAssignmentRule(\n rule: CrmAssignmentRule,\n memberLabel: (uid: string) => string,\n): { when: string; assign: string } {\n const parts: string[] = []\n if (rule.when.source) parts.push(`source is ${CONTACT_SOURCE_LABELS[rule.when.source]}`)\n if (rule.when.formId) parts.push(`form is ${rule.when.formId}`)\n if (rule.when.emailDomain) parts.push(`email domain is ${rule.when.emailDomain}`)\n if (rule.when.tag) parts.push(`tagged ${rule.when.tag}`)\n return {\n when: parts.length ? parts.join(' and ') : 'Every capture',\n assign:\n 'roundRobin' in rule.assign ? 'Round robin' : memberLabel(rule.assign.memberUid),\n }\n}\n\n/**\n * A fresh rule id, unique among the ones the org already holds. Time and\n * entropy rather than a counter, so two admins adding a rule in two tabs\n * do not mint the same id and have one reorder clobber the other's rule.\n */\nexport function newAssignmentRuleId(existing: readonly string[]): string {\n for (;;) {\n const id = `rule-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}`\n if (!existing.includes(id)) return id\n }\n}\n\n/**\n * The `visibleTo` every CRM creator stamps — client or server, whichever\n * door the record comes in through.\n *\n * The contact create path's own expression (`upsertHostContact`), lifted\n * here so that a company, a deal or a task created from a site's console\n * lands in exactly the scope a contact captured on that site would: the\n * whole org when the org's CRM default (`crmDefaultScopeOf`) is `'org'`, and\n * otherwise the sites that present as one sender — which, undeclared, is\n * this site alone. That is the agency's isolation, arrived at with nothing\n * configured, and the reason this is a function rather than a convention\n * eight creators are asked to remember.\n *\n * Widening past the group is an ACT — an org-wide member editing the scope\n * on the record — never a default. The rules refuse a scoped member creating\n * a record outside their own tokens, so a creator that bypassed this could\n * only make its record invisible to itself, never wider.\n */\nexport function crmScopeTokens(\n org: Record<string, unknown> | null | undefined,\n group: ConsentGroup,\n): ScopeToken[] {\n return crmDefaultScopeOf(org) === 'org'\n ? [ORG_SCOPE_TOKEN]\n : consentGroupScope(group)\n}\n\n/**\n * The `visibleTo` tokens a reader of this group may LIST, for the\n * `array-contains-any` every CRM listener filters by.\n *\n * The other half of {@link crmScopeTokens}: that is what a creator stamps,\n * this is what a reader asks for, and the two differ by exactly one token.\n * `'org'` leads because an org-wide record is visible to every site — an org\n * that widened its default deliberately still sees its own rows — and the\n * group's sites follow. The contacts list computes this inline; every other\n * CRM reader takes it from here so that none of them can drop the org token\n * and lose the org-wide rows, or forget the cap and have Firestore refuse\n * the query outright.\n *\n * Capped at {@link MAX_SCOPE_HOSTS}: a group wider than the operator's limit\n * is listed as far as the limit reaches, which is the contacts list's\n * behavior and the only one short of an error.\n */\nexport function crmReadTokens(group: ConsentGroup): ScopeToken[] {\n return [ORG_SCOPE_TOKEN, ...consentGroupScope(group)].slice(0, MAX_SCOPE_HOSTS)\n}\n\n/*==========================================\n * THE TEAM, AS A RECORD NAMES THEM (AGL-2614).\n *\n * An owner and an assignee are stored by uid — `orgs/{orgId}/members/{uid}`\n * is keyed by it, and a uid outlives every address change. But the people\n * who NAME an owner do not think in uids: an automation step is typed, a\n * CSV column is an address, and a picker lists names. So a reference\n * arrives as one of two things, and every reader that turns a reference\n * into a person has to accept both or the roster splits into the members\n * who can be named and the members who cannot.\n *\n * The member documents make the second case real. Two production paths\n * create a member document WITHOUT its `email` — a host-access re-grant,\n * and an add whose auth record carried none — and such a member was\n * unnameable everywhere a surface asked for an address: an automation\n * could not assign to them, and a roster mapper that dropped a row with no\n * address could not even list them. A name they still have (the roster's\n * `displayName`), and a uid they always have, so a label falls through to\n * the uid rather than to nothing, and a reference is resolved by uid first\n * and by address second.\n *\n * Roster-only, deliberately. The project's Auth records could resolve an\n * address the roster cannot, and would resolve people who are not on this\n * organization at all (AGL-1122); a reference that names nobody on the\n * roster names nobody.\n *=========================================*/\n\n/** One person on the team, as a picker lists them and a reference resolves to them. */\nexport interface CrmMemberOption {\n /** The account uid — what `ownerUid` and `assigneeUid` store. */\n uid: string\n /** The name a colleague recognizes: display name, else address, else the uid. */\n label: string\n /** The roster's address, when the member document carries one. */\n email?: string\n}\n\n/**\n * How a step or a column names somebody on the team: an address when the\n * text is one, otherwise a uid. `null` for a blank, which every caller\n * treats as \"nobody named\" rather than as a member called \"\".\n */\nexport type CrmMemberRef =\n | { kind: 'uid'; uid: string }\n | { kind: 'email'; email: string }\n\nexport function parseCrmMemberRef(value: unknown): CrmMemberRef | null {\n const text = String(value ?? '').trim()\n if (!text) return null\n if (text.includes('@')) {\n const email = normalizeContactEmail(text)\n return email ? { kind: 'email', email } : null\n }\n return { kind: 'uid', uid: text }\n}\n\n/**\n * A roster row as every CRM picker and column shows it.\n *\n * One mapping rather than four, because four is what the CRM had — and one\n * of them dropped a member whose document had neither a display name nor an\n * address, which is precisely the member the uid fallback exists for.\n */\nexport function crmMemberOption(\n member: Record<string, unknown>,\n): CrmMemberOption | null {\n const uid = String(member['$id'] ?? member['uid'] ?? '').trim()\n if (!uid) return null\n const displayName = String(member['displayName'] ?? '').trim()\n const email = String(member['email'] ?? '').trim()\n return {\n uid,\n label: displayName || email || uid,\n ...(email ? { email } : {}),\n }\n}\n\n/**\n * A roster row as a PICKER lists it: the name, and the address in\n * parentheses when the roster holds one that the name does not already\n * spell. Two teammates can share a display name — a workspace with two\n * \"Zach Gover\" rows cannot tell them apart by name alone, and the address is\n * the one thing the roster guarantees is unique. A column keeps `label`,\n * because a column is read at a glance and a picker is chosen from.\n */\nexport function crmMemberPickerLabel(\n member: Pick<CrmMemberOption, 'label' | 'email'>,\n): string {\n const label = String(member.label ?? '').trim()\n const email = String(member.email ?? '').trim()\n if (!email || email.toLowerCase() === label.toLowerCase()) return label || email\n return `${label} (${email})`\n}\n\n/**\n * The member a stored reference names: by uid first — the stored shape —\n * and by address second, so a record that carries an address where a uid\n * belongs (an older import, a hand edit) still names the person the roster\n * has under it. Undefined for nobody, which the caller renders honestly as\n * \"former member\" or as the reference itself rather than as unassigned.\n */\nexport function findOrgMember<T extends { uid: string; email?: string | null }>(\n members: readonly T[],\n ref: string | null | undefined,\n): T | undefined {\n const parsed = parseCrmMemberRef(ref)\n if (!parsed) return undefined\n if (parsed.kind === 'uid') {\n return members.find((member) => member.uid === parsed.uid)\n }\n return members.find(\n (member) => normalizeContactEmail(member.email) === parsed.email,\n )\n}\n\n/*==========================================\n * LEADS (AGL-2608).\n *\n * A lead is NOT one of the six collections above. It lives at\n * `hosts/{hostId}/leads/{personKey}`, written by `addHostLead` when a\n * visitor signs up, books or submits a form, and it is host-scoped by PATH:\n * no `visibleTo`, no facet map, private to the site that captured it. What\n * the CRM adds is the working state a sales team keeps on such a capture —\n * a status, an owner, notes — and the record of its conversion into the\n * contact, company and deal that live in the org collections.\n *\n * These fields are typed here rather than beside `addHostLead` because the\n * writer of the capture and the reader of the working state are different\n * programs: the capture door stamps none of them, and a lead that predates\n * this block carries none of them, which is why every field is optional and\n * {@link crmLeadStatus} reads an absent status as `new`.\n *=========================================*/\n\n/**\n * Where a lead stands, in the order a person works one.\n *\n * `nurturing` is the stage automated email holds a lead in: a sequence step\n * or a campaign email has reached it and no person has engaged yet. It is\n * open — the lead still needs somebody — but it is not untouched, so the\n * digest's unworked list leaves it out. Automation writes it only over\n * `new` (a person may also set it by hand), and a reply moves it on to\n * `working`.\n *\n * `qualified` is the CONVERTED state — a lead becomes a contact by being\n * qualified, and the conversion stamps `convertedContactId` beside it — and\n * `unqualified` is the closed-without-conversion state with its reason. A\n * fixed list rather than free text for the reason the lifecycle stages are:\n * the section filters on it and a report counts by it.\n */\nexport const CRM_LEAD_STATUSES = [\n 'new',\n 'nurturing',\n 'working',\n 'qualified',\n 'unqualified',\n] as const\n\nexport type CrmLeadStatus = (typeof CRM_LEAD_STATUSES)[number]\n\n/** How a lead status reads on screen — typed so a status cannot ship unlabeled. */\nexport const CRM_LEAD_STATUS_LABELS: Record<CrmLeadStatus, string> = {\n new: 'New',\n nurturing: 'Nurturing',\n working: 'Working',\n qualified: 'Qualified',\n unqualified: 'Unqualified',\n}\n\n/**\n * The statuses a lead still needs somebody's attention in — what the Leads\n * section shows by default, so the list opens on the work rather than on\n * the history.\n */\nexport const CRM_LEAD_OPEN_STATUSES: readonly CrmLeadStatus[] = [\n 'new',\n 'nurturing',\n 'working',\n]\n\n/**\n * The statuses that mean nobody needs to work the lead any more — the\n * operand of the `in` clause an open-lead figure subtracts with. Subtracts,\n * because an untouched lead carries no status field at all and Firestore\n * cannot select on a field's absence; the closed statuses are always\n * written, so they can be counted, and what remains is open. See\n * `openLeadsFromCounts`.\n */\nexport const CRM_LEAD_CLOSED_STATUSES: readonly CrmLeadStatus[] =\n CRM_LEAD_STATUSES.filter((status) => !CRM_LEAD_OPEN_STATUSES.includes(status))\n\n/**\n * The leads still needing somebody, from two server counts: every lead,\n * less the ones closed one way or the other.\n *\n * Subtraction rather than a count of the open statuses because a lead\n * nobody has touched carries NO status field — see `crmLeadStatus` — and\n * Firestore cannot select a document by a field's absence. The closed\n * statuses are always written, so they can be counted; what remains is\n * open. Clamped at zero for the moment between the two counts in which a\n * lead was closed.\n */\nexport function openLeadsFromCounts(total: number, closed: number): number {\n return Math.max(0, Math.round(Number(total) || 0) - Math.round(Number(closed) || 0))\n}\n\nexport function isCrmLeadStatus(value: unknown): value is CrmLeadStatus {\n return (\n typeof value === 'string' &&\n (CRM_LEAD_STATUSES as readonly string[]).includes(value)\n )\n}\n\n/**\n * The working state the CRM writes onto a lead document, beside what the\n * capture door wrote (`email`, `name`, `sources`, `submissionCount`,\n * `firstSeenAtMs`, `lastSeenAtMs`, the consent map).\n *\n * The four `converted*`/`dealId`/`companyId` fields are stamped ONLY by the\n * `crm/lead-convert` server route, in one write after the contact exists, so\n * a lead that carries `convertedContactId` names a contact that was really\n * created and a lead without it was never converted, whatever its status\n * says. `unqualifiedReason` travels with `status: 'unqualified'` and is the\n * one free-text field a report will want to read back.\n */\nexport interface CrmLeadFields extends CrmLeadProfile {\n status?: CrmLeadStatus\n /**\n * Which of the org's Lead status values of `status`'s meaning the lead\n * holds (AGL-3512); absent, the lead shows its meaning's default label —\n * see `crmLeadStatusLabel`.\n */\n statusLabel?: string\n /** The team member working the lead. */\n ownerUid?: string\n notes?: string\n unqualifiedReason?: string\n /** `orgs/{orgId}/contacts/{contactId}` — the person this lead became. */\n convertedContactId?: string\n convertedAtMs?: number\n /** The deal the conversion opened, when the converter asked for one. */\n dealId?: string\n /** The company the conversion created or linked, when it named one. */\n companyId?: string\n /**\n * The last verdict on the lead's address (AGL-3245) — bounced, blocked,\n * unsubscribed, do-not-contact — written by the platform's senders only,\n * never by a form or the lead's editor. Absent means nothing is known.\n */\n emailState?: EmailState\n /**\n * The campaigns the lead is filed under (AGL-3254): container ids from\n * `orgs/{orgId}/emailCampaigns`, at the top of the document the way a\n * form carries them (`container-membership.ts`), never names. Written by\n * the New lead drawer, the import, the bulk bar and a sequence's enroll;\n * handed to the contact's facet when the lead converts.\n */\n campaignIds?: string[]\n /**\n * The org's custom LEAD fields, keyed by each definition's `key`\n * (AGL-3272) — the same map a contact facet, a company and a deal keep,\n * judged against the definitions whose `object` is `lead`.\n *\n * It does NOT travel to the contact on convert. Keys are unique per\n * object, so a lead's `budget` and a contact's `budget` are two\n * definitions that may hold two types, and copying one map into the\n * other would write a value the receiving definition never agreed to.\n * Salesforce answers this with an explicit lead field mapping; until\n * there is one here the values stay on the lead, which a converted lead\n * is still read back for.\n */\n custom?: Record<string, CrmCustomValue>\n}\n\n/*==========================================\n * THE LEAD'S OWN PROFILE (AGL-3231).\n *\n * A Salesforce lead is a record of its own: it carries the person AND\n * their company as text — company, title, phone, website, address, the\n * source that produced it — and none of it is a contact until the lead\n * converts, when the convert step hands every field to the contact and the\n * account it creates. A lead that held only an address and a name could\n * not be worked without a contact beside it, which is how one person came\n * to sit in both lists; these fields are what let the lead stand alone.\n *\n * ## Text, not links\n *\n * `company` is the company's NAME, never a company id. A lead has not been\n * qualified, so the account it names may not deserve a record yet — a\n * thousand imported leads must not mint a thousand companies — and the\n * conversion is where the text becomes a link: it finds the company by\n * that name or by the address's domain, or creates it. The `companyId` on\n * `CrmLeadFields` is the conversion's stamp, written once, and never what\n * a lead carries while it is open.\n *\n * ## The same shapes the contact keeps\n *\n * The phone is E.164 through `normalizePhone`, the address is\n * `AglynPostalAddress` through `normalizeAddress`, tags are lower-cased and\n * capped as the contact's are, and the website is normalized as a\n * company's is — so the conversion copies values rather than translating\n * them, and a lead typed in the drawer and a contact typed in its drawer\n * refuse the same phone number in the same sentence.\n *\n * ## `leadSource` beside `sources`\n *\n * `sources` is the capture door's record of which SURFACES met the person\n * — `signup`, `booking`, `form:{id}`, `import` — and no file may rewrite\n * it. `leadSource` is what Salesforce calls Lead Source: one of the org's\n * own picklist values (\"Trade show\", \"Webinar\" — see THE CRM'S PICKLISTS\n * below), reported on and filtered by, and never derived. Two\n * fields because they answer two questions, and a file that could write\n * the first would be rewriting the site's own history.\n *=========================================*/\n\n/** The longest text one profile field holds — the contact's own cap. */\nexport const CRM_LEAD_TEXT_MAX = 120\n/** The most tags one lead keeps — the contact's own cap. */\nexport const CRM_LEAD_TAGS_MAX = 20\n/** The longest note a lead keeps — the lead page's own cap. */\nexport const CRM_LEAD_NOTES_MAX = 4000\n\nexport interface CrmLeadProfile {\n /** The company's name, as text — see the block header. */\n company?: string\n jobTitle?: string\n /** E.164 — `normalizePhone` before writing. */\n phone?: string\n /** An http(s) URL — `normalizeCompanyWebsite` before writing. */\n website?: string\n address?: AglynPostalAddress | null\n /** Lower-cased, deduplicated, at most {@link CRM_LEAD_TAGS_MAX}. */\n tags?: string[]\n /**\n * Where the lead came from — Salesforce's Lead Source: the LABEL of one\n * of the org's lead source values (see {@link CrmPicklist}).\n */\n leadSource?: string\n /*\n * SALESFORCE'S STANDARD LEAD FIELDS (AGL-3513), in the shapes the contact\n * and the company keep them, so a conversion copies values rather than\n * translating them.\n */\n /** One of the org's salutation values, by label (`Mr.`, `Dr.` …) — the contact's list. */\n salutation?: string\n /**\n * The person's given and family names. While either is set, the lead's\n * `name` is their composition ({@link crmLeadComposedName}), so the name\n * the list shows and searches is the person the structured fields name. A\n * lead holding only `name` keeps it as captured: nothing splits a stored\n * name into parts after the fact.\n */\n firstName?: string\n lastName?: string\n /** E.164, like {@link phone}. */\n mobilePhone?: string\n /** E.164. */\n fax?: string\n /** A label of the `industry` picklist — the list companies keep (AGL-3514). */\n industry?: string\n /** A label of the `rating` picklist — the list companies keep. */\n rating?: string\n /** Annual revenue in the minor unit of {@link currency}, the company's convention. */\n annualRevenueCents?: number\n /** Lowercase ISO 4217 of the revenue; `'usd'` when absent. */\n currency?: string\n numberOfEmployees?: number\n /**\n * The person asked not to be phoned. Stored only as `true`; absent is\n * \"may be called\". A hint beside every number and on the Call button,\n * never a block — the contact's rule.\n */\n doNotCall?: boolean\n}\n\n/** The profile's keys, in the order a card lists them. */\nexport const CRM_LEAD_PROFILE_KEYS = [\n 'salutation',\n 'firstName',\n 'lastName',\n 'company',\n 'jobTitle',\n 'phone',\n 'mobilePhone',\n 'fax',\n 'doNotCall',\n 'website',\n 'address',\n 'tags',\n 'leadSource',\n 'industry',\n 'rating',\n 'annualRevenueCents',\n 'currency',\n 'numberOfEmployees',\n] as const satisfies readonly (keyof CrmLeadProfile)[]\n\nexport type CrmLeadProfileKey = (typeof CRM_LEAD_PROFILE_KEYS)[number]\n\n/**\n * A profile as a writer receives it: a key present is a value to store, a\n * key present and `null` is a field to clear, a key absent is left alone.\n * The PATCH semantics every CRM write has.\n */\nexport type CrmLeadProfilePatch = {\n [K in CrmLeadProfileKey]?: NonNullable<CrmLeadProfile[K]> | null\n}\n\n/** Why one field of a profile could not be read, under the field. */\nexport type CrmLeadProfileErrors = Partial<Record<CrmLeadProfileKey, string>>\n\n/** The sentence an unreadable phone number is refused with, under the field. */\nexport const CRM_LEAD_PHONE_REFUSAL =\n 'That phone number could not be read. Enter it with its country code, like ' +\n '+1 512 555 0107.'\n\n/** The sentence an unreadable website is refused with, under the field. */\nexport const CRM_LEAD_WEBSITE_REFUSAL = 'Enter a web address, like acme.com'\n\n/**\n * Tags as a lead stores them: lower-cased, trimmed, deduplicated, capped —\n * from an array or a comma-separated string, so a file cell and a form\n * field are one input.\n */\nexport function normalizeCrmLeadTags(value: unknown): string[] {\n const items = Array.isArray(value)\n ? value\n : typeof value === 'string'\n ? value.split(',')\n : []\n return [\n ...new Set(\n items\n .map((tag) =>\n String(tag ?? '')\n .trim()\n .slice(0, 40)\n .toLowerCase(),\n )\n .filter(Boolean),\n ),\n ].slice(0, CRM_LEAD_TAGS_MAX)\n}\n\n/**\n * Read a profile off an untrusted body, one field at a time.\n *\n * Every key the body names is answered: as a normalized value, as `null`\n * when the body cleared it (an empty string, `null`, an empty list, a blank\n * address), or as an error under the field when the value cannot be\n * stored as typed. Keys the body does not name are not in the answer, so\n * a caller can write exactly what was asked. A `website` is normalized the\n * way a company's is; a phone the way a contact's is; both refuse rather\n * than store a value the record would then render as a link to nowhere.\n */\nexport function normalizeCrmLeadProfile(\n input: Record<string, unknown> | null | undefined,\n): { patch: CrmLeadProfilePatch; errors: CrmLeadProfileErrors } {\n const body = input ?? {}\n const patch: CrmLeadProfilePatch = {}\n const errors: CrmLeadProfileErrors = {}\n const text = (\n key: 'company' | 'jobTitle' | 'leadSource' | 'salutation' | 'industry' | 'rating',\n ) => {\n if (!(key in body)) return\n const value = String(body[key] ?? '')\n .trim()\n .replace(/\\s+/g, ' ')\n .slice(0, CRM_LEAD_TEXT_MAX)\n patch[key] = value || null\n }\n text('company')\n text('jobTitle')\n text('leadSource')\n text('salutation')\n text('industry')\n text('rating')\n // A name part as the contact's facet keeps one — see `composeContactName`.\n for (const key of ['firstName', 'lastName'] as const) {\n if (!(key in body)) continue\n const value = composeContactName(body[key], '')\n patch[key] = value || null\n }\n for (const key of ['phone', 'mobilePhone', 'fax'] as const) {\n if (!(key in body)) continue\n const raw = String(body[key] ?? '').trim()\n if (!raw) patch[key] = null\n else {\n const phone = normalizePhone(raw)\n if (phone) patch[key] = phone\n else errors[key] = CRM_LEAD_PHONE_REFUSAL\n }\n }\n if ('doNotCall' in body) {\n // Stored only as `true`: anything else is \"may be called\", a clear.\n patch.doNotCall = body['doNotCall'] === true ? true : null\n }\n /*\n * The revenue, its currency and the head count, read by the company's\n * own reader so a value one record refuses the other refuses in the same\n * words. A head count typed as digits is read as the number it spells.\n */\n const account: Record<string, unknown> = {}\n if ('annualRevenueCents' in body) account['annualRevenueCents'] = body['annualRevenueCents'] ?? null\n if ('currency' in body) account['currency'] = body['currency'] ?? null\n if ('numberOfEmployees' in body) {\n const raw = body['numberOfEmployees']\n const digits = typeof raw === 'string' ? raw.replace(/[\\s,]/g, '') : null\n account['numberOfEmployees'] =\n digits === null ? (raw ?? null) : digits === '' ? null : /^\\d+$/.test(digits) ? Number(digits) : raw\n }\n if (Object.keys(account).length) {\n const read = readCrmCompanyAccountFields(account)\n const into = patch as Record<string, unknown>\n for (const key of ['annualRevenueCents', 'currency', 'numberOfEmployees'] as const) {\n if (read.errors[key]) errors[key] = read.errors[key]\n else if (key in read.values) into[key] = read.values[key] ?? null\n }\n }\n if ('website' in body) {\n const raw = String(body['website'] ?? '').trim()\n if (!raw) patch.website = null\n else {\n const website = normalizeCompanyWebsite(raw)\n if (website) patch.website = website\n else errors.website = CRM_LEAD_WEBSITE_REFUSAL\n }\n }\n if ('address' in body) {\n const raw = body['address']\n patch.address =\n raw && typeof raw === 'object' && !Array.isArray(raw)\n ? normalizeAddress(raw as AglynPostalAddress)\n : null\n }\n if ('tags' in body) {\n const tags = normalizeCrmLeadTags(body['tags'])\n patch.tags = tags.length ? tags : null\n }\n return { patch, errors }\n}\n\n/**\n * The `name` a write must store beside a lead's first and last names\n * (AGL-3513), the contact's rule: while either part is set the name is\n * their composition, written whenever either moves. `undefined` when the\n * write names neither part, or when it leaves both blank — a name-only\n * lead keeps its name, and clearing both parts leaves the name as it\n * stands.\n */\nexport function crmLeadComposedName(\n stored: Readonly<Record<string, unknown>> | null | undefined,\n patch: Readonly<Pick<CrmLeadProfilePatch, 'firstName' | 'lastName'>>,\n): string | undefined {\n if (patch.firstName === undefined && patch.lastName === undefined) return undefined\n const part = (key: 'firstName' | 'lastName') =>\n patch[key] === undefined ? stored?.[key] : (patch[key] ?? '')\n return composeContactName(part('firstName'), part('lastName')) || undefined\n}\n\n/**\n * Each lead picklist field beside the lead source (AGL-3513): the list it\n * holds a value of, and the key the Leads list filters by. The lists are\n * the contact's and the company's own — one Salutation, one Industry, one\n * Rating per org — so a lead converts into the same values.\n */\nexport const CRM_LEAD_PICKLIST_FIELDS = [\n { field: 'salutation', picklistId: 'salutation' },\n { field: 'industry', picklistId: 'industry', keyField: 'industryKey' },\n { field: 'rating', picklistId: 'rating', keyField: 'ratingKey' },\n] as const\n\nexport type CrmLeadPicklistField = (typeof CRM_LEAD_PICKLIST_FIELDS)[number]['field']\n\n/**\n * The lead picklist fields a write names, judged against the org's lists\n * — the company's {@link judgeCrmCompanyPicklists} over the lead's fields:\n * `lists` holds each list the caller read (a list it did not read is\n * judged as the standard values alone), the lead's `current` value is kept\n * even when the list no longer offers it, and on a create a field not\n * named starts from its list's default.\n */\nexport function judgeCrmLeadPicklists(\n lists: Readonly<Partial<Record<CrmPicklistId, CrmPicklist>>>,\n requested: Readonly<Partial<Record<string, unknown>>>,\n options: { current?: Readonly<Partial<Record<string, unknown>>> | null; created?: boolean } = {},\n): {\n values: Partial<Record<CrmLeadPicklistField, string | null>>\n errors: Partial<Record<CrmLeadPicklistField, string>>\n} {\n const values: Partial<Record<CrmLeadPicklistField, string | null>> = {}\n const errors: Partial<Record<CrmLeadPicklistField, string>> = {}\n for (const { field, picklistId } of CRM_LEAD_PICKLIST_FIELDS) {\n const list = lists[picklistId] ?? effectiveCrmPicklist(picklistId, undefined)\n const raw = requested[field]\n if (raw === undefined) {\n const fallback = options.created ? picklistDefaultLabel(list) : null\n if (fallback) values[field] = fallback\n continue\n }\n if (raw === null || (typeof raw === 'string' && !raw.trim())) {\n values[field] = null\n continue\n }\n if (typeof raw !== 'string') {\n errors[field] = 'Must be text'\n continue\n }\n const judged = judgeCrmPicklistValue(picklistId, list, raw, options.current?.[field])\n if (judged.ok === false) errors[field] = judged.error\n else values[field] = judged.value\n }\n return { values, errors }\n}\n\n/*==========================================\n * THE CRM'S PICKLISTS (AGL-3298, AGL-3510).\n *\n * Salesforce gives every object standard picklist fields — Lead Source,\n * Lead Status, Industry, Stage — each shipping a STANDARD set of values\n * every org has, to which an admin adds the org's own. Here each such field\n * is one {@link CrmPicklistDefinition} in {@link CRM_PICKLIST_DEFINITIONS},\n * and the org's list for it is one document,\n * `orgs/{orgId}/crmPicklists/{picklistId}`, holding the values in order as\n * `{ id, label, active, group?, meaning? }` and an optional default for new\n * records. The engine under it — the merge with the standard values, the\n * judge, the options — is the platform's `picklists` module; this block is\n * the CRM's registry and the names every CRM reader already uses.\n *\n * ## Records store the LABEL\n *\n * A lead carries `leadSource: \"Trade show\"`, not an id. Every reader that\n * existed before the picklist — the CSV export, the REST resource, the\n * record facts, a saved view's filter, a report grouped by the field —\n * reads the text and keeps working, and a file exported from here\n * re-imports as is. What an id would have bought is a free rename; here a\n * rename (and a delete) REPLACES the old label on every record the\n * definition's `targets` name in the same operation, the way Salesforce's\n * own Replace does, so a report grouped by the field follows the rename\n * rather than splitting in two. The value's `id` is what the list itself is\n * keyed by — the default, a reorder, the manage page's rows — and it never\n * changes.\n *\n * ## Standard values, and the org's own\n *\n * A standard value is in every org's effective list whether or not the org\n * stored one; the org may relabel, reorder, regroup, deactivate or default\n * it — stored under the standard id — but not delete it. A stored value\n * whose id is a standard id is that standard value overridden, which is why\n * a list written down from the earlier starter set (whose slugs `web`,\n * `phone-inquiry`, `purchased-list`, `trade-show` and `other` are standard\n * ids) needs no record rewritten. Its `referral` and `partner` are not\n * standard, and stay as the org's own values. An org with NO document reads\n * the standard values only, so a record such an org holds as \"Referral\" or\n * \"Partner\" shows as not in the list until that value is written down as\n * the org's own.\n *\n * ## Inactive, not gone\n *\n * A deactivated value leaves every picker but stays on the records that\n * hold it, shown with an \"(inactive)\" hint, and a write that keeps a\n * record's current value is never refused for it. An added value is\n * removed only by Delete, which names the value its records move to — of\n * the same meaning, on a definition with meanings.\n *=========================================*/\n\n/** The CRM objects a picklist's values are kept on. */\nexport type CrmPicklistObject = CrmFieldObject | 'task'\n\n/**\n * The Fields tabs, in order: every object with custom fields, then Tasks,\n * which has standard picklists and no custom fields (AGL-3517).\n */\nexport const CRM_PICKLIST_OBJECTS: readonly CrmPicklistObject[] = [...CRM_FIELD_OBJECTS, 'task']\n\n/** How each object reads on the Fields tabs and in a refusal. */\nexport const CRM_PICKLIST_OBJECT_LABELS: Record<CrmPicklistObject, string> = {\n ...CRM_FIELD_OBJECT_LABELS,\n task: 'Tasks',\n}\n\nexport function isCrmPicklistObject(value: unknown): value is CrmPicklistObject {\n return typeof value === 'string' && (CRM_PICKLIST_OBJECTS as readonly string[]).includes(value)\n}\n\n/**\n * One place records hold a picklist's label. `field` is the record's own\n * field; `keyField`, when set, is the query key written beside it\n * ({@link crmPicklistKey}); `facet` means the label lives in each holder's\n * facet on a contact rather than on the record itself.\n *\n * `arrayKey` (AGL-3521) means `field` is a LIST of objects and the label is\n * each entry's `arrayKey` — a deal's contact roles, `{ contactId, role }`.\n * Such a target always names a `keyField`, which is then the list of the\n * keys its entries hold: the one array a query can find the records by.\n */\nexport interface CrmPicklistTarget {\n object: CrmPicklistObject\n field: string\n keyField?: string\n facet?: true\n arrayKey?: string\n /**\n * The field holding the GROUP of the value the record holds (AGL-3577) —\n * a lead's `leadSourceDirection` — which every move that can change a\n * label's group (a regroup, an add, a rename, a delete) rewrites.\n */\n groupField?: string\n}\n\n/** A standard picklist field of the CRM. */\nexport interface CrmPicklistDefinition extends PicklistSpec {\n /** The document id under `crmPicklists`, and the field's name in code. */\n id: string\n /** The field's name in a sentence's subject: \"Lead source must be one of: …\". */\n label: string\n /** The values in a sentence: \"no active lead sources\". */\n plural: string\n /** The Fields tab its values are managed on. */\n object: CrmPicklistObject\n /** Every place records hold its label — what a rename and a delete rewrite. */\n targets: readonly CrmPicklistTarget[]\n /** How each of `meanings` reads on screen — the Fields page's Means column (AGL-3512). */\n meaningLabels?: Readonly<Record<string, string>>\n /**\n * Meanings only the platform sets, which an org-added value may not take\n * (AGL-3512): a lead is Qualified by its conversion alone.\n */\n reservedMeanings?: readonly string[]\n}\n\n/**\n * Lead Source, with Salesforce's standard values and Aglyn's own (AGL-3519).\n * Each carries the group a report splits pipeline by: Inbound for a lead\n * who came to the org, Outbound for one the org went to, and Other in\n * neither. Aglyn's are the doors and the outreach the platform runs itself\n * — a website form, a booking, a sequence — stamped on a record the door\n * met first ({@link CRM_LEAD_SOURCE_ORIGINS}); a vendor the platform does\n * not run (a data provider, a sending tool) is an org's own value.\n */\nconst LEAD_SOURCE_DEFINITION = {\n id: 'leadSource',\n label: 'Lead source',\n plural: 'lead sources',\n object: 'lead',\n restricted: true,\n groups: [\n { id: 'inbound', label: 'Inbound' },\n { id: 'outbound', label: 'Outbound' },\n ],\n standardValues: [\n { id: 'web', label: 'Web', group: 'inbound' },\n { id: 'phone-inquiry', label: 'Phone inquiry', group: 'inbound' },\n { id: 'email-inquiry', label: 'Email inquiry', group: 'inbound' },\n { id: 'partner-referral', label: 'Partner referral', group: 'inbound' },\n { id: 'employee-referral', label: 'Employee referral', group: 'inbound' },\n { id: 'external-referral', label: 'External referral', group: 'inbound' },\n { id: 'advertisement', label: 'Advertisement', group: 'inbound' },\n { id: 'trade-show', label: 'Trade show', group: 'inbound' },\n { id: 'webinar', label: 'Webinar', group: 'inbound' },\n { id: 'word-of-mouth', label: 'Word of mouth', group: 'inbound' },\n // Aglyn's own doors (AGL-3519), each stamped by the door that met the person.\n { id: 'website-form', label: 'Website form', group: 'inbound' },\n { id: 'booking', label: 'Booking', group: 'inbound' },\n { id: 'newsletter-sign-up', label: 'Newsletter sign-up', group: 'inbound' },\n { id: 'site-member-sign-up', label: 'Site member sign-up', group: 'inbound' },\n { id: 'online-purchase', label: 'Online purchase', group: 'inbound' },\n { id: 'account-sign-up', label: 'Account sign-up', group: 'inbound' },\n { id: 'purchased-list', label: 'Purchased list', group: 'outbound' },\n // Aglyn's own outreach (AGL-3519).\n { id: 'sequence', label: 'Sequence', group: 'outbound' },\n { id: 'email-campaign', label: 'Email campaign', group: 'outbound' },\n { id: 'other', label: 'Other' },\n ],\n targets: [\n // The key the Leads list filters by moves with the label (AGL-3321).\n // So is its direction, the value's group (AGL-3577).\n {\n object: 'lead',\n field: 'leadSource',\n keyField: 'leadSourceKey',\n groupField: 'leadSourceDirection',\n },\n // A contact's lead source is each holder's own, like the rest of its profile.\n { object: 'contact', field: 'leadSource', facet: true },\n // A company's Account Source is a lead source value (AGL-3514).\n { object: 'company', field: 'accountSource', keyField: 'accountSourceKey' },\n // A deal's, stamped by the conversion that opened it (AGL-3516).\n { object: 'deal', field: 'leadSource', keyField: 'leadSourceKey' },\n ],\n} as const satisfies CrmPicklistDefinition\n\n/*------------------------------------------\n * LEAD STATUS (AGL-3512) — a SEMANTIC picklist.\n *\n * Every value MEANS one of {@link CRM_LEAD_STATUSES}, and the meaning stays\n * on the lead as `status`, which every query, count, index, automation and\n * rule reads exactly as before. The value's label sits beside it as\n * `statusLabel` — see {@link crmLeadStatusLabel}. Salesforce ships one\n * standard value per meaning; an org adds its own (\"Contacted\", \"Meeting\n * set\") under a meaning, never under Qualified, which only a conversion\n * sets.\n *-----------------------------------------*/\nconst LEAD_STATUS_DEFINITION = {\n id: 'leadStatus',\n label: 'Lead status',\n plural: 'lead statuses',\n object: 'lead',\n restricted: true,\n meanings: CRM_LEAD_STATUSES,\n meaningLabels: CRM_LEAD_STATUS_LABELS,\n reservedMeanings: ['qualified'],\n standardValues: [\n { id: 'new', label: CRM_LEAD_STATUS_LABELS.new, meaning: 'new' },\n { id: 'nurturing', label: CRM_LEAD_STATUS_LABELS.nurturing, meaning: 'nurturing' },\n { id: 'working', label: CRM_LEAD_STATUS_LABELS.working, meaning: 'working' },\n { id: 'qualified', label: CRM_LEAD_STATUS_LABELS.qualified, meaning: 'qualified' },\n { id: 'unqualified', label: CRM_LEAD_STATUS_LABELS.unqualified, meaning: 'unqualified' },\n ],\n targets: [{ object: 'lead', field: 'statusLabel' }],\n} as const satisfies CrmPicklistDefinition\n\n/*------------------------------------------\n * THE COMPANY PICKLISTS (AGL-3514).\n *\n * Salesforce's Account Type, Industry, Rating and Ownership, each kept on\n * the Companies tab. Industry and Rating are shared with leads, whose\n * targets join these definitions rather than repeating them. Account\n * Source is not a list of its own: it holds a lead source value, so it is\n * a target of the lead source definition above.\n *-----------------------------------------*/\n\n/** Salesforce's Account Type. */\nconst ACCOUNT_TYPE_DEFINITION = {\n id: 'accountType',\n label: 'Type',\n plural: 'account types',\n object: 'company',\n restricted: true,\n standardValues: [\n { id: 'analyst', label: 'Analyst' },\n { id: 'press', label: 'Press' },\n { id: 'competitor', label: 'Competitor' },\n { id: 'prospect', label: 'Prospect' },\n { id: 'customer', label: 'Customer' },\n { id: 'reseller', label: 'Reseller' },\n { id: 'integrator', label: 'Integrator' },\n { id: 'investor', label: 'Investor' },\n { id: 'partner', label: 'Partner' },\n { id: 'consulting', label: 'Consulting' },\n { id: 'other', label: 'Other' },\n ],\n targets: [{ object: 'company', field: 'type', keyField: 'typeKey' }],\n} as const satisfies CrmPicklistDefinition\n\n/**\n * Salesforce's Industry. A company written while the field was free text\n * keeps its text; a write that keeps it is never refused.\n */\nconst INDUSTRY_DEFINITION = {\n id: 'industry',\n label: 'Industry',\n plural: 'industries',\n object: 'company',\n restricted: true,\n standardValues: [\n { id: 'agriculture', label: 'Agriculture' },\n { id: 'apparel', label: 'Apparel' },\n { id: 'banking', label: 'Banking' },\n { id: 'biotechnology', label: 'Biotechnology' },\n { id: 'chemicals', label: 'Chemicals' },\n { id: 'communications', label: 'Communications' },\n { id: 'construction', label: 'Construction' },\n { id: 'consulting', label: 'Consulting' },\n { id: 'education', label: 'Education' },\n { id: 'electronics', label: 'Electronics' },\n { id: 'energy', label: 'Energy' },\n { id: 'engineering', label: 'Engineering' },\n { id: 'entertainment', label: 'Entertainment' },\n { id: 'environmental', label: 'Environmental' },\n { id: 'finance', label: 'Finance' },\n { id: 'food-and-beverage', label: 'Food & Beverage' },\n { id: 'government', label: 'Government' },\n { id: 'healthcare', label: 'Healthcare' },\n { id: 'hospitality', label: 'Hospitality' },\n { id: 'insurance', label: 'Insurance' },\n { id: 'machinery', label: 'Machinery' },\n { id: 'manufacturing', label: 'Manufacturing' },\n { id: 'media', label: 'Media' },\n { id: 'not-for-profit', label: 'Not For Profit' },\n { id: 'recreation', label: 'Recreation' },\n { id: 'retail', label: 'Retail' },\n { id: 'shipping', label: 'Shipping' },\n { id: 'technology', label: 'Technology' },\n { id: 'telecommunications', label: 'Telecommunications' },\n { id: 'transportation', label: 'Transportation' },\n { id: 'utilities', label: 'Utilities' },\n { id: 'other', label: 'Other' },\n ],\n targets: [\n { object: 'company', field: 'industry', keyField: 'industryKey' },\n // Leads (AGL-3513): the same list, so a lead converts into its value.\n { object: 'lead', field: 'industry', keyField: 'industryKey' },\n ],\n} as const satisfies CrmPicklistDefinition\n\n/** Salesforce's Rating. */\nconst RATING_DEFINITION = {\n id: 'rating',\n label: 'Rating',\n plural: 'ratings',\n object: 'company',\n restricted: true,\n standardValues: [\n { id: 'hot', label: 'Hot' },\n { id: 'warm', label: 'Warm' },\n { id: 'cold', label: 'Cold' },\n ],\n targets: [\n { object: 'company', field: 'rating', keyField: 'ratingKey' },\n // Leads (AGL-3513).\n { object: 'lead', field: 'rating', keyField: 'ratingKey' },\n ],\n} as const satisfies CrmPicklistDefinition\n\n/** Salesforce's Ownership. */\nconst OWNERSHIP_DEFINITION = {\n id: 'ownership',\n label: 'Ownership',\n plural: 'ownership values',\n object: 'company',\n restricted: true,\n standardValues: [\n { id: 'public', label: 'Public' },\n { id: 'private', label: 'Private' },\n { id: 'subsidiary', label: 'Subsidiary' },\n { id: 'other', label: 'Other' },\n ],\n targets: [{ object: 'company', field: 'ownership' }],\n} as const satisfies CrmPicklistDefinition\n\n/*------------------------------------------\n * CONTACT picklists (AGL-3515).\n *-----------------------------------------*/\n\n/**\n * Salutation, with Salesforce's standard values. Shared by every object\n * that addresses a person: defined here with the contact target, where the\n * label lives in each holder's facet like the rest of the profile.\n */\nconst SALUTATION_DEFINITION = {\n id: 'salutation',\n label: 'Salutation',\n plural: 'salutations',\n object: 'contact',\n restricted: true,\n standardValues: [\n { id: 'mr', label: 'Mr.' },\n { id: 'ms', label: 'Ms.' },\n { id: 'mrs', label: 'Mrs.' },\n { id: 'dr', label: 'Dr.' },\n { id: 'prof', label: 'Prof.' },\n ],\n targets: [\n { object: 'contact', field: 'salutation', facet: true },\n // Leads (AGL-3513): on the lead itself, carried to the facet on convert.\n { object: 'lead', field: 'salutation' },\n ],\n} as const satisfies CrmPicklistDefinition\n\n/*------------------------------------------\n * DEALS (AGL-3516).\n *-----------------------------------------*/\n\n/**\n * Salesforce's Opportunity Type. Plain: the deal stores the label, and\n * `typeKey` beside it for the Deals list's filter.\n */\nconst OPPORTUNITY_TYPE_DEFINITION = {\n id: 'opportunityType',\n label: 'Type',\n plural: 'deal types',\n object: 'deal',\n restricted: true,\n standardValues: [\n { id: 'existing-business', label: 'Existing Business' },\n { id: 'new-business', label: 'New Business' },\n ],\n targets: [{ object: 'deal', field: 'type', keyField: 'typeKey' }],\n} as const satisfies CrmPicklistDefinition\n\n/**\n * Salesforce's Opportunity Contact Role (AGL-3521): the part a contact\n * plays on a deal. Plain; the label sits in each entry of the deal's\n * `contactRoles`, and `contactRoleKeys` lists their keys, which is how a\n * rename or a delete finds the deals to rewrite.\n */\nconst OPPORTUNITY_CONTACT_ROLE_DEFINITION = {\n id: 'opportunityContactRole',\n label: 'Contact role',\n plural: 'contact roles',\n object: 'deal',\n restricted: true,\n standardValues: [\n { id: 'business-user', label: 'Business User' },\n { id: 'decision-maker', label: 'Decision Maker' },\n { id: 'economic-buyer', label: 'Economic Buyer' },\n { id: 'economic-decision-maker', label: 'Economic Decision Maker' },\n { id: 'evaluator', label: 'Evaluator' },\n { id: 'executive-sponsor', label: 'Executive Sponsor' },\n { id: 'influencer', label: 'Influencer' },\n { id: 'technical-buyer', label: 'Technical Buyer' },\n { id: 'other', label: 'Other' },\n ],\n targets: [{ object: 'deal', field: 'contactRoles', arrayKey: 'role', keyField: 'contactRoleKeys' }],\n} as const satisfies CrmPicklistDefinition\n\n/*\n * TASKS (AGL-3517) — Salesforce's Task Status, Priority, Type and Subject.\n *\n * Status, Priority and Type are SEMANTIC: each value means one of the\n * task's existing `status`, `priority` or `kind` values, which stay on the\n * document untouched for every query, reminder, due state, digest and\n * automation. The value's LABEL sits beside it in `statusLabel`,\n * `priorityLabel` or `typeLabel`; a task with no label shows the first\n * active value of its meaning (see `crmTaskPicklistLabel`). Subject is an\n * unrestricted combobox over the title: its values are suggestions, the\n * title stays free text, and no record holds a subject \"value\", so a\n * rename or a delete rewrites nothing.\n */\nconst TASK_STATUS_DEFINITION = {\n id: 'taskStatus',\n label: 'Status',\n plural: 'task statuses',\n object: 'task',\n restricted: true,\n meanings: ['open', 'done'],\n standardValues: [\n { id: 'not-started', label: 'Not Started', meaning: 'open' },\n { id: 'in-progress', label: 'In Progress', meaning: 'open' },\n { id: 'waiting', label: 'Waiting on someone else', meaning: 'open' },\n { id: 'deferred', label: 'Deferred', meaning: 'open' },\n { id: 'completed', label: 'Completed', meaning: 'done' },\n ],\n defaultValueId: 'not-started',\n targets: [{ object: 'task', field: 'statusLabel' }],\n} as const satisfies CrmPicklistDefinition\n\nconst TASK_PRIORITY_DEFINITION = {\n id: 'taskPriority',\n label: 'Priority',\n plural: 'task priorities',\n object: 'task',\n restricted: true,\n meanings: ['low', 'normal', 'high'],\n standardValues: [\n { id: 'high', label: 'High', meaning: 'high' },\n { id: 'normal', label: 'Normal', meaning: 'normal' },\n { id: 'low', label: 'Low', meaning: 'low' },\n ],\n defaultValueId: 'normal',\n targets: [{ object: 'task', field: 'priorityLabel' }],\n} as const satisfies CrmPicklistDefinition\n\nconst TASK_TYPE_DEFINITION = {\n id: 'taskType',\n label: 'Type',\n plural: 'task types',\n object: 'task',\n restricted: true,\n meanings: CRM_TASK_KINDS,\n standardValues: [\n { id: 'call', label: 'Call', meaning: 'call' },\n { id: 'email', label: 'Email', meaning: 'email' },\n { id: 'meeting', label: 'Meeting', meaning: 'meeting' },\n // Salesforce's \"Other\": a task that is none of the three conversations.\n { id: 'todo', label: 'To-do', meaning: 'todo' },\n ],\n defaultValueId: 'todo',\n targets: [{ object: 'task', field: 'typeLabel' }],\n} as const satisfies CrmPicklistDefinition\n\nconst TASK_SUBJECT_DEFINITION = {\n id: 'taskSubject',\n label: 'Subject',\n plural: 'task subjects',\n object: 'task',\n restricted: false,\n standardValues: [\n { id: 'call', label: 'Call' },\n { id: 'send-letter', label: 'Send Letter' },\n { id: 'send-quote', label: 'Send Quote' },\n { id: 'other', label: 'Other' },\n ],\n // Suggestions for a free-text title: nothing holds them, nothing moves.\n targets: [],\n} as const satisfies CrmPicklistDefinition\n\n/** Every standard picklist field the CRM keeps, one document each. */\nexport const CRM_PICKLIST_DEFINITIONS = [\n LEAD_SOURCE_DEFINITION,\n LEAD_STATUS_DEFINITION,\n // Companies (AGL-3514).\n ACCOUNT_TYPE_DEFINITION,\n INDUSTRY_DEFINITION,\n RATING_DEFINITION,\n OWNERSHIP_DEFINITION,\n // Contacts (AGL-3515).\n SALUTATION_DEFINITION,\n // Deals (AGL-3516).\n OPPORTUNITY_TYPE_DEFINITION,\n // Deal contact roles (AGL-3521).\n OPPORTUNITY_CONTACT_ROLE_DEFINITION,\n // Tasks (AGL-3517).\n TASK_STATUS_DEFINITION,\n TASK_PRIORITY_DEFINITION,\n TASK_TYPE_DEFINITION,\n TASK_SUBJECT_DEFINITION,\n] as const satisfies readonly CrmPicklistDefinition[]\n\nexport type CrmPicklistId = (typeof CRM_PICKLIST_DEFINITIONS)[number]['id']\n\n/** The picklists an org keeps, by document id. */\nexport const CRM_PICKLIST_IDS: readonly CrmPicklistId[] = CRM_PICKLIST_DEFINITIONS.map(\n (definition) => definition.id,\n)\n\n/** The lead source value set's document id. */\nexport const CRM_LEAD_SOURCE_PICKLIST: CrmPicklistId = 'leadSource'\n\n/** The salutation value set's document id (AGL-3515). */\nexport const CRM_SALUTATION_PICKLIST: CrmPicklistId = 'salutation'\n/** A deal's Type value set's document id (AGL-3516). */\nexport const CRM_OPPORTUNITY_TYPE_PICKLIST: CrmPicklistId = 'opportunityType'\n/** A deal contact's role value set's document id (AGL-3521). */\nexport const CRM_OPPORTUNITY_CONTACT_ROLE_PICKLIST: CrmPicklistId = 'opportunityContactRole'\n\n/**\n * The built-in Lead source value each first-party door stamps (AGL-3519), by\n * the door's word for how it met a person — a capture's source, or the\n * outreach that reached them. `api`, `import` and `manual` are ways a record\n * was ADDED, not where a person came from, and stamp nothing.\n */\nexport const CRM_LEAD_SOURCE_ORIGINS: Readonly<Record<string, string>> = {\n form: 'website-form',\n booking: 'booking',\n newsletter: 'newsletter-sign-up',\n member: 'site-member-sign-up',\n order: 'online-purchase',\n account: 'account-sign-up',\n sequence: 'sequence',\n emailCampaign: 'email-campaign',\n}\n\nexport function isCrmPicklistId(value: unknown): value is CrmPicklistId {\n return typeof value === 'string' && (CRM_PICKLIST_IDS as readonly string[]).includes(value)\n}\n\n/** The definition an id names, or `null` for one the registry does not hold. */\nexport function crmPicklistDefinition(id: unknown): CrmPicklistDefinition | null {\n return CRM_PICKLIST_DEFINITIONS.find((definition) => definition.id === id) ?? null\n}\n\n/** The definitions whose values are managed on one Fields tab, in registry order. */\nexport function crmPicklistDefinitionsFor(object: CrmPicklistObject): CrmPicklistDefinition[] {\n return CRM_PICKLIST_DEFINITIONS.filter((definition) => definition.object === object)\n}\n\n/** The definition behind a registered id — total, because the id is typed. */\nfunction definitionOf(id: CrmPicklistId): CrmPicklistDefinition {\n return crmPicklistDefinition(id) as CrmPicklistDefinition\n}\n\n/** The most values one picklist holds — a menu, not a table. */\nexport const CRM_PICKLIST_VALUES_MAX = PICKLIST_VALUES_MAX\n\n/** One value of a picklist. `id` never changes; `label` is what records store. */\nexport type CrmPicklistValue = PicklistValue\n\n/** A picklist as stored and as every reader takes it. */\nexport type CrmPicklist = PicklistValueSet\n\n/** One option of a picklist select. */\nexport type CrmPicklistOption = PicklistOption\n\n/**\n * The values an org without a lead source document offered before the\n * standard set was built in. \"Referral\" and \"Partner\" are not standard: a\n * record holding one, in an org that never stored its list, reads as not in\n * the list until the value is written down as the org's own.\n */\nexport const CRM_LEAD_SOURCE_STARTER_LABELS: readonly string[] = [\n 'Web',\n 'Phone inquiry',\n 'Referral',\n 'Partner',\n 'Purchased list',\n 'Trade show',\n 'Other',\n]\n\n/** A label as a picklist stores it: trimmed, single-spaced, capped. */\nexport const normalizeCrmPicklistLabel = normalizePicklistLabel\n\n/** A new value's id — see `mintPicklistValueId`. */\nexport const crmPicklistValueId = mintPicklistValueId\n\n/** A picklist of active values from labels, in order, with no default. */\nexport const crmPicklistFromLabels = picklistFromLabels\n\n/**\n * A stored document as a picklist, or `null` for one that holds none —\n * tolerant, as `normalizePicklistValueSet` reads it, and without the\n * standard values merged in. {@link effectiveCrmPicklist} is what a reader\n * judges against.\n */\nexport function normalizeCrmPicklist(raw: unknown): CrmPicklist | null {\n return normalizePicklistValueSet(raw)\n}\n\n/** An org's list for one picklist as every reader should take it — see the block header. */\nexport function effectiveCrmPicklist(id: CrmPicklistId, raw: unknown): CrmPicklist {\n return effectivePicklistValueSet(definitionOf(id), raw)\n}\n\n/** The org's lead source list as every reader should take it. */\nexport function effectiveCrmLeadSourcePicklist(raw: unknown): CrmPicklist {\n return effectiveCrmPicklist(CRM_LEAD_SOURCE_PICKLIST, raw)\n}\n\n/**\n * The label a door stamps for `origin` (AGL-3519): its built-in value as the\n * org spells it, while that value is ACTIVE — an org that deactivated it has\n * said not to file people under it — else `null`, as for a word with none.\n */\nexport function crmLeadSourceForOrigin(picklist: CrmPicklist, origin: unknown): string | null {\n const id = typeof origin === 'string' ? CRM_LEAD_SOURCE_ORIGINS[origin] : undefined\n if (!id || !Object.hasOwn(CRM_LEAD_SOURCE_ORIGINS, origin as string)) return null\n const value = picklist.values.find((entry) => entry.id === id)\n return value?.active ? value.label : null\n}\n\n/** Whether a value is one of a picklist's standard values — computed from its id, never stored. */\nexport function isStandardCrmPicklistValue(id: CrmPicklistId, valueId: string): boolean {\n return isStandardPicklistValueId(definitionOf(id), valueId)\n}\n\n/** The value a label names, in any case or spacing — `null` for none. */\nexport const crmPicklistValueByLabel = picklistValueByLabel\n\n/** The values a picker offers. */\nexport const crmPicklistActiveValues = picklistActiveValues\n\n/** The label a new record starts with — only while the default value is active. */\nexport const crmPicklistDefaultLabel = picklistDefaultLabel\n\n/**\n * The sentence a value outside a restricted picklist is refused with,\n * naming what the list allows, and where to add one when it allows none.\n */\nexport function crmPicklistRefusal(id: CrmPicklistId, picklist: CrmPicklist): string {\n const definition = definitionOf(id)\n return picklistRefusalSentence(picklist, {\n field: definition.label,\n empty:\n `This organization has no active ${definition.plural}. Add one under ` +\n `CRM › Fields › ${CRM_PICKLIST_OBJECT_LABELS[definition.object]}.`,\n })\n}\n\n/** The sentence a lead source outside the list is refused with. */\nexport function crmLeadSourceRefusal(picklist: CrmPicklist): string {\n return crmPicklistRefusal(CRM_LEAD_SOURCE_PICKLIST, picklist)\n}\n\n/**\n * Whether a write may store `value` as a record's value of picklist `id`,\n * and the label it stores — `judgePicklistValue` under the definition's\n * restriction: blank clears, an active value stores the list's spelling,\n * the record's `current` value is always kept, and on a restricted\n * picklist anything else is refused with {@link crmPicklistRefusal}.\n */\nexport function judgeCrmPicklistValue(\n id: CrmPicklistId,\n picklist: CrmPicklist,\n value: unknown,\n current?: unknown,\n): PicklistJudgement {\n return judgePicklistValue(picklist, value, {\n restricted: definitionOf(id).restricted,\n current,\n refusal: () => crmPicklistRefusal(id, picklist),\n })\n}\n\n/**\n * A deal's contact roles with each role judged against the org's\n * `opportunityContactRole` list (AGL-3521): an active value stored as the\n * list spells it, a blank cleared, and the role a contact already holds on\n * the deal (`current`) kept — or the first refusal, naming what the list\n * allows.\n */\nexport function judgeDealContactRoles(\n picklist: CrmPicklist,\n roles: readonly CrmDealContactRole[],\n current: readonly CrmDealContactRole[] = [],\n): { ok: true; roles: CrmDealContactRole[] } | { ok: false; error: string } {\n const judged: CrmDealContactRole[] = []\n for (const row of roles) {\n const held = current.find((entry) => entry.contactId === row.contactId)?.role\n const verdict = judgeCrmPicklistValue(\n 'opportunityContactRole',\n picklist,\n row.role ?? '',\n held,\n )\n if (verdict.ok === false) return verdict\n judged.push({\n contactId: row.contactId,\n ...(verdict.value ? { role: verdict.value } : {}),\n primary: row.primary,\n })\n }\n return { ok: true, roles: judged }\n}\n\n/** {@link judgeCrmPicklistValue} for the lead source. */\nexport function judgeCrmLeadSource(\n picklist: CrmPicklist,\n value: unknown,\n current?: unknown,\n): PicklistJudgement {\n return judgeCrmPicklistValue(CRM_LEAD_SOURCE_PICKLIST, picklist, value, current)\n}\n\n/**\n * What a select offers for a record holding `current`: every active value\n * in order, then the record's own value when the list would not otherwise\n * show it, so the select keeps what the record holds until it is changed.\n */\nexport const crmPicklistOptions = picklistOptions\n\n/**\n * Where a label sorts: its value's position in the list, unlisted values\n * after every listed one, and a record with none last. The order the admin\n * chose is the order a sort by the field reads in, as Salesforce's does.\n */\nexport const crmPicklistRank = picklistRank\n\n/*------------------------------------------\n * A LEAD'S STATUS LABEL (AGL-3512).\n *\n * `status` is the meaning and stays the truth: a writer that knows only the\n * meaning — an automation, an inbox reply, a rule — sets `status` alone and\n * the lead still reads right. `statusLabel` names which of the org's values\n * of that meaning the lead holds, and is believed only while it IS one of\n * that meaning; otherwise, or when absent, the lead shows the default value\n * when it is active and of that meaning, else the first active value of\n * that meaning, else the standard label.\n *-----------------------------------------*/\n\n/** The lead status value set's document id. */\nexport const CRM_LEAD_STATUS_PICKLIST: CrmPicklistId = 'leadStatus'\n\n/** The field a lead's status label is stored in, beside `status`. */\nexport const CRM_LEAD_STATUS_LABEL_FIELD = 'statusLabel'\n\n/** The org's lead status list as every reader should take it. */\nexport function effectiveCrmLeadStatusPicklist(raw: unknown): CrmPicklist {\n return effectiveCrmPicklist(CRM_LEAD_STATUS_PICKLIST, raw)\n}\n\n/** The standard lead statuses alone — what a reader answers before the org's list is read. */\nexport const STANDARD_CRM_LEAD_STATUS_PICKLIST: CrmPicklist = effectiveCrmLeadStatusPicklist(null)\n\n/**\n * The value a lead with `status` shows when it names none of its own: the\n * default when active and of that meaning, else the first active value of\n * that meaning, else the first of that meaning at all.\n */\nexport function crmLeadStatusValueFor(\n picklist: CrmPicklist,\n status: CrmLeadStatus,\n): CrmPicklistValue | null {\n const ofMeaning = picklist.values.filter((value) => value.meaning === status)\n const preferred = ofMeaning.find((value) => value.id === picklist.defaultValueId && value.active)\n return preferred ?? ofMeaning.find((value) => value.active) ?? ofMeaning[0] ?? null\n}\n\n/** The label a writer stamps beside `status` when it sets a meaning and names no value. */\nexport function crmLeadStatusLabelFor(picklist: CrmPicklist, status: CrmLeadStatus): string {\n return crmLeadStatusValueFor(picklist, status)?.label ?? CRM_LEAD_STATUS_LABELS[status]\n}\n\n/**\n * How a lead's status reads: its own `statusLabel` while that is a value of\n * its `status`'s meaning (as the list spells it), else the meaning's label\n * by {@link crmLeadStatusLabelFor}. A label the list no longer holds is shown\n * as stored while nothing contradicts it — a value deleted without moving\n * every lead off it reads as what the lead was given.\n */\nexport function crmLeadStatusLabel(\n lead: Pick<CrmLeadFields, 'status' | 'statusLabel'> | null | undefined,\n picklist: CrmPicklist = STANDARD_CRM_LEAD_STATUS_PICKLIST,\n): string {\n const status = crmLeadStatus(lead)\n const held = normalizeCrmPicklistLabel(lead?.statusLabel)\n if (held) {\n const value = picklistValueByLabel(picklist, held)\n if (value?.meaning === status) return value.label\n if (!value) return held\n }\n return crmLeadStatusLabelFor(picklist, status)\n}\n\n/** A status write, as both fields a lead stores — or why it is refused. */\nexport type CrmLeadStatusWrite =\n | { ok: true; status: CrmLeadStatus; statusLabel: string }\n | { ok: false; error: string }\n\n/**\n * What a write naming a lead status stores (AGL-3512): an ACTIVE value's\n * label, in any case or spacing, stores its meaning and its label; one of\n * the meanings themselves (`working`) stores that meaning and the label\n * {@link crmLeadStatusLabelFor} gives it; the lead's `current` label is\n * kept even when inactive. `allowed` narrows the meanings this door may\n * set — every door but a conversion leaves Qualified out — and anything\n * else is refused naming what the door accepts.\n */\nexport function resolveCrmLeadStatusWrite(\n picklist: CrmPicklist,\n requested: unknown,\n options: {\n allowed?: readonly CrmLeadStatus[]\n current?: Pick<CrmLeadFields, 'status' | 'statusLabel'> | null\n } = {},\n): CrmLeadStatusWrite {\n const allowed = options.allowed ?? CRM_LEAD_STATUSES\n const text = normalizeCrmPicklistLabel(requested)\n const refuse = (): CrmLeadStatusWrite => {\n const labels = picklist.values\n .filter((value) => value.active && allowed.includes(value.meaning as CrmLeadStatus))\n .map((value) => value.label)\n return { ok: false, error: `Lead status must be one of: ${labels.join(', ')}.` }\n }\n if (!text) return refuse()\n const value = picklistValueByLabel(picklist, text)\n const current = options.current ? normalizeCrmPicklistLabel(options.current.statusLabel) : ''\n const keeps = Boolean(value && current && picklistLabelKeyOf(current) === picklistLabelKeyOf(value.label))\n if (value && isCrmLeadStatus(value.meaning) && (value.active || keeps)) {\n return allowed.includes(value.meaning)\n ? { ok: true, status: value.meaning, statusLabel: value.label }\n : refuse()\n }\n const meaning = text.toLowerCase()\n if (isCrmLeadStatus(meaning) && allowed.includes(meaning)) {\n return { ok: true, status: meaning, statusLabel: crmLeadStatusLabelFor(picklist, meaning) }\n }\n return refuse()\n}\n\nconst picklistLabelKeyOf = (label: string): string => normalizeCrmPicklistLabel(label).toLowerCase()\n\n/**\n * The values a lead status select offers for a lead holding `current`:\n * every active value whose meaning `allowed` admits, in the list's order,\n * then the lead's own value when the list would not otherwise show it.\n */\nexport function crmLeadStatusOptions(\n picklist: CrmPicklist,\n allowed: readonly CrmLeadStatus[] = CRM_LEAD_STATUSES,\n current?: Pick<CrmLeadFields, 'status' | 'statusLabel'> | null,\n): Array<{ label: string; status: CrmLeadStatus; inactive: boolean }> {\n const options = picklist.values\n .filter((value) => value.active && allowed.includes(value.meaning as CrmLeadStatus))\n .map((value) => ({ label: value.label, status: value.meaning as CrmLeadStatus, inactive: false }))\n if (current) {\n const label = crmLeadStatusLabel(current, picklist)\n if (!options.some((option) => option.label === label)) {\n options.push({ label, status: crmLeadStatus(current), inactive: true })\n }\n }\n return options\n}\n\n/*==========================================\n * SEMANTIC PICKLISTS ON A TASK (AGL-3517).\n *\n * Status, Priority and Type keep the task's own `status`, `priority` and\n * `kind` as the MEANING every query, reminder and automation reads, and\n * store the org's label beside it. These helpers are the two directions:\n * what a task shows for its meaning and label, and what a write stores for\n * a label or a meaning a person, a file or an API caller named.\n *=========================================*/\n\n/** The label a value of `meaning` reads as: the first active one, else the first at all. */\nexport function crmPicklistMeaningLabel(picklist: CrmPicklist, meaning: unknown): string | null {\n if (typeof meaning !== 'string' || !meaning) return null\n const held = picklist.values.filter((value) => value.meaning === meaning)\n return (held.find((value) => value.active) ?? held[0])?.label ?? null\n}\n\n/**\n * What a record shows for a semantic field: its own label when it holds\n * one, else the first active value of its meaning, else the meaning itself.\n */\nexport function crmPicklistShownLabel(\n picklist: CrmPicklist,\n meaning: unknown,\n label: unknown,\n): string {\n const own = normalizeCrmPicklistLabel(label)\n if (own) return own\n return crmPicklistMeaningLabel(picklist, meaning) ?? String(meaning ?? '')\n}\n\n/**\n * The label a NEW record of `meaning` starts with: the list's default when\n * it means that, else the first active value that does.\n */\nexport function crmPicklistLabelForNew(picklist: CrmPicklist, meaning: unknown): string | null {\n const fallback = crmPicklistDefaultLabel(picklist)\n const value = fallback ? crmPicklistValueByLabel(picklist, fallback) : null\n return value && value.meaning === meaning ? value.label : crmPicklistMeaningLabel(picklist, meaning)\n}\n\n/** What a semantic write stores: the meaning and the label beside it. */\nexport type CrmSemanticPicklistWrite =\n | { ok: true; meaning: string; label: string | null }\n | { ok: false; error: string }\n\n/**\n * A label OR a meaning, as a write names it, resolved against picklist `id`:\n *\n * - an active value's label (any case or spacing) stores that value's\n * label and its meaning;\n * - the record's `current` label is kept, with its value's meaning, even\n * when the value has since been deactivated;\n * - one of the definition's meanings (`high`, `done`, `call`) stores that\n * meaning with the first active value's label — or the record's current\n * label when that already means it;\n * - anything else is refused naming the values the list allows.\n *\n * `null` answers a blank input: the caller keeps its own default.\n */\nexport function resolveCrmSemanticPicklistWrite(\n id: CrmPicklistId,\n picklist: CrmPicklist,\n input: unknown,\n current?: unknown,\n): CrmSemanticPicklistWrite | null {\n const text = normalizeCrmPicklistLabel(input)\n if (!text) return null\n const definition = definitionOf(id)\n const meanings = (definition.meanings ?? []) as readonly string[]\n const held = normalizeCrmPicklistLabel(current)\n const byLabel = crmPicklistValueByLabel(picklist, text)\n const keeps = held && held.toLowerCase() === text.toLowerCase()\n if (byLabel?.meaning && (byLabel.active || keeps)) {\n return { ok: true, meaning: byLabel.meaning, label: keeps ? held : byLabel.label }\n }\n const meaning = meanings.find((entry) => entry === text.toLowerCase())\n if (meaning) {\n const heldValue = held ? crmPicklistValueByLabel(picklist, held) : null\n return {\n ok: true,\n meaning,\n label: heldValue?.meaning === meaning ? held : crmPicklistMeaningLabel(picklist, meaning),\n }\n }\n return { ok: false, error: crmPicklistRefusal(id, picklist) }\n}\n\n/** A task's three semantic picklists, as every task surface reads them. */\nexport interface CrmTaskPicklists {\n status: CrmPicklist\n priority: CrmPicklist\n type: CrmPicklist\n}\n\n/** The picklist id behind each of a task's semantic fields. */\nexport const CRM_TASK_PICKLIST_IDS = {\n status: 'taskStatus',\n priority: 'taskPriority',\n type: 'taskType',\n} as const satisfies Record<keyof CrmTaskPicklists, CrmPicklistId>\n\n/** The org's three task lists from their stored documents — standard values alone for none. */\nexport function effectiveCrmTaskPicklists(\n raw: Partial<Record<keyof CrmTaskPicklists, unknown>> = {},\n): CrmTaskPicklists {\n return {\n status: effectiveCrmPicklist(CRM_TASK_PICKLIST_IDS.status, raw.status),\n priority: effectiveCrmPicklist(CRM_TASK_PICKLIST_IDS.priority, raw.priority),\n type: effectiveCrmPicklist(CRM_TASK_PICKLIST_IDS.type, raw.type),\n }\n}\n\n/** What a task shows for its status, priority and type — see `crmPicklistShownLabel`. */\nexport function crmTaskPicklistLabels(\n task: Partial<Pick<CrmTask, 'status' | 'statusLabel' | 'priority' | 'priorityLabel' | 'kind' | 'typeLabel'>>,\n picklists: CrmTaskPicklists,\n): { status: string; priority: string; type: string } {\n return {\n status: crmPicklistShownLabel(picklists.status, task.status ?? 'open', task.statusLabel),\n priority: crmPicklistShownLabel(picklists.priority, task.priority ?? 'normal', task.priorityLabel),\n type: crmPicklistShownLabel(picklists.type, task.kind ?? 'todo', task.typeLabel),\n }\n}\n\n/**\n * The status a tick or an untick writes: done with the first active done\n * value's label (\"Completed\"), or open with the label a new task starts\n * with (\"Not Started\").\n */\nexport function crmTaskStatusWrite(\n picklist: CrmPicklist,\n done: boolean,\n): { status: CrmTaskStatus; statusLabel: string | null } {\n return done\n ? { status: 'done', statusLabel: crmPicklistMeaningLabel(picklist, 'done') }\n : { status: 'open', statusLabel: crmPicklistLabelForNew(picklist, 'open') }\n}\n\n/** The labels a new task of these meanings starts with. */\nexport function crmTaskLabelsForNew(\n picklists: CrmTaskPicklists,\n meanings: Pick<CrmTask, 'kind' | 'priority'> & { status?: CrmTaskStatus },\n): { statusLabel: string | null; priorityLabel: string | null; typeLabel: string | null } {\n return {\n statusLabel: crmPicklistLabelForNew(picklists.status, meanings.status ?? 'open'),\n priorityLabel: crmPicklistLabelForNew(picklists.priority, meanings.priority),\n typeLabel: crmPicklistLabelForNew(picklists.type, meanings.kind),\n }\n}\n\n/** The name a lead is listed under: the name it carries, else its address. */\nexport function crmLeadDisplayName(\n lead: Record<string, unknown> | null | undefined,\n): string {\n const name = String(lead?.['name'] ?? '').trim()\n return name || String(lead?.['email'] ?? '').trim()\n}\n\n/**\n * A lead's status as the list and the filter should read it.\n *\n * `new` for a lead that carries no status at all — every lead captured\n * before the CRM existed, and every lead the capture door writes today,\n * because `addHostLead` stamps none. Reading the absence as `new` is what\n * lets the section open on the leads a site already holds rather than on an\n * empty list until each one has been touched once. A stored value the union\n * does not name also reads as `new`: it is a document some other writer\n * produced, and refusing to list it would hide a person.\n */\nexport function crmLeadStatus(\n lead: Pick<CrmLeadFields, 'status'> | null | undefined,\n): CrmLeadStatus {\n const status = lead?.status\n return isCrmLeadStatus(status) ? status : 'new'\n}\n\n/** Whether a lead still needs working — see {@link CRM_LEAD_OPEN_STATUSES}. */\nexport function isCrmLeadOpen(\n lead: Pick<CrmLeadFields, 'status'> | null | undefined,\n): boolean {\n return CRM_LEAD_OPEN_STATUSES.includes(crmLeadStatus(lead))\n}\n\n/*==========================================\n * SAVED VIEWS (AGL-2617) — a list the way one person works it.\n *\n * \"My open leads in Texas\" is a list a rep opens every morning, and it is\n * three things at once: the filters that pick the people, the columns they\n * want to see about them, and the order they want them in. A `ContactSegment`\n * kept the first of those for two dimensions — tags and capture sources —\n * and nothing kept the rest, so every reload put the reader back at the\n * whole list. A view keeps all three, for every list in the CRM, under a\n * name, and is addressable: `?view=<id>` on the section's own URL opens it.\n *\n * ## The filters are the LIST'S grammar, not a second one\n *\n * A clause here is `{ field, op, value }` — structurally the\n * `ListFilterRequest` the shared list-filter grammar already speaks, the\n * request a grid's filter panel produces and the one the query translator\n * and the in-memory matcher both read. A view stores a list of them and\n * nothing more, so a view can only ask what the list can already answer,\n * and a list that gains a filterable field gains it for its views at no\n * cost. The type is restated here rather than imported because this module\n * is pure data and the grammar's home is a UI library; the shape is the\n * contract, and `normalizeCrmViewFilters` is what holds a stored document\n * to it.\n *\n * ## Mine, and shared\n *\n * A view is one person's working arrangement until they say otherwise, so\n * it lists only for its owner; `shared` puts it in front of everyone who\n * can read the section. Both are stamped `visibleTo` like every CRM row,\n * because a view is a description of the people it selects and answers to\n * the same authority they do — a private view is hidden by the LISTING, not\n * by the rules, and the rules let the creator or an org-wide member change\n * or remove it.\n *\n * ## The default is the reader's, not the view's\n *\n * Which view a section opens on is a preference of one person for one\n * organization, so it lives on the reader's own profile document beside\n * their notification mutes rather than on the view — a shared view somebody\n * else made their default must not become everybody's.\n *=========================================*/\n\n/** The lists a view can be saved for — every CRM section that has one. */\nexport const CRM_VIEW_SECTIONS = [\n 'contacts',\n 'companies',\n 'deals',\n 'tasks',\n 'leads',\n] as const\n\nexport type CrmViewSection = (typeof CRM_VIEW_SECTIONS)[number]\n\nexport function isCrmViewSection(value: unknown): value is CrmViewSection {\n return (\n typeof value === 'string' &&\n (CRM_VIEW_SECTIONS as readonly string[]).includes(value)\n )\n}\n\n/**\n * One filter a view carries: a field, an operator and a value, exactly as a\n * list's own filter panel would send them to its query.\n *\n * `label` is the human reading of an OPAQUE value — a team member's name\n * beside their uid, a company's name beside its id — kept on the clause so\n * the chip that shows it can say \"Owner is Dana\" without a read. Display\n * only: nothing matches on it, and a clause without one shows its value.\n */\nexport interface CrmViewFilterClause {\n field: string\n op: string\n value: string\n label?: string\n}\n\n/** The column a view orders by, and which way. */\nexport interface CrmViewSort {\n field: string\n direction: 'asc' | 'desc'\n}\n\n/**\n * What a view holds about a list — the part a section reads and writes,\n * without the name and the ownership around it.\n *\n * `columns` is the VISIBLE set, by column field, or empty for the list's\n * own default; `sort` is one column or none, because a table sorts by one.\n */\nexport interface CrmViewState {\n filters: CrmViewFilterClause[]\n columns: string[]\n sort: CrmViewSort | null\n}\n\n/** `orgs/{orgId}/crmViews/{viewId}`. */\nexport interface CrmSavedView extends CrmScoped, CrmViewState {\n section: CrmViewSection\n name: string\n /** Whose working arrangement this is — the creator, and the one member the rules let change it. */\n ownerUid: string\n createdByUid: string\n /** Listed for everybody who can read the section, rather than the owner alone. */\n shared: boolean\n}\n\nexport const CRM_VIEW_NAME_MAX = 60\n/**\n * The most clauses one view carries. A bound on the predicate, not the\n * audience: the list matches most of them in memory over a bounded window,\n * and a stored array has no natural end.\n */\nexport const CRM_VIEW_MAX_FILTERS = 20\nexport const CRM_VIEW_MAX_COLUMNS = 60\n/** Filter operators that carry no value, so an empty `value` is not a dropped clause. */\nconst VALUELESS_VIEW_OPS: ReadonlySet<string> = new Set(['isEmpty', 'isNotEmpty'])\n\nexport const EMPTY_CRM_VIEW_STATE: CrmViewState = Object.freeze({\n filters: [],\n columns: [],\n sort: null,\n}) as CrmViewState\n\nconst asViewText = (value: unknown, max: number): string =>\n typeof value === 'string' ? value.trim().slice(0, max) : ''\n\n/**\n * A stored view's filters, held to the clause shape.\n *\n * Strict about what it keeps: a clause with no field or no operator is not\n * a filter, and a valued operator with no value would read as a filter\n * that matches nothing — so both are dropped rather than kept, for the same\n * reason the dynamic-list rule drops an empty branch: a view must not\n * quietly select a different population than the one it reads as.\n */\nexport function normalizeCrmViewFilters(value: unknown): CrmViewFilterClause[] {\n if (!Array.isArray(value)) return []\n const clauses: CrmViewFilterClause[] = []\n for (const raw of value) {\n const entry = (raw ?? {}) as Record<string, unknown>\n const field = asViewText(entry['field'], 80)\n const op = asViewText(entry['op'], 40)\n const text = asViewText(entry['value'], 500)\n if (!field || !op) continue\n if (!text && !VALUELESS_VIEW_OPS.has(op)) continue\n const label = asViewText(entry['label'], 120)\n clauses.push({ field, op, value: text, ...(label ? { label } : {}) })\n if (clauses.length >= CRM_VIEW_MAX_FILTERS) break\n }\n return clauses\n}\n\nexport function normalizeCrmViewColumns(value: unknown): string[] {\n if (!Array.isArray(value)) return []\n const seen = new Set<string>()\n for (const raw of value) {\n const column = asViewText(raw, 80)\n if (column) seen.add(column)\n if (seen.size >= CRM_VIEW_MAX_COLUMNS) break\n }\n return [...seen]\n}\n\nexport function normalizeCrmViewSort(value: unknown): CrmViewSort | null {\n const entry = (value ?? null) as Record<string, unknown> | null\n const field = entry ? asViewText(entry['field'], 80) : ''\n if (!field) return null\n return { field, direction: entry?.['direction'] === 'desc' ? 'desc' : 'asc' }\n}\n\n/** A stored document's three list-facing fields, held to their shapes. */\nexport function normalizeCrmViewState(value: unknown): CrmViewState {\n const entry = (value ?? {}) as Record<string, unknown>\n return {\n filters: normalizeCrmViewFilters(entry['filters']),\n columns: normalizeCrmViewColumns(entry['columns']),\n sort: normalizeCrmViewSort(entry['sort']),\n }\n}\n\n/**\n * Whether two states describe the same list — what \"unsaved changes\" means.\n *\n * Clause order matters (a list applies them in order, and the first one the\n * query can serve is the one it serves); the label on a clause does not,\n * because nothing matches on it.\n */\nexport function crmViewStateEquals(a: CrmViewState, b: CrmViewState): boolean {\n if (a.filters.length !== b.filters.length) return false\n for (let index = 0; index < a.filters.length; index += 1) {\n const left = a.filters[index]\n const right = b.filters[index]\n if (\n left.field !== right.field ||\n left.op !== right.op ||\n left.value !== right.value\n ) {\n return false\n }\n }\n if (a.columns.length !== b.columns.length) return false\n if (a.columns.some((column, index) => column !== b.columns[index])) {\n return false\n }\n if (!a.sort || !b.sort) return a.sort === b.sort\n return a.sort.field === b.sort.field && a.sort.direction === b.sort.direction\n}\n\n/**\n * Whether a view belongs in this reader's menu: their own, or one somebody\n * shared. The rules admit the read either way — see the module note — so\n * this is what keeps one person's private arrangement out of another's\n * list.\n */\nexport function crmViewIsListed(\n view: Pick<CrmSavedView, 'shared' | 'ownerUid'>,\n uid: string | null | undefined,\n): boolean {\n return view.shared === true || (Boolean(uid) && view.ownerUid === uid)\n}\n\n/**\n * The Contacts list's filterable fields, by the names a view clause carries.\n *\n * A contract between three readers that cannot import one another: the\n * list's own filter grammar (which declares these as its columns), a saved\n * view (which stores them), and the dynamic-list translator below in\n * `dynamic-list-rule.ts` (which turns them into audience dimensions). Named\n * once so a rename on the list cannot silently stop an audience matching.\n * `custom` is a PREFIX: a custom field's column is the prefix and its key.\n */\nexport const CRM_CONTACT_VIEW_FIELDS = {\n tags: 'tags',\n source: 'source',\n owner: 'ownerUid',\n stage: 'lifecycleStage',\n company: 'companyId',\n createdAt: 'createdAt',\n updatedAt: 'updatedAt',\n orders: 'ordersCount',\n ltv: 'ltvCents',\n custom: 'custom_',\n} as const\n\n/** The column a custom contact field filters and shows as. */\nexport const crmContactCustomColumn = (key: string): string =>\n `${CRM_CONTACT_VIEW_FIELDS.custom}${key}`\n\n/** The field key a custom column stands for, or `null` for any other column. */\nexport function crmContactCustomKey(column: string): string | null {\n const prefix = CRM_CONTACT_VIEW_FIELDS.custom\n return column.startsWith(prefix) && column.length > prefix.length\n ? column.slice(prefix.length)\n : null\n}\n\n/**\n * A stored phone number as the `href` of a click-to-call link, or `null`\n * when the value is not one (AGL-2661).\n *\n * The console stores E.164 (`normalizePhone` runs before every write), and\n * a `tel:` URL wants exactly that: digits with a leading `+`, nothing else.\n * Spaces, dots, dashes and parentheses are dropped rather than refused\n * because a value written before normalization existed may carry them;\n * anything that is not a run of digits after that — a word, an extension\n * typed as `x123` — is refused, because a dialer handed it would ring\n * nothing and the link would be a lie.\n */\nexport function crmTelHref(phone: unknown): string | null {\n const text = String(phone ?? '').trim()\n if (!text) return null\n const compact = text.replace(/[\\s().-]/g, '')\n return /^\\+?\\d{3,20}$/.test(compact) ? `tel:${compact}` : null\n}\n\nconst CRM_VIEW_TAGS_FIELD = CRM_CONTACT_VIEW_FIELDS.tags\nconst CRM_VIEW_SOURCE_FIELD = CRM_CONTACT_VIEW_FIELDS.source\n\n/**\n * A saved segment, as the filters of a contacts view.\n *\n * A segment is \"any of these tags AND any of these sources\", so each\n * becomes one `isAnyOf` clause — OR within, AND across, which is the\n * reading `contactMatchesSegment` gives it. A single tag is emitted as\n * `contains` instead: that operator is the one the contacts query can serve\n * from the index, so a one-tag segment opened as a view reaches the whole\n * collection rather than the loaded window. The values are the segment's\n * own, joined the way the grammar's `isAnyOf` splits them.\n */\nexport function crmViewFiltersFromSegment(\n segment: Pick<ContactSegment, 'tags' | 'sources'>,\n): CrmViewFilterClause[] {\n const tags = (segment.tags ?? []).map((tag) => tag.trim()).filter(Boolean)\n const sources = (segment.sources ?? []).filter(\n (source) => source in CONTACT_SOURCE_LABELS,\n )\n return [\n ...(tags.length === 1\n ? [{ field: CRM_VIEW_TAGS_FIELD, op: 'contains', value: tags[0] }]\n : tags.length\n ? [{ field: CRM_VIEW_TAGS_FIELD, op: 'isAnyOf', value: tags.join(',') }]\n : []),\n ...(sources.length === 1\n ? [{ field: CRM_VIEW_SOURCE_FIELD, op: 'equals', value: sources[0] }]\n : sources.length\n ? [\n {\n field: CRM_VIEW_SOURCE_FIELD,\n op: 'isAnyOf',\n value: sources.join(','),\n },\n ]\n : []),\n ]\n}\n\n/**\n * The other direction: the segment a view's filters describe, or `null`\n * when the view carries no tag or source clause a segment could hold.\n *\n * Only the two dimensions a segment has are read, and only through the\n * operators that mean \"has one of these\"; a `name startsWith` or an owner\n * clause is not a segment's to keep. What comes back is what \"Save as\n * segment\" writes, so a campaign audience built from it selects exactly the\n * tags and sources the reader could see on the chips.\n */\nexport function crmViewSegmentFilters(\n filters: readonly CrmViewFilterClause[],\n): Pick<ContactSegment, 'tags' | 'sources'> | null {\n const tags = new Set<string>()\n const sources = new Set<ContactSource>()\n const split = (value: string) =>\n value\n .split(',')\n .map((entry) => entry.trim())\n .filter(Boolean)\n for (const clause of filters) {\n if (clause.field === CRM_VIEW_TAGS_FIELD) {\n if (clause.op === 'contains') tags.add(clause.value.trim().toLowerCase())\n else if (clause.op === 'isAnyOf') {\n for (const tag of split(clause.value)) tags.add(tag.toLowerCase())\n }\n } else if (clause.field === CRM_VIEW_SOURCE_FIELD) {\n const values = clause.op === 'equals' ? [clause.value.trim()] : clause.op === 'isAnyOf' ? split(clause.value) : []\n for (const source of values) {\n if (source in CONTACT_SOURCE_LABELS) sources.add(source as ContactSource)\n }\n }\n }\n tags.delete('')\n if (!tags.size && !sources.size) return null\n return {\n ...(tags.size ? { tags: [...tags] } : {}),\n ...(sources.size ? { sources: [...sources] } : {}),\n }\n}\n\n/**\n * `users/{uid}.crmDefaultViews` — `{ [orgId]: { [section]: viewId } }`.\n *\n * On the reader's own profile document, beside `notificationPrefs`, because\n * it is the same kind of fact: how one person wants their console to\n * behave, owned and written by them alone. Keyed by org first because one\n * account sits on several organizations and a view id is only meaningful\n * inside the one that holds it.\n */\nexport const CRM_DEFAULT_VIEWS_FIELD = 'crmDefaultViews'\n\n/** The view a section opens on for this reader in this org, or none. */\nexport function crmDefaultViewId(\n profile: Record<string, unknown> | null | undefined,\n orgId: string | null | undefined,\n section: CrmViewSection,\n): string | null {\n if (!profile || !orgId) return null\n const byOrg = profile[CRM_DEFAULT_VIEWS_FIELD]\n if (!byOrg || typeof byOrg !== 'object' || Array.isArray(byOrg)) return null\n const bySection = (byOrg as Record<string, unknown>)[orgId]\n if (!bySection || typeof bySection !== 'object' || Array.isArray(bySection)) {\n return null\n }\n const viewId = (bySection as Record<string, unknown>)[section]\n return typeof viewId === 'string' && viewId.trim() ? viewId : null\n}\n\n/**\n * The merge patch that sets — or, with `null`, clears — one default.\n *\n * Shaped for a merged write so the other organizations' and sections'\n * defaults on the same document survive it; `null` rather than a delete\n * sentinel because this module carries no Firestore, and the reader above\n * treats a null as no default.\n */\nexport function crmDefaultViewPatch(\n orgId: string,\n section: CrmViewSection,\n viewId: string | null,\n): Record<string, unknown> {\n return { [CRM_DEFAULT_VIEWS_FIELD]: { [orgId]: { [section]: viewId } } }\n}\n\n/*==========================================\n * FILES ON A RECORD (AGL-2662)\n *=========================================*/\n\n/** The field a record's attached files are stored under, on all three. */\nexport const CRM_MEDIA_IDS_FIELD = 'mediaIds'\n\n/**\n * The most files one record may carry.\n *\n * A platform ceiling in the family of {@link CRM_ACTIVITIES_PER_RECORD_CEILING}\n * rather than a plan dimension: attachments are bounded by human effort, and\n * this is the bound. It is also what the Firestore rules can actually check\n * — a rule can assert a list and its size, and cannot walk one — so the\n * number is enforced rather than merely documented.\n */\nexport const CRM_MEDIA_IDS_MAX = 20\n\n/**\n * A stored or submitted attachment list, cleaned.\n *\n * Ids rather than URLs, deduplicated, trimmed, non-empty and capped. Written\n * through by every path that sets the field — the console card, the REST\n * write — so a record cannot hold a list one of them would refuse.\n *\n * An id is a media DOCUMENT id, resolved against the organization's library\n * (`orgs/{orgId}/media/{mediaId}`) at read time. Storing the id and not the\n * URL is what lets a file move between folders, and what keeps a private\n * asset behind the signed CDN door rather than pinned to a raw storage URL\n * that names its current location.\n */\nexport function normalizeCrmMediaIds(value: unknown): string[] {\n if (!Array.isArray(value)) return []\n return [\n ...new Set(\n value\n .map((id) => String(id ?? '').trim())\n .filter((id) => id.length > 0 && id.length <= 200),\n ),\n ].slice(0, CRM_MEDIA_IDS_MAX)\n}\n\n/*==========================================\n * WHAT A CRM LIST QUERIES BY (AGL-3321).\n *\n * Every filter and every search word on the Leads, Contacts, Companies,\n * Deals and Tasks lists is a predicate on the list's Firestore query, and a\n * query can only ask about a field a WRITER stored in the shape the query\n * asks for. Three kinds of field exist for that and for nothing else:\n *\n * `searchTokens` word prefixes of what the list's search box reads\n * (a lead's name, address, company, title and tags;\n * a deal's title), for `array-contains` on one typed\n * word — see `nameSearchTokens`.\n * `scopedSearchTokens` the same tokens behind each of the record's\n * `visibleTo` tokens (`host:abc~acme`). A list under\n * a site already spends its one array clause on\n * `visibleTo array-contains-any`, so its search asks\n * `array-contains-any` over the site's tokens joined\n * to the word — one clause answering both.\n * keys a normalized scalar where the stored value cannot\n * be asked as it is: a lead's status (absent on a\n * capture), its lead source (compared the way the\n * picklist compares labels), a verdict on the\n * address (`none` when there is none) and a contact's\n * per-holder facet values (`facetKeys`).\n *\n * All of them are DERIVED: a pure function of the document — save a lead's\n * `leadSourceDirection`, which is the document and the org's lead source\n * list (see `crmLeadSourceDirection`) — computed here and nowhere else,\n * and written by every path that writes one of its inputs — see\n * `CRM_LIST_FIELD_INPUTS` — so a record a list cannot find is a writer\n * that forgot, never a second formula that disagrees. A server writer that\n * holds the whole document spreads `crmListFields`; one that patched a field\n * restamps the record from what was stored (`restampCrmListFields` in the\n * admin library); the one-time backfill (`backfill-crm-list-fields.mjs`)\n * restates them for a plain Node script and is held to the same worked\n * examples (`tools/scripts/lib/org-record-list-fields.fixtures.json`), which this\n * library's spec asserts too.\n *=========================================*/\n\n/** The search box's word-prefix tokens, on every CRM record a list searches. */\nexport const CRM_SEARCH_TOKENS_FIELD = 'searchTokens'\n/** The same tokens behind each `visibleTo` token — see the block header. */\nexport const CRM_SCOPED_SEARCH_TOKENS_FIELD = 'scopedSearchTokens'\n/** What joins a scope token to a search token — the platform's one join (`SCOPED_SEARCH_JOIN`). */\nexport const CRM_SCOPED_SEARCH_JOIN = SCOPED_SEARCH_JOIN\n/**\n * The most search tokens one record keeps. A name, an address, a company\n * and a title are a few dozen prefixes; the cap is what keeps a record with\n * twenty long tags from spending hundreds of index entries on them.\n */\nexport const CRM_SEARCH_TOKENS_MAX = 200\n\n/** The collections whose lists query the fields below. */\nexport type CrmListCollection = 'leads' | 'contacts' | 'companies' | 'deals' | 'crmTasks'\n\nconst SEARCH_KEY = nameSearchKey\n\n/**\n * The word-prefix tokens of several values, merged: every prefix (up to\n * twelve characters) of every whitespace-separated word, lower-cased —\n * exactly `nameSearchTokens` per value — plus, for an email address, the\n * words it is made of, so `acme` finds `dana@acme.com` and `dana` does too.\n */\nexport function crmSearchTokens(values: readonly unknown[]): string[] {\n const tokens = new Set<string>()\n const addWord = (word: string) => {\n const capped = word.slice(0, NAME_TOKEN_MAX_PREFIX)\n for (let end = 1; end <= capped.length; end += 1) {\n if (tokens.size >= CRM_SEARCH_TOKENS_MAX) return\n tokens.add(capped.slice(0, end))\n }\n }\n const addText = (text: string) => {\n const key = SEARCH_KEY(text)\n if (!key) return\n for (const word of key.split(' ')) {\n if (!word) continue\n addWord(word)\n // An address, a domain or a hyphenated name is also its parts.\n if (/[@.+_-]/.test(word)) {\n for (const part of word.split(/[@.+_-]+/)) if (part) addWord(part)\n }\n }\n }\n for (const value of values) {\n if (Array.isArray(value)) {\n for (const entry of value) if (typeof entry === 'string') addText(entry)\n } else if (typeof value === 'string') {\n addText(value)\n }\n }\n return [...tokens]\n}\n\n/**\n * The two search fields for a record whose search reads `values`: the\n * tokens, and the same tokens behind each of its scope tokens through the\n * platform's one scoped writer (`scopedSearchTokens`).\n */\nexport function crmSearchFields(\n visibleTo: unknown,\n values: readonly unknown[],\n): { searchTokens: string[]; scopedSearchTokens: string[] } {\n const searchTokens = crmSearchTokens(values)\n return { searchTokens, scopedSearchTokens: scopedSearchTokens(visibleTo, searchTokens) }\n}\n\n/**\n * The address verdict as a list asks it (AGL-3245): the state's status, or\n * `none` when nothing is known — stored rather than left absent, because a\n * query cannot find a field's absence and \"Nothing known\" is a filter.\n */\nexport const CRM_EMAIL_STATUS_FIELD = 'emailStatus'\nexport const CRM_EMAIL_STATUS_NONE = 'none'\n\nexport function crmEmailStatusKey(record: Record<string, unknown> | null | undefined): string {\n const raw = record?.['emailState']\n const status =\n raw && typeof raw === 'object' ? (raw as Record<string, unknown>)['status'] : undefined\n // A status outside the vocabulary reads as nothing known, as `readEmailState` reads it.\n return isEmailStateStatus(status) ? status : CRM_EMAIL_STATUS_NONE\n}\n\n/**\n * A picklist label as a list query compares it — the key a target's\n * `keyField` holds beside the label — `null` for none.\n */\nexport function crmPicklistKey(value: unknown): string | null {\n const key = SEARCH_KEY(normalizeCrmPicklistLabel(value))\n return key || null\n}\n\n/** A lead's lead source as the list compares it: the picklist's key, `null` for none. */\nexport const CRM_LEAD_SOURCE_KEY_FIELD = 'leadSourceKey'\n\nexport function crmLeadSourceKey(value: unknown): string | null {\n return crmPicklistKey(value)\n}\n\n/*------------------------------------------\n * A LEAD'S LEAD SOURCE DIRECTION (AGL-3577).\n *\n * The group of the Lead source picklist its value sits in — Inbound for a\n * person who came to the organization, Outbound for one it went to — or\n * `null` for no lead source, a value in no group, or a label the list does\n * not hold. STORED on the lead as `leadSourceDirection`, so the Leads list\n * asks `leadSourceDirection == inbound` — one equality, however many values\n * the group holds. (It was asked as `leadSourceKey in [...]` over the\n * group's values, and Inbound holds more than one `in` may.)\n *\n * The one list field that is not a function of the record alone: it is\n * read off the org's list. So a writer passes that list\n * ({@link CrmListFieldsContext}) or, without it, writes the direction only\n * where it needs no list — `null` for a lead with no lead source — and\n * leaves a held value's direction as stored. Every move of the list that\n * can change a label's group rewrites the leads holding it (the\n * `groupField` of the picklist's lead target).\n *-----------------------------------------*/\n\nexport const CRM_LEAD_SOURCE_DIRECTION_FIELD = 'leadSourceDirection'\n\n/** The directions a lead source can have: the Lead source picklist's groups. */\nexport const CRM_LEAD_SOURCE_DIRECTIONS = ['inbound', 'outbound'] as const\n\nexport type CrmLeadSourceDirection = (typeof CRM_LEAD_SOURCE_DIRECTIONS)[number]\n\nexport function isCrmLeadSourceDirection(value: unknown): value is CrmLeadSourceDirection {\n return (CRM_LEAD_SOURCE_DIRECTIONS as readonly unknown[]).includes(value)\n}\n\n/** The direction `label` has in the org's lead source list — `null` for none. */\nexport function crmLeadSourceDirection(\n leadSources: PicklistValueSet,\n label: unknown,\n): CrmLeadSourceDirection | null {\n const group = picklistValueByLabel(leadSources, label)?.group\n return isCrmLeadSourceDirection(group) ? group : null\n}\n\n/** What a list-field writer knows beyond the record. */\nexport interface CrmListFieldsContext {\n /**\n * The org's Lead source list as every reader takes it\n * (`effectiveCrmLeadSourcePicklist`), which a lead's direction is read\n * off. Without it a lead holding a lead source keeps the direction it has.\n */\n leadSources?: PicklistValueSet | null\n}\n\n/** A lead's direction field — see the block above. */\nfunction leadSourceDirectionField(\n lead: Record<string, unknown>,\n context: CrmListFieldsContext,\n): { leadSourceDirection?: CrmLeadSourceDirection | null } {\n if (!crmLeadSourceKey(lead['leadSource'])) return { leadSourceDirection: null }\n return context.leadSources\n ? { leadSourceDirection: crmLeadSourceDirection(context.leadSources, lead['leadSource']) }\n : {}\n}\n\n/** What a lead's search box reads (AGL-3246). */\nexport const CRM_LEAD_SEARCH_SOURCES = ['name', 'email', 'company', 'jobTitle', 'tags'] as const\n\n/**\n * A lead's campaigns behind each of its `visibleTo` tokens\n * (`host:abc~{campaignId}`), for the Leads list's Campaign filter under a\n * site: there the scope clause is the query's one array clause, and a\n * campaign asked of these keys answers both at once — the way the search\n * folds into the scope through `scopedSearchTokens`.\n */\nexport const CRM_LEAD_SCOPED_CAMPAIGNS_FIELD = 'scopedCampaignIds'\n\nfunction leadCampaignIds(lead: Record<string, unknown>): string[] {\n const raw = lead['campaignIds']\n return Array.isArray(raw)\n ? raw.filter((entry): entry is string => typeof entry === 'string' && entry.length > 0)\n : []\n}\n\n/**\n * Every field the Leads list queries by, from the lead as stored (or as it\n * will be once written): the search tokens, its campaigns behind its scope,\n * the status — `new` for a lead nobody has touched, written so `status in\n * [new, working]` finds it — the lead source key and the address verdict.\n */\nexport function crmLeadListFields(\n record: object,\n context: CrmListFieldsContext = {},\n): {\n searchTokens: string[]\n scopedSearchTokens: string[]\n scopedCampaignIds: string[]\n status: CrmLeadStatus\n leadSourceKey: string | null\n /** Absent when the lead holds a lead source and no list was given. */\n leadSourceDirection?: CrmLeadSourceDirection | null\n emailStatus: string\n industryKey: string | null\n ratingKey: string | null\n} {\n const lead = record as Record<string, unknown>\n return {\n ...crmSearchFields(\n lead['visibleTo'],\n CRM_LEAD_SEARCH_SOURCES.map((field) => lead[field]),\n ),\n scopedCampaignIds: scopedSearchTokens(lead['visibleTo'], leadCampaignIds(lead)),\n status: crmLeadStatus(lead as Pick<CrmLeadFields, 'status'>),\n leadSourceKey: crmLeadSourceKey(lead['leadSource']),\n // Its group in the org's list (AGL-3577).\n ...leadSourceDirectionField(lead, context),\n emailStatus: crmEmailStatusKey(lead),\n // The Industry and Rating the Leads list filters by (AGL-3513).\n industryKey: crmPicklistKey(lead['industry']),\n ratingKey: crmPicklistKey(lead['rating']),\n }\n}\n\n/** What a company's search box reads: its name and its domain. */\nexport const CRM_COMPANY_SEARCH_SOURCES = ['name', 'domain'] as const\n\n/**\n * Every field the Companies list queries by: the search tokens, and the\n * key of each picklist field that names one (AGL-3514) — `null` for none,\n * so \"no value\" is a value a query can ask for.\n */\nexport function crmCompanyListFields(record: object): {\n searchTokens: string[]\n scopedSearchTokens: string[]\n typeKey: string | null\n industryKey: string | null\n ratingKey: string | null\n accountSourceKey: string | null\n} {\n const company = record as Record<string, unknown>\n return {\n ...crmSearchFields(\n company['visibleTo'],\n CRM_COMPANY_SEARCH_SOURCES.map((field) => company[field]),\n ),\n typeKey: crmPicklistKey(company['type']),\n industryKey: crmPicklistKey(company['industry']),\n ratingKey: crmPicklistKey(company['rating']),\n accountSourceKey: crmPicklistKey(company['accountSource']),\n }\n}\n\n/**\n * What a deal's search box reads: its title (AGL-3315) — as word prefixes,\n * and as `titleLower`, the whole title's key, which a reader whose access\n * is some sites searches by its start. And the keys the Deals list filters\n * its Type and Lead source by (AGL-3516): each label as the picklist\n * compares it, `null` for none.\n */\nexport function crmDealListFields(record: object): {\n searchTokens: string[]\n scopedSearchTokens: string[]\n titleLower: string\n typeKey: string | null\n leadSourceKey: string | null\n contactRoleContactIds: string[]\n scopedContactRoleContactIds: string[]\n contactRoleKeys: string[]\n} {\n const deal = record as Record<string, unknown>\n return {\n ...crmSearchFields(deal['visibleTo'], [deal['title']]),\n titleLower: SEARCH_KEY(typeof deal['title'] === 'string' ? deal['title'] : ''),\n typeKey: crmPicklistKey(deal['type']),\n leadSourceKey: crmPicklistKey(deal['leadSource']),\n ...crmDealContactRoleListFields(deal),\n }\n}\n\n/**\n * The arrays a deal's contact roles are found by (AGL-3521): every contact\n * on it — `contactRoleContactIds`, which a contact's page asks\n * `array-contains` at the organization level, and the same ids behind each\n * scope token (`scopedContactRoleContactIds`, `host:x~contactId`), which it\n * asks under a site in the one array clause a query has — and the keys of\n * the roles they hold, which a rename of a role finds the deals by.\n */\nexport function crmDealContactRoleListFields(deal: Record<string, unknown>): {\n contactRoleContactIds: string[]\n scopedContactRoleContactIds: string[]\n contactRoleKeys: string[]\n} {\n const roles = dealContactRolesOf(deal)\n const contactRoleContactIds = roles.map((row) => row.contactId)\n const keys = new Set<string>()\n for (const row of roles) {\n const key = crmPicklistKey(row.role)\n if (key) keys.add(key)\n }\n return {\n contactRoleContactIds,\n scopedContactRoleContactIds: scopedSearchTokens(deal['visibleTo'], contactRoleContactIds),\n contactRoleKeys: [...keys],\n }\n}\n\n/** What a task's search box reads: its title. */\nexport function crmTaskListFields(record: object): {\n searchTokens: string[]\n scopedSearchTokens: string[]\n} {\n const task = record as Record<string, unknown>\n return crmSearchFields(task['visibleTo'], [task['title']])\n}\n\n/*------------------------------------------\n * A CONTACT'S FACET KEYS.\n *\n * An owner, a stage, a source, a company, a tag and a custom value live on\n * a holder's facet — `facets.{groupId}` — so a `where` on one would be a path\n * per holder and an index per holder. `facetKeys` flattens them into one\n * array a query can ask `array-contains` of:\n *\n * `{groupId}:{field}={value}` this holder's value\n * `{groupId}:{field}` this holder has one (for \"is set\")\n * `*:{field}={value}`, `*:{field}` any holder's, for the organization level\n *\n * `field` is `owner`, `stage`, `source`, `company`, `tag`, `custom.{key}` or\n * `leadSource` (AGL-3511), plus two presence-only fields: `orders` for a holder the person has bought\n * from, and `ltv` for one they are worth something to — the per-holder\n * figures a range cannot reach, asked only whether there are any.\n * A text value is keyed lower-cased and single-spaced; a number or a flag\n * as its string. Under a site the viewing group's keys are asked; at the\n * organization level, where no group is viewing, the `*` keys are.\n *-----------------------------------------*/\n\nexport const CRM_CONTACT_FACET_KEYS_FIELD = 'facetKeys'\n/** The group a key names at the organization level: any holder. */\nexport const CRM_FACET_KEY_ANY_GROUP = '*'\n/** The most facet keys one contact keeps. */\nexport const CRM_FACET_KEYS_MAX = 600\n\n/** The facet fields a contact list filters by, as their keys name them. */\nexport type CrmFacetKeyField =\n | 'owner'\n | 'stage'\n | 'source'\n | 'company'\n | 'tag'\n | 'orders'\n | 'ltv'\n | `custom.${string}`\n // A holder's lead source, by its label (AGL-3511).\n | 'leadSource'\n\n/** A facet value as its key spells it, or `null` for a value a key cannot hold. */\nexport function crmFacetKeyValue(value: unknown): string | null {\n if (typeof value === 'boolean') return value ? 'true' : 'false'\n if (typeof value === 'number') return Number.isFinite(value) ? String(value) : null\n if (typeof value !== 'string') return null\n const key = SEARCH_KEY(value).slice(0, 120)\n return key || null\n}\n\n/** One facet key: `{group}:{field}` for presence, `{group}:{field}={value}` for a value. */\nexport function crmFacetKey(group: string, field: CrmFacetKeyField, value?: string): string {\n return value === undefined ? `${group}:${field}` : `${group}:${field}=${value}`\n}\n\n/** The keys one holder's facet contributes, under `group`. */\nfunction facetKeysOf(group: string, facet: Record<string, unknown>, into: Set<string>): void {\n const add = (field: CrmFacetKeyField, raw: unknown) => {\n const value = crmFacetKeyValue(raw)\n if (value === null) return\n into.add(crmFacetKey(group, field))\n into.add(crmFacetKey(group, field, value))\n }\n add('owner', facet['ownerUid'])\n if (isContactLifecycleStage(facet['lifecycleStage'])) add('stage', facet['lifecycleStage'])\n add('company', facet['companyId'])\n const sources = facet['sources']\n if (sources && typeof sources === 'object' && !Array.isArray(sources)) {\n for (const [source, on] of Object.entries(sources as Record<string, unknown>)) {\n if (on) add('source', source)\n }\n }\n if (Array.isArray(facet['tags'])) {\n for (const tag of facet['tags']) add('tag', tag)\n }\n // Presence only: a holder's commercial figures are asked \"any?\", never ranged.\n if (typeof facet['ordersCount'] === 'number' && facet['ordersCount'] > 0) {\n into.add(crmFacetKey(group, 'orders'))\n }\n if (typeof facet['ltvCents'] === 'number' && facet['ltvCents'] > 0) {\n into.add(crmFacetKey(group, 'ltv'))\n }\n const custom = facet['custom']\n if (custom && typeof custom === 'object' && !Array.isArray(custom)) {\n for (const [key, value] of Object.entries(custom as Record<string, unknown>)) {\n if (/^[A-Za-z0-9_-]{1,64}$/.test(key)) add(`custom.${key}`, value)\n }\n }\n // Keyed as `crmPicklistKey` compares a label, so a rename's rewrite restamps it.\n add('leadSource', facet['leadSource'])\n}\n\n/** Every facet key a contact carries — see the block above. */\nexport function crmContactFacetKeys(record: object): string[] {\n const facets = (record as Record<string, unknown>)[CONTACT_FACETS_FIELD]\n if (!facets || typeof facets !== 'object' || Array.isArray(facets)) return []\n const groups = new Set<string>()\n const any = new Set<string>()\n for (const [groupId, facet] of Object.entries(facets as Record<string, unknown>)) {\n if (!groupId || !facet || typeof facet !== 'object' || Array.isArray(facet)) continue\n facetKeysOf(groupId, facet as Record<string, unknown>, groups)\n facetKeysOf(CRM_FACET_KEY_ANY_GROUP, facet as Record<string, unknown>, any)\n }\n return [...any, ...groups].slice(0, CRM_FACET_KEYS_MAX)\n}\n\n/**\n * What a contact's search box reads: the shared identity — the canonical\n * name and every address — and the two profile values a person is looked\n * up by, through their top-level search echoes (`HostContact.phone`,\n * `HostContact.companyName`): the company's words, and the phone's digits.\n */\nexport const CRM_CONTACT_SEARCH_SOURCES = [\n 'name',\n 'email',\n CONTACT_ALTERNATE_EMAILS_FIELD,\n 'companyName',\n] as const\n\n/**\n * A phone number as the words a search box finds it by: its digits whole,\n * without a leading country code of one to three digits, and its last seven\n * and last four — so `5551234`, `555123` and `4567` all find +1 555 123 4567.\n * Typed as digits: a search reads one word, and `(555)` is not one of these.\n */\nexport function crmPhoneSearchWords(phone: unknown): string[] {\n const digits = typeof phone === 'string' ? phone.replace(/\\D/g, '') : ''\n if (digits.length < 4) return []\n const words = new Set<string>([digits])\n for (let strip = 1; strip <= 3; strip += 1) {\n if (digits.length - strip >= 7) words.add(digits.slice(strip))\n }\n if (digits.length > 7) words.add(digits.slice(-7))\n words.add(digits.slice(-4))\n return [...words]\n}\n\nexport function crmContactListFields(record: object): {\n searchTokens: string[]\n scopedSearchTokens: string[]\n facetKeys: string[]\n emailStatus: string\n} {\n const contact = record as Record<string, unknown>\n return {\n ...crmSearchFields(contact['visibleTo'], [\n ...CRM_CONTACT_SEARCH_SOURCES.map((field) => contact[field]),\n crmPhoneSearchWords(contact['phone']),\n ]),\n facetKeys: crmContactFacetKeys(contact),\n emailStatus: crmEmailStatusKey(contact),\n }\n}\n\n/**\n * The stored fields each collection's list fields are computed from. A\n * write that sets one of them must restamp the record — or spread\n * {@link crmListFields} over the document it wrote.\n */\nexport const CRM_LIST_FIELD_INPUTS: Readonly<Record<CrmListCollection, readonly string[]>> = {\n leads: [\n 'visibleTo',\n ...CRM_LEAD_SEARCH_SOURCES,\n 'status',\n 'leadSource',\n 'emailState',\n 'campaignIds',\n // AGL-3513.\n 'industry',\n 'rating',\n ],\n contacts: ['visibleTo', ...CRM_CONTACT_SEARCH_SOURCES, 'phone', CONTACT_FACETS_FIELD, 'emailState'],\n companies: ['visibleTo', ...CRM_COMPANY_SEARCH_SOURCES, 'type', 'industry', 'rating', 'accountSource'],\n deals: ['visibleTo', 'title', 'type', 'leadSource', 'contactRoles', 'contactId'],\n crmTasks: ['visibleTo', 'title'],\n}\n\n/**\n * Every list field of one record, from the record as stored — and, for a\n * lead's direction, the org's lead source list when `context` holds it.\n */\nexport function crmListFields(\n collection: CrmListCollection,\n record: object,\n context: CrmListFieldsContext = {},\n): Record<string, unknown> {\n switch (collection) {\n case 'leads':\n return crmLeadListFields(record, context)\n case 'contacts':\n return crmContactListFields(record)\n case 'companies':\n return crmCompanyListFields(record)\n case 'deals':\n return crmDealListFields(record)\n case 'crmTasks':\n return crmTaskListFields(record)\n }\n}\n\nconst sameValue = (a: unknown, b: unknown): boolean => {\n if (Array.isArray(a) && Array.isArray(b)) {\n return a.length === b.length && a.every((entry, at) => entry === b[at])\n }\n return a === b\n}\n\n/**\n * The list fields a stored record carries WRONGLY — the patch that brings\n * it level, `{}` when it already is. What a restamp and a backfill write.\n */\nexport function crmListFieldsPatch(\n collection: CrmListCollection,\n record: object,\n context: CrmListFieldsContext = {},\n): Record<string, unknown> {\n const fields = crmListFields(collection, record, context)\n const stored = record as Record<string, unknown>\n const patch: Record<string, unknown> = {}\n for (const [field, value] of Object.entries(fields)) {\n if (!sameValue(stored[field], value)) patch[field] = value\n }\n return patch\n}\n\n/**\n * The field each collection's list ORDERS or asks `null` of, which a record\n * must therefore carry even when it has no value: a contact, a company and a\n * deal carry `nextTaskAtMs` (\"No next activity\" asks `== null`), and a task\n * carries `dueAtMs` (every task view orders by it, and Firestore leaves a\n * document without the ordered field out of the answer). An absent field is\n * neither `null` nor anything else a query can ask for.\n */\nexport const CRM_LIST_NULLABLE_FIELD: Readonly<Record<CrmListCollection, string | null>> = {\n leads: null,\n contacts: 'nextTaskAtMs',\n companies: 'nextTaskAtMs',\n deals: 'nextTaskAtMs',\n crmTasks: 'dueAtMs',\n}\n\n/**\n * The list fields a NEW record is written with: {@link crmListFields}, and\n * the collection's {@link CRM_LIST_NULLABLE_FIELD} as `null` when the record\n * does not set it — nothing is scheduled against a record that did not\n * exist, and a task created undated is still one its views must reach.\n */\nexport function crmNewRecordListFields(\n collection: CrmListCollection,\n record: object,\n context: CrmListFieldsContext = {},\n): Record<string, unknown> {\n const fields = crmListFields(collection, record, context)\n const nullable = CRM_LIST_NULLABLE_FIELD[collection]\n const stored = (record as Record<string, unknown>)[nullable ?? '']\n if (!nullable || (nullable in record && stored !== undefined)) return fields\n return { ...fields, [nullable]: null }\n}\n\n/** Whether a patch touches a field the collection's list fields are computed from. */\nexport function crmListFieldsTouched(\n collection: CrmListCollection,\n patch: Record<string, unknown>,\n): boolean {\n const inputs = CRM_LIST_FIELD_INPUTS[collection]\n return Object.keys(patch).some((key) =>\n inputs.some((input) => key === input || key.startsWith(`${input}.`)),\n )\n}\n"],"names":["normalizeAddress","normalizePhone","consentGroupScope","CONTACT_LIFECYCLE_STAGES","CRM_TASK_KINDS","isContactLifecycleStage","CONTACT_ALTERNATE_EMAILS_FIELD","CONTACT_FACETS_FIELD","CONTACT_SOURCE_LABELS","composeContactName","normalizeContactEmail","readContactFacet","isEmailStateStatus","NAME_TOKEN_MAX_PREFIX","nameSearchKey","nameSearchTokens","SCOPED_SEARCH_JOIN","scopedSearchTokens","effectivePicklistValueSet","isStandardPicklistValueId","judgePicklistValue","mintPicklistValueId","normalizePicklistLabel","normalizePicklistValueSet","PICKLIST_VALUES_MAX","picklistActiveValues","picklistDefaultLabel","picklistFromLabels","picklistOptions","picklistRank","picklistRefusalSentence","picklistValueByLabel","MAX_SCOPE_HOSTS","ORG_SCOPE_TOKEN","CRM_COLLECTIONS","companies","pipelines","deals","tasks","activities","contactFields","views","emailTemplates","picklists","CRM_RECORD_COLLECTIONS","CRM_RECORDS_BAND_FULL_MESSAGE","CRM_ACTIVITIES_PER_RECORD_CEILING","CRM_ACTIVITY_LOG_FULL_MESSAGE","toLocaleString","crmActivityCeilingLink","link","contactId","field","id","String","companyId","dealId","leadId","crmActivityLogHasRoom","existing","count","Number","isFinite","CONTACT_LIFECYCLE_STAGE_LABELS","subscriber","lead","opportunity","customer","evangelist","other","advanceContactLifecycleStage","current","floor","held","undefined","order","indexOf","contactLifecycleStageAfterPurchase","CRM_FORECAST_CATEGORIES","CRM_FORECAST_CATEGORY_LABELS","omitted","pipeline","bestCase","commit","closed","isCrmForecastCategory","value","includes","readCrmForecastCategory","key","trim","replace","toLowerCase","find","category","dealStageForecastCategory","stage","forecastCategory","kind","dealForecastCategory","deal","status","DEAL_NEXT_STEP_MAX","readDealProbability","number","isInteger","dealProbability","own","probability","Math","min","max","dealStageMoveFields","isPipelineArchived","archivedAt","DEFAULT_DEAL_STAGES","name","DEAL_LINE_ITEMS_MAX","DEAL_LINE_ITEM_NAME_MAX","DEAL_LINE_ITEM_QUANTITY_MAX","DEAL_CONTACT_ROLES_MAX","readDealContactRoles","raw","Array","isArray","roles","seen","Set","primaryTaken","entry","row","length","has","add","role","primary","push","dealContactRolesWithPrimary","next","map","Boolean","some","unshift","slice","dealContactRolesOf","contactRoles","dealPrimaryContactId","dealContactRolesWithout","filter","dealContactRolesRepointed","from","to","merged","dealContactRoleFields","CRM_TASK_KIND_LABELS","call","email","meeting","todo","CRM_ACTIVITY_KIND_LABELS","note","activityKindHasOutcome","CRM_EMAIL_DELIVERY_STATES","CRM_EMAIL_DELIVERY_STATE_LABELS","sent","delivered","opened","clicked","bounced","complained","isCrmEmailDeliveryState","isCrmEmailDeliveryFailure","state","CRM_EMAIL_DELIVERY_RANK","nextCrmEmailDeliveryState","incoming","CRM_EMAIL_SUBJECT_MAX","CRM_EMAIL_BODY_MAX","CRM_EMAIL_CONTEXT","CRM_EMAIL_ACTIVITY_TAG","CRM_EMAIL_ORG_TAG","PROVIDER_TAG_VALUE","crmEmailDeliveryTags","input","orgId","activityId","hostId","test","CRM_ACTIVITY_DIRECTIONS","CRM_ACTIVITY_DIRECTION_LABELS","outbound","inbound","internal","crmActivityDirection","allowed","text","crmActivityKindTitle","direction","known","noun","buildCrmEmailActivity","subject","body","deliveryState","deliveryAtMs","atMs","byUid","byName","sourceActionId","visibleTo","campaignEmailSummary","counted","word","parts","deliveredAtMs","openedAtMs","openCount","clickedAtMs","clickCount","bouncedAtMs","complainedAtMs","sortableMs","NEGATIVE_INFINITY","mergeContactTimeline","interactions","campaignEmails","entries","interaction","index","type","refId","activity","$id","messageId","sentAtMs","sort","a","b","crmActivityRecordLink","record","MINUTE_MS","HOUR_MS","DAY_MS","activityTimeLabel","nowMs","elapsed","Date","toLocaleDateString","CRM_FIELD_OBJECTS","CRM_FIELD_OBJECT_LABELS","contact","company","isCrmFieldObject","fieldDefinitionObject","definition","object","crmFieldListFields","label","required","searchTokens","join","CONTACT_FIELD_KEY_PATTERN","normalizeContactFieldKey","dealStageById","stageId","stages","weightedDealAmountCents","amount","round","amountCents","dealHasLineItems","lineItems","lineItemsTotalCents","items","total","item","quantity","unit","unitAmountCents","readDealLineItems","currency","error","dealCurrency","line","at","lineCurrency","toUpperCase","productId","taskDueState","task","dueAtMs","due","now","sameDay","getFullYear","getMonth","getDate","DOMAIN_LABEL","normalizeCompanyDomain","split","labels","every","COMPANY_WEBSITE_MAX","normalizeCompanyWebsite","candidate","url","URL","protocol","href","CRM_COMPANY_PICKLIST_FIELDS","picklistId","keyField","CRM_COMPANY_TEXT_MAX","accountNumber","site","tickerSymbol","sicCode","CRM_COMPANY_EMPLOYEES_MAX","CRM_COMPANY_ACCOUNT_FIELDS","normalizeCompanyText","blank","readCrmCompanyAccountFields","values","errors","isSafeInteger","code","fax","judgeCrmCompanyPicklists","lists","requested","options","list","effectiveCrmPicklist","fallback","created","judged","judgeCrmPicklistValue","ok","CRM_COMPANY_PARENT_DEPTH_MAX","crmCompanyParentRefusal","parentId","read","parent","cursor","parentCompanyId","depth","crmCompanyParentId","PUBLIC_MAILBOX_DOMAINS","isPublicMailboxDomain","domainOrEmail","domain","lastIndexOf","companyDomainForEmail","companyNameForDomain","charAt","CONTACT_COMPANY_IDS_FIELD","COMPANY_CONTACTS_COUNT_FIELD","readContactCompanyLink","groupId","document","facets","heldElsewhere","holder","facet","Object","named","mirror","companyIds","contactGroupsNamingCompany","planContactCompanyLink","previous","previousLeaves","counts","change","op","delta","kept","ORG_CRM_SETTINGS_FIELD","CRM_AUTO_CREATE_COMPANIES_PATH","CRM_DEFAULT_RECORD_SCOPE_PATH","crmDefaultScopeOf","orgDocument","settings","legacy","orgAutoCreatesCompanies","CRM_ASSIGNMENT_RULES_PATH","CRM_ROUND_ROBIN_POOL_PATH","CRM_ROUND_ROBIN_LAST_ASSIGNED_PATH","CRM_ASSIGNMENT_RULES_MAX","CRM_ROUND_ROBIN_POOL_MAX","CRM_HOST_SETTINGS_KEY","crmHostDefaultOwnerSegments","Error","isContactSourceValue","cleanText","readCrmAssignmentRule","whenRaw","when","source","formId","emailDomain","tag","assignRaw","assign","roundRobin","memberUid","readCrmAssignmentSettings","crm","rules","rule","memberUids","uid","hostSettings","hosts","hostDefaultOwners","owner","pool","lastAssignedUid","crmHostDefaultOwner","assignmentEmailDomain","assignmentRuleMatches","capture","worn","tags","roundRobinOrder","describeAssignmentRule","memberLabel","newAssignmentRuleId","toString","random","crmScopeTokens","org","group","crmReadTokens","parseCrmMemberRef","crmMemberOption","member","displayName","crmMemberPickerLabel","findOrgMember","members","ref","parsed","CRM_LEAD_STATUSES","CRM_LEAD_STATUS_LABELS","new","nurturing","working","qualified","unqualified","CRM_LEAD_OPEN_STATUSES","CRM_LEAD_CLOSED_STATUSES","openLeadsFromCounts","isCrmLeadStatus","CRM_LEAD_TEXT_MAX","CRM_LEAD_TAGS_MAX","CRM_LEAD_NOTES_MAX","CRM_LEAD_PROFILE_KEYS","CRM_LEAD_PHONE_REFUSAL","CRM_LEAD_WEBSITE_REFUSAL","normalizeCrmLeadTags","normalizeCrmLeadProfile","patch","phone","doNotCall","account","digits","keys","into","website","address","crmLeadComposedName","stored","firstName","lastName","part","CRM_LEAD_PICKLIST_FIELDS","judgeCrmLeadPicklists","CRM_PICKLIST_OBJECTS","CRM_PICKLIST_OBJECT_LABELS","isCrmPicklistObject","LEAD_SOURCE_DEFINITION","plural","restricted","groups","standardValues","targets","groupField","LEAD_STATUS_DEFINITION","meanings","meaningLabels","reservedMeanings","meaning","ACCOUNT_TYPE_DEFINITION","INDUSTRY_DEFINITION","RATING_DEFINITION","OWNERSHIP_DEFINITION","SALUTATION_DEFINITION","OPPORTUNITY_TYPE_DEFINITION","OPPORTUNITY_CONTACT_ROLE_DEFINITION","arrayKey","TASK_STATUS_DEFINITION","defaultValueId","TASK_PRIORITY_DEFINITION","TASK_TYPE_DEFINITION","TASK_SUBJECT_DEFINITION","CRM_PICKLIST_DEFINITIONS","CRM_PICKLIST_IDS","CRM_LEAD_SOURCE_PICKLIST","CRM_SALUTATION_PICKLIST","CRM_OPPORTUNITY_TYPE_PICKLIST","CRM_OPPORTUNITY_CONTACT_ROLE_PICKLIST","CRM_LEAD_SOURCE_ORIGINS","form","booking","newsletter","sequence","emailCampaign","isCrmPicklistId","crmPicklistDefinition","crmPicklistDefinitionsFor","definitionOf","CRM_PICKLIST_VALUES_MAX","CRM_LEAD_SOURCE_STARTER_LABELS","normalizeCrmPicklistLabel","crmPicklistValueId","crmPicklistFromLabels","normalizeCrmPicklist","effectiveCrmLeadSourcePicklist","crmLeadSourceForOrigin","picklist","origin","hasOwn","active","isStandardCrmPicklistValue","valueId","crmPicklistValueByLabel","crmPicklistActiveValues","crmPicklistDefaultLabel","crmPicklistRefusal","empty","crmLeadSourceRefusal","refusal","judgeDealContactRoles","verdict","judgeCrmLeadSource","crmPicklistOptions","crmPicklistRank","CRM_LEAD_STATUS_PICKLIST","CRM_LEAD_STATUS_LABEL_FIELD","effectiveCrmLeadStatusPicklist","STANDARD_CRM_LEAD_STATUS_PICKLIST","crmLeadStatusValueFor","preferred","ofMeaning","crmLeadStatusLabelFor","crmLeadStatusLabel","crmLeadStatus","statusLabel","resolveCrmLeadStatusWrite","refuse","keeps","picklistLabelKeyOf","crmLeadStatusOptions","inactive","option","crmPicklistMeaningLabel","crmPicklistShownLabel","crmPicklistLabelForNew","resolveCrmSemanticPicklistWrite","byLabel","heldValue","CRM_TASK_PICKLIST_IDS","priority","effectiveCrmTaskPicklists","crmTaskPicklistLabels","priorityLabel","typeLabel","crmTaskStatusWrite","done","crmTaskLabelsForNew","crmLeadDisplayName","isCrmLeadOpen","CRM_VIEW_SECTIONS","isCrmViewSection","CRM_VIEW_NAME_MAX","CRM_VIEW_MAX_FILTERS","CRM_VIEW_MAX_COLUMNS","VALUELESS_VIEW_OPS","EMPTY_CRM_VIEW_STATE","freeze","filters","columns","asViewText","normalizeCrmViewFilters","clauses","normalizeCrmViewColumns","column","size","normalizeCrmViewSort","normalizeCrmViewState","crmViewStateEquals","left","right","crmViewIsListed","view","shared","ownerUid","CRM_CONTACT_VIEW_FIELDS","createdAt","updatedAt","orders","ltv","custom","crmContactCustomColumn","crmContactCustomKey","prefix","startsWith","crmTelHref","compact","CRM_VIEW_TAGS_FIELD","CRM_VIEW_SOURCE_FIELD","crmViewFiltersFromSegment","segment","sources","crmViewSegmentFilters","clause","delete","CRM_DEFAULT_VIEWS_FIELD","crmDefaultViewId","profile","section","byOrg","bySection","viewId","crmDefaultViewPatch","CRM_MEDIA_IDS_FIELD","CRM_MEDIA_IDS_MAX","normalizeCrmMediaIds","CRM_SEARCH_TOKENS_FIELD","CRM_SCOPED_SEARCH_TOKENS_FIELD","CRM_SCOPED_SEARCH_JOIN","CRM_SEARCH_TOKENS_MAX","SEARCH_KEY","crmSearchTokens","tokens","addWord","capped","end","addText","crmSearchFields","CRM_EMAIL_STATUS_FIELD","CRM_EMAIL_STATUS_NONE","crmEmailStatusKey","crmPicklistKey","CRM_LEAD_SOURCE_KEY_FIELD","crmLeadSourceKey","CRM_LEAD_SOURCE_DIRECTION_FIELD","CRM_LEAD_SOURCE_DIRECTIONS","isCrmLeadSourceDirection","crmLeadSourceDirection","leadSources","leadSourceDirectionField","context","leadSourceDirection","CRM_LEAD_SEARCH_SOURCES","CRM_LEAD_SCOPED_CAMPAIGNS_FIELD","leadCampaignIds","crmLeadListFields","scopedCampaignIds","leadSourceKey","emailStatus","industryKey","ratingKey","CRM_COMPANY_SEARCH_SOURCES","crmCompanyListFields","typeKey","accountSourceKey","crmDealListFields","titleLower","crmDealContactRoleListFields","contactRoleContactIds","scopedContactRoleContactIds","contactRoleKeys","crmTaskListFields","CRM_CONTACT_FACET_KEYS_FIELD","CRM_FACET_KEY_ANY_GROUP","CRM_FACET_KEYS_MAX","crmFacetKeyValue","crmFacetKey","facetKeysOf","on","crmContactFacetKeys","any","CRM_CONTACT_SEARCH_SOURCES","crmPhoneSearchWords","words","strip","crmContactListFields","facetKeys","CRM_LIST_FIELD_INPUTS","leads","contacts","crmTasks","crmListFields","collection","sameValue","crmListFieldsPatch","fields","CRM_LIST_NULLABLE_FIELD","crmNewRecordListFields","nullable","crmListFieldsTouched","inputs"],"mappings":"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"}