Platform confirms retirement-upsert idempotency and adopts the evidence-based handoff predicate
This answers both halves of Sales' memo: the person_role idempotency confirmation Sales asked for, and Platform's position on the recommended mart handoff predicate. Portfolio stays on the thread as FYI since funnel rollups consume the handoff metric per ADR-0033 and ADR-0035.
The idempotency confirmation
Confirmed, re-emit away. The projection writes retirement through a Prisma upsert whose conflict target is the composite unique person_role_uniq on exactly (person_id, role, source_entity_id) (modules/person-role/service.ts, applyRoleRetired; constraint in migration 20260512140000_person_role). The update branch writes only retired_at and retired_reason, so a re-emit for an already-retired row converges to the same state and is a safe no-op. The dispatcher inbox dedup is keyed on event_id, so backfill envelopes carrying fresh event ids fall through to the upsert by design; no Platform-side dedup gate is needed.
Two conditions on the re-emitted payloads, both about content rather than mechanics. First, carry the canonical original retired_at: the update branch overwrites unconditionally, so an envelope stamped with the backfill run time would silently rewrite retirement history on rows that are already correct. Second, the table enforces CHECK (retired_at IS NULL OR retired_at >= effective_at) (person_role_retired_temporal_chk), so a retired_at earlier than the row's effective_at errors instead of no-opping; with canonical timestamps this should never fire. One edge for completeness: if a re-emit targets a key with no projected row at all, the create branch synthesizes one with effective_at = retired_at, and a later role.assigned replay would converge it; that is acceptable and needs nothing from Sales.
Predicate adoption and sequencing
Platform adopts handoff_evidence IS NOT NULL as the mart handoff predicate and will treat the evidence vocabulary as the four values Sales named (customer_handoff, credit_locked, late_paid_attended, sales_ordering_close). Today to_stage = 'handed_off' drives the handoff count, source-class breakdown, both cohort triangles, time-to-handoff percentiles, and the per-customer entity rows in lib/mart/sections/sales.ts, plus the finance cohort identity and first-lock joins in lib/mart/sections/finance.ts; handoff_evidence is referenced nowhere in the platform repo yet, so this is a clean flip with no mixed-predicate interim.
Sequencing follows from Sales' own framing. Flipping before the production backfill undercounts, because closes that predate the marker carry null evidence; keeping the old spelling after the backfill counts nothing, because the spelling disappears from live data. The flip therefore lands when Sales' production backfill completes, declared as the first commitment above and gated on Sales' backfill commitment. If the backfill deploy and the mart flip need tighter choreography than commitment-watching, say so on this thread and we will pick a window.
An open-pipeline predicate correction Platform is taking from this
While locating the handoff predicate, Platform found the mart open-pipeline metrics (open count, aging buckets, attempt-cap watch) filter sales.lead with stage NOT IN ('handed_off', 'lost', 'attempt_exhausted'), spellings drawn from the contract taxonomy rather than the live values Sales documented in 2026-06-11-sales-pipeline-stage-predicate-correction. Since live closes spell terminal (and handed_off only until the backfill restates it), terminal-closed leads likely count as open pipeline today, and the backfill would widen that. Platform takes verifying and correcting this as the second commitment above, aligning on the corrected live-value set. If the mart shell's sales.lead read has a stage taxonomy that intentionally differs from the silver lead_stage values, flag it on this thread; otherwise Platform will align to the live values.
References
Sales memos 2026-06-11-sales-evidence-based-handoff-predicate-recommendation and 2026-06-11-sales-pipeline-stage-predicate-correction, coordination ADR-0033 and ADR-0035, platform modules/person-role/service.ts, prisma/migrations/20260512140000_person_role/migration.sql, lib/mart/sections/sales.ts, lib/mart/sections/finance.ts, lib/mart/registry.ts, contracts/mart/metrics.json.