Skip to content

refactor(kyc): render the backend ResolvedRail verdict (step 4)#2418

Merged
kushagrasarathe merged 2 commits into
devfrom
refactor/kyc-resolved-verdict
Jul 16, 2026
Merged

refactor(kyc): render the backend ResolvedRail verdict (step 4)#2418
kushagrasarathe merged 2 commits into
devfrom
refactor/kyc-resolved-verdict

Conversation

@kushagrasarathe

@kushagrasarathe kushagrasarathe commented Jul 15, 2026

Copy link
Copy Markdown
Contributor

Summary

PR-3 of the KYC single-source-of-truth refactor (TASK-20629) — the FE renders the backend's per-rail verdict instead of re-deriving it. Companion to peanut-api-ts #1177 (step 1) + #1181 (steps 2/2b/3/5); all three merge as one train, reviewed together.

  • capability-gate.ts: deriveGate branches on rail.resolved (the BE-derived ResolvedRail) via railVerdict(). A local fallback mirroring the BE collapse rules covers rails from older/cached responses — it's deleted when the BE's step-5 cleanup makes resolved guaranteed. Gate priority order is unchanged; all 32 pre-existing gate tests pass through the new path unmodified, plus 7 new tests pinning that the verdict wins over contradictory legacy fields.
  • provider-rejection.utils.ts / qr-pay / ActivationCTAs: fixable-vs-terminal / restart-identity / provide-email decisions now read resolved.blocking.selfHealKind instead of sniffing reason codes and action kinds.
  • Types: ResolvedRail added to the hand-written contract; api.generated.ts regenerated from fix: properly format currency code when fetching exchange rate #1181's OpenAPI snapshot (resolved on both capability routes; provide-email/effectiveDate/requirementKey/hintActions now present — the old snapshot lagged). Dead tosStatus/tosAcceptedAt types deleted (zero live reads); InitiateKycResponse.tosStatus dropped (BE never emits it).

Deliberate behavioral deltas (small, flagged)

  • Wait-marked rails (resolved: pending + wait action) no longer surface a fixable CTA on provider-rejection surfaces — there is nothing user-actionable; the legacy status check offered a flow that dead-ended.
  • The country_not_supported restart-identity branch is preserved verbatim via resolved.blocking.code, but note: today's BE never emits that code — the branch predates the current resolver and appears dead. Kept for parity; worth a product decision on whether Manteca country-ineligibility should re-emit it (BE change, not this PR).

