Why one renter became two people: read of the six split records

Read-only production read, 2026-11-04, for the spine-health nightly red (Alert-ID automation_run_failure/co.propflow.spine-health-daily). Code read at origin/main 8c2ac122cbe. No writes. Phone numbers and emails are redacted; companies are named by class only. Board: spine-split-writer.

In one line. When a lead has no phone or email we can normalise, the conversation save keys a temporary person on the raw contact, but the prospect save either never sees that contact or refuses to reuse the conversation's temporary person, so it makes a second one. Fixing the key on the prospect side merges two of the four classes. The other two are clean-up only, and one of those must never be merged.
The mechanism, the four classes, and what was not read

The mechanism

The four classes

ClassRecordsWhat the two saves hadVerdict
A · international phone (voice)3 prospects, 1 propertyConversation keyed on the raw +593… phone; all 3 calls already share one person. Prospect side: contact dropped by the resolver. Order: conversation first, prospect 1–3 min later (post-call capture).Converges with the fix. Merge the 3 sentinels into the call person.
B · listing-site relay (email)1 prospectBoth sides held the same relay address; the prospect side keyed on prospect:<id> instead. Order: prospect first. Original writer (tour-reply processor) retired by #25005; Clara's tour tools hit the same seam today.Converges with the fix. Merge the sentinel into the relay person.
C · company's own forwarded line1 prospectThe caller came in on the customer's forwarded main line (an org-own number in config/phone-registry.json). dropOrgOwnContacts strips it, so the conversation is keyed on conv:<id>. The prospect person is a real staff/test person with a stale phone claim on that line.Must NOT converge: keying on that line would merge every after-hours caller into one person. Heal the stale claim only.
D · guest card1 prospectThe prospect's sentinel was already merged into a real backfilled person (guest card), which still carries a stray sentinel claim. The conversation's relay person is separate.Clean-up only (deferred guest-card link). Merge the relay person into the real one.

Separately, one duplicated tenancy (a former primary row left after a co-tenant swap) heals through scripts/dedup-occupancy-rows.ts; it is not a save-path problem.

What was not read

PropFlow Docs