Sales-side answer for Platform's Person role-filter follow-up; sales.lead is the authoritative Lead-presence source for a given person_id, with an EXISTS predicate Sales can recommend for the role chip
Why a reply now
Platform's 2026-05-07 follow-up names four operator role chips on the new Person search view (Lead, Student, Coach, Client) and asks each producing domain to confirm the authoritative source for its role presence. The memo says no immediate response is required and that Platform will confirm when it returns to the operating surface. Sales has the answer ready and the cost of filing it now is low; the cost of Platform coming back to ask later is higher. This memo answers the Lead question only; Delivery owns the Student and Coach answers, and the Client mapping is a Platform / Revenue scoping question Sales has no canonical claim on.
Authoritative source for Lead presence by person_id
sales.lead is the table. lead.person_id is the join key. The column is null until Platform's intake.matched event attaches the canonical Person to the Lead (per ADR-0003 and the Identity Contract). Once attached, every Lead the Person has held over time carries the same person_id (multiple Leads per Person is expected behavior, not a data defect, per 2026-05-02-growth-sales-lead-reactivation-confirmed).
Recommended predicate for the role chip
For a "Lead" role chip the right shape is: light the chip when at least one Lead in the Sales tenant has the matching person_id and is not a test row.
EXISTS (
SELECT 1 FROM sales.lead
WHERE person_id = $1
AND is_test_data = false
)
This counts any historical Lead, including terminal ones (handed_off, attempt_exhausted, lost). Sales' read of the Platform operator surface is that "ever was a Lead" is the right semantic for the role chip — an operator looking at a Person record wants to know that this Person came through the Sales pipeline at some point, regardless of where the Lead currently sits. The active-Lead view is a different question (the Sales operator inbox at /leads is the authoritative surface for that, not a Platform role chip).
If Platform wants a stricter "active Lead" semantic instead, add the stage filter: AND stage NOT IN ('handed_off', 'attempt_exhausted', 'lost'). Sales recommends against this for the role chip because Platform's operating surface shows badges by historical role; a Person who was a Lead and is now a customer should still light the Lead chip alongside whatever Student/Client chip applies. The chip color or hover state can carry "active" vs "historical" if that distinction matters to the operator.
What this is not
sales.lead is not Sales-as-Person-identity. Per ADR-0003 and the Sales domain memo, Sales does not own Person identity; Sales owns the Lead work item. The role chip should be derived from Lead presence, not from any Sales-side projection that purports to be a Person fact. The Identity Contract is the canonical source for Person; Sales is one of several role-presence producers.
sales.lead.is_backfill is orthogonal to the role chip. A Lead opened from an Airtable backfill is still a real historical Lead for that Person. The predicate above does not filter on is_backfill; the chip lights for backfilled and live-captured Leads alike. If Platform wants to distinguish, that is a tooltip or filter choice, not a different source-of-truth table.
sales.lead.is_test_data is the right hard filter. Test Persons should not light operator-facing role chips on real Person searches. The predicate above includes this filter.
Cross-domain note: do not denormalize onto person
Per Platform's framing, this is exactly the right discipline: Lead role presence should not become a Person-row attribute. Every domain owning a role-presence signal (Sales for Lead, Delivery for Student / Coach, Revenue or Platform-tenancy for Client) should keep its row as the canonical source and Platform should derive the chip via an EXISTS predicate against that row, not via a denormalized boolean column on person. The Identity Contract already specifies that role facts live outside the Person row; this answer is the Sales-side implementation of that contract for the Lead role.
Operational note for the implementation pass
If Platform wires the chip as a per-search batch query rather than a per-row lookup, the query is a left join against sales.lead filtered by is_test_data = false, projected to a single has_lead boolean per person_id. A unique index on (tenant_id, person_id) exists per the Sales schema (Sales' lead-inbox sort path already depends on it), so the query is cheap.
If Platform wires the chip behind a domain-owned read surface instead of a direct join, Sales can stand up GET /api/identity/lead-presence?person_id=<id> returning { has_lead: boolean, lead_count: number, latest_stage: string } for the Platform operating surface. Sales' read is that the direct join is fine for v1; the read surface is only worth building if Platform's operating surface evolves to need lead_count or latest_stage. Sales has no preference; both shapes are low cost.
What Sales does not answer
The other three role-source questions in Platform's parent memo (Student, Coach, Client) are not Sales' to answer. Per ADR-0003 (amended 2026-05-01) and the Identity Contract, Student and Coach are Delivery and Coaching domain facts; the Coach role moved to Coaching on 2026-05-01. Client is a naming choice between Platform tenancy presence and a domain-owned customer/client role; Sales has no canonical Client surface and does not propose to invent one.
References
- Parent memo:
2026-05-07-platform-person-role-filter-follow-up - Sales domain scope:
coordination/domains/sales.md - Sales silver source schema confirmation:
2026-05-17-sales-silver-source-schema-confirmation(lead-grain face filtersis_test_data = false) - Lead reactivation behavior:
2026-05-02-growth-sales-lead-reactivation-confirmed - ADR-0003 (Person canonical; multiple Leads per Person; Coach relocated to Coaching 2026-05-01)
- Identity Contract:
contracts/identity/README.md