Not in scope

  • general.utils.ts payerAccount.user.bridgeKycStatus read: keeps working — fix: properly format currency code when fetching exchange rate #1181 derives and re-emits the same wire field. The isVerified-boolean migration for the charges flow remains its own follow-up (pre-existing note in the code).
  • bridgeCustomerId readers (withdraw/bank, BankFlowManager, Crisp deep-link): unchanged — the column and wire field stay this release (dual-homed; retirement is the BE's next-release item).
  • SelfHealResubmissionResponse.requiredAction drift flagged in the plan: already consistent with the BE union — nothing to fix.

Risks / breaking changes

QA

  • 1937 unit tests green (39 gate tests: 32 legacy pinning behavior-preservation + 7 verdict-authority); typecheck clean; production build compiles.
  • Staging state checklist before the prod release (in the plan doc): mid-self-heal, rejected-but-healable, terminal duplicate, ToS-pending, dormant stale-rail, future-dated advisory.

Summary by CodeRabbit

  • New Features

    • Added clearer, server-provided status handling for identity verification and payment capabilities.
    • Improved guidance for blocked, fixable, pending, and identity-restart scenarios.
    • Added support for account deletion, enhanced card and deposit status details, and expanded off-ramp information.
    • Added advisory actions and effective dates for upcoming capability requirements.
  • Bug Fixes

    • Improved rejection messages and routing for email, Terms of Service, and provider-related issues.
    • Ensured pending capabilities do not incorrectly appear as user-actionable errors.

the capability gate, provider-rejection surfaces, qr-pay and
ActivationCTAs now branch on rail.resolved - the BE-derived verdict -
instead of re-deriving fixable/terminal from status + reason codes +
action kinds. a local fallback mirroring the BE collapse rules covers
rails from older/cached responses and gets deleted with the BE's step-5
cleanup. gate priority order is unchanged; all 32 pre-existing gate
tests pass through the new path unmodified, plus 7 new tests pinning
that the verdict wins over contradictory legacy fields.

hygiene riding along: api.generated.ts regenerated from the PR-2
openapi snapshot (resolved on both capability routes, provide-email/
restart-identity kinds now present); dead tosStatus/tosAcceptedAt
types deleted (zero live reads); InitiateKycResponse.tosStatus dropped
(BE never emits it).
@vercel

vercel Bot commented Jul 15, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
peanut-wallet Ready Ready Preview, Comment Jul 15, 2026 9:05am

Request Review

@coderabbitai

coderabbitai Bot commented Jul 15, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 7482e8e7-00f8-4fa9-9315-b3e070d85ede

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Changes

The PR makes rail.resolved the preferred source for capability-gate, provider-rejection, activation, and QR-pay KYC decisions, with legacy fallbacks. It adds resolved capability contracts and updates the OpenAPI specification across user, bridge, payment, card, notification, and development endpoints.

Verdict-driven capability gating

Layer / File(s) Summary
Resolved rail verdict contracts
src/types/capabilities.ts, src/types/api.openapi.json, src/app/actions/types/users.types.ts
Capability schemas define resolved statuses, blocking metadata, next actions, hints, and the renamed tosLink response field.
Verdict-based gate derivation
src/utils/capability-gate.ts, src/utils/capability-gate.test.ts
Gate derivation prioritizes resolved verdicts and covers blocked, fixable, pending, advisory, ToS, email, and identity-restart outcomes.
Rejection and KYC UI integration
src/utils/provider-rejection.utils.ts, src/components/Home/ActivationCTAs.tsx, src/app/(mobile-ui)/qr-pay/page.tsx
Provider rejection and KYC UI flows use resolved blocking messages and self-heal metadata with legacy fallbacks.

Additional API contract changes

Layer / File(s) Summary
User account API contracts
src/interfaces/interfaces.ts, src/types/api.openapi.json
User fields and update, deletion, and lookup endpoint schemas are adjusted.
Bridge and payment endpoint contracts
src/types/api.openapi.json
Bridge endpoints, guest off-ramp data and errors, deposit status, and Rhino transfer fields are updated.
Cards, notifications, and development contracts
src/types/api.openapi.json
Card fields, notification authentication, development descriptions, and badge enums are updated.

Estimated code review effort: 4 (Complex) | ~60 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Backend
  participant CapabilityGate
  participant ProviderRejection
  participant KYCUI
  Backend->>CapabilityGate: resolved rail verdict
  CapabilityGate->>ProviderRejection: normalized gate state
  ProviderRejection->>KYCUI: rejection status and user message
Loading

Possibly related PRs

Suggested labels: enhancement

Suggested reviewers: hugo0, jjramirezn

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately reflects the main change: switching KYC UI logic to render backend ResolvedRail verdicts.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch refactor/kyc-resolved-verdict

Comment @coderabbitai help to get the list of available commands.

@github-actions

github-actions Bot commented Jul 15, 2026

Copy link
Copy Markdown
Contributor

Code-analysis diff

Painscore total: 6186.26 → 6176.09 (-10.17)
Findings: +2 net (+381 new, -379 resolved)

🆕 New findings (381)

  • critical complexity — src/app/(mobile-ui)/qr-pay/page.tsx — CC 304, MI 53.59, SLOC 971
  • critical complexity — src/utils/capability-gate.ts — CC 89, MI 60.9, SLOC 239
  • critical method-complexity — src/app/(mobile-ui)/qr-pay/page.tsx:102 — QRPayPage CC 75 SLOC 328
  • critical complexity — src/components/Home/ActivationCTAs.tsx — CC 54, MI 59.07, SLOC 186
  • high hotspot — src/app/(mobile-ui)/qr-pay/page.tsx — 93 commits, +1073/-1030 lines since 6 months ago
  • high structural-dup — types/api.generated.ts:346 — 83 duplicate lines / 384 tokens with types/api.generated.ts:1145
  • high structural-dup — types/api.generated.ts:356 — 74 duplicate lines / 344 tokens with types/api.generated.ts:1155
  • high structural-dup — types/api.generated.ts:139 — 72 duplicate lines / 269 tokens with types/api.generated.ts:9571
  • high structural-dup — types/api.generated.ts:139 — 70 duplicate lines / 264 tokens with types/api.generated.ts:9657
  • high structural-dup — types/api.generated.ts:139 — 67 duplicate lines / 249 tokens with types/api.generated.ts:9248
  • high structural-dup — types/api.generated.ts:9193 — 55 duplicate lines / 162 tokens with types/api.generated.ts:9345
  • high structural-dup — types/api.generated.ts:9193 — 55 duplicate lines / 162 tokens with types/api.generated.ts:9516
  • high structural-dup — types/api.generated.ts:9193 — 55 duplicate lines / 165 tokens with types/api.generated.ts:9774
  • medium react-long-component — src/app/(mobile-ui)/qr-pay/page.tsx:102 — QRPayPage is 1520 lines — split it
  • medium high-mdd — src/app/(mobile-ui)/qr-pay/page.tsx:102 — QRPayPage: MDD 338.7 (uses across many lines from declarations)
  • medium high-dlt — src/app/(mobile-ui)/qr-pay/page.tsx:102 — QRPayPage: DLT 115 (calls 115 distinct functions — high context load)
  • medium structural-dup — types/api.generated.ts:3392 — 49 duplicate lines / 139 tokens with types/api.generated.ts:6638
  • medium structural-dup — types/api.generated.ts:3392 — 49 duplicate lines / 137 tokens with types/api.generated.ts:7213
  • medium structural-dup — app/(mobile-ui)/dev/shake-test/page.tsx:27 — 46 duplicate lines / 216 tokens with app/(mobile-ui)/qr-pay/page.tsx:889
  • medium high-mdd — src/components/Home/ActivationCTAs.tsx:80 — ActivationCTAs: MDD 44.0 (uses across many lines from declarations)

…and 361 more.

✅ Resolved (379)

  • src/app/(mobile-ui)/qr-pay/page.tsx — CC 298, MI 53.32, SLOC 952
  • src/app/(mobile-ui)/qr-pay/page.tsx:101 — QRPayPage CC 75 SLOC 327
  • src/utils/capability-gate.ts — CC 72, MI 63.64, SLOC 196
  • src/components/Home/ActivationCTAs.tsx — CC 52, MI 59.07, SLOC 176
  • src/app/(mobile-ui)/qr-pay/page.tsx — 91 commits, +1021/-996 lines since 6 months ago
  • types/api.generated.ts:139 — 72 duplicate lines / 269 tokens with types/api.generated.ts:9593
  • types/api.generated.ts:139 — 70 duplicate lines / 264 tokens with types/api.generated.ts:9679
  • types/api.generated.ts:139 — 67 duplicate lines / 249 tokens with types/api.generated.ts:9270
  • types/api.generated.ts:346 — 61 duplicate lines / 269 tokens with types/api.generated.ts:1089
  • types/api.generated.ts:9215 — 55 duplicate lines / 162 tokens with types/api.generated.ts:9367
  • types/api.generated.ts:9215 — 55 duplicate lines / 162 tokens with types/api.generated.ts:9538
  • types/api.generated.ts:9215 — 55 duplicate lines / 165 tokens with types/api.generated.ts:9796
  • types/api.generated.ts:356 — 52 duplicate lines / 229 tokens with types/api.generated.ts:1099
  • src/app/(mobile-ui)/qr-pay/page.tsx:101 — QRPayPage is 1503 lines — split it
  • src/app/(mobile-ui)/qr-pay/page.tsx:101 — QRPayPage: MDD 340.7 (uses across many lines from declarations)
  • src/app/(mobile-ui)/qr-pay/page.tsx:101 — QRPayPage: DLT 111 (calls 111 distinct functions — high context load)
  • src/components/Home/ActivationCTAs.tsx:79 — ActivationCTAs: MDD 51.3 (uses across many lines from declarations)
  • types/api.generated.ts:3466 — 49 duplicate lines / 139 tokens with types/api.generated.ts:6661
  • types/api.generated.ts:3466 — 49 duplicate lines / 137 tokens with types/api.generated.ts:7236
  • app/(mobile-ui)/dev/shake-test/page.tsx:27 — 46 duplicate lines / 216 tokens with app/(mobile-ui)/qr-pay/page.tsx:871

…and 359 more.

📈 Painscore deltas (top movers)

File Before After Δ
src/utils/capability-gate.ts 8.5 9.5 +1.0
src/utils/provider-rejection.utils.ts 4.1 5.2 +1.0

@github-actions

github-actions Bot commented Jul 15, 2026

Copy link
Copy Markdown
Contributor

🧪 UI test report — ✅ all green

Suites

  • unit: 1958 ran, 0 failed, 0 skipped, 33.0s

📊 Coverage (unit)

metric %
statements 59.0%
branches 43.0%
functions 47.8%
lines 59.2%
⏱ 10 slowest test cases
time test
3.9s src/components/Card/share-asset/__tests__/shareAssetLayout.test.ts › never places two stickers in heavy overlap (broad seed sweep)
1.1s src/utils/__tests__/demo-api.test.ts › isDemoMode() is false when not running under Capacitor
0.5s src/components/Card/share-asset/__tests__/shareAssetLayout.test.ts › every sticker stays within canvas at any count
0.3s src/app/actions/__tests__/api-headers.test.ts › should include Content-Type in validateInviteCode
0.3s src/components/Card/share-asset/__tests__/shareAssetLayout.test.ts › keeps stickers off the username pill (final pass respects the keep-out)
0.3s src/app/actions/__tests__/api-headers-extended.test.ts › should not include apiKey in validateInviteCode body
0.2s src/components/Global/SupportDrawer/__tests__/SupportDrawer.test.tsx › shows the mailto fallback + retry when the proxy reports CRISP_FAILED
0.1s src/utils/__tests__/demo-balance.test.ts › keeps a spent-down balance across a cold start within the TTL
0.1s src/utils/__tests__/demo-balance.test.ts › starts at the full balance on a fresh install and stamps a timestamp
0.1s src/utils/__tests__/demo-balance.test.ts › auto-refills a wallet older than the TTL on cold start
📍 Inline annotations are in the **Unit test report** check above. Coverage artifact: `coverage-unit`. Generated by `.github/workflows/tests.yml`.

@kushagrasarathe

Copy link
Copy Markdown
Contributor Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jul 15, 2026

Copy link
Copy Markdown
Contributor
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/app/`(mobile-ui)/qr-pay/page.tsx:
- Around line 195-233: Exclude rails whose resolved blocking selfHealKind is
"provide-email" from the fixableRail selection in the QR payment flow. Preserve
the existing PROVIDER_REJECTION_FIXABLE handling for other fixable rails,
allowing provide-email cases to fall through to the existing
PROVIDER_REJECTION_BLOCKED/contact-support path.

In `@src/utils/provider-rejection.utils.ts`:
- Around line 66-104: The verdict resolution logic is duplicated and incomplete
across two consumers. In src/utils/provider-rejection.utils.ts lines 66-104,
update deriveProviderRejection and its verdictOf fallback to preserve
selfHealKind, including provide-email, by reusing or extracting the shared
resolver used by capability-gate.ts or ActivationCTAs.tsx. In
src/app/(mobile-ui)/qr-pay/page.tsx lines 195-233, update the CTA selection to
exclude provide-email blocks on both resolved and legacy-derived verdicts,
routing them according to the existing provide-email handling rather than
document upload; have all affected consumers use one shared rail-verdict
resolver where practical.
- Around line 66-104: Update deriveProviderRejection and its verdictOf fallback
to detect legacy email-required rails using the same action lookup approach as
ActivationCTAs.tsx, such as a by-key or next-actions lookup threaded into the
function. Ensure the fallback sets or exposes provide-email classification so
isProvideEmail routes these rails through the dedicated fixable email path,
while preserving current handling for resolved verdicts and other legacy
statuses.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 88e64553-3d67-4137-850e-5f5cf8b49c28

📥 Commits

Reviewing files that changed from the base of the PR and between a9cb2d9 and ad903ab.

⛔ Files ignored due to path filters (1)
  • src/types/api.generated.ts is excluded by !**/*.generated.*
📒 Files selected for processing (9)
  • src/app/(mobile-ui)/qr-pay/page.tsx
  • src/app/actions/types/users.types.ts
  • src/components/Home/ActivationCTAs.tsx
  • src/interfaces/interfaces.ts
  • src/types/api.openapi.json
  • src/types/capabilities.ts
  • src/utils/capability-gate.test.ts
  • src/utils/capability-gate.ts
  • src/utils/provider-rejection.utils.ts
💤 Files with no reviewable changes (2)
  • src/interfaces/interfaces.ts
  • src/app/actions/types/users.types.ts

Comment thread src/app/(mobile-ui)/qr-pay/page.tsx Outdated
Comment thread src/utils/provider-rejection.utils.ts
…view round)

/code-review (47-agent verified pass) confirmed 8 findings; all fixed:

fallback fidelity (the live path until the BE ships resolved):
- railVerdict keys on STATUS first - action kinds only refine within a
  status. a blocked rail carrying a stale sumsub/tos/wait action stays
  blocked (the old gate never offered those CTAs on blocked rails);
  requires-info keeps its wait/self-serve/document-punt tiers
- ladder parity restored: the blocked-family CTA is decided on the FIRST
  blocked rail only (a terminal block beats a sibling's restart-identity,
  which cannot unblock it and burns Sumsub attempts); fixable-with-action
  outranks pending, action-less fixable ranks below it (the old 7b
  placement); requires-info + contact-support no longer outranks a
  fixable sumsub sibling

one collapse, one copy source (was four diverging hand-rolled copies):
- railVerdict + railUserMessage are exported; provider-rejection.utils,
  qr-pay and ActivationCTAs consume them (deriveProviderRejection gains
  an optional nextActions param for the fallback's action-kind
  refinement - callers without it keep pure status semantics)

qr-pay: a provide-email verdict maps to the unavailable modal, never the
document-upload flow (that surface has no email form; the upload flow
dead-ends an email-blocked user)

tests: +6 fallback-fidelity pins on the gate, +11 new
provider-rejection.utils suite, +1 qr-pay provide-email pin
@kushagrasarathe kushagrasarathe merged commit 5d5d66c into dev Jul 16, 2026
18 of 20 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants