Record how rapid identification matched, so adoption is measurable - #44
Merged
Conversation
`matchedBy` shipped in v1.10.0 as the adoption observable and then went nowhere: clients select
it and discard it, and nothing records it. The only signal was `noticeError`, which counts
failures with no denominator -- you could see refusals but not the rate.
Records three attributes on the transaction instead. One is not enough: `matchedBy: 'email'`
conflates three populations that need completely different responses --
- the member has no stored id yet (client rollout, or a genuinely new member)
- the member's ids are all dead, malformed, or unreachable
- the member's ids point at two simultaneously-active customers
The first resolves itself as the fleet adopts, the second is an infrastructure or data question,
and the third is a duplicate pair that needs merging in Omeda. So `resolveOmedaCustomerId` now
returns `{ customerId, outcome }` rather than a bare id, and the resolver reports:
omedaMatchedBy customerId | email -- what Omeda actually matched on
omedaIdResolution resolved | diverged | none-active | unresolvable | too-many |
malformed-only | none-supplied
omedaCandidateIdCount how many ids the caller sent -- the adoption denominator
The reason is returned rather than only reported because distinguishing those cases by parsing
`noticeError` message strings would be fragile.
`resolved` alongside `matchedBy: 'email'` is meaningful on its own: that is the lookup-to-post
race, where the id resolved cleanly and SCAO then rejected it.
Additive and internal -- no schema change, no client change, so no coordinated deploy. The agent
no-ops when disabled or outside a transaction, which matters on the blocking path of
authentication.
14 specs (was 13): every outcome in the vocabulary is now asserted, including a new
malformed-only case that nothing previously covered.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The gap
matchedByshipped in v1.10.0 as the adoption observable for deterministic customer matching —and then went nowhere. Clients select it in the mutation document and discard it
(
rapid-identify.jsdestructures only{ id, encryptedCustomerId }), and the service never recordsit. Outside
DEBUG=omedaon a laptop, nothing sees it.The consequence is asymmetric: refusals are observable — every fallback path calls
noticeError— but there is no denominator, so you can count divergent members and not theadoption rate. The verification plan for #43 said "facet
matchedByand watch adoption goemail→customerId". That was not actually possible. This makes it possible.Why three attributes and not one
matchedBy: 'email'conflates three populations that need completely different responses:So
resolveOmedaCustomerIdnow returns{ customerId, outcome }rather than a bare id, and theresolver records:
The reason is returned rather than only reported because telling those cases apart by parsing
noticeErrormessage strings would be fragile.resolvedalongsidematchedBy: 'email'is meaningful on its own: that is the lookup-to-post race,where the id resolved cleanly and SCAO then rejected it. The two attributes are complementary, not
redundant.
Queries this enables
Scope
Additive and internal. No schema change, no client change, so no coordinated deploy — unlike
#43, this one ships on its own. The New Relic agent no-ops when disabled or outside a transaction,
so the call is inert rather than fatal, which matters on the blocking path of authentication.
Tests
14 specs, up from 13. Every outcome in the vocabulary is now asserted, including a new
malformed-onlycase that nothing previously covered — deliberately distinct fromnone-supplied,since "the client stored something unusable" and "the client stored nothing" are different problems.
Lint clean; schema verified to still build via introspection.
After merge
Bump
lerna.json+services/graphql/package.jsonto 1.10.1 and push thev1.10.1tag — thattag triggers the deploy.
🤖 Generated with Claude Code