> ## Documentation Index
> Fetch the complete documentation index at: https://docs.myfundingmachine.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Onboarding integrations

# Onboarding integrations and agent handoff

Start here for onboarding work, after reading [VISION.md](../../VISION.md),
[AGENTS.md](../../AGENTS.md), and [CODEBASE.md](../../CODEBASE.md). This is the current
integration map. For pilot enablement, rollout order, and recovery steps see the
[onboarding runbook](onboarding-runbook.md). Engine cutover lives in
[services/onboarding-engine/docs/RUNTIME.md](../../services/onboarding-engine/docs/RUNTIME.md)
— do not duplicate it here.

Open code-review findings and the recommended fix order are tracked in the [October 2 code review](onboarding-review-2026-10-02.md) (which supersedes the [September 29 review](onboarding-review-2026-09-29.md)); check it before changing onboarding code.

**Last audited: September 29, 2026.** Source baseline: `origin/main` at
`d61ceac8`. Production evidence below came from read-only Convex configuration/table
queries and provider GETs. No settings, customer records, sessions, appointments, or
messages were changed. Recheck deployment and configuration before a rollout; a merged
feature, an enabled flag, and a completed customer journey are different evidence.

## Decided boundaries (2026-09-29)

* **Cloud sender only.** The Convex cloud runner in this repo is the only communications
  sender. The Mac mini onboarding-engine job retires (its Slack support gateway stays).
  The cutover procedure is
  [RUNTIME.md](../../services/onboarding-engine/docs/RUNTIME.md) (cloud cutover runbook;
  do not duplicate it here).
