From 6029d12cf3f80cf87d976b8a2ceb9506ffe7aae1 Mon Sep 17 00:00:00 2001 From: Ben Stokes Date: Thu, 20 Aug 2026 12:49:57 -0700 Subject: [PATCH 1/2] feat: identity verification override dashboard option --- .../IdentityVerificationSession.spec.ts | 33 +++++++++- apps/api/src/modules/identity/IdentityLite.ts | 62 ++++++++++++++++++- apps/api/src/routes/accounts.routes.ts | 25 ++++++++ .../src/app/data/services/account.service.ts | 11 ++++ .../connected-account-detail.component.html | 57 ++++++++++++----- .../connected-account-detail.component.ts | 29 +++++++++ .../src/lib/IdentityValidators.ts | 5 ++ 7 files changed, 203 insertions(+), 19 deletions(-) diff --git a/apps/api/src/__tests__/IdentityVerificationSession.spec.ts b/apps/api/src/__tests__/IdentityVerificationSession.spec.ts index 968775a..3d28e7d 100644 --- a/apps/api/src/__tests__/IdentityVerificationSession.spec.ts +++ b/apps/api/src/__tests__/IdentityVerificationSession.spec.ts @@ -14,7 +14,11 @@ import { IdentityVerificationSession, Person, } from '@zoneless/shared-types'; -import { IDENTITY_REQUIREMENT_FIELDS } from '@zoneless/shared-schemas'; +import { + IDENTITY_DOCUMENT_WAIVED, + IDENTITY_DOCUMENT_WAIVER_METADATA_KEY, + IDENTITY_REQUIREMENT_FIELDS, +} from '@zoneless/shared-schemas'; import { CreateMockDatabase, DeterministicId, @@ -800,6 +804,33 @@ describe('Identity volume threshold gating', () => { IDENTITY_REQUIREMENT_FIELDS.verificationDocument ); }); + + it('waives document IDV and keeps it cleared after later volume evals', async () => { + mockDb.Aggregate.mockResolvedValue([{ gross: 250_000 }]); + + await module.EvaluateAndApply(connectedId); + expect( + storedAccounts.get(connectedId)?.requirements?.currently_due + ).toContain(IDENTITY_REQUIREMENT_FIELDS.verificationDocument); + + const waived = await module.WaiveIdentityDocument(connectedId); + + expect(waived.metadata?.[IDENTITY_DOCUMENT_WAIVER_METADATA_KEY]).toBe( + IDENTITY_DOCUMENT_WAIVED + ); + expect(waived.requirements?.currently_due).not.toContain( + IDENTITY_REQUIREMENT_FIELDS.verificationDocument + ); + expect(waived.requirements?.eventually_due).not.toContain( + IDENTITY_REQUIREMENT_FIELDS.verificationDocument + ); + + const evaluation = await module.EvaluateAndApply(connectedId); + expect(evaluation.blocking).toBe(false); + expect(evaluation.currentlyDue).not.toContain( + IDENTITY_REQUIREMENT_FIELDS.verificationDocument + ); + }); }); describe('AccountModule identity settings merge', () => { diff --git a/apps/api/src/modules/identity/IdentityLite.ts b/apps/api/src/modules/identity/IdentityLite.ts index f078974..ae6a68c 100644 --- a/apps/api/src/modules/identity/IdentityLite.ts +++ b/apps/api/src/modules/identity/IdentityLite.ts @@ -45,6 +45,8 @@ import { CheckPhoneNumber, CheckPostalCode, CountriesCompatible, + IDENTITY_DOCUMENT_WAIVED, + IDENTITY_DOCUMENT_WAIVER_METADATA_KEY, IDENTITY_ERROR_CODES, IDENTITY_LITE_REVIEW_DISMISSED, IDENTITY_LITE_REVIEW_METADATA_KEY, @@ -225,6 +227,54 @@ export class IdentityLiteModule { return updated; } + /** + * Platform operator waives hosted document IDV for one connected account. + * Persists across later volume-threshold evaluations until verification + * completes or the waiver metadata is removed. + */ + async WaiveIdentityDocument(accountId: string): Promise { + const account = await this.accountModule.GetAccount(accountId); + if (!account) { + throw new AppError( + ERRORS.ACCOUNT_NOT_FOUND.message, + ERRORS.ACCOUNT_NOT_FOUND.status, + ERRORS.ACCOUNT_NOT_FOUND.type + ); + } + + if (IsRejectedAccountReason(account.requirements?.disabled_reason)) { + throw new AppError( + 'Cannot waive identity verification for a rejected account', + 400, + 'invalid_request_error' + ); + } + + await this.accountModule.UpdateAccount(accountId, { + metadata: { + ...account.metadata, + [IDENTITY_DOCUMENT_WAIVER_METADATA_KEY]: IDENTITY_DOCUMENT_WAIVED, + }, + }); + + await this.EvaluateAndApply(accountId); + const restored = await this.RestorePayoutsIfEligible(accountId); + if (restored) { + return restored; + } + + const updated = await this.accountModule.GetAccount(accountId); + if (!updated) { + throw new AppError( + ERRORS.ACCOUNT_NOT_FOUND.message, + ERRORS.ACCOUNT_NOT_FOUND.status, + ERRORS.ACCOUNT_NOT_FOUND.type + ); + } + + return updated; + } + /** * Assert the account may attach a wallet / enable payouts. * Hosted document IDV does not block wallet attach — payouts are @@ -458,7 +508,10 @@ export class IdentityLiteModule { const docField = IDENTITY_REQUIREMENT_FIELDS.verificationDocument; const verificationStatus = person.verification?.status ?? 'unverified'; - if (verificationStatus === 'verified') { + if ( + verificationStatus === 'verified' || + this.IsDocumentVerificationWaived(account) + ) { evaluation.currentlyDue = evaluation.currentlyDue.filter( (f) => f !== docField ); @@ -740,6 +793,13 @@ export class IdentityLiteModule { ); } + private IsDocumentVerificationWaived(account: AccountType): boolean { + return ( + account.metadata?.[IDENTITY_DOCUMENT_WAIVER_METADATA_KEY] === + IDENTITY_DOCUMENT_WAIVED + ); + } + private async ClearReviewDismissMetadata( account: AccountType ): Promise { diff --git a/apps/api/src/routes/accounts.routes.ts b/apps/api/src/routes/accounts.routes.ts index 8dfd89a..49f9dd3 100644 --- a/apps/api/src/routes/accounts.routes.ts +++ b/apps/api/src/routes/accounts.routes.ts @@ -481,6 +481,9 @@ router.post( accountId, }); + if (enabled) { + await identityLiteModule.EvaluateAndApply(accountId); + } const account = enabled ? await accountModule.PayoutsEnabled(accountId) : await accountModule.PayoutsDisabled(accountId); @@ -512,6 +515,28 @@ router.post( }) ); +// ───────────────────────────────────────────────────────────────────────────── +// POST /v1/accounts/:id/waive_identity_document - Skip hosted document IDV +// Zoneless extension: operator accepts risk for this connected account +// ───────────────────────────────────────────────────────────────────────────── +router.post( + '/:id/waive_identity_document', + RequirePlatform(), + AsyncHandler(async (req: express.Request, res: express.Response) => { + const accountId = req.params.id; + await RequirePlatformOwnedAccount(accountId, req.user.account); + + Logger.info('Waiving identity document requirement', { accountId }); + + const account = await identityLiteModule.WaiveIdentityDocument(accountId); + const populatedAccount = await PopulateAccountResources(account, true); + + Logger.info('Identity document requirement waived', { accountId }); + + res.json(populatedAccount); + }) +); + // ───────────────────────────────────────────────────────────────────────────── // POST /v1/accounts/:id/agree_terms - Agree to terms of service // This is a Zoneless extension that handles TOS acceptance from the frontend diff --git a/apps/web/src/app/data/services/account.service.ts b/apps/web/src/app/data/services/account.service.ts index 0376503..593228d 100644 --- a/apps/web/src/app/data/services/account.service.ts +++ b/apps/web/src/app/data/services/account.service.ts @@ -84,6 +84,17 @@ export class AccountService { ); } + /** + * Platform-only: waive hosted document identity verification. + */ + async WaiveIdentityDocument(accountId: string): Promise { + return this.api.Call( + 'POST', + `accounts/${accountId}/waive_identity_document`, + {} + ); + } + /** * Platform-only: reject a connected account. */ diff --git a/apps/web/src/app/features/account/connected-accounts/views/connected-account-detail/connected-account-detail.component.html b/apps/web/src/app/features/account/connected-accounts/views/connected-account-detail/connected-account-detail.component.html index 7a5c672..722dbf3 100644 --- a/apps/web/src/app/features/account/connected-accounts/views/connected-account-detail/connected-account-detail.component.html +++ b/apps/web/src/app/features/account/connected-accounts/views/connected-account-detail/connected-account-detail.component.html @@ -34,13 +34,25 @@

Information needed

{{ identityDocumentMissingLabel() }} - +
+ + @if (CanWaiveIdentityDocument()) { + + } +
@@ -364,16 +376,27 @@

Capabilities

Identity requirements

- @if (NeedsIdentityReview()) { - - } +
+ @if (CanWaiveIdentityDocument()) { + + } @if (NeedsIdentityReview()) { + + } +
@if (GetIdentityStatusLabel(); as statusLabel) {
diff --git a/apps/web/src/app/features/account/connected-accounts/views/connected-account-detail/connected-account-detail.component.ts b/apps/web/src/app/features/account/connected-accounts/views/connected-account-detail/connected-account-detail.component.ts index fba142a..00b94b3 100644 --- a/apps/web/src/app/features/account/connected-accounts/views/connected-account-detail/connected-account-detail.component.ts +++ b/apps/web/src/app/features/account/connected-accounts/views/connected-account-detail/connected-account-detail.component.ts @@ -98,6 +98,7 @@ export class ConnectedAccountDetailViewComponent implements OnInit, OnDestroy { account: WritableSignal = signal(null); loading: WritableSignal = signal(false); approvingIdentity: WritableSignal = signal(false); + waivingIdentityDocument: WritableSignal = signal(false); activeTab: WritableSignal = signal('overview'); detailPanel: WritableSignal = signal('main'); moneyMovementTab: WritableSignal = signal('payouts'); @@ -599,6 +600,34 @@ export class ConnectedAccountDetailViewComponent implements OnInit, OnDestroy { } } + CanWaiveIdentityDocument(): boolean { + const state = this.identityDocumentState(); + return ( + state === 'currently_due' || + state === 'pending' || + state === 'eventually_due' + ); + } + + async OnWaiveIdentityDocument(): Promise { + const account = this.account(); + if (!account || this.waivingIdentityDocument()) return; + + this.waivingIdentityDocument.set(true); + try { + const updated = await this.accountService.WaiveIdentityDocument( + account.id + ); + this.account.set(updated); + this.actions.SetActiveAccount(updated); + this.actions.events$.next({ type: 'updated', account: updated }); + } catch (error) { + console.error('Failed to waive identity verification:', error); + } finally { + this.waivingIdentityDocument.set(false); + } + } + OpenIdentityDocumentDetail(): void { this.detailPanel.set('identity_document'); } diff --git a/libs/shared-schemas/src/lib/IdentityValidators.ts b/libs/shared-schemas/src/lib/IdentityValidators.ts index 36bbebc..696723b 100644 --- a/libs/shared-schemas/src/lib/IdentityValidators.ts +++ b/libs/shared-schemas/src/lib/IdentityValidators.ts @@ -393,6 +393,11 @@ export const IDENTITY_UNDER_REVIEW = 'under_review'; export const IDENTITY_LITE_REVIEW_METADATA_KEY = 'identity_lite_review'; export const IDENTITY_LITE_REVIEW_DISMISSED = 'dismissed'; +/** Account.metadata key: operator waived hosted document IDV for this account */ +export const IDENTITY_DOCUMENT_WAIVER_METADATA_KEY = + 'identity_verification_document'; +export const IDENTITY_DOCUMENT_WAIVED = 'waived'; + /** Error codes for non-blocking lite review signals */ export const IDENTITY_REVIEW_ERROR_CODES: ReadonlySet = new Set([ IDENTITY_ERROR_CODES.disposableEmail, From b8cee335c08d51775af6d09dacdd2c056b2cfb50 Mon Sep 17 00:00:00 2001 From: Ben Stokes Date: Thu, 20 Aug 2026 18:14:13 -0700 Subject: [PATCH 2/2] feat: dashboard option to refresh identity checks after threshold changes --- .../IdentityVerificationSession.spec.ts | 35 ++++++----- apps/api/src/modules/identity/IdentityLite.ts | 41 ++++--------- apps/api/src/routes/accounts.routes.ts | 14 +++-- .../src/app/data/services/account.service.ts | 6 +- .../connected-account-detail.component.html | 59 ++++++------------- .../connected-account-detail.component.ts | 32 +++++----- .../src/lib/IdentityValidators.ts | 5 -- 7 files changed, 77 insertions(+), 115 deletions(-) diff --git a/apps/api/src/__tests__/IdentityVerificationSession.spec.ts b/apps/api/src/__tests__/IdentityVerificationSession.spec.ts index 3d28e7d..487aa18 100644 --- a/apps/api/src/__tests__/IdentityVerificationSession.spec.ts +++ b/apps/api/src/__tests__/IdentityVerificationSession.spec.ts @@ -14,11 +14,7 @@ import { IdentityVerificationSession, Person, } from '@zoneless/shared-types'; -import { - IDENTITY_DOCUMENT_WAIVED, - IDENTITY_DOCUMENT_WAIVER_METADATA_KEY, - IDENTITY_REQUIREMENT_FIELDS, -} from '@zoneless/shared-schemas'; +import { IDENTITY_REQUIREMENT_FIELDS } from '@zoneless/shared-schemas'; import { CreateMockDatabase, DeterministicId, @@ -805,29 +801,38 @@ describe('Identity volume threshold gating', () => { ); }); - it('waives document IDV and keeps it cleared after later volume evals', async () => { + it('refresh clears document currently_due when volume is now under threshold', async () => { mockDb.Aggregate.mockResolvedValue([{ gross: 250_000 }]); - await module.EvaluateAndApply(connectedId); expect( storedAccounts.get(connectedId)?.requirements?.currently_due ).toContain(IDENTITY_REQUIREMENT_FIELDS.verificationDocument); - const waived = await module.WaiveIdentityDocument(connectedId); + mockDb.Aggregate.mockResolvedValue([{ gross: 50_000 }]); + const refreshed = await module.RefreshIdentityRequirements(connectedId); - expect(waived.metadata?.[IDENTITY_DOCUMENT_WAIVER_METADATA_KEY]).toBe( - IDENTITY_DOCUMENT_WAIVED - ); - expect(waived.requirements?.currently_due).not.toContain( + expect(refreshed.requirements?.currently_due).not.toContain( IDENTITY_REQUIREMENT_FIELDS.verificationDocument ); - expect(waived.requirements?.eventually_due).not.toContain( + expect(refreshed.requirements?.eventually_due).toContain( IDENTITY_REQUIREMENT_FIELDS.verificationDocument ); + mockDb.Aggregate.mockResolvedValue([{ gross: 250_000 }]); const evaluation = await module.EvaluateAndApply(connectedId); - expect(evaluation.blocking).toBe(false); - expect(evaluation.currentlyDue).not.toContain( + expect(evaluation.blocking).toBe(true); + expect(evaluation.currentlyDue).toContain( + IDENTITY_REQUIREMENT_FIELDS.verificationDocument + ); + }); + + it('refresh keeps document currently_due when still over threshold', async () => { + mockDb.Aggregate.mockResolvedValue([{ gross: 250_000 }]); + await module.EvaluateAndApply(connectedId); + + const refreshed = await module.RefreshIdentityRequirements(connectedId); + + expect(refreshed.requirements?.currently_due).toContain( IDENTITY_REQUIREMENT_FIELDS.verificationDocument ); }); diff --git a/apps/api/src/modules/identity/IdentityLite.ts b/apps/api/src/modules/identity/IdentityLite.ts index ae6a68c..3f2a516 100644 --- a/apps/api/src/modules/identity/IdentityLite.ts +++ b/apps/api/src/modules/identity/IdentityLite.ts @@ -45,8 +45,6 @@ import { CheckPhoneNumber, CheckPostalCode, CountriesCompatible, - IDENTITY_DOCUMENT_WAIVED, - IDENTITY_DOCUMENT_WAIVER_METADATA_KEY, IDENTITY_ERROR_CODES, IDENTITY_LITE_REVIEW_DISMISSED, IDENTITY_LITE_REVIEW_METADATA_KEY, @@ -228,11 +226,11 @@ export class IdentityLiteModule { } /** - * Platform operator waives hosted document IDV for one connected account. - * Persists across later volume-threshold evaluations until verification - * completes or the waiver metadata is removed. + * Platform operator rebuilds currently_due from live person + threshold + * rules. Does not persist an exemption — a later volume crossing can put + * document IDV back on currently_due. */ - async WaiveIdentityDocument(accountId: string): Promise { + async RefreshIdentityRequirements(accountId: string): Promise { const account = await this.accountModule.GetAccount(accountId); if (!account) { throw new AppError( @@ -244,23 +242,18 @@ export class IdentityLiteModule { if (IsRejectedAccountReason(account.requirements?.disabled_reason)) { throw new AppError( - 'Cannot waive identity verification for a rejected account', + 'Cannot refresh identity requirements for a rejected account', 400, 'invalid_request_error' ); } - await this.accountModule.UpdateAccount(accountId, { - metadata: { - ...account.metadata, - [IDENTITY_DOCUMENT_WAIVER_METADATA_KEY]: IDENTITY_DOCUMENT_WAIVED, - }, - }); - - await this.EvaluateAndApply(accountId); - const restored = await this.RestorePayoutsIfEligible(accountId); - if (restored) { - return restored; + const evaluation = await this.EvaluateAndApply(accountId); + if (!evaluation.blocking) { + const restored = await this.RestorePayoutsIfEligible(accountId); + if (restored) { + return restored; + } } const updated = await this.accountModule.GetAccount(accountId); @@ -508,10 +501,7 @@ export class IdentityLiteModule { const docField = IDENTITY_REQUIREMENT_FIELDS.verificationDocument; const verificationStatus = person.verification?.status ?? 'unverified'; - if ( - verificationStatus === 'verified' || - this.IsDocumentVerificationWaived(account) - ) { + if (verificationStatus === 'verified') { evaluation.currentlyDue = evaluation.currentlyDue.filter( (f) => f !== docField ); @@ -793,13 +783,6 @@ export class IdentityLiteModule { ); } - private IsDocumentVerificationWaived(account: AccountType): boolean { - return ( - account.metadata?.[IDENTITY_DOCUMENT_WAIVER_METADATA_KEY] === - IDENTITY_DOCUMENT_WAIVED - ); - } - private async ClearReviewDismissMetadata( account: AccountType ): Promise { diff --git a/apps/api/src/routes/accounts.routes.ts b/apps/api/src/routes/accounts.routes.ts index 49f9dd3..8de4a73 100644 --- a/apps/api/src/routes/accounts.routes.ts +++ b/apps/api/src/routes/accounts.routes.ts @@ -516,22 +516,24 @@ router.post( ); // ───────────────────────────────────────────────────────────────────────────── -// POST /v1/accounts/:id/waive_identity_document - Skip hosted document IDV -// Zoneless extension: operator accepts risk for this connected account +// POST /v1/accounts/:id/refresh_identity - Rebuild currently_due from live rules +// Zoneless extension: does not persist an IDV exemption // ───────────────────────────────────────────────────────────────────────────── router.post( - '/:id/waive_identity_document', + '/:id/refresh_identity', RequirePlatform(), AsyncHandler(async (req: express.Request, res: express.Response) => { const accountId = req.params.id; await RequirePlatformOwnedAccount(accountId, req.user.account); - Logger.info('Waiving identity document requirement', { accountId }); + Logger.info('Refreshing identity requirements', { accountId }); - const account = await identityLiteModule.WaiveIdentityDocument(accountId); + const account = await identityLiteModule.RefreshIdentityRequirements( + accountId + ); const populatedAccount = await PopulateAccountResources(account, true); - Logger.info('Identity document requirement waived', { accountId }); + Logger.info('Identity requirements refreshed', { accountId }); res.json(populatedAccount); }) diff --git a/apps/web/src/app/data/services/account.service.ts b/apps/web/src/app/data/services/account.service.ts index 593228d..894bad7 100644 --- a/apps/web/src/app/data/services/account.service.ts +++ b/apps/web/src/app/data/services/account.service.ts @@ -85,12 +85,12 @@ export class AccountService { } /** - * Platform-only: waive hosted document identity verification. + * Platform-only: rebuild currently_due from live identity rules. */ - async WaiveIdentityDocument(accountId: string): Promise { + async RefreshIdentityRequirements(accountId: string): Promise { return this.api.Call( 'POST', - `accounts/${accountId}/waive_identity_document`, + `accounts/${accountId}/refresh_identity`, {} ); } diff --git a/apps/web/src/app/features/account/connected-accounts/views/connected-account-detail/connected-account-detail.component.html b/apps/web/src/app/features/account/connected-accounts/views/connected-account-detail/connected-account-detail.component.html index 722dbf3..576211d 100644 --- a/apps/web/src/app/features/account/connected-accounts/views/connected-account-detail/connected-account-detail.component.html +++ b/apps/web/src/app/features/account/connected-accounts/views/connected-account-detail/connected-account-detail.component.html @@ -34,25 +34,13 @@

Information needed

{{ identityDocumentMissingLabel() }}
-
- - @if (CanWaiveIdentityDocument()) { - - } -
+
@@ -374,29 +362,18 @@

Capabilities

-
+

Identity requirements

-
- @if (CanWaiveIdentityDocument()) { - - } @if (NeedsIdentityReview()) { - - } -
+ @if (NeedsIdentityReview()) { + + }
@if (GetIdentityStatusLabel(); as statusLabel) {
diff --git a/apps/web/src/app/features/account/connected-accounts/views/connected-account-detail/connected-account-detail.component.ts b/apps/web/src/app/features/account/connected-accounts/views/connected-account-detail/connected-account-detail.component.ts index 00b94b3..91a8b81 100644 --- a/apps/web/src/app/features/account/connected-accounts/views/connected-account-detail/connected-account-detail.component.ts +++ b/apps/web/src/app/features/account/connected-accounts/views/connected-account-detail/connected-account-detail.component.ts @@ -98,7 +98,7 @@ export class ConnectedAccountDetailViewComponent implements OnInit, OnDestroy { account: WritableSignal = signal(null); loading: WritableSignal = signal(false); approvingIdentity: WritableSignal = signal(false); - waivingIdentityDocument: WritableSignal = signal(false); + refreshingIdentity: WritableSignal = signal(false); activeTab: WritableSignal = signal('overview'); detailPanel: WritableSignal = signal('main'); moneyMovementTab: WritableSignal = signal('payouts'); @@ -306,6 +306,15 @@ export class ConnectedAccountDetailViewComponent implements OnInit, OnDestroy { external: true, action: () => this.OnViewDashboard(), }, + { + title: 'Refresh identity checks', + section: 'Actions', + action: () => void this.OnRefreshIdentityChecks(), + hidden: (account: Account) => + (account.requirements?.currently_due?.length ?? 0) === 0 || + this.actions.IsAccountRejected(account), + disabled: () => this.refreshingIdentity(), + }, { title: 'Pause payouts', section: 'Actions', @@ -600,31 +609,22 @@ export class ConnectedAccountDetailViewComponent implements OnInit, OnDestroy { } } - CanWaiveIdentityDocument(): boolean { - const state = this.identityDocumentState(); - return ( - state === 'currently_due' || - state === 'pending' || - state === 'eventually_due' - ); - } - - async OnWaiveIdentityDocument(): Promise { + async OnRefreshIdentityChecks(): Promise { const account = this.account(); - if (!account || this.waivingIdentityDocument()) return; + if (!account || this.refreshingIdentity()) return; - this.waivingIdentityDocument.set(true); + this.refreshingIdentity.set(true); try { - const updated = await this.accountService.WaiveIdentityDocument( + const updated = await this.accountService.RefreshIdentityRequirements( account.id ); this.account.set(updated); this.actions.SetActiveAccount(updated); this.actions.events$.next({ type: 'updated', account: updated }); } catch (error) { - console.error('Failed to waive identity verification:', error); + console.error('Failed to refresh identity requirements:', error); } finally { - this.waivingIdentityDocument.set(false); + this.refreshingIdentity.set(false); } } diff --git a/libs/shared-schemas/src/lib/IdentityValidators.ts b/libs/shared-schemas/src/lib/IdentityValidators.ts index 696723b..36bbebc 100644 --- a/libs/shared-schemas/src/lib/IdentityValidators.ts +++ b/libs/shared-schemas/src/lib/IdentityValidators.ts @@ -393,11 +393,6 @@ export const IDENTITY_UNDER_REVIEW = 'under_review'; export const IDENTITY_LITE_REVIEW_METADATA_KEY = 'identity_lite_review'; export const IDENTITY_LITE_REVIEW_DISMISSED = 'dismissed'; -/** Account.metadata key: operator waived hosted document IDV for this account */ -export const IDENTITY_DOCUMENT_WAIVER_METADATA_KEY = - 'identity_verification_document'; -export const IDENTITY_DOCUMENT_WAIVED = 'waived'; - /** Error codes for non-blocking lite review signals */ export const IDENTITY_REVIEW_ERROR_CODES: ReadonlySet = new Set([ IDENTITY_ERROR_CODES.disposableEmail,