From 1782e528bc712ecb1da3da9ec5fb8b55361f3014 Mon Sep 17 00:00:00 2001 From: Mawar2 Date: Thu, 2 Oct 2025 11:17:50 -0400 Subject: [PATCH 1/5] LG-16417 Remove KYC Shadowmode --- app/jobs/resolution_proofing_job.rb | 40 +- app/jobs/socure_shadow_mode_proofing_job.rb | 129 ----- app/services/analytics_events.rb | 25 - app/services/idv/analytics_events_enhancer.rb | 2 - config/application.yml.default | 3 - config/initializers/ab_tests.rb | 8 - lib/identity_config.rb | 3 - lib/reporting/api_transaction_count_report.rb | 24 - spec/config/initializers/ab_tests_spec.rb | 40 -- spec/jobs/resolution_proofing_job_spec.rb | 149 ----- .../socure_shadow_mode_proofing_job_spec.rb | 515 ------------------ 11 files changed, 1 insertion(+), 937 deletions(-) delete mode 100644 app/jobs/socure_shadow_mode_proofing_job.rb delete mode 100644 spec/jobs/socure_shadow_mode_proofing_job_spec.rb diff --git a/app/jobs/resolution_proofing_job.rb b/app/jobs/resolution_proofing_job.rb index fd10aa46f1d..64b8625952d 100644 --- a/app/jobs/resolution_proofing_job.rb +++ b/app/jobs/resolution_proofing_job.rb @@ -26,7 +26,7 @@ def perform( service_provider_issuer: nil, threatmetrix_session_id: nil, request_ip: nil, - proofing_components: nil + proofing_components: nil # rubocop:disable Lint/UnusedMethodArgument ) timer = JobHelpers::Timer.new @@ -77,34 +77,6 @@ def perform( timing: timer.results, user_id: user.uuid, ) - - if use_shadow_mode?(user:, proofing_components:) - SocureShadowModeProofingJob.perform_later( - document_capture_session_result_id: document_capture_session&.result_id, - encrypted_arguments:, - service_provider_issuer:, - user_email: user_email_for_proofing(user), - user_uuid: user.uuid, - ) - end - end - - # @param user [User] - # @param proofing_components [Hash,nil] - def use_shadow_mode?(user:, proofing_components:) - # Let idv_socure_shadow_mode_enabled setting control shadow mode globally - disabled_globally = !IdentityConfig.store.idv_socure_shadow_mode_enabled - return false if disabled_globally - - # If the user went through Socure docv, they are already a Socure user and - # are thus eligible for shadow mode. - enabled_for_docv_users = - IdentityConfig.store.idv_socure_shadow_mode_enabled_for_docv_users - is_docv_user = proofing_components&.dig(:document_check) == Idp::Constants::Vendors::SOCURE - return true if enabled_for_docv_users && is_docv_user - - # Otherwise fall back to A/B test - shadow_mode_ab_test_bucket(user:) == :socure_shadow_mode_for_non_docv_users end private @@ -163,14 +135,4 @@ def progressive_proofer(user:, proofing_vendor:) user_uuid: user.uuid, proofing_vendor:, user_email: user_email_for_proofing(user), ) end - - def shadow_mode_ab_test_bucket(user:) - AbTests::SOCURE_IDV_SHADOW_MODE_FOR_NON_DOCV_USERS.bucket( - request: nil, - service_provider: nil, - session: nil, - user:, - user_session: nil, - ) - end end diff --git a/app/jobs/socure_shadow_mode_proofing_job.rb b/app/jobs/socure_shadow_mode_proofing_job.rb deleted file mode 100644 index b5616c32c05..00000000000 --- a/app/jobs/socure_shadow_mode_proofing_job.rb +++ /dev/null @@ -1,129 +0,0 @@ -# frozen_string_literal: true - -class SocureShadowModeProofingJob < ApplicationJob - include JobHelpers::StaleJobHelper - - queue_as :low - - discard_on JobHelpers::StaleJobHelper::StaleJobError - - # @param [String] document_capture_session_result_id - # @param [String] encrypted_arguments - # @param [String,nil] service_provider_issuer - # @param [String] user_email - # @param [String] user_uuid - def perform( - document_capture_session_result_id:, - encrypted_arguments:, - service_provider_issuer:, - user_email:, - user_uuid: - ) - raise_stale_job! if stale_job?(enqueued_at) - - user = User.find_by(uuid: user_uuid) - raise "User not found: #{user_uuid}" if !user - - analytics = create_analytics( - user:, - service_provider_issuer:, - ) - - proofing_result = load_proofing_result(document_capture_session_result_id:) - if !proofing_result - analytics.idv_socure_shadow_mode_proofing_result_missing - return - end - - applicant = build_applicant(encrypted_arguments:, user_email:) - - socure_result = proofer(user:).proof(applicant) - - analytics.idv_socure_shadow_mode_proofing_result( - resolution_result: format_proofing_result_for_logs(proofing_result), - socure_result: socure_result.to_h, - phone_source: applicant[:phone_source], - user_id: user.uuid, - pii_like_keypaths: [ - [:errors, :ssn], - [:resolution_result, :context, :stages, :resolution, :errors, :ssn], - [:resolution_result, :context, :stages, :residential_address, :errors, :ssn], - [:resolution_result, :context, :stages, :threatmetrix, :response_body, :first_name], - [:resolution_result, :context, :stages, :state_id, :state_id_jurisdiction], - [:resolution_result, :context, :stages, :state_id, :state_id_jurisdiction], - [:resolution_result, :biographical_info, :identity_doc_address_state], - [:resolution_result, :biographical_info, :state_id_jurisdiction], - [:resolution_result, :biographical_info, :same_address_as_id], - ], - ) - end - - def create_analytics( - user:, - service_provider_issuer: - ) - Analytics.new( - user:, - request: nil, - sp: service_provider_issuer, - session: {}, - ) - end - - def format_proofing_result_for_logs(proofing_result) - proofing_result.to_h.tap do |hash| - hash.dig(:context, :stages, :threatmetrix)&.delete(:response_body) - end - end - - def load_proofing_result(document_capture_session_result_id:) - DocumentCaptureSession.new( - result_id: document_capture_session_result_id, - ).load_proofing_result&.result - end - - def build_applicant( - encrypted_arguments:, - user_email: - ) - decrypted_arguments = JSON.parse( - Encryption::Encryptors::BackgroundProofingArgEncryptor.new.decrypt(encrypted_arguments), - symbolize_names: true, - ) - - applicant_pii = decrypted_arguments[:applicant_pii] - if applicant_pii[:phone].nil? && applicant_pii[:best_effort_phone_number_for_socure] - applicant_pii[:phone] = applicant_pii[:best_effort_phone_number_for_socure][:phone] - applicant_pii[:phone_source] = applicant_pii[:best_effort_phone_number_for_socure][:source] - end - - { - **applicant_pii.slice( - :first_name, - :last_name, - :address1, - :address2, - :city, - :state, - :zipcode, - :phone, - :phone_source, - :dob, - :ssn, - :consent_given_at, - ), - email: user_email, - } - end - - def proofer(user:) - @proofer ||= Proofing::Socure::IdPlus::Proofer.new( - Proofing::Socure::IdPlus::Config.new( - user_uuid: user.uuid, - api_key: IdentityConfig.store.socure_idplus_api_key, - base_url: IdentityConfig.store.socure_idplus_base_url, - timeout: IdentityConfig.store.socure_idplus_timeout_in_seconds, - ), - ) - end -end diff --git a/app/services/analytics_events.rb b/app/services/analytics_events.rb index 7587b1a5367..61ab541de81 100644 --- a/app/services/analytics_events.rb +++ b/app/services/analytics_events.rb @@ -5567,31 +5567,6 @@ def idv_socure_reason_code_download( ) end - # Logs a Socure KYC result alongside a resolution result for later comparison. - # @param [Hash] socure_result Result from Socure KYC API call - # @param [Hash] resolution_result Result from resolution proofing - # @param [String,nil] phone_source Whether the phone number is from MFA or hybrid handoff - def idv_socure_shadow_mode_proofing_result( - socure_result:, - resolution_result:, - phone_source:, - **extra - ) - track_event( - :idv_socure_shadow_mode_proofing_result, - resolution_result: resolution_result.to_h, - phone_source:, - socure_result: socure_result.to_h, - **extra, - ) - end - - # Indicates that no proofing result was found when SocureShadowModeProofingJob - # attempted to look for one. - def idv_socure_shadow_mode_proofing_result_missing(**extra) - track_event(:idv_socure_shadow_mode_proofing_result_missing, **extra) - end - # @param [Boolean] success Whether form validation was successful # @param [Hash] errors Errors resulting from form validation # @param [String] exception diff --git a/app/services/idv/analytics_events_enhancer.rb b/app/services/idv/analytics_events_enhancer.rb index 20a3af1ea08..746c5c164cb 100644 --- a/app/services/idv/analytics_events_enhancer.rb +++ b/app/services/idv/analytics_events_enhancer.rb @@ -62,8 +62,6 @@ module AnalyticsEventsEnhancer idv_in_person_usps_proofing_results_job_user_sent_to_fraud_review idv_ipp_deactivated_for_never_visiting_post_office idv_socure_reason_code_download - idv_socure_shadow_mode_proofing_result - idv_socure_shadow_mode_proofing_result_missing idv_socure_verification_data_requested idv_usps_auth_token_refresh_job_completed idv_usps_auth_token_refresh_job_network_error diff --git a/config/application.yml.default b/config/application.yml.default index f15c6850647..cbe9c79fab0 100644 --- a/config/application.yml.default +++ b/config/application.yml.default @@ -211,8 +211,6 @@ idv_socure_reason_code_download_enabled: false idv_socure_reason_codes_docv_selfie_fail: '[]' idv_socure_reason_codes_docv_selfie_not_processed: '[]' idv_socure_reason_codes_docv_selfie_pass: '[]' -idv_socure_shadow_mode_enabled: false -idv_socure_shadow_mode_enabled_for_docv_users: true idv_sp_required: false in_person_completion_survey_delivery_enabled: false in_person_completion_survey_url: 'https://login.gov' @@ -457,7 +455,6 @@ socure_docv_webhook_secret_key: '' socure_docv_webhook_secret_key_queue: '[]' socure_idplus_api_key: '' socure_idplus_base_url: '' -socure_idplus_shadow_mode_percent: 0 socure_idplus_timeout_in_seconds: 5 socure_reason_code_api_key: '' socure_reason_code_base_url: '' diff --git a/config/initializers/ab_tests.rb b/config/initializers/ab_tests.rb index 00d688b435a..ea87484d767 100644 --- a/config/initializers/ab_tests.rb +++ b/config/initializers/ab_tests.rb @@ -159,14 +159,6 @@ def self.all user&.uuid end.freeze - SOCURE_IDV_SHADOW_MODE_FOR_NON_DOCV_USERS = AbTest.new( - experiment_name: 'Socure shadow mode', - should_log: ['IdV: doc auth verify proofing results'].to_set, - buckets: { - socure_shadow_mode_for_non_docv_users: IdentityConfig.store.socure_idplus_shadow_mode_percent, - }, - ).freeze - PROOFING_VENDOR = AbTest.new( experiment_name: 'Proofing Vendor', should_log: /^idv/i, diff --git a/lib/identity_config.rb b/lib/identity_config.rb index f30f3f42ea9..9a8c7cd146d 100644 --- a/lib/identity_config.rb +++ b/lib/identity_config.rb @@ -238,8 +238,6 @@ def self.store config.add(:idv_socure_reason_codes_docv_selfie_fail, type: :json) config.add(:idv_socure_reason_codes_docv_selfie_not_processed, type: :json) config.add(:idv_socure_reason_codes_docv_selfie_pass, type: :json) - config.add(:idv_socure_shadow_mode_enabled, type: :boolean) - config.add(:idv_socure_shadow_mode_enabled_for_docv_users, type: :boolean) config.add(:idv_sp_required, type: :boolean) config.add(:idv_aamva_split_last_name_states, type: :json) config.add(:in_person_completion_survey_delivery_enabled, type: :boolean) @@ -481,7 +479,6 @@ def self.store config.add(:socure_docv_webhook_secret_key, type: :string) config.add(:socure_idplus_api_key, type: :string) config.add(:socure_idplus_base_url, type: :string) - config.add(:socure_idplus_shadow_mode_percent, type: :integer) config.add(:socure_idplus_timeout_in_seconds, type: :integer) config.add(:socure_reason_code_api_key, type: :string) config.add(:socure_reason_code_base_url, type: :string) diff --git a/lib/reporting/api_transaction_count_report.rb b/lib/reporting/api_transaction_count_report.rb index d4574721a8d..c79103f835a 100644 --- a/lib/reporting/api_transaction_count_report.rb +++ b/lib/reporting/api_transaction_count_report.rb @@ -319,30 +319,6 @@ def fraud_score_and_attribute_query QUERY end - def socure_kyc_shadow_query - <<~QUERY - fields - properties.event_properties.socure_result.success as success, - properties.event_properties.socure_result.timed_out as timed_out, - properties.event_properties.socure_result.transaction_id as transaction_id, - properties.event_properties.socure_result.vendor_name as vendor_name, - properties.event_properties.socure_result.verified_attributes.0 as v0, - properties.event_properties.socure_result.verified_attributes.1 as v1, - properties.event_properties.socure_result.verified_attributes.2 as v2, - properties.event_properties.socure_result.verified_attributes.3 as v3, - properties.event_properties.socure_result.verified_attributes.4 as v4, - properties.event_properties.socure_result.verified_attributes.5 as v5, - properties.event_properties.socure_result.errors.I352 as I352, - properties.event_properties.socure_result.errors.I900 as I900, - properties.event_properties.socure_result.errors.I901 as I901, - properties.event_properties.socure_result.errors.I902 as I902, - properties.event_properties.socure_result.errors.I919 as I919, - properties.event_properties.socure_result.errors.R354 as R354 - | filter name = "idv_socure_shadow_mode_proofing_result" - | limit 10000 - QUERY - end - def socure_kyc_non_shadow_query <<~QUERY fields @timestamp, @message, @logStream, @log diff --git a/spec/config/initializers/ab_tests_spec.rb b/spec/config/initializers/ab_tests_spec.rb index 623edb5d0c8..06ac96c69bb 100644 --- a/spec/config/initializers/ab_tests_spec.rb +++ b/spec/config/initializers/ab_tests_spec.rb @@ -481,46 +481,6 @@ end end - describe 'SOCURE_IDV_SHADOW_MODE_FOR_NON_DOCV_USERS' do - let(:user) { create(:user) } - - subject(:bucket) do - AbTests::SOCURE_IDV_SHADOW_MODE_FOR_NON_DOCV_USERS.bucket( - request: nil, - service_provider: nil, - session: nil, - user:, - user_session: nil, - ) - end - - before do - allow(IdentityConfig.store).to receive( - :socure_idplus_shadow_mode_percent, - ).and_return(0) - reload_ab_tests - end - - context 'when the A/B test is disabled' do - it 'does not return a bucket' do - expect(bucket).to be_nil - end - end - - context 'when the A/B test is enabled' do - before do - allow(IdentityConfig.store).to receive( - :socure_idplus_shadow_mode_percent, - ).and_return(100) - reload_ab_tests - end - - it 'returns a bucket' do - expect(bucket).to eq :socure_shadow_mode_for_non_docv_users - end - end - end - describe 'PROOFING_VENDOR' do let(:ab_test) { :PROOFING_VENDOR } diff --git a/spec/jobs/resolution_proofing_job_spec.rb b/spec/jobs/resolution_proofing_job_spec.rb index eb0e1b29c9e..374e632d675 100644 --- a/spec/jobs/resolution_proofing_job_spec.rb +++ b/spec/jobs/resolution_proofing_job_spec.rb @@ -548,155 +548,6 @@ end end - context 'Socure shadow mode test' do - let(:idv_socure_shadow_mode_enabled_for_docv_users) { false } - let(:idv_socure_shadow_mode_enabled) { false } - let(:doc_auth_vendor) { nil } - let(:in_shadow_mode_ab_test_bucket) { false } - - before do - allow(IdentityConfig.store).to receive(:idv_socure_shadow_mode_enabled_for_docv_users) - .and_return(idv_socure_shadow_mode_enabled_for_docv_users) - allow(IdentityConfig.store).to receive(:idv_socure_shadow_mode_enabled) - .and_return(idv_socure_shadow_mode_enabled) - - allow(instance).to receive(:shadow_mode_ab_test_bucket) do |user:| - expect(user).not_to eql(nil) - if in_shadow_mode_ab_test_bucket - :socure_shadow_mode_for_non_docv_users - end - end - - stub_vendor_requests - end - - context 'when enabled' do - let(:idv_socure_shadow_mode_enabled) { true } - - context 'and user is selected in A/B test' do - let(:in_shadow_mode_ab_test_bucket) { true } - - it 'schedules a SocureShadowModeProofingJob' do - expect(SocureShadowModeProofingJob).to receive(:perform_later).with( - user_email: user.email, - user_uuid: user.uuid, - document_capture_session_result_id: document_capture_session.result_id, - encrypted_arguments: satisfy do |ciphertext| - json = JSON.parse( - Encryption::Encryptors::BackgroundProofingArgEncryptor.new.decrypt(ciphertext), - symbolize_names: true, - ) - expect(json[:applicant_pii]).to eql( - { - first_name: 'FAKEY', - middle_name: nil, - last_name: 'MCFAKERSON', - name_suffix: 'JR', - address1: '1 FAKE RD', - identity_doc_address1: '1 FAKE RD', - identity_doc_address2: '', - identity_doc_city: 'GREAT FALLS', - identity_doc_address_state: 'MT', - identity_doc_zipcode: '59010-1234', - issuing_country_code: 'US', - address2: '', - same_address_as_id: 'true', - city: 'GREAT FALLS', - state: 'MT', - zipcode: '59010-1234', - dob: '1938-10-06', - sex: 'male', - height: 72, - weight: nil, - eye_color: nil, - ssn: '900661234', - state_id_jurisdiction: 'ND', - state_id_expiration: '2099-12-31', - state_id_issued: '2019-12-31', - state_id_number: '1111111111111', - document_type_received: 'drivers_license', - }, - ) - end, - service_provider_issuer: service_provider.issuer, - ) - - perform - end - - context 'and shadow mode also enabled for docv users' do - let(:idv_socure_shadow_mode_enabled_for_docv_users) { true } - - context 'when the user is a docv user' do - let(:proofing_components) do - { - document_check: Idp::Constants::Vendors::SOCURE, - } - end - it 'only schedules 1 SocureShadowModeProofingJob' do - expect(SocureShadowModeProofingJob).to receive(:perform_later).once - perform - end - end - end - end - - context 'and user is NOT selected in A/B test' do - let(:in_shadow_mode_ab_test_bucket) { false } - - it 'does not schedule a shadow mode job' do - expect(SocureShadowModeProofingJob).not_to receive(:perform_later) - perform - end - - context 'but shadow mode is enabled for docv users' do - let(:idv_socure_shadow_mode_enabled_for_docv_users) { true } - - context 'and the user happens to be a docv user' do - let(:proofing_components) do - { - document_check: Idp::Constants::Vendors::SOCURE, - } - end - - it 'schedules a SocureShadowModeProofingJob' do - expect(SocureShadowModeProofingJob).to receive(:perform_later).once - perform - end - end - - context 'except the user did not use Socure docv' do - it 'does not schedule a SocureShadowModeProofingJob' do - expect(SocureShadowModeProofingJob).not_to receive(:perform_later) - perform - end - end - end - end - end - - context 'when disabled' do - let(:idv_socure_shadow_mode_enabled) { false } - - it 'does not schedule a SocureShadowModeProofingJob' do - stub_vendor_requests - expect(SocureShadowModeProofingJob).not_to receive(:perform_later) - perform - end - - context 'but the flag to enable shadow mode for docv users was left on' do - let(:idv_socure_shadow_mode_enabled_for_docv_users) { true } - context 'when user is a docv user' do - it 'does not schedule a SocureShadowModeProofingJob' do - stub_vendor_requests - expect(SocureShadowModeProofingJob).not_to receive(:perform_later) - perform - end - end - end - end - end - it 'determines the UUID and UUID prefix and passes it to the downstream proofing vendors' do uuid_info = { uuid_prefix: service_provider.app_id, diff --git a/spec/jobs/socure_shadow_mode_proofing_job_spec.rb b/spec/jobs/socure_shadow_mode_proofing_job_spec.rb deleted file mode 100644 index a9fc8b2b886..00000000000 --- a/spec/jobs/socure_shadow_mode_proofing_job_spec.rb +++ /dev/null @@ -1,515 +0,0 @@ -# frozen_string_literal: true - -require 'rails_helper' - -RSpec.describe SocureShadowModeProofingJob do - let(:job) do - described_class.new - end - - let(:document_capture_session) do - DocumentCaptureSession.create(user:).tap do |dcs| - dcs.create_proofing_session - end - end - - let(:document_capture_session_result_id) do - document_capture_session.result_id - end - - let(:applicant_pii) do - Idp::Constants::MOCK_IDV_APPLICANT_WITH_SSN - end - - let(:encrypted_arguments) do - Encryption::Encryptors::BackgroundProofingArgEncryptor.new.encrypt( - JSON.generate({ applicant_pii: }), - ) - end - - let(:user) { create(:user) } - - let(:user_uuid) { user.uuid } - - let(:user_email) { user.email } - - let(:proofing_result) do - FormResponse.new( - success: true, - errors: {}, - extra: { - exception: nil, - timed_out: false, - threatmetrix_review_status: 'pass', - context: { - device_profiling_adjudication_reason: 'device_profiling_result_pass', - resolution_adjudication_reason: 'pass_resolution_and_state_id', - should_proof_state_id: true, - stages: { - resolution: { - success: true, - errors: {}, - exception: nil, - timed_out: false, - transaction_id: 'resolution-mock-transaction-id-123', - reference: 'aaa-bbb-ccc', - can_pass_with_additional_verification: false, - attributes_requiring_additional_verification: [], - vendor_name: 'ResolutionMock', - vendor_workflow: nil, - verified_attributes: nil, - }, - residential_address: { - success: true, - errors: {}, - exception: nil, - timed_out: false, - transaction_id: '', - reference: '', - can_pass_with_additional_verification: false, - attributes_requiring_additional_verification: [], - vendor_name: 'ResidentialAddressNotRequired', - vendor_workflow: nil, - verified_attributes: nil, - }, - state_id: { - success: true, - errors: {}, - exception: nil, - mva_exception: nil, - requested_attributes: {}, - timed_out: false, - transaction_id: 'state-id-mock-transaction-id-456', - vendor_name: 'StateIdMock', - verified_attributes: [], - }, - threatmetrix: { - client: nil, - success: true, - errors: {}, - exception: nil, - timed_out: false, - transaction_id: 'ddp-mock-transaction-id-123', - review_status: 'pass', - response_body: { - request_id: '1234', - request_result: 'success', - review_status: 'pass', - risk_rating: 'trusted', - summary_risk_score: '-6', - tmx_risk_rating: 'neutral', - tmx_summary_reason_code: ['Identity_Negative_History'], - first_name: '[redacted]', - }, - }, - }, - }, - biographical_info: { - birth_year: 1938, - identity_doc_address_state: nil, - same_address_as_id: nil, - state: 'MT', - state_id_jurisdiction: 'ND', - state_id_number: '#############', - }, - ssn_is_unique: true, - }, - ) - end - - let(:socure_idplus_base_url) { 'https://example.org' } - - before do - document_capture_session.store_proofing_result(proofing_result.to_h) - - allow(IdentityConfig.store).to receive(:socure_idplus_base_url) - .and_return(socure_idplus_base_url) - end - - describe '#perform' do - subject(:perform) do - allow(job).to receive(:create_analytics).and_return(analytics) - - job.perform( - document_capture_session_result_id:, - encrypted_arguments:, - service_provider_issuer: nil, - user_email:, - user_uuid:, - ) - end - - let(:analytics) do - FakeAnalytics.new - end - - let(:socure_response_body) do - { - referenceId: 'a1234b56-e789-0123-4fga-56b7c890d123', - kyc: { - reasonCodes: [ - 'I123', - 'R890', - ], - fieldValidations: { - firstName: 0.99, - surName: 0.99, - streetAddress: 0.99, - city: 0.99, - state: 0.99, - zip: 0.99, - mobileNumber: 0.99, - dob: 0.99, - ssn: 0.99, - }, - }, - customerProfile: { - customerUserId: user.uuid, - userId: 'u8JpWn4QsF3R7tA2', - }, - } - end - - let(:known_reason_codes) do - { - I123: 'Person is over seven feet tall.', - R567: 'Person may be an armadillo.', - R890: 'Help! I am trapped in a reason code factory!', - } - end - - before do - known_reason_codes.each do |(code, description)| - SocureReasonCode.create(code:, description:) - end - - stub_request(:post, 'https://example.org/api/3.0/EmailAuthScore') - .to_return( - headers: { - 'Content-Type' => 'application/json', - }, - body: JSON.generate(socure_response_body), - ) - end - - context 'when document capture session result is present in redis' do - let(:expected_event_body) do - { - user_id: user.uuid, - resolution_result: { - success: true, - errors: nil, - context: { - device_profiling_adjudication_reason: 'device_profiling_result_pass', - resolution_adjudication_reason: 'pass_resolution_and_state_id', - should_proof_state_id: true, - stages: { - residential_address: { - attributes_requiring_additional_verification: [], - can_pass_with_additional_verification: false, - errors: {}, - exception: nil, - reference: '', - success: true, - timed_out: false, - transaction_id: '', - vendor_name: 'ResidentialAddressNotRequired', - vendor_workflow: nil, - verified_attributes: nil, - }, - resolution: { - attributes_requiring_additional_verification: [], - can_pass_with_additional_verification: false, - errors: {}, - exception: nil, - reference: 'aaa-bbb-ccc', - success: true, - timed_out: false, - transaction_id: 'resolution-mock-transaction-id-123', - vendor_name: 'ResolutionMock', - vendor_workflow: nil, - verified_attributes: nil, - }, - state_id: { - errors: {}, - exception: nil, - mva_exception: nil, - requested_attributes: {}, - success: true, - timed_out: false, - transaction_id: 'state-id-mock-transaction-id-456', - vendor_name: 'StateIdMock', - verified_attributes: [], - }, - threatmetrix: { - client: nil, - errors: {}, - exception: nil, - review_status: 'pass', - success: true, - timed_out: false, - transaction_id: 'ddp-mock-transaction-id-123', - }, - }, - }, - exception: nil, - ssn_is_unique: true, - threatmetrix_review_status: 'pass', - timed_out: false, - biographical_info: { - birth_year: 1938, - identity_doc_address_state: nil, - same_address_as_id: nil, - state: 'MT', - state_id_jurisdiction: 'ND', - state_id_number: '#############', - }, - }, - socure_result: { - attributes_requiring_additional_verification: [], - can_pass_with_additional_verification: false, - errors: {}, - reason_codes: { - 'I123' => 'Person is over seven feet tall.', - 'R890' => 'Help! I am trapped in a reason code factory!', - }, - exception: nil, - reference: '', - success: true, - timed_out: false, - transaction_id: 'a1234b56-e789-0123-4fga-56b7c890d123', - customer_user_id: user.uuid, - vendor_name: 'socure_kyc', - vendor_workflow: nil, - verified_attributes: %i[address first_name last_name phone ssn dob].to_set, - }, - } - end - - it 'makes a proofing call' do - expect(job.proofer(user: user)).to receive(:proof).and_call_original - perform - end - - it 'does not log an idv_socure_shadow_mode_proofing_result_missing event' do - perform - expect(analytics).not_to have_logged_event(:idv_socure_shadow_mode_proofing_result_missing) - end - - it 'logs an event' do - perform - expect(analytics).to have_logged_event( - :idv_socure_shadow_mode_proofing_result, - expected_event_body, - ) - end - - context 'when the user has an MFA phone number' do - let(:applicant_pii) do - Idp::Constants::MOCK_IDV_APPLICANT_WITH_SSN.merge( - best_effort_phone_number_for_socure: { - source: :mfa, - phone: '1 202-555-0000', - }, - ) - end - - let(:encrypted_arguments) do - Encryption::Encryptors::BackgroundProofingArgEncryptor.new.encrypt( - JSON.generate({ applicant_pii: applicant_pii }), - ) - end - - it 'logs an event with the phone number' do - perform - expect(analytics).to have_logged_event( - :idv_socure_shadow_mode_proofing_result, - expected_event_body.merge(phone_source: 'mfa'), - ) - end - end - - context 'when socure proofer raises an error' do - before do - allow(job.proofer(user: user)).to receive(:proof).and_raise - end - - it 'does not squash the error' do - # If the Proofer encounters an error while _making_ a request, that - # will be returned as a Result with the `exception` property set. - # Other errors will be raised as normal. - expect { perform }.to raise_error - end - end - end - - context 'when document capture session result is not present in redis' do - let(:document_capture_session_result_id) { 'some-id-that-is-not-valid' } - - it 'logs an idv_socure_shadow_mode_proofing_result_missing event' do - perform - - expect(analytics).to have_logged_event( - :idv_socure_shadow_mode_proofing_result_missing, - ) - end - end - - context 'when job is stale' do - before do - allow(job).to receive(:stale_job?).and_return(true) - end - it 'raises StaleJobError' do - expect { perform }.to raise_error(JobHelpers::StaleJobHelper::StaleJobError) - end - end - - context 'when user is not found' do - let(:user_uuid) { 'some-user-id-that-does-not-exist' } - it 'raises an error' do - expect do - perform - end.to raise_error(RuntimeError, 'User not found: some-user-id-that-does-not-exist') - end - end - - context 'when encrypted_arguments cannot be decrypted' do - let(:encrypted_arguments) { 'bG9sIHRoaXMgaXMgbm90IGV2ZW4gZW5jcnlwdGVk' } - it 'raises an error' do - expect { perform }.to raise_error(Encryption::EncryptionError) - end - end - - context 'when encrypted_arguments contains invalid JSON' do - let(:encrypted_arguments) do - Encryption::Encryptors::BackgroundProofingArgEncryptor.new.encrypt( - 'this is not valid JSON', - ) - end - it 'raises an error' do - expect { perform }.to raise_error(JSON::ParserError) - end - end - - context 'when an unknown reason code is encountered' do - let(:socure_response_body) do - super().tap do |body| - body[:kyc][:reasonCodes] << 'I000' - end - end - - it 'still logs it' do - perform - expect(analytics).to have_logged_event( - :idv_socure_shadow_mode_proofing_result, - satisfy do |attributes| - reason_codes = attributes.dig(:socure_result, :reason_codes) - expect(reason_codes).to include( - 'I000' => '[unknown]', - ) - end, - ) - end - end - end - - describe '#build_applicant' do - subject(:build_applicant) do - job.build_applicant(encrypted_arguments:, user_email:) - end - - let(:expected_attributes) do - { - first_name: 'FAKEY', - last_name: 'MCFAKERSON', - address1: '1 FAKE RD', - address2: '', - city: 'GREAT FALLS', - state: 'MT', - zipcode: '59010-1234', - dob: '1938-10-06', - ssn: '900661234', - email: user.email, - } - end - - context 'when the user has a phone directly passed in' do - let(:applicant_pii) do - Idp::Constants::MOCK_IDV_APPLICANT_WITH_SSN.merge( - phone: '12025551212', - ) - end - - let(:encrypted_arguments) do - Encryption::Encryptors::BackgroundProofingArgEncryptor.new.encrypt( - JSON.generate({ applicant_pii: }), - ) - end - - it 'builds an applicant structure with that phone number' do - expect(build_applicant).to eql( - expected_attributes.merge(phone: '12025551212'), - ) - end - end - - context 'when the user has a hybrid-handoff phone' do - let(:applicant_pii_no_phone) do - Idp::Constants::MOCK_IDV_APPLICANT_WITH_SSN.merge( - best_effort_phone_number_for_socure: { - source: :hybrid_handoff, - phone: '12025556789', - }, - ) - end - - let(:encrypted_arguments) do - Encryption::Encryptors::BackgroundProofingArgEncryptor.new.encrypt( - JSON.generate({ applicant_pii: applicant_pii_no_phone }), - ) - end - - it 'builds an applicant using the hybrid handoff number' do - expect(build_applicant).to eql( - expected_attributes.merge( - phone: '12025556789', - phone_source: 'hybrid_handoff', - ), - ) - end - end - - context 'when no phone is available for the user' do - it 'does not set phone at all' do - expect(build_applicant).to eql(expected_attributes) - end - end - end - - describe '#create_analytics' do - it 'creates an Analytics instance with user and sp configured' do - analytics = job.create_analytics( - user:, - service_provider_issuer: 'some-issuer', - ) - expect(analytics.sp).to eql('some-issuer') - expect(analytics.user).to eql(user) - end - end - - describe '#proofer' do - it 'returns a configured proofer' do - allow(IdentityConfig.store).to receive(:socure_idplus_api_key).and_return('an-api-key') - allow(IdentityConfig.store).to receive(:socure_idplus_base_url).and_return('https://example.org') - allow(IdentityConfig.store).to receive(:socure_idplus_timeout_in_seconds).and_return(6) - - expect(job.proofer(user:).config.to_h).to eql( - user_uuid: user.uuid, - user_email: nil, - api_key: 'an-api-key', - base_url: 'https://example.org', - timeout: 6, - ) - end - end -end From 32792697cb3823b8e3ce9bfec9743aebd4eb342c Mon Sep 17 00:00:00 2001 From: Mawar2 Date: Thu, 2 Oct 2025 16:03:51 -0400 Subject: [PATCH 2/5] changelog: Internal, Code cleanup, Remove KYC Shadowmode evaluation code --- lib/reporting/api_transaction_count_report.rb | 8 -------- 1 file changed, 8 deletions(-) diff --git a/lib/reporting/api_transaction_count_report.rb b/lib/reporting/api_transaction_count_report.rb index c79103f835a..b651921116a 100644 --- a/lib/reporting/api_transaction_count_report.rb +++ b/lib/reporting/api_transaction_count_report.rb @@ -73,7 +73,6 @@ def api_transaction_count 'Instant verify', 'Phone Finder', 'Socure (DocV)', - 'Socure (KYC) - Shadow', 'Socure (KYC) - Non-Shadow', 'Fraud Score and Attribute', 'Threat Metrix (IDV)', @@ -85,7 +84,6 @@ def api_transaction_count instant_verify_table.first, phone_finder_table.first, socure_table.first, - socure_kyc_shadow_table.first, socure_kyc_non_shadow_table.first, fraud_score_and_attribute_table.first, threat_metrix_idv_table.first, @@ -128,12 +126,6 @@ def socure_kyc_non_shadow_table [socure_table_count, result] end - def socure_kyc_shadow_table - result = fetch_results(query: socure_kyc_shadow_query) - socure_table_count = result.count - [socure_table_count, result] - end - def instant_verify_table result = fetch_results(query: instant_verify_query) instant_verify_table_count = result.count From e3831fa65cfaed59744d0abd92dbcec9903704a8 Mon Sep 17 00:00:00 2001 From: Mawar2 Date: Thu, 2 Oct 2025 17:48:02 -0400 Subject: [PATCH 3/5] update for failing spec --- spec/lib/reporting/api_transaction_count_report_spec.rb | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/spec/lib/reporting/api_transaction_count_report_spec.rb b/spec/lib/reporting/api_transaction_count_report_spec.rb index 42f10eee3f5..39439e965ea 100644 --- a/spec/lib/reporting/api_transaction_count_report_spec.rb +++ b/spec/lib/reporting/api_transaction_count_report_spec.rb @@ -19,7 +19,6 @@ allow(report).to receive(:phone_finder_table).and_return([20, mock_results]) allow(report).to receive(:socure_table).and_return([25, mock_results]) allow(report).to receive(:socure_kyc_non_shadow_table).and_return([30, mock_results]) - allow(report).to receive(:socure_kyc_shadow_table).and_return([35, mock_results]) allow(report).to receive(:fraud_score_and_attribute_table).and_return([40, mock_results]) allow(report).to receive(:threat_metrix_idv_table).and_return([45, mock_results]) allow(report).to receive(:threat_metrix_auth_only_table).and_return([50, mock_results]) @@ -37,11 +36,11 @@ expect(header_row).to eq( ['Week', 'True ID', 'Instant verify', 'Phone Finder', 'Socure (DocV)', - 'Socure (KYC) - Shadow', 'Socure (KYC) - Non-Shadow', + 'Socure (KYC) - Non-Shadow', 'Fraud Score and Attribute', 'Threat Metrix (IDV)', 'Threat Metrix (Auth Only)'], ) expect(data_row.first).to eq("#{time_range.begin.to_date} - #{time_range.end.to_date}") - expect(data_row[1..]).to eq([10, 15, 20, 25, 35, 30, 40, 45, 50]) + expect(data_row[1..]).to eq([10, 15, 20, 25, 30, 40, 45, 50]) end end @@ -56,7 +55,7 @@ expect(csv).to match( / Week,True\ ID,Instant\ verify,Phone\ Finder, - Socure\ \(DocV\),Socure\ \(KYC\)\s-\sShadow,Socure\ \(KYC\)\s-\sNon-Shadow, + Socure\ \(DocV\),Socure\ \(KYC\)\s-\sNon-Shadow, Fraud\ Score\ and\ Attribute,Threat\ Metrix\s\(IDV\),Threat\ Metrix\s\(Auth\ Only\) /x, ) From f5f17a44dc2ec63c3e89787fd36459f4a8e3ae72 Mon Sep 17 00:00:00 2001 From: Mawar2 Date: Tue, 7 Oct 2025 11:12:56 -0400 Subject: [PATCH 4/5] address pr feedback --- .../concerns/idv/verify_info_concern.rb | 1 - app/jobs/resolution_proofing_job.rb | 3 +-- app/services/idv/agent.rb | 4 ---- lib/reporting/api_transaction_count_report.rb | 2 +- .../idv/in_person/verify_info_controller_spec.rb | 6 ------ .../api_transaction_count_report_spec.rb | 4 ++-- spec/services/idv/agent_spec.rb | 16 ---------------- 7 files changed, 4 insertions(+), 32 deletions(-) diff --git a/app/controllers/concerns/idv/verify_info_concern.rb b/app/controllers/concerns/idv/verify_info_concern.rb index b1a0be540a1..cbc996241cc 100644 --- a/app/controllers/concerns/idv/verify_info_concern.rb +++ b/app/controllers/concerns/idv/verify_info_concern.rb @@ -41,7 +41,6 @@ def shared_update threatmetrix_session_id: idv_session.threatmetrix_session_id, request_ip: request.remote_ip, ipp_enrollment_in_progress: ipp_enrollment_in_progress?, - proofing_components: ProofingComponents.new(idv_session:), proofing_vendor:, ) diff --git a/app/jobs/resolution_proofing_job.rb b/app/jobs/resolution_proofing_job.rb index 64b8625952d..7221f89ed7f 100644 --- a/app/jobs/resolution_proofing_job.rb +++ b/app/jobs/resolution_proofing_job.rb @@ -25,8 +25,7 @@ def perform( user_id: nil, service_provider_issuer: nil, threatmetrix_session_id: nil, - request_ip: nil, - proofing_components: nil # rubocop:disable Lint/UnusedMethodArgument + request_ip: nil ) timer = JobHelpers::Timer.new diff --git a/app/services/idv/agent.rb b/app/services/idv/agent.rb index 3c30d54353c..856e97a975a 100644 --- a/app/services/idv/agent.rb +++ b/app/services/idv/agent.rb @@ -6,8 +6,6 @@ def initialize(applicant) @applicant = applicant.symbolize_keys end - # @param document_capture_session [DocumentCaptureSession] - # @param proofing_components [Idv::ProofingComponents] def proof_resolution( document_capture_session, trace_id:, @@ -15,7 +13,6 @@ def proof_resolution( threatmetrix_session_id:, request_ip:, ipp_enrollment_in_progress:, - proofing_components:, proofing_vendor: ) document_capture_session.create_proofing_session @@ -33,7 +30,6 @@ def proof_resolution( threatmetrix_session_id:, request_ip:, ipp_enrollment_in_progress:, - proofing_components: proofing_components.to_h, proofing_vendor:, } diff --git a/lib/reporting/api_transaction_count_report.rb b/lib/reporting/api_transaction_count_report.rb index b651921116a..d26f21e5a60 100644 --- a/lib/reporting/api_transaction_count_report.rb +++ b/lib/reporting/api_transaction_count_report.rb @@ -73,7 +73,7 @@ def api_transaction_count 'Instant verify', 'Phone Finder', 'Socure (DocV)', - 'Socure (KYC) - Non-Shadow', + 'Socure (KYC)', 'Fraud Score and Attribute', 'Threat Metrix (IDV)', 'Threat Metrix (Auth Only)', diff --git a/spec/controllers/idv/in_person/verify_info_controller_spec.rb b/spec/controllers/idv/in_person/verify_info_controller_spec.rb index f942e27c03e..1da966a52dc 100644 --- a/spec/controllers/idv/in_person/verify_info_controller_spec.rb +++ b/spec/controllers/idv/in_person/verify_info_controller_spec.rb @@ -357,7 +357,6 @@ user_id: anything, request_ip: request.remote_ip, ipp_enrollment_in_progress: false, - proofing_components: Idv::ProofingComponents, proofing_vendor: :mock, ) @@ -379,7 +378,6 @@ user_id: anything, request_ip: anything, ipp_enrollment_in_progress: true, - proofing_components: Idv::ProofingComponents, proofing_vendor: :mock, ) @@ -409,7 +407,6 @@ user_id: anything, request_ip: request.remote_ip, ipp_enrollment_in_progress: true, - proofing_components: Idv::ProofingComponents, proofing_vendor: :mock, ) @@ -489,7 +486,6 @@ user_id: anything, request_ip: request.remote_ip, ipp_enrollment_in_progress: true, - proofing_components: Idv::ProofingComponents, proofing_vendor: :default_vendor, ) @@ -508,7 +504,6 @@ user_id: anything, request_ip: request.remote_ip, ipp_enrollment_in_progress: true, - proofing_components: Idv::ProofingComponents, proofing_vendor: :instant_verify, ) @@ -528,7 +523,6 @@ user_id: anything, request_ip: request.remote_ip, ipp_enrollment_in_progress: true, - proofing_components: Idv::ProofingComponents, proofing_vendor: :socure_kyc, ) diff --git a/spec/lib/reporting/api_transaction_count_report_spec.rb b/spec/lib/reporting/api_transaction_count_report_spec.rb index 39439e965ea..9f826805c0e 100644 --- a/spec/lib/reporting/api_transaction_count_report_spec.rb +++ b/spec/lib/reporting/api_transaction_count_report_spec.rb @@ -36,7 +36,7 @@ expect(header_row).to eq( ['Week', 'True ID', 'Instant verify', 'Phone Finder', 'Socure (DocV)', - 'Socure (KYC) - Non-Shadow', + 'Socure (KYC)', 'Fraud Score and Attribute', 'Threat Metrix (IDV)', 'Threat Metrix (Auth Only)'], ) expect(data_row.first).to eq("#{time_range.begin.to_date} - #{time_range.end.to_date}") @@ -55,7 +55,7 @@ expect(csv).to match( / Week,True\ ID,Instant\ verify,Phone\ Finder, - Socure\ \(DocV\),Socure\ \(KYC\)\s-\sNon-Shadow, + Socure\ \(DocV\),Socure\ \(KYC\), Fraud\ Score\ and\ Attribute,Threat\ Metrix\s\(IDV\),Threat\ Metrix\s\(Auth\ Only\) /x, ) diff --git a/spec/services/idv/agent_spec.rb b/spec/services/idv/agent_spec.rb index ccbf3e7175c..4595f2126e6 100644 --- a/spec/services/idv/agent_spec.rb +++ b/spec/services/idv/agent_spec.rb @@ -26,10 +26,6 @@ idv_session.pii_from_doc = applicant end end - let(:proofing_components) do - Idv::ProofingComponents.new(idv_session:) - end - let(:proofing_vendor) do IdentityConfig.store.idv_resolution_default_vendor end @@ -53,7 +49,6 @@ threatmetrix_session_id: nil, request_ip: request_ip, ipp_enrollment_in_progress: ipp_enrollment_in_progress, - proofing_components:, proofing_vendor:, ) end @@ -143,17 +138,6 @@ proof_resolution end - it 'passes proofing components to ResolutionProofingJob' do - expect(ResolutionProofingJob).to receive(:perform_later).with( - hash_including( - proofing_components: { - document_type_received: 'drivers_license', - }, - ), - ) - proof_resolution - end - context 'when a proofing timeout occurs' do let(:applicant) do super().merge(first_name: 'Time Exception') From 5e1da41076bd0f6265a3617ab48dea8359449c73 Mon Sep 17 00:00:00 2001 From: Mawar2 Date: Tue, 7 Oct 2025 11:32:30 -0400 Subject: [PATCH 5/5] Remove proofing_components from resolution_proofing_job_spec --- spec/jobs/resolution_proofing_job_spec.rb | 2 -- 1 file changed, 2 deletions(-) diff --git a/spec/jobs/resolution_proofing_job_spec.rb b/spec/jobs/resolution_proofing_job_spec.rb index 374e632d675..0fc6cbec3d2 100644 --- a/spec/jobs/resolution_proofing_job_spec.rb +++ b/spec/jobs/resolution_proofing_job_spec.rb @@ -18,7 +18,6 @@ let(:proofing_device_profiling) { :enabled } let(:lexisnexis_threatmetrix_mock_enabled) { false } let(:ipp_enrollment_in_progress) { false } - let(:proofing_components) { nil } before do allow(IdentityConfig.store).to receive(:proofing_device_profiling) @@ -44,7 +43,6 @@ threatmetrix_session_id: threatmetrix_session_id, request_ip: request_ip, ipp_enrollment_in_progress: ipp_enrollment_in_progress, - proofing_components: proofing_components, proofing_vendor: IdentityConfig.store.idv_resolution_default_vendor, ) end