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 usesgpt-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/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 orfalse; 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 valuetrue 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
BOOLEANfield; other types are skipped. - Billing Canceled Date or Billing Cancellation Date: one
DATEfield. - Billing Cancellation Reason: one
TEXTorLARGE_TEXTfield.
Optional Stripe webhook
- 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’shttps://<deployment>.convex.site, not the Next.js origin or.convex.cloud). - Subscribe only to
customer.subscription.created,customer.subscription.updated,customer.subscription.deleted,invoice.paidandinvoice.payment_failed; place that destination’s signing secret inRETENTION_STRIPE_WEBHOOK_SECRETon the same Convex deployment. Keep billing disabled during setup. - 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.
- 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.
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.
- 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.
- 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.
- 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.
- 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.
- 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. - 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 forretentionBilling.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
SetRETENTION_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. NeitherGHL_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.
