LG-14725: Identity resolution with Socure#11479
Conversation
More generic, now state_address_resolution_result
Go with more generic residential_address_resolution_result
Pull out all the logic into an include-able module so all you have to provide is a proofer + cost token.
Gonna share some code here.
State ID + Residential address variants
Add 3 new configs: - idv_resolution_default_vendor - idv_resolution_alternate_vendor - idv_resolution_alternate_vendor_percent These configs allow randomly using an alternate identity resolution vendor in the ProgressiveProofer. changelog: Internal, Identity verification, Enable using multiple identity resolution vendors
If an exception happens earlier in the job, then document_capture_session can be nil here. Previously this would raise a NoMethodError, obscuring the root cause.
There were some autoloading issues in cases where calling code wants access to the Error class but does not load the Request class first
In practice, applicant will have extra fields like `state_id_jurisdiction`, `state_id_number`, etc.
Remove vendor-specific plugins, and go with general-purpose plugins that can have a proofer and sp_cost_token injected into them.
Temporarily fall back to proofer_mock_fallback Until we have idv_resolution_default_vendor specified in all production environments, fall back to using proofer_mock_fallback.
| acuant_result | ||
| lexis_nexis_resolution | ||
| lexis_nexis_address | ||
| mock_resolution |
There was a problem hiding this comment.
We were previously creating SP cost records for mock resolutions as lexis_nexis_resolution. This PR now starts sending mock_resolution in those cases.
| lexis_nexis_address | ||
| mock_resolution | ||
| gpo_letter | ||
| socure_resolution |
There was a problem hiding this comment.
Checking with biz ops to make sure this new SP cost type doesn't make anything explode.
There was a problem hiding this comment.
Chris had expressed to me a desire to have FA1 and FA3 Socure calls recorded separately, since they are separate products. I think this naming is probably a good precedent if Timnit will be socure_docauth or such.
There was a problem hiding this comment.
✅ got the ok that sending a new sp cost type will not break anything
| proofer:, | ||
| sp_cost_token: | ||
| ) | ||
| @proofer = proofer |
There was a problem hiding this comment.
Responsibility for creating Proofer instances is moved into ProgressiveProofer, allowing these *AddressPlugin classes to be shared between Socure / InstantVerify.
| transaction_id: result.transaction_id, | ||
| ) | ||
| end | ||
| end | ||
|
|
||
| def proofer |
There was a problem hiding this comment.
Logic moved into ProgressiveProofer
| super(build_message) | ||
| end | ||
| class Request | ||
| class Error < StandardError |
There was a problem hiding this comment.
There were some autoloading issues with RequestError not being inside Request, so I moved it inside and renamed it to just Error.
n1zyy
left a comment
There was a problem hiding this comment.
I've now made my way through this and it seems good. I haven't done the manual testing proposed so I am going to hold off on leaving an approval, but otherwise I'm on board with this.
| lexis_nexis_address | ||
| mock_resolution | ||
| gpo_letter | ||
| socure_resolution |
There was a problem hiding this comment.
Chris had expressed to me a desire to have FA1 and FA3 Socure calls recorded separately, since they are separate products. I think this naming is probably a good precedent if Timnit will be socure_docauth or such.
| end | ||
|
|
||
| def sp_cost_token | ||
| token = PROOFING_VENDOR_SP_COST_TOKENS[proofing_vendor] |
There was a problem hiding this comment.
This elicited in my mind a funny image of us paying vendors with tokens like an arcade. 🪙👾
There was a problem hiding this comment.
I'm only half-way through this review (wanted to post for the sake of getting some of these comments up), so I'll just post as a comment, but and I do think there are a couple of blocking changes around config validation and a couple of open questions.
| idv_resolution_alternate_vendor: mock | ||
| idv_resolution_alternate_vendor_percent: 0 | ||
| idv_resolution_default_vendor: mock |
There was a problem hiding this comment.
(suggestion, non-blocking): consider combining these into a :json type that takes a hash that maps vendor to traffic percentage. It adds additional complexity, but feels easier to discern where the traffic is going at a glance. It would also eliminate the need for the default/alternate keys.
Maybe something like:
idv_resolution_vendor_traffic_balance_config: '{ "instant_verify": 0, "socure": 0, "mock": 100}'There was a problem hiding this comment.
Yeah that's a good suggestion, but in this case I'm match patterns in place elsewhere in our config (not saying those are correct, but I think consistency is good). In practice right now you won't ever use 3 vendors, so this feels like a problem we can figure out how to solve when we get there.
Some of this is also speculative--I'm assuming that our strategy for determining which vendor to send to will be random chance, but it could also be informed by what we learn during the shadow mode pilot.
spec/services/proofing/resolution/plugins/state_id_address_plugin_spec.rb
Show resolved
Hide resolved
| end | ||
|
|
||
| context 'when proofing_vendor is another value' do | ||
| let(:proofing_vendor) { :🦨 } |
There was a problem hiding this comment.
todo: Make sure to update the class doc comment!
| end | ||
|
|
||
| if default_vendor.blank? | ||
| raise InvalidProofingVendorError, 'idv_resolution_default_vendor not configured' |
There was a problem hiding this comment.
issue: This type of configuration loading/validation should happen during startup automatically. Consider moving additional validation to an initializer.
There was a problem hiding this comment.
This is a good call-out. I'll look into how we handle this in intializers
There was a problem hiding this comment.
The closest we have to config validation prior art is unused_identity_config_keys.rb, but setting up globally available config is probably best demonstrated by the telephony initializer.
There was a problem hiding this comment.
Would using an enum in the definition of the config work?
As an example of an existing one:
config.add(
:openid_connect_redirect,
type: :string,
enum: ['server_side', 'client_side', 'client_side_js'],
)| return default_vendor | ||
| end | ||
|
|
||
| if (rand * 100) <= alternate_vendor_percent |
There was a problem hiding this comment.
suggestion: use rand(1..101), this will always give you an integer between (inclusive) 1 and 100.
There was a problem hiding this comment.
I had to double check the docs (link), but it looks like rand(1..101) will include 101. ... excludes the ending value, but .. is inclusive.
| when :mock then create_mock_proofer | ||
| when :socure then create_socure_proofer | ||
| else | ||
| raise InvalidProofingVendorError |
There was a problem hiding this comment.
suggestion: Add a message for the InvalidProofingVendorError like
"Cannot create unknown proofing vendor: '#{proofing_vendor}'"| # Initially we will have production environments configured with | ||
| # `proofer_mock_fallback: false` | ||
| # We need to honor that configuration until all environments are | ||
| # updated to use idv_resolution_default_vendor | ||
| if !IdentityConfig.store.proofer_mock_fallback | ||
| if default_vendor == :mock | ||
| return :instant_verify | ||
| end | ||
| end | ||
|
|
There was a problem hiding this comment.
question: does this mean the mock proofer is never used when proofer_mock_fallback is false?
There was a problem hiding this comment.
Currently, when proofer_mock_fallback is true (the default), we use mocked versions of instant verify / phone finder proofers. When it is configured to be false, (i.e., in staging + prod), we use the actual proofers
There was a problem hiding this comment.
Ah, I see. I had a hard time wrapping my head around this. I don't know if that means we a more detailed doc comment here, but it's probably fine as is since it is temporary?
There was a problem hiding this comment.
I may actually pull this bit out into a separate PR as it's genuinely tricky and I don't want it to get lost among everything else
|
On the first step, I ran into this: It does technically fulfill the requirement:
But the fact that it returns a 500 for unclear reasons is not great. I don't know if this is a separate issue or related, but I initially had Edit: That block was a mess, but the salient bit: "proofing_results":{"exception":"the server responded with status 500 (500)","timed_out":false |
That is indeed weird, I will take a look |
n1zyy
left a comment
There was a problem hiding this comment.
After some (self-induced?) bumps away, I have successfully run through the manual testing steps, so I'm giving this an approval. The 500 error I got talking to Socure is concerning, but I understand that it's not strictly relevant here. If you're confident it's not a new issue introduced here, I'm good.
|
Clearing my previous "request changes" with "approval" knowing that some of this PR will get split up. |
|
I'm going to close this PR and open a new one on Monday--I'd like to clean up the history a bit so it is a little clearer what's going on. |
🎫 Ticket
Link to the relevant ticket:
LG-14725
🛠 Summary of changes
This PR adds the ability to configure the vendor used by the
ProgressiveProoferfor identity resolution. To do this, it does a couple of things:instant_verify_state_id_address_resultbecomesstate_id_address_result--this is relatively noisy, sorry)case proofing_vendor-type code to create the correct proofer based on the configured vendorNew configuration flags
Historically, we've had one flag controlling resolution proofing vendor:
proofer_mock_fallback. Iftrue, then mock proofers are used for both identity and address resolution. Iffalse, then LexisNexis InstantVerify is used for identity resolution and PhoneFinder is used for address resolution.This PR adds three new config flags:
idv_resolution_default_vendormockidv_resolution_alternate_vendormockidv_resolution_alternate_vendor_percent0Either
_vendorconfig can be set to one of the following:mockinstant_verifysocureIf
idv_resolution_alternate_vendor_percentis greater than zero, then that percentage of resolutions will use the alternate vendor. Note that whichever vendor is selected is used for both state ID and residential address transactions.While this update rolls out, we will, um, fall back to the
proofer_mock_fallbackconfig ifidv_resolution_default_vendoris set tomock. So in production, if no new configuration is applied, the existingproofer_mock_fallbackconfig will take precedence andinstant_verifywill be used.The intention here is to allow us to clean up configurations in staging and production after this code goes live. LG-15010 covers eventually removing the
proofer_mock_fallbackconfig entirely.📜 Testing Plan
First, configure Socure in your
application.yml:Next, send all of your resolutions to Socure:
Now, run through IdV. Check your
log/events.logfile and verify that your most recentIdV: doc auth verify proofing resultsevent uses the vendorsocure_kyc.Now, send all your resolutions to the mock proofer:
Run through IdV and verify that your most recent
IdV: doc auth verify proofing resultsevent uses the vendormock.Now, remove the three
idv_resolution_*configs from yourapplication.ymland disableproofer_mock_fallback(simulating the configuration in prod):Go through IdV one more time. This should fail on the "Verify info" step. If you look at your most recent
IdV: doc auth verify proofing results, you should see that it attempted to proof you with LexisNexis InstantVerify but got an exception (because you can't connect to IV from your developer laptop).