Skip to main content

Retention admin workspace

Approved behavior (October 9, 2026)

The Retention pipeline is the owner roster, including owners still onboarding. Default attention work prioritizes live owners; All owners and Onboarding retain the full roster. Onboarding work remains in Admin → Onboarding. Slack questions never establish an onboarding stall. Use the existing Funding Machine admin UI and design system. Assignment is not a primary workflow. Show the retention stage, explainable attention tags, latest application and last completed report request, conversation context, open issues and next follow-up.
  • AI reviews changed Slack channels at 07:00, 12:00 and 17:00 America/Denver, weekdays only. An unresolved question qualifies after four elapsed hours, including nights and weekends; it surfaces at the next review. Unchanged conversations do not need repeated model calls. Show review freshness.
  • AI identifies and summarizes issues and suggests resolution or stage changes. Staff confirms closure and stage changes. Existing retention automation is separate; these UI attention thresholds do not silently rewrite engine rules.
  • Tags include Needs answer, Unresolved issue, Waiting on client, Usage slowing, and No recent applications. Tags follow their underlying facts; moving stages does not close unrelated issues. CRM tag writes preserve unrelated tags.
  • Staff can immediately mark answered, mark resolved, wait on client and reopen. Resolution needs no mandatory note and has undo. Old analyzed messages cannot undo a staff decision; a new client follow-up can create fresh attention. A client-level tag remains while any qualifying issue remains.
  • Usage slowing starts after seven days without a real application attempt; No recent applications replaces it after fourteen days. The baseline is the later of the Onboarding Completed Date and the latest application (updated October 13, below). Pre-live owners are excluded. Unknown dates remain unknown. Newer application activity clears usage attention.
  • Usage concerns support snooze to a required future date and immediate unsnooze. Expiry restores attention if the underlying condition still holds. The actual report date and follow-up date remain visible.
  • Staff replies from the workspace are explicitly sent to the selected Slack thread through FM Bot, with staff attribution. The bot may later be renamed FM Support. Sending does not resolve an issue. Preserve drafts and distinguish confirmed failure from uncertain delivery; never blindly retry an uncertain send. Keep Open in Slack available.
  • Read and write the linked CRM retention opportunity with verified Operations and product Location mapping. Show synchronization failures and stale data. Pre-provisioning owners remain visible without exposing unrelated private data.

Design consultation

Claude Design project: https://claude.ai/design/p/1c79528a-73c8-4633-8b0c-7ca6305fdbd7 Selected design system: Funding Machine — Codebase Design System 2.0. The brief references the existing Admin onboarding pipeline project and directs Claude to reuse the current admin UI rather than inventing another shell.

Implementation boundaries

The feature is human-authorized, including necessary additive schema, API, Convex, tests and documentation work. Production activation, live customer messages, historical CRM backfills, commits, pushes and deployment are separate actions and have not been performed by this task. Existing reusable paths include the signed Slack receivers, verified channel links, successful-report indexes, CRM provider adapter and session-authenticated portal operations. Review must be bounded and incremental; human decisions need a message watermark/revision so concurrent review cannot reopen old work.

Verification

Acceptance checks must cover:
  • All CRM opportunity statuses and pre-account owners remain in the roster; incomplete provider pagination and ambiguous mappings cannot look complete.
  • Four elapsed hours, Friday-to-Monday carryover, and both Denver DST offsets.
  • Real non-sample application attempts, including Credit Not Found; seven/fourteen-day boundaries from the later of completion date and latest application; no go-live fallback to payment date; an unknown baseline as a visible setup state; and no usage tags during onboarding.
  • Snooze expiry/unsnooze; successful usage supersedes an old snooze.
  • Resolve one of multiple open issues; immediate aggregate tags, undo/reopen, duplicate/out-of-order events, and review racing with human resolution.
  • Verified session/agency/contact/Location/channel boundaries on all reads and writes; no private history from an unknown or conflicting binding.
  • Human-authored Slack send, delivery receipt, failed draft preservation, and uncertain outcomes that cannot be blindly retried.
  • CRM stale-edit detection, selective managed-tag updates, per-family tag preservation when a source is unavailable, readback and visible failures. Existing engine stage transitions remain independently owned.
  • Browser review at desktop and narrow iframe widths, keyboard access, and loading, empty, unavailable and pending states.

Activation configuration

