What lifecycle stages are
A lifecycle stage describes where a Contact sits in your sales funnel as a person — independent of any specific deal they’re part of. The same vocabulary every B2B sales team uses: Lead → MQL → SQL → Opportunity → Customer, with Disqualified as the terminal off-ramp. Connie speaks this vocabulary as a convention on the ContactsStage column, not a separate entity. There’s no lifecycle picker UI, no new table, no special migration. Just a workspace config field plus a helper that downstream analytics read.
The default seed
Every new workspace gets this list out of the box. Override it any time without touching code:
Terminal stages exit the funnel. They’re how analytics distinguish “won” / “lost” from “still in flight” — for example, the rep performance breakdown (G.4) computes win-rate by counting
is_terminal: true && value == 'Customer' against the total terminal count.
Where it lives
workspaces.settings.lifecycle_stages — a JSON array on the existing settings blob. No new table.
When the field is unset, the API returns the default seed above with is_default: true. As soon as you PATCH a custom list, that’s what reads back.
Configuring it
Validation rules
The PATCH endpoint enforces two invariants that the schema alone can’t:- Distinct values. Two stages with the same
value(case-insensitive) would make the Stage cell ambiguous. - At least one terminal. Without a terminal stage, the win-rate analytics in G.4 divide by zero and emit a meaningless number — so the endpoint rejects a list where every entry has
is_terminal: false.
is_terminal: true so analytics treat it as the end.
How analytics read it
TheisTerminalLifecycleStage(stages, value) helper in @conigma/shared/lifecycle-stages resolves a stage value against the configured list — case-insensitively — and returns whether it’s a terminal stage. G.4 (rep performance) calls this to split deals into “completed” (terminal) and “in-flight” buckets before computing win-rate and cycle-time.
This is the only thing reading the config today. Future analytics (G.1 funnel conversion, G.2 stage velocity) can plug into the same helper.
What this is NOT
- ❌ A lifecycle picker UI. The Contacts
Stagecolumn’s existing select-options dropdown is the picker. - ❌ A new entity / lookup table. It’s a JSON array on
workspaces.settings. - ❌ A migration that touches existing Stage column data. Existing workspaces keep whatever Stage values they already configured.
- ❌ A soft-sync warning system between this config and the Stage column’s actual select-options. They drift independently by design; the config informs analytics, the column drives data entry.