Skip to Content

← All archived runs

Run: service-lead-ownership-and-handoff

run.md

Run: service-lead-ownership-and-handoff

  • branch: claude/service-lead-ownership-and-handoff
  • pr: #847

02_define/output/spec.md

Spec: Service-lifecycle ownership — Sales to CSM, with no gap

  • slug: service-lead-ownership-and-handoff
  • personas: CSM, Customer
  • touches: packages/services/src/shared/{roles,hierarchy}.ts, packages/services/src/db/models/{user,lead}.ts, packages/services/src/db/services/leads (ownership, scope, party, approveBrd, create), packages/services/src/db/workflows/schema.ts, packages/services/src/permissions/{defaults,licence}.ts, packages/services/src/notifications/service-leads, packages/services/src/db/migrations, apps/web/lib/auth.ts, apps/web/lib/resolve-app-user-for-tenant.ts, apps/web/components/service-leads/lead-header, apps/web/components/users/users-table
  • complexity: complex

Problem

At every stage of the customer's service lifecycle there should be exactly one named owner, and today that ownership is unclear either side of BRD approval. No one formally owns a service lead before the customer approves, and the handover to service delivery is a conversation that sometimes does not happen (FR-5..FR-8). This advances Q2-2026 Objective 1: the success measure is 100% of service leads showing exactly one named owner at every stage of their life.

(The stub names Sales as a persona; sales joins the platform's role vocabulary in this run — the pipeline's PR-label persona set is unchanged, so the header lists the label-vocabulary personas.)

Proposed change