The user explicitly approved bounded client Slack excerpts being sent to OpenAI GPT-6 Luna on October 9, 2026. The reviewer uses gpt-6-luna, store: false, no tools, no automatic retries, at most 40 requests per check, at most 24 messages of 1,000 characters per request, and a three-minute processing budget. Unfinished conversations remain queued. Eligibility promotion processes at most five batches of 200 within the shared time budget. Unavailable mappings defer for an hour so they cannot block later eligible conversations; their messages remain excluded from model requests. Staff retain control of replies and closure. Deployment switches default off: RETENTION_REVIEW_ENABLED, RETENTION_SLACK_REPLIES_ENABLED, and RETENTION_CRM_WRITES_ENABLED. Review also requires OPENAI_API_KEY; replies also require the existing FM_SLACK_BOT_REPLY_ENABLED and verified workspace bot configuration. RETENTION_REVIEW_MAX_THREADS can reduce the per-check cap below 40. Signed dedicated and legacy Slack receivers capture only verified account/channel mappings. Legacy links without an explicit workspace and Operations contact need mapping repair before private conversation tracking can begin. No backfill runs.

Approved October 9 follow-up implementation

The owner clarified that Applicants and report attempts are the same intake event; Credit Not Found is still application activity. Seven/fourteen-day tags now use stored real application attempts. Window totals count unique Contacts, with explicit legacy request-ID fallback and incomplete-history warnings. Successful credit-file retrieval and processing milestones are separate; funding dates remain unknown without reliable evidence. Existing successful-report history remains visible. Added owner goals/blockers/next milestones, commitment-level responsibility, reversible follow-ups and outcomes, onboarding blocker handoff, and bounded shared Slack/CRM/call contact history. A future same-client commitment defers generic retention touches; it does not change stage rules or necessary onboarding handoffs. The user approved cancellation lifecycle implementation based on a real stale-stage example. No production record was changed. RETENTION_BILLING_ENABLED=true enables verification; CRM dispatch also requires RETENTION_CRM_WRITES_ENABLED=true. Every 15 minutes, bounded account-link pages enqueue spaced verification jobs. Optional Stripe events use the signature-verified /retention/stripe-webhook endpoint and RETENTION_STRIPE_WEBHOOK_SECRET; subscribe to subscription created, updated and deleted events in the independently verified Stripe account. Event bodies are only refresh hints, never authoritative stage commands. Endpoint configuration itself has not been changed by this task. Fresh account-bound CRM/Stripe evidence, complete subscription lists and explicit service-end evidence are required for automatic Churned/lost. Other unknown active subscriptions, payment failures, ambiguous identity or lookup failures require review. An explicit staff-recorded paid-through arrangement defers service-end churn; historical paid invoices do not silently grant continuing service. Canceled owners are suppressed at the automated outreach decision and dispatch boundaries. Reactivation restores only an unchanged automation-owned closure. Trial/paid history remains distinguishable; native billing state is retained even when optional CRM billing field definitions are unavailable. Existing 21/30-day engine stage transitions and inbound-reply policy were not silently changed. Provider references: https://docs.stripe.com/billing/subscriptions/cancel and https://docs.stripe.com/api/subscriptions/object . Synthetic preview includes an illustrative trial cancellation and next-action handoff, never real customer data.

Attention reliability (October 13, 2026)

Approved Retention Workstream 1. Attention reliability changes only; billing, Slack rollout and deployment switches are unchanged.
  • Usage baseline. CRM usage is measured from the later of the Onboarding Completed Date and the latest real application attempt. A missing completion date no longer hides an owner with an old latest application. With neither date, the owner shows Usage baseline unknown in Needs attention at mapping review priority and receives no usage tag. Dates are never invented or backfilled. A completion date that cannot be read (zero or duplicate field definitions, or a value that is not a date or falls before 1973) is unknown, not blank: usage stays unknown, existing usage tags are preserved, and the row shows Completion date unreadable · usage unknown without entering Needs attention. Zoho-linked owners keep the fresh-snapshot rule.
  • Per-family CRM tags. Managed tags reconcile in four families: Slack issues, usage, retrieval and risk. A family changes only when its source was read: a verified Slack channel; a known latest application with a readable completion date, or an active snooze; retrieval counts with two or more trouble outcomes, or too few unknown outcomes to reach two (complete coverage alone is not enough); and exactly one Churn Risk Reason definition whose value is blank, text, or a list of text (any other value, or a missing or duplicate definition, shows Churn Risk Reason unreadable in CRM · existing risk tags kept). A recognized non-live stage and status settles usage, retrieval and risk; an unrecognized stage or status settles only Slack issues. Otherwise existing CRM tags in that family are preserved. Desired tags always come from a settled family, and unmanaged tags are never changed.
  • No Slack prerequisite. CRM tag sync needs only the verified account mapping. Slack issue tags still need verified Slack evidence. Scope, contact, duplicate card, lease and readback protections are unchanged. A held tag lease still retries after it expires, but owner state bound to a different Location or contact stops sync without retrying, so removing the Slack gate cannot start an endless retry.
  • Stale Zoho. A snapshot older than 48 hours no longer enters Needs attention by itself. This supersedes the earlier Zoho refresh due expectation. The row and panel show the Zoho checked date and usage unknown; usage tags stay paused. No polling was added.
  • Report wording. The former Last successful report label reflected request status only and is now Last completed report request.
  • Known follow-up. Roster-wide CRM tag sync is scheduled only by the Slack review run. Without review, tags sync after owner events such as an issue change, snooze, platform link, stage confirmation or retry. Separately, owner state recorded before a Product Location ID correction stays bound to the old Location, so that owner’s local context and tag sync stay paused with no visible sync error until a maintainer reviews it; this predates Workstream 1.
Tests: tests/retentionAttentionReliability.test.ts plus updates to tests/retentionSafety.test.ts, tests/retentionPlatformView.test.ts and tests/retentionWorkspace.test.tsx.

Billing rollout runbook

This is an activation procedure for maintainers. It does not authorize production configuration changes, historical mapping repairs, subscription changes or customer messages. Merge with the four Retention switches unset or false; enabling any switch in production requires a separately approved environment and target.

Deployment settings and provider access

Set these on the Convex deployment that runs the actions, not just Next.js. Only the literal value true enables a switch. The billing credential needs read access to the Stripe account, the customer’s complete subscription list, invoices and paginated invoice lines. The adapter pins Stripe-Version: 2026-06-24.dahlia. It does not change or charge subscriptions. The CRM connection needs the existing Operations Location installation for opportunities, pipelines, opportunity custom fields and payment-subscription reads, plus the matching agency installation for the SaaS subscription lookup. Enabling CRM sync additionally requires opportunity update permission. Token refresh can rotate and persist installation credentials; a provider action is not a purely read-only database audit. Before activation, verify one Operations installation, the product installation in the same agency, independent Operations-contact evidence, a linked onboardingAccountLinks row with billing evidence, and exactly one matching Retention card carrying the product Location. The payment record must agree on contact, Operations Location, Stripe account and test/live mode; Stripe must agree on customer and subscription. Never infer this chain from an owner’s email or display name. Keep owners with missing or conflicting evidence in mapping review. Billing-only relationship coverage. A product Location without a complete onboardingAccountLinks row (billing evidence, contact and opportunity), or one with a separately verified billing-only link after app removal, can instead be covered by a verified retentionAccountRelationships row in the Operations source. retentionBillingBinding accepts it for billing only; retentionLocationBinding and other Location-only consumers still reject neutral relationships. The relationship’s authority is its exact Retention card, contact, product Location and CRM SaaS subscription. Each refresh re-proves them before saving anything: the agency SaaS subscription must be CRM-billed with the saved subscription and contact, the contact must belong to the Operations Location, the payment subscription must match that contact in live mode, and, after the final provider read, the contact’s only Retention card carrying the product Location must be the relationship’s opportunity with a valid billing binding. Another contact’s card carrying the same Location is not bound (the roster shows it as an account mapping issue) and receives no billing write. Only sk_live_/rk_live_ keys grant this coverage. A mismatch, ownership conflict, coverage-source switch, revoke or relationship revision change during provider reads saves no billing evidence, makes no CRM write and leaves outreach unchanged; existing state becomes billing review. Matching relationship-covered state can move to a complete onboarding link (coverage cleared, onboardingOpportunityId becomes the link’s card, opportunityId keeps the Retention card, revision bumped) when the link is bound, agrees with the relationship, and matches the saved contact, CRM subscription and payment customer, and the relationship is the saved one and still names the saved Retention card. The claim saves the switch from those stored records; cancellation, restoration and baseline fields carry over, and the refresh re-proves both sources before saving any billing result. After app removal, an explicitly verified billing-only relationship may take over matching saved onboarding coverage. The retained link must remain linked and agree on contact, CRM subscription and payment customer; the saved Retention card and any saved relationship must agree too. The original onboarding card, cancellation suppression and restoration baseline stay recorded. A relationship can also incorporate an agreeing retained link while keeping billing-only coverage. Reinstall and explicit reverification allow the agreeing onboarding link to take over again; reinstall alone grants no private account data. When both sources describe a Location, the onboarding account link wins unless the relationship is billing-only. In either case, its refresh still relies on the relationship: the claim snapshots the relationship revision, the two must name the same CRM subscription before any provider read, the relationship’s Retention card is proven after the final provider read and before saving, and the revision, coverage and subscription agreement are rechecked after provider I/O. A disagreement, revoke or re-verification holds the owner for review. Re-verifying the relationship does not clear a subscription disagreement; the onboarding link’s identity evidence must be corrected and relinked first. The workspace shows the source as Coverage. Revoking the relationship moves its billing state to review on the next sweep and Verify billing now refuses the owner; earlier cancellation safeguards stay in effect. The workspace projection only shows billing for a bound owner, so after a revoke the saved state is no longer shown and the owner leaves Billing review (open question: show the last state). The Operations account needs exactly one Retention pipeline and exactly one Churned stage. The Product Location field is resolved by the exact key opportunity.product_location_id. Billing status and dates are written only through the keyed CRM billing field contract; a field named Billing Status is never written by name. Optional cancellation metadata is written only when the field definitions are unique, supported and not one of the keyed billing fields:
  • Billing Canceled: a native BOOLEAN field; other types are skipped.
  • Billing Canceled Date or Billing Cancellation Date: one DATE field.
  • Billing Cancellation Reason: one TEXT or LARGE_TEXT field.
Missing optional fields do not disable the native state or a verified stage transition. Their absence appears in the CRM sync message. Do not create or change production field definitions as part of a readiness audit.

Optional Stripe webhook

  1. In the independently verified Stripe account and correct test/live mode, configure an HTTPS destination pointing to the selected Convex HTTP actions origin plus /retention/stripe-webhook (normally the deployment’s https://<deployment>.convex.site, not the Next.js origin or .convex.cloud).
  2. Subscribe only to customer.subscription.created, customer.subscription.updated, customer.subscription.deleted, invoice.paid and invoice.payment_failed; place that destination’s signing secret in RETENTION_STRIPE_WEBHOOK_SECRET on the same Convex deployment. Keep billing disabled during setup.
  3. Validate signed delivery in the test setup below. Missing, invalid or expired signatures return HTTP 400. An authentic request while billing is disabled returns HTTP 200 without enqueueing work. Other event types and conflicting account/mode identities also produce no billing refresh.
  4. After approval to activate, enable billing verification. A valid subscription or invoice event only queues a fresh account-bound provider lookup. HTTP 200 means the event was accepted, not that the owner was verified or CRM changed. Confirm the action result and the owner’s displayed verification/sync state separately.
The receiver does not backfill events acknowledged while disabled. Periodic reconciliation and Verify billing now recover current state after activation.

Initial reconciliation and CRM activation

Read-only production mapping audit at 2026-10-09 19:50:19 UTC: Coverage by current Retention stage: Most verified mapping coverage is still onboarding. Of 221 post-onboarding live owners, only 3 have independent account bindings and stored billing evidence; none have verified Slack bindings. The 83 Churned and 7 CEO owners have no independent account binding. Broad production rollout is blocked on legacy mapping verification and repair, as well as the provider test prerequisites below. Preserve the binding rules; incomplete historical coverage does not justify inferring identity or exposing private activity. The 17 rows with evidence present have not been verified against fresh provider responses and are not an activation approval. The remaining roster needs mapping review or repair before automatic billing coverage is possible. No production record or configuration was changed by the audit.
  1. Record the read-only mapping audit totals and exceptions before enabling the feature. The sweep visits existing account-link rows, then verified account relationships; it does not discover or repair historical owners without either. Roster visibility is not proof of billing coverage.
  2. In the approved environment, keep CRM writes off and enable billing. Review the selected owner’s Subscription & service → Verify billing now result, then allow the scheduled reconciliation to finish. This saves native billing state and can pause outreach for a confirmed service end; it is not a dry run of outreach behavior. No trial activation in production is implied here.
  3. The sweep runs every 15 minutes. It paginates 100 account-link rows per page, spaces refresh jobs one second apart and schedules the next page after 100 seconds. After the last account-link page it pages verified account relationships the same way, skipping Locations an account link already covers. A large roster takes multiple pages; do not treat the first completed page as a full audit. Refresh jobs have a 15-minute lease to prevent overlap.
  4. Check that verified active, scheduled cancellation, terminal cancellation, incomplete signup and review exceptions match the provider evidence. While writes are off, expected billing sync text is Billing state saved. CRM stage sync is disabled. Check all audited mapped owners, including closed owners; unmapped owners need an explicit follow-up instead of an assumed healthy state.
  5. After the provider flow is validated and CRM activation is approved, enable RETENTION_CRM_WRITES_ENABLED. It is global to Retention, not an owner canary. Use Verify billing now on the approved owner, confirm the exact Retention card changed to Churned/lost and inspect CRM readback. Fresh billing evidence is fetched again immediately before dispatch. The next sweep reconciles the remaining eligible links; there is no additional historical mapping backfill.
  6. Verify a reactivation restores the prior stage/status only when the card still matches the automation’s own closure. A later staff edit stays in review and outreach remains paused until restoration is confirmed.

Monitoring and recovery

Monitor Convex cron and scheduled-action runs for retentionBilling.reconcile, retentionBilling.refresh and retentionBilling.subscriptionEvent, along with Stripe destination delivery failures. In the workspace, inspect Billing review, Historical owners, each owner’s Verified timestamp, CRM sync status, sync message and subscription history. There is no separate billing alert sender. An enabled switch alone proves no coverage: missing/malformed Stripe credentials return without updating state; missing links are skipped. Compare audit coverage to fresh per-owner results. With CRM writes enabled, a settled result is read twice and saved once, after the second read and the post-I/O coverage checks; the second read is pinned to the first read’s Stripe customer and is the evidence saved. After provider I/O, every saving mutation re-derives the onboarding link’s status, contact, card and Stripe customer, so a link change before observation or CRM dispatch holds the owner for review instead of saving or reporting an unconfirmed update. A provider failure, including a failed second read, produces Billing needs review while preserving prior cancellation and recovery safeguards and introducing none. An onboarding link that now names a different Stripe customer than the saved state holds it for review. Review exceptions include account/mode/customer conflicts, partial provider pagination, an unknown active subscription, unavailable CRM mappings and a changed reactivation card. For uncertain CRM updates, inspect the current provider card and rerun verification to let readback establish the outcome. Do not repeat a manual write from an old snapshot. Correct mapping or provider access only through a separately approved targeted repair, then run Verify billing now. Paid-through arrangements require an explicit staff-confirmed reason and future date; they never change Stripe.

Disable safely

Set RETENTION_CRM_WRITES_ENABLED=false to stop newly gated Retention CRM dispatches, and RETENTION_BILLING_ENABLED=false to stop new billing sweeps, refreshes and webhook-triggered work. Disable Slack review/replies with their own switches if those surfaces also need to stop. An action that already passed its dispatch check may still finish; inspect in-flight jobs and provider readback before declaring the pause effective. Disabling switches does not revert CRM cards, delete billing history or clear billingCanceled / crmRestorePending. Existing canceled or incompletely restored owners stay suppressed at outreach decision and dispatch boundaries. Do not clear those safeguards to resume outreach. Restore access/configuration in the approved environment, enable verification and let current provider evidence plus confirmed CRM recovery establish reactivation. Removing the optional webhook alone does not stop the 15-minute sweep.

Provider test prerequisites and evidence

The synthetic action and signed-webhook tests cover cancellation, outreach suppression and restoration, including provider failures and CRM readback. They do not establish real provider delivery or CRM permissions. A real-provider test needs an isolated development Convex deployment, a matching Stripe test account/credential and test customer/subscriptions, a signed webhook destination, and a fully verified synthetic owner/contact/account-link chain. The CRM adapter always calls the real CRM API and Retention uses the fixed Operations Location. Neither GHL_API_DOMAIN nor GHL_ADMIN_LOCATION_ID redirects this flow. Copying live installation tokens into development can still affect real CRM records if writes are enabled. A provider test that writes Operations records therefore needs approval naming the exact synthetic CRM targets; a local or development Convex URL alone does not make it a CRM sandbox. For those approved test targets, record the event delivery, native state, exact CRM stage/status readback, and outreach decision/dispatch eligibility for each case: Keep actual outbound messaging disabled; verify eligibility and stored dispatch outcomes through the existing synthetic outreach tests. Do not send real messages or create live charges to demonstrate suppression. Read-only local configuration inspection on October 9 found CRM/Convex variable names in the base and worktree .env.local, but no Stripe, Retention, Slack or OpenAI variables in those files or the process environment. No real provider test flow or destination configuration has been performed; provider-test credentials, independent account/mode identity and exact synthetic CRM targets remain unverified.