Skip to content

KYC: investigate root cause behind DFX_APPROVAL persisting as IN_PROGRESS (PR #3794 follow-up) #3812

@TaprootFreak

Description

@TaprootFreak

Context

PR #3794 fixed the user-visible symptom of a KycStepName.DFX_APPROVAL step reaching the client as a user-actionable currentStep with processStatus = InProgress, which has no UI on the client side (blank screen on RealUnit app, see realunit-app#618).

That fix is at the DTO boundary in KycInfoMapper. Stacked PR #3811 hardens the test coverage of the same boundary and adds a logger.warn so we get visibility when the broken state surfaces.

Neither PR touches the upstream cause: why does a DFX_APPROVAL step ever persist in ReviewStatus.IN_PROGRESS long enough for the mapper to see it?

Expected behaviour

By design, every initiateStep path for DFX_APPROVAL transitions the step away from IN_PROGRESS before save:

  • src/subdomains/generic/kyc/services/kyc.service.ts:1337-1348kycLevel >= LEVEL_50complete(); no missing steps → manualReview(); otherwise → onHold().
  • kycStep.complete() / manualReview() / onHold() are synchronous mutations on the entity (src/subdomains/generic/kyc/entities/kyc-step.entity.ts:234,331,339).

The default IN_PROGRESS from KycStep.create() (src/subdomains/generic/kyc/entities/kyc-step.entity.ts:157) should be a transient in-memory state, never persisted for DFX_APPROVAL.

Actual behaviour

Empirically the mapper has seen DFX_APPROVAL steps in actionable status (covered by the new tests in #3794 / #3811). Possible vectors to investigate:

  1. A code path that constructs a DFX_APPROVAL step without going through initiateStep (direct KycStep.create + save).
  2. An error path in initiateStep that saves the step before the switch transitions it.
  3. A retry / restart / repair path that re-emits the step in IN_PROGRESS.
  4. Historical data: legacy rows from before the transition logic was added.

Proposal

  1. Audit every write site for DFX_APPROVAL (grep KycStep.create, kycStepRepo.save, manual entity construction) and confirm no path persists IN_PROGRESS.
  2. Optionally tighten KycStep.create() so a DFX_APPROVAL (and any future name in KycStepNonUserActionable) cannot be constructed without an explicit non-actionable status — moves the invariant into the factory.
  3. Or, narrower: assert in kycStepRepo.save (or via a pre-save hook) that no KycStepNonUserActionable row leaves the service in IN_PROGRESS.
  4. Backfill: a one-off migration scanning for existing rows in this state and routing them to manualReview / onHold based on the user's kycLevel and missing-steps set.

Once the root cause is fixed, the mapper guard from #3794 and the logger.warn from #3811 become belt-and-braces — no behaviour change on the client, but the warn line stops firing in production logs, which is the canary.

References

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions