How often guest-card joins refuse on names (Oct 2026)

Read-only count over the join's whole life (shipped 2026-09-24; 30-day query window). Counts only, no personal data. Board row: measure-how-often-a-guest-card-join-refuses-because-the-names-do-not-match.

Result: 8 real-company attempts in 8.3 days — 2 joined, 6 refused because first names differ (4 share a last name; 1 looks like a nickname); 0 errors. No decision page warranted.

Full counts and queries
# Guest-card join refusals — counts (redacted; counts only)

Measured 2026-10-02 ~16:00Z, read-only. Source: CloudWatch Logs Insights over
/aws/lambda/propflow-appfolio-sync-prod (retention: never expire), window = last 30 days.
The join (bridge-record-join.ts, PR #11505, commit b50b0f84249) first logged 2026-09-24 08:48Z,
so the window covers its WHOLE life: ~8.3 days, not 30. Nothing earlier exists to read.

## Method caveats
- "Attempts" has no log line of its own: 10 = joined (2) + name-refused (8) + heal-skipped (0) + error (0), derived.
- The `name_unconfirmed` log line proves each refusal; the name breakdown below is plain lowercase equality on stored first/last fields (not the production comparator) and only explains WHY.

## Counts (distinct phone-record/email-record pairs; one guest card per pair)
| | all orgs | real companies only (sandbox orgs removed) |
|---|---|---|
| join attempts (pairs that reached the join) | 10 | 8 |
| joined | 2 | 2 |
| refused: names did not confirm (name_unconfirmed) | 8 | 6 |
| refused: merge rule said no (heal skip, any reason) | 0 | 0 |
| failed transiently (error) | 0 | 0 |
Refusal share: 8/10 all orgs, 6/8 real companies. Real refusals span 2 companies; the 2 joins are both in one of them.
Raw log lines: 3,477 refusal lines for those 8 pairs (see retry note), 2 join lines.

## Why the names failed (read-only GetItem of the 16 Person profile rows; booleans only)
- 8/8 refused pairs: both records carry a first name and the first names DIFFER.
- 4/8 also share a last name (different first name, same surname — the household shape the guard exists for).
- 3/8 differ on both first and last name. 1/8 has no last name on either record.
- 1/8 (a real company) looks like a short-form/nickname: same initial, one name a prefix of the other. That is the only plausible false refusal.
- 0/8 refused because a name was simply missing while the rest agreed.

## Side finding (not this row's question)
4 of the 8 refused pairs (all in one real company) are re-logged on EVERY sync tick (~860–880 lines each over 8 days),
though the code comment says a refused card settles as a miss until its contact details change. The other 4 logged
exactly twice (first tick + settle), as designed. Harmless to customers (no merge happens), but the settle does not stick for some cards.

## Positive controls
- Same query found 818,404 `pms_record_mirror_summary` lines in the group (query is reading the right log group).
- Success counterpart found: 2 `bridge_record_join` lines + 2 matching `identity_merge_event` merged lines.
- All 16 Person profile reads returned a row (no missing reads counted as "no name").

## Queries
Insights (30d): `fields @message | filter @message like /bridge_record_join|GuestCardSync\[mirror\]/ | parse @message /"message":"(?<msg>[a-z_]+)/ | stats count(*) as n, min(@timestamp), max(@timestamp) by msg`
Split: `filter @message like /bridge_record_join_skipped/ | parse reason, phonePersonId, emailPersonId, organizationId | stats count(*), count_distinct(concat(pp,"|",ep)), count_distinct(org) by reason`
Heal-side: `filter @message like /identity_merge_event/ and @message like /GuestCardSync/ | parse outcome, reason | stats count(*) by outcome, reason`
Runner: _idcount/q.sh. Per-pair ids are in pairs.private.jsonl (mode 0600, never shared).
PropFlow Docs