* **Stage ownership.** `reconcilePage` (`convex/crons.ts` → `convex/onboardingAdmin.ts`)
  is the only automated mover of **onboarding** stages. The live runner **never**
  executes an onboarding-pipeline `move_stage`, even when a cohort allows that kind
  (`lib/onboardingEngineLive.ts`, merged in [#1807](https://github.com/Funding-Machine/ghl-leadbuilt-marketplace-app-fundingmachine/pull/1807)).
  The engine owns messages, tasks, field stamps, retention-card creation,
  retention-pipeline moves, and `mark_won`. Native CRM workflows must stop moving
  onboarding stages. A card moved by hand is never moved back automatically.
  After [#1814](https://github.com/Funding-Machine/ghl-leadbuilt-marketplace-app-fundingmachine/pull/1814),
  W5 still sends E2 while the card is in Compliance **or** Setup In Progress, and W8
  still creates the Final QA task while it is in Setup In Progress **or** Final QA —
  so those handoffs survive when progression moves the card first.
* **Compliance checklist (progression + engine).** Shared items are **Payment Verified**,
  **Slack Joined**, and **Skool Joined**. **Slack Channel Created** is retired from the
  shared checklist (staff UI already omits it; engine `COMPLIANCE_CHECKLIST_ITEMS`
  matches after [#1814](https://github.com/Funding-Machine/ghl-leadbuilt-marketplace-app-fundingmachine/pull/1814)).
* **DND.** A send is allowed unless the CRM says DND is on. Block when `dnd === true`,
  or `dndSettings[channel].status` is `active` or `permanent`, or the contact has no
  address for that channel. Missing DND data is **not** a block. Same rule for email
  and SMS. Implemented in `lib/onboardingEngineLive.ts` (`messageAllowed`;
  [#1807](https://github.com/Funding-Machine/ghl-leadbuilt-marketplace-app-fundingmachine/pull/1807)).
  Members only waiting for permission report `waiting`, not a failed run.
* **Custom values (after [#1815](https://github.com/Funding-Machine/ghl-leadbuilt-marketplace-app-fundingmachine/pull/1815)).**
  The live runner reads location custom values from the CRM on every pass and fails
  closed on a bad read; the imported snapshot's copy of those values is never used for
  sends. CRM IDs still come from the configured snapshot.
* **FM1 / FM2 calendars.** Ops assigns FM2 a separate **FM Follow-up** calendar in
  Admin → Settings → Onboarding. Never reuse one appointment for both slots.
* **Help-center links.** Operator-page help-center URLs are kept by decision.

## Current state and next work

| Area | Verified state on September 29 | Remaining work |
| - | - | - |
| Onboarding Labs | Agency/default off; four explicit Locations enabled (below) | Keep expansion explicit |
| Automatic account linking | Enabled; one pilot Operator's Operations contact is mapped | Investigate ambiguous cases instead of selecting by name |
| Stripe Identity | Session and KYC-result write gates enabled; live account readable; signed webhook configured | Pilot Operator is ready to start, not verified; customer completes their own KYC; prove matching-name success and CRM readback |
| Calendar configuration | `onboardingSetupConfig` empty; `adminConfig` returns blank kickoff/FM1/FM2, revision 0 | Revalidate and approve/save intended mappings (FM2 on a separate Follow-up calendar), then inspect actual appointments |
| General admin CRM writes | `ONBOARDING_ADMIN_CRM_WRITES_ENABLED` unset | Review intended staff write scope before activation; KYC's separate enabled gate does not enable this |
| Automatic stage progression | Flag and Location allowlist unset | Separate rollout via `reconcilePage`; final Live transition still requires staff QA |
| Slack workspace replacement | Deployment and dedicated app configuration need approval | Automatic private channel + canonical-email member assignment; staff recover uncertain attempts |
| Cloud communications | Shadow/live flags unset, state tables empty, PIT credentials absent | No cloud sender active; follow [RUNTIME.md](../../services/onboarding-engine/docs/RUNTIME.md) for cutover |
| Mac mini engine job | Retired as a sender (decision) | Stop the job and revoke its CRM token before enabling cloud live; do not run two writers |

This document is not merge or production-write authorization.

The earlier audit below described manual kickoff channel creation. The replacement uses a dedicated support workspace, automatic private channels and free members; it is not live until its code and workspace configuration are approved. Missing Slack in the earlier audit was expected
before then; do not create a channel or start identity verification on the customer's
behalf. Agreement and card ownership review remain manual compliance duties.
The optional staff introductions section is deferred.

## Locations, environments, and entry points

These IDs are routing identifiers, not credentials. Never substitute the admin entry
Location for the Operations source or the customer's product Location.

| Role | Identifier / entry |
| - | - |
| FM agency | `TY8NPnDY2A1uP5yZ8YKJ` |
| Operations source Location | `ReoQmuh4ooNuHeE5stlj` — contacts, Onboarding opportunities, calendars |
| Admin entry Location | `HUjFTz0AbjoNOcWuS1h7` — [Funding Machine admin menu](https://app.leadbuilt.com/v2/location/HUjFTz0AbjoNOcWuS1h7/custom-menu-link/fundingmachineadmin) |
| Staff workspace | Admin → Onboarding (`/admin/onboarding`); select client → current-stage task rows; Troubleshooting for mapping and recovery |
| Calendar and Slack invitation settings | Admin → Settings → Onboarding (`/admin/settings/onboarding`); verified Never expires invitation, per-link usage baseline, distinct-member counter, capacity threshold, bot alert destination and weekly browser-check permission |
| Operator workspace | Customer Location's Onboarding & Setup (`/onboarding/setup`); no staff controls |
| Production app / `APP_URL` | `https://marketplace-app.myfundingmachine.com` |
| Production Convex | `brainy-crocodile-746`; `.convex.cloud` for data, `.convex.site` for HTTP webhooks |
| Vercel project | `ghl-marketplace-app-fundingmachine`, team `my-figures` |
| Live Stripe Identity | Account `acct_1SwTnyJIA23cYEIp`, flow `vf_1Tg8xdJIA23cYEIpEdcV0cEz` |
| Stripe sandbox | “My Funding Machine sandbox”, account `acct_1SwTo9JKxmHLR96U`; separate key, flow, webhook and backend settings |
| Onboarding pipeline | `Pfnl7aNXOFvQxzjYvrjj` |
| Retention pipeline | `M1qJVId6EtPGaXq0FGeG` |

The four enabled Labs Locations (not an automatic-progression or communications
allowlist):

| Role | Location ID |
| - | - |
| Internal / test | `oE9ILHco0XXZUu79wAA0` |
| Pilot Operator A | `Ttl6xzeFebzFEi9QMlXR` |
| Pilot Operator B | `AakcOVxyrekzvV7uCkXv` |
| Pilot Operator C | `AsrA02DtutMnKjXpseym` |

Pilot Operator B's intended Operations contact is `KvUhx6Luui3Vw3POkUuY` and Onboarding
opportunity is `C2gg7sYKYkpgym20UrZA`. An older opportunity in another pipeline is
not the onboarding destination. Revalidate these bindings before any repair; never
choose a similarly named record or infer identity solely from email/name.

## Data ownership and API map

The relationship is **product Location → agency SaaS subscription → Operations
contact/payment subscription → Stripe subscription/customer → existing Onboarding
opportunity → Location-scoped setup and Identity record**. Each edge is checked;
an ambiguous or mismatched edge blocks linking. CRM supplies business facts, Convex
stores scoped state/audit/recovery, and Stripe supplies identity verification evidence.

| Connection | Reads / writes and authentication | Implementation |
| - | - | - |
| CRM iframe → app | Session-token Bearer header; server derives actor and Location, never trusts client admin claims | `app/api/onboarding/setup/route.ts`, `app/api/prospecting/portal/route.ts`, `convex/onboardingSetup.ts` |
| Agency SaaS → Operations | Agency OAuth SaaS lookup, Location OAuth contact/payment reads; guarded Product Location ID write/readback | `convex/onboardingAccountLink.ts`, `convex/onboardingAccountLinkStore.ts`, `convex/admin/saasLookup.ts` |
| Stripe subscription evidence | Server key; account/subscription/customer/saved payment method GETs, no charge or subscription mutation | `convex/onboardingAccountLink.ts` |
| Stripe Identity | Create/resume/recover session; signed webhook reads current session/report; bound KYC field write/readback | `convex/onboardingIdentity.ts`, `convex/onboardingIdentityStore.ts`, `convex/onboardingIdentityWebhook.ts` |
| Operations calendars | Location OAuth list/contact/event/availability reads; guarded appointment changes and attendance/date synchronization | `convex/onboardingSetup.ts`, `convex/onboardingAppointments.ts`, `convex/onboardingCallStatus.ts` |
| Customer business settings/forms | Product Location OAuth reads; website, timezone, intake-form discovery | `convex/onboardingLocationLinks.ts`, `lib/onboardingTimezone.ts` |
| Phone inventory/purchase | Product Location OAuth; inventory and availability reads, separately guarded billable purchase | `convex/onboardingPhone.ts` |
| Slack | Dedicated support workspace + verified CRM mapping + canonical-email Operator membership + signed events | `convex/onboardingSlackWorkspace.ts`, `lib/onboardingSlackWorkspace.ts`, `convex/http.ts` |
| Staff CRM changes/stages | Server-checked admin actor, write gate, scoped opportunity, audit and readback | `convex/onboardingAdmin.ts`, `lib/onboardingProgression.ts` |
| Communications | Cloud engine credentials, state and execution ledger (cloud runner only; the package holds rules and templates only) | `services/onboarding-engine/` (shared rules/templates), `convex/onboardingEngineLive.ts` (compute/send cohort modes) |
| Operator Pulse | Read-only last-login / last-credit-pull for retention signals | `convex/agentPulse.ts`, `convex/http.ts` |

Tokens come from `ghlInstallations` keyed by Location; agency installs use
`agency:{companyId}`. Same-agency checks do not remove Location isolation. Provider
refresh helpers can persist rotated tokens, so invoking a read-like action is not
necessarily a read-only audit. Use known queries or authenticated provider GETs with
valid existing credentials when investigating production without write approval.

### Provider routes and version contracts

CRM APIs use `https://services.leadconnectorhq.com`. Do not normalize all clients to
one version or one pagination/search shape:

* SaaS lookup: `/saas-api/public-api/get-saas-subscription/{locationId}?companyId=…`
  using agency credentials. CRM payment subscription: `/payments/subscriptions/{id}`
  with Operations `altId` and `altType=location`, Version `v3`.
* Account-link/Identity contact, field, pipeline and opportunity paths use Version
  `2021-07-28`; opportunity search uses `location_id` / `contact_id`.
* Calendar listing (`/calendars/?locationId=…`) uses Version `2021-07-28`.
  Appointment synchronization/management and phone APIs use Version `v3`. Appointment routes include
  `/calendars/events/appointments/{id}`, `/calendars/events`, and
  `/calendars/{id}/free-slots`; phone routes start `/phone-system/numbers/location/{id}`.
* Slack's Operations opportunity lookup and general admin CRM writes use Version
  `v3`; Slack search uses `locationId` / `contactId`. Keep each reader's completeness
  checks; partial/paginated results cannot establish a unique destination.
* Business settings and form discovery use Version `2021-07-28`.
* Stripe uses `https://api.stripe.com/v1`, server Bearer authentication and version
  `2026-06-24.dahlia`. Source modules remain authoritative for request bodies and retries.

## Linking, payments, and KYC

Automatic linking requires its flag, Location Labs, same agency, exactly one eligible
payment subscription and a unique open unassigned **Onboarding → New - Paid**
opportunity. Existing valid mappings survive later stages. It fills Product Location
ID, reads it back, then prepares contact/Identity state. It never creates an opportunity
or moves a stage. Reservations protect concurrent/uncertain writes; refresh is cached
for one minute and an in-flight attempt has a five-minute lease.

A saved card on an eligible trial can prepare KYC. That is not proof of a collected
payment. Missing cardholder names are allowed; compliance confirms card ownership and
the agreement manually. Automatic evidence has `source: saas_subscription` and never
claims a human attestation. The displayed KYC name is the Operations contact name.

There is **no new onboarding payment-success/failure writer** in this integration.
CRM payment workflows and the manual Payment Verified requirement remain distinct
from subscription evidence and KYC. Other app domains have Stripe payment webhooks;
do not repurpose them or mark Payment Verified from `active`/`trialing` alone.

Identity needs matching account, mode, approved flow, session generation, successful
document/selfie report, and normalized contact name. Completion additionally requires
successful CRM KYC Verified readback. The return page is navigation only. Standalone
one-off verification links created by compliance are not automatically app-bound.
Automatic linking does not import an existing CRM Verified value as a new KYC pass.
Stripe's verified first name and full surname must match the Operations contact and
saved identity evidence after case, Unicode and whitespace normalization. Middle
names may differ or be absent. Staff can explicitly override a name mismatch after
reviewing the current names and supplying a reason. `reviewNameMismatch` requests
the comparison; callers that omit it retain the existing mismatch refusal. A
SHA-256 review key binds confirmation to the Location, contact, CRM target, identity
generation, saved evidence, Stripe session/report and names; it is a stale-review
guard, not an access credential. Every request still validates staff access and
provider evidence. Missing names cannot be overridden. Automatic matching is unchanged.
`identity_name_override_confirmed` persists reviewer/time/session/report/reason
before the CRM write; `identity_linked_name_override` records completed linking.
Only staff see `linked.nameOverrideReason`. Reopening removes the current link but
retains the audit. Verified names are returned for staff review, never persisted.
Staff can explicitly accept an external verified document session with no selfie
report and no selfie requirement under earlier requirements. Confirmation is bound
to the resolved session ID. The linked record stores `legacyDocumentOnly: true`, the
reviewer and timestamp; `identity_linked_legacy_document_only` records the acceptance
in the audit log. Failed checks and app-owned flows cannot use this exception.

The live webhook is enabled at
`https://brainy-crocodile-746.convex.site/onboarding/identity-webhook`, destination
`we_1UKip1JIA23cYEIpI5PcTsT0`, for `identity.verification_session.` events:
`created`, `processing`, `requires_input`, `verified`, `canceled`, `redacted`.
It validates raw-body signatures, ignores unrelated sessions and retrieves current
Stripe state. Configuration/read access does not prove delivery or CRM completion.
See [staff account linking](../staff/onboarding-account-linking.mdx).

### Identity recovery (still true)

* Session IDs and progress are stored per Location. Hosted session URLs and Stripe
  client secrets are never persisted or logged. Document images and selfies are never
  downloaded.
* A crashed action's lock expires after 35 minutes. Writes recheck generation, lock,
  Labs, agency, and contact mapping. Initial creation uses a stable idempotency key.
* After 23 hours an unresolved creation is held for staff to recover the existing
  `vs_` session; never start a replacement because a response was lost.
* Reopen records the staff request, clears local completion, cancels an unfinished
  Stripe session, writes Incomplete with readback, and replaces the generation.
  Old-session webhooks cannot complete the new attempt.
* Synthetic preview: `ONBOARDING_PREVIEW_IDENTITY=true` intercepts providers (no real
  Stripe sessions or CRM writes). CRM shape fixture:
  `tests/fixtures/onboardingIdentityCrm.json`.

## Calendars and appointment semantics

Calendar configuration belongs to the **Operations source Location**, not each
customer's calendar inventory. Intended IDs below were inspected September 22 and
were **not saved in production** as of the September 29 audit; recheck availability
and obtain approval for the exact configuration write before saving.

| Slot | Intended calendar | Matching rule |
| - | - | - |
| Kickoff | `bautvHXGNkJIBlyqDq5r` | Linked Operations contact's kickoff event |
| FM 1 | `eBnjV6gL89DqWsgydGcI` | Exact appointment for FM onboarding 1 |
| FM 2 | Separate **FM Follow-up** calendar ID set in onboarding settings | Distinct appointment; never reuse the FM 1 event or calendar |
| Technical 1 / 2 | Manually managed | Team records completion; do not fabricate calendar mapping |

Setup reads contact appointments and bounded calendar windows, retains pinned IDs,
and fetches exact remembered appointments to detect moves/cancellations. One event
cannot satisfy both FM slots. Broad discovery is bounded and rejects saturated or
incomplete results. Review `lib/onboardingCalendar.ts` before changing matching rules.

Display uses the customer Location's business timezone, explicitly falling back to
UTC. CRM milestone date serialization is UTC. Elapsed time never proves attendance:
an ended booking awaits team confirmation; **Showed** establishes completion.
Kickoff updates Date Compliance Call Completed, FM 2 updates Date Onboarding Call
Completed; FM 1 does not complete FM 2. FM 2 “Not needed” is a dated staff waiver,
not attendance and not an appointment cancellation.

Before enabling live edits, validate provider scopes, host/duration preservation,
availability, notification behavior, and readback on approved test appointments.
Synthetic previews do not establish any of those live properties.

## Website, phone, Slack, and staff ownership

* Website and timezone come from the product Location's business settings. There is
  no Operator website-entry step. Intake form discovery uses exactly
  **1. MAIN Apply For Funding Form**; do not substitute the processing application.
  Staff's application-funnel verification is still a separate outcome.
* Phone-provider identity is separate from Stripe KYC. Inventory errors are not
  evidence that an existing number disappeared. Purchasing is billable and needs
  the exact approved target; never purchase to validate documentation.
* An account admin can mark phone not needed (Complete · Phone not needed); that does
  not cancel a number or change billing, and simply deferring a number leaves the
  step incomplete.
* Slack workspace setup uses the exact Operations contact and Product Location ID, a private non-Connect channel and the approved email-matched Slack user.
  The returned channel ID is saved to the exact opportunity field and read back before the local link is published. The provisioning ledger records creation and access attempts before external writes.
* The dedicated signed receiver is `POST /slack/onboarding-events`; it validates the app and workspace IDs. Membership events re-read actual access. Missed events and interrupted attempts are checked by a bounded recovery job. A removed member or uncertain invitation needs staff approval before another invitation.
* Admin client selection, expansion and task disclosures read saved setup state only. Explicit connection refresh retains provisioning behavior; it is not triggered by navigation.
* Configure the dedicated app and gates per [Slack workspace setup](../staff/slack-workspace.mdx). These changes are implementation requirements, not evidence of live activation. Internal staff Slack keeps its existing app and credentials.
* Staff own checks, blockers, ownership, holds, notes, attendance and final QA in
  **Admin → Onboarding**. The Operator sees the shared readiness and next action.
  “Ready for QA” does not automatically move a client Live.

## Operator Pulse (retention signals)

```
GET https://brainy-crocodile-746.convex.site/agent/operator-pulse?locationId=<id>
Authorization: Bearer <AGENT_PULSE_TOKEN>
```

Response `200`: `{ "locationId": string, "lastLoginAt": number|null, "lastCreditPullAt": number|null }`
(`401` when the token is missing/wrong or unset — fail closed; `400` on missing `locationId`.)

* **`lastLoginAt`** — `ghlInstallations.lastSeenAt` via login bootstrap →
  `lib/operatorLastSeen.ts` → `internal.ghlInstallations.touchInstallationLastSeen`.
* **`lastCreditPullAt`** — newest `creditReportRequests.createdAt` per Location
  (`by_location_createdAt`, `take(1)`).

Implementation: `convex/agentPulse.ts` + `convex/http.ts`. The marketplace MCP
`get_last_login` tool reads the same data through
`internal.agentPulse.getAgencyOperatorPulse`. These timestamps do **not** populate
the admin queue's verified Last Active column by themselves.

Opportunity fields the engine may stamp from pulse/mapping (Operations Location):

| Field | Historical field ID |
| - | - |
| Last App Login | `Vk5td7RYmFDis48mabBQ` |
| Last Credit Pull | `7DtlDwTpCbIleN8FODEs` |
| Product Location ID | `cokiropDHpj8atwrDrqF` |
| Slack Channel ID | `ACD1FcIE6IfXaje1wnkH` |

## Configuration inventory (no secret values)

Convex owns the server integration configuration below. Store credentials in deployment
secret settings, never source, screenshots, logs, public docs, or browser variables.

| Setting | Purpose / September 29 production observation |
| - | - |
| `ONBOARDING_AUTO_LINK_ENABLED` | `true`; independently authorizes guarded linking when Labs/agency checks pass |
| `ONBOARDING_AUTO_LINK_RECOVERY_ENABLED` | New opt-in flag. Together with automatic linking, enables hourly recovery of at most ten eligible installed customer Locations with missing/stale pending attempts. Checks the full bounded Operations-agency installation inventory (100 per query, 2,000 total cap), prioritizes oldest attempts, spaces dispatches ten seconds apart, and preserves linked/review-required records and Labs choices. Unset/false stops queued recovery work and subsequent retries; already-running attempts may finish. Activation is separate from deployment. |
| `ONBOARDING_NEW_LOCATION_ENROLLMENT_ENABLED` | New opt-in flag; `true` enables Onboarding & Setup for verified Operations-agency SaaS Locations created at/after `2026-10-05T06:00:00.000Z` (midnight Denver). Existing Location overrides and agency opt-outs are preserved. Unset/false stops new enrollment without reverting existing flags. Installation, reauthorization and completed provisioning check CRM creation time; reinstall time is never eligibility evidence. This does not activate live-engine communications. |
| `ONBOARDING_RECONCILIATION_ENABLED` | New opt-in flag; `true` schedules the read-only full-agency report daily at 12:00 UTC (06:00 MDT / 05:00 MST). Unset/false stops scheduled checks and preserves reports; staff can still run an explicit check. This does not enable linking, progression or communications. |
| `ONBOARDING_IDENTITY_ENABLED` | `true`; Labs also required |
| `ONBOARDING_IDENTITY_CRM_WRITES_ENABLED` | `true`; bound KYC field only |
| `STRIPE_IDENTITY_SECRET_KEY` | Optional dedicated key absent; fallback `STRIPE_NEW_SECRET_KEY` then `STRIPE_SECRET_KEY` works for live account |
| `STRIPE_IDENTITY_ACCOUNT_ID`, `STRIPE_IDENTITY_FLOW_ID` | Unset; live defaults above; sandbox must override both appropriately |
| `STRIPE_IDENTITY_WEBHOOK_SECRET` | Configured; separate per account/environment/destination |
| `APP_URL` | Production origin above; return path `/onboarding/identity/return` |
| `SLACK_CHANNEL_DISCOVERY_TOKEN` / `SLACK_BOT_TOKEN` | Discovery token absent; bot fallback configured |
| `SLACK_SIGNING_SECRET` | Configured |
| `ONBOARDING_SLACK_WORKSPACE_ENABLED`, `ONBOARDING_SLACK_PROVISION_ENABLED`, `ONBOARDING_SLACK_PAID_PROVISION_ENABLED`, `ONBOARDING_SLACK_PROVISION_LOCATION_IDS` | Workspace flow and write controls. For new paid customers only, keep general provisioning off and enable paid provisioning. Verified payment can prepare a channel before a product Location exists when the payment policy is in send mode and both live-engine controls are enabled. Any Location pilot list blocks that pre-product path. Deployment does not activate it; see [Payment welcome cutover](onboarding-payment-welcome.md). |
| `ONBOARDING_SLACK_CHANNEL_STAFF` | Optional JSON entries `{locationId, teamId, channelId, userIds}` identify approved existing staff in one exact customer channel. No invitations or cross-Location access; removal revokes staff recognition, while current human members may still participate as customer teammates. See [Slack workspace setup](../staff/slack-workspace.mdx). |
| `AGENT_PULSE_TOKEN` | Fail-closed bearer for `/agent/operator-pulse` |
| `ONBOARDING_ADMIN_CRM_WRITES_ENABLED` | Unset; general CRM staff writes disabled |
| `ONBOARDING_AUTO_PROGRESS_ENABLED`, `ONBOARDING_AUTO_PROGRESS_LOCATION_IDS` | Unset; also requires general CRM write gate; 15-minute cron registration is not activation |
| `ONBOARDING_ENGINE_SHADOW_ENABLED`, `ONBOARDING_ENGINE_LIVE_ENABLED` | Unset; each also needs enabled persisted state |
| `ONBOARDING_ENGINE_SHADOW_PIT`, `ONBOARDING_ENGINE_LIVE_PIT` | Absent; optional corresponding `…_SLACK_TOKEN` settings are separate engine credentials |

## New customer enrollment activation

Before enabling `ONBOARDING_NEW_LOCATION_ENROLLMENT_ENABLED`, refresh the full agency
inventory and review customer Locations created at or after `2026-10-05T06:00:00.000Z`.
For approved catch-up targets installed before activation, invoke the internal action
`onboardingEnrollment:afterProvisioning` with `{ "locationId": "<verified Location ID>" }`.
It takes a Location ID directly; no `provisioningJobs` row is needed. Use the same action
to retry enrollment after fixing provider evidence. Never use installation time as the
cutoff or bulk-set the agency Labs default. The new enrollment flag must be enabled;
the action still checks current ownership, CRM creation date, subscription binding and
manual opt-outs. Production configuration and each catch-up target require approval.
Successful enrollment schedules account linking atomically with a fresh retry window;
its independent flag and billing/identity guards still apply.

## Communications

The October 2 local payment-welcome implementation and its uncompleted production
cutover are documented in [Payment welcome cutover](onboarding-payment-welcome.md).
It preserves the existing Operations survey and scheduling funnel. This is not
evidence that the production settings recorded below have changed.

The cloud shadow runner computes proposals without external writes. Cloud live has a
separate cohort and durable execution ledger. Both ten-minute cron entries exist but
neither is active in production. A Labs pilot does not authorize messages.

Cutover (cohort review, final ledger/snapshot import for historical-receipt
dedupe, single-writer token revoke, native stage-move retirement, enablement, and
rollback) lives only in
[RUNTIME.md](../../services/onboarding-engine/docs/RUNTIME.md). Never run
two writers. Do not treat the retired Mac mini job or a local package `run`/`bootstrap`
as the current sender.

Zoom supplies the onboarding-call summary; E3 and its send-recap owner task are retired. E5 Monday and day-three training digests are removed for now; legacy template settings do not trigger sends. E5a remains the personal week-one check-in. W10's day-14 action is a
**human Slack-touch task** anchored to retention-card creation. The new workspace flow has no Slack Pro upgrade reminder (see the [runbook](onboarding-runbook.md)).

## Temporary preview infrastructure

The following preview resources were prepared earlier, **not freshly inspected in
this audit**. Inventory exact current owners/dependencies before any cleanup:

* `onboarding-preview.myfundingmachine.com`: DNS-only CNAME to
  `f2d5d98e7e99d209.vercel-dns-016.com`; assigned to a sandbox Vercel Preview with login
  protection retained. Do not assume the original deployment ID is still current.
* Restored branch `codex/onboarding-shared-readiness` supported branch-only development
  secret configuration. That historical preview branch is not the starting point for new work.
* Admin-only **FM Onboarding — Sandbox Test** menu in internal/test Location
  `oE9ILHco0XXZUu79wAA0`, plus preview/development backend and sandbox Stripe settings.

These resources are not the live Identity webhook. Keep them until the sandbox
acceptance work is finished or explicitly retired. Cleanup needs exact-target approval;
preserve the live app domain, Zoho wildcard, production secrets and unrelated services.

## Recovery and verification for the next agent

1. Fetch current `origin/main`, read this map and the [runbook](onboarding-runbook.md),
   and check open PR/deployed revisions. Work in an isolated current branch.
2. Inspect flags, Labs, source calendar config and the selected Location's mappings
   without printing tokens or invoking write-capable refresh actions unintentionally.
3. Resolve ambiguous linking at the source; do not overwrite another Location's
   reservation, choose by display name, create a duplicate opportunity, or fake completion.
4. For uncertain KYC creation, recover the existing `vs_` session rather than creating
   a one-off link. Respect lock/retry guidance; reopening must clear CRM completion and
   protect against old-session webhooks. For uncertain admin/appointment writes, read
   back the exact destination before retrying. Preserve pending recovery records.
5. Complete approved acceptance separately (see runbook): calendar mappings and real
   event matching; customer KYC → signed event → CRM readback; post-kickoff Slack;
   authorized staff writes. Record dated outcomes, not merely green feature flags.
6. For source changes run the affected tests; CI runs the full suite. Never deploy Convex production or
   perform live provider writes just to run tests.

Update this document's state table and evidence date when configuration changes.
Keep the [staff connection guide](../staff/onboarding-connections.mdx),
[staff workspace guide](../staff/onboarding.mdx), and
[Operator guide](../operators/getting-started/onboarding-setup.mdx) aligned with actual
behavior.

## Engine audience rollout

The engine audience is stored per customer and processed in ten-member pages.
Deployment preserves existing pilot state until staff reconfigure it. Automatic
new-customer admission requires a separate reviewed enrollment policy and verified
original CRM creation evidence. Pre-message-cutover customers join in compute mode.
See [Engine rollout](../staff/onboarding-engine-rollout.mdx) for configuration,
recovery, review, pause/resume and the production cutover checklist.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.