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.
# 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).