Skip to content

Account merge: status endpoint cannot distinguish 'merge in progress' from 'idle' — surface a MergeProcessing state #3802

@TaprootFreak

Description

@TaprootFreak

Problem

After the user confirms a merge, the backend continues processing (step re-parenting, KYC follow-up, level transition, mail). The polling status endpoint that the client uses while the merge processes — KycController.getKycInfo (src/subdomains/generic/kyc/controllers/kyc.controller.ts:119–124KycService.getInfoKycInfoMapper.toDto) — has no way to communicate "your merge is still being processed". The response is computed synchronously from userData + kycSteps and never references the AccountMerge lifecycle.

The AccountMerge entity (src/subdomains/generic/user/models/account-merge/account-merge.entity.ts) tracks only:

  • isCompleted: boolean (default false)
  • expiration: Date
  • derived isExpired = expiration < now()

…with no intermediate "user-confirmed, backend processing" state. Once executeMerge() runs, isCompleted flips to true (account-merge.entity.ts:48–58). Between the user clicking "confirm" and the merge committing, the entity row says isCompleted = false, !isExpired — indistinguishable from "merge requested, not yet confirmed".

User impact

The realunit-app polls the status endpoint with a 30-second client-side timeout. When the merge is still processing (e.g. checkDfxApproval follow-up running, see #3801), the timeout fires and the user sees "Fehler beim Laden" even though nothing actually failed. The accompanying app#611 already plans to drop the local timeout interpretation, but the app cannot render a meaningful waiting state until the API exposes one.

Suggested fix shape

Two parts, both additive on the API side:

  1. Add a processing marker to AccountMerge. Smallest change: one new nullable timestamp column (e.g. processingStartedAt: Date | null). Set when executeMerge() begins; clear when it finishes (or when isCompleted = true). The "merge in progress" predicate is then isCompleted = false && processingStartedAt != null && !isExpired.

  2. Surface the predicate in the status response. Either:

    • Extend KycLevelDto with a new optional field (e.g. accountMergeState?: 'requested' | 'processing'), populated by KycInfoMapper.toDto after a single query against accountMergeRepo.
    • Or extend the existing KycProcessStatus enum (kyc-info.dto.ts) with a new value like MergeProcessing, returned by computeProcessStatus when an in-flight merge exists for the userData. This is the form aligned with how the app already routes on processStatus (Wave 2 / app#494).

Either form is additive; old clients ignore the new field / treat the new enum value as a no-op.

Pair-PR

Companion app PR required: realunit-app renders the new state as a waiting screen (with appropriate copy) instead of interpreting the polling timeout as failure. The pair-PR contract is documented in realunit-app/CONTRIBUTING.md:23–67 ("API as Decision Authority") and the wave plan in realunit-app/docs/api-authority-plan.md.

Related

  • #3801 (KYC level-50 completion mail delayed ~20 min) overlaps in trigger and benefits from the same event-driven post-merge dispatch.
  • #3800 (slave KycStep FK not reassigned) is independent but also under "post-merge correctness".

Source

Surfaced in DFXswiss/realunit-app#611 (item 5).

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