Sales becomes a real user role that owns the lead, and ownership transfers by itself at approval. Paul's terminology governs throughout: this is service lifecycle ownership — the customer's complete service journey — never "pipeline ownership".

  • The Sales role joins the platform: the role enum, a user-model discriminator, permission template defaults (own-scoped lead/BRD/proposal/activity views — the lead owner's working set), the reporting hierarchy (internal, D-1: Vendor is the organisation and stays a tenant concept; the six people-roles are Admin, Sales, CSM, SDM, Expert, Customer), the licence-class map (Sales — the full remap is licence-class-reporting), lead read scope and party predicates, the disabled-staff sign-in block, and the web role plumbing (role home, grantable roles, users table).
  • Ownership is derived, never stored twice (design notes). The lead records who holds it before approval (salesOwner), who it lands on after (csm, existing), and where it came from (source, from the configured service-lead source list — sales, customer success, support, professional services, AI, product onboarding; default sales, BR-21/D-10). The stage decides which field answers today: before BRD approval the Sales owner (BR-24), from the moment of approval the assigned CSM (FR-8) — so "exactly one owner at every moment" (BR-20) is true by construction, with no field to keep in step.
  • No lead is ever left unowned: at creation the pre-approval owner resolves as the explicit input, else the creator when they hold the Sales role, else the tenant's first active Sales user, else the organisation's top-level user. A backfill migration gives every existing lead its source (sales) and a salesOwner (its creator, falling back the same chain).
  • The CSM assignment gate (BR-22, D-9): the customer cannot approve the BRD while the lead has no CSM — approveBrd refuses with a clear message, the approval UI surfaces the refusal, and the Sales owner is notified of exactly what is missing (scope §13).
  • The automatic handoff (BR-23, BR-25): at the moment approval succeeds, ownership transfers to the assigned CSM with no manual step — and both the outgoing Sales owner and the incoming CSM are notified (service-lead.ownership-transferred).
  • If the assigned CSM is disabled, ownership is held by their manager until another CSM is assigned — derived at read time from the reporting hierarchy (stub 1's effective-holder rule), so customer visibility is unchanged throughout. If the Expert leaves during delivery, ownership remains with the CSM by construction — the owner derivation never reads the expert slot.
  • The owner, on the lead record: the workspace lead header gains an owner chip — the one named owner at the current stage; before approval it also shows who it will transfer to, or "No CSM assigned — approval cannot proceed"; a disabled owner shows as held for them.

Acceptance criteria

  • Every service lead shows exactly one named owner at every stage of its life.
  • A lead raised by a workflow rather than a Sales user is owned per its configured source.
  • The customer cannot approve the BRD with no CSM assigned, and the Sales owner is told what is missing.
  • At approval, ownership moves from Sales to the assigned CSM with no manual step.
  • Both the outgoing Sales owner and the incoming CSM are notified at the handoff.
  • The CSM named at approval stays the owner through delivery, acceptance and the satisfaction survey.
  • Disabling the assigned CSM moves ownership to their manager without changing what the customer sees.

Out of scope

  • Changes to expert matching or bidding — the bid pool works as it does today.
  • Manager powers to act on an owned lead — visibility only.
  • Customer-side organisation structures.
  • A tenant-facing editor for per-source ownership rules — v1 keeps the source list in code with Sales as every source's default owner (Paul: general model, unchanged V1 behaviour).
  • The pipeline's PR-label persona vocabulary — unchanged this run.

Open questions

  • none

03_build/output/notes.md

Build notes: service-lead-ownership-and-handoff

  • commits: feat: service-lead-ownership-and-handoff — Sales to CSM, with no gap

What changed

  • The Sales role across the platform: USER_ROLES (+ workflow persona schema), a sales user-model discriminator, own-scoped permission template defaults (the lead owner's working set), HIERARCHY_ROLES (internal), LICENCE_CLASS_BY_ROLE.sales = "Sales" (the full remap is stub 6), the lead read-scope/party/line predicates, the disabled-staff sign-in block, and the web role plumbing (role home /service-leads, grantable roles, nav, labels, board/workspace copy). Clerk needs the matching org:sales role configured — noted below for Verify.
  • Ownership on the lead, derived never stored twice: salesOwner (who holds it before approval) and source (where it came from — shared/lead-sources.ts, default sales, all sources Sales-owned in v1 per D-10). shared/lead-ownership.tsleadOwnershipView answers the one named owner for the current stage, the transfer target, the BR-22 blocked state, and a disabled owner (held by their manager); pure + unit-tested, client-importable.
  • No lead unowned: leadService.create resolves the pre-approval owner (explicit → Sales-role creator → oldest active Sales user → the organisation's top). Migration 1787702400000-lead-sales-ownership backfills source + salesOwner (creator, same fallback chain) and builds tenantId_salesOwner.
  • The CSM gate (BR-22/D-9): approveBrd refuses while lead.csm is empty — "A CSM must be assigned to this lead before the customer can approve the BRD." — covering both the live approval and the canned demo path (both funnel through the same service). The Sales owner is notified (service-lead.approval-blocked) of exactly what is missing.
  • The handoff (BR-23/BR-25): on approval success both the outgoing Sales owner and the incoming CSM are notified (service-lead.ownership-transferred, email-deduped per person). Notifiers are best-effort by design and never unwind the approval.
  • The owner, on the lead record: the workspace lead header's new owner chip — "Owner (Sales)" with "Transfers to <CSM> at BRD approval" or "No CSM assigned — approval cannot proceed"; "Owner (CSM)" after; a disabled owner reads "— disabled, held by their manager". salesOwner (and csm) populate with isActive so the chip derives in place.

Acceptance criteria status

  • Every service lead shows exactly one named owner at every stage of its life — derived by construction; owner chip on the lead record; creation and backfill guarantee an owner exists.
  • A lead raised by a workflow rather than a Sales user is owned per its configured source — source recorded, LEAD_SOURCE_OWNER_ROLE resolves the owner (all Sales in v1, Paul's general model with unchanged V1 behaviour).
  • The customer cannot approve the BRD with no CSM assigned, and the Sales owner is told what is missing — service-level gate + service-lead.approval-blocked notification + visible blocked state on the chip.
  • At approval, ownership moves from Sales to the assigned CSM with no manual step — the stage flip is the transfer; nothing to run.
  • Both the outgoing Sales owner and the incoming CSM are notified at the handoff — service-lead.ownership-transferred to both, one email per person.
  • The CSM named at approval stays the owner through delivery, acceptance and the satisfaction survey — post-approval owner reads only lead.csm; nothing later rewrites it (the expert slot is never consulted, so an expert leaving changes nothing).
  • Disabling the assigned CSM moves ownership to their manager without changing what the customer sees — derived at read time (ownerDisabled + stub 1's effective-holder rule); no data changes, so customer visibility is untouched.

Notes for Verify

  • Clerk must define the org:sales organisation role before a Sales primary role can be assigned (assignPrimaryRole writes org:${role}); the in-app grantable-extras path works as soon as the claim carries sales.
  • The kanban card doesn't yet carry the owner line — the lead record (workspace header) does; card surfacing can ride with the stub 5 board work if wanted.
  • The service-lead.approval-blocked notifier fires on each blocked attempt; rare in practice, flagged in case Verify wants a dedupe window.
  • Stacked on inherited-visibility.