Skip to content

efc367bf - fix(kyc): scope the KYC route by URL and report unmapped steps - #933

Draft
Danswar wants to merge 4 commits into
stagingfrom
fix/kyc-context-and-unmapped-step-report
Draft

efc367bf - fix(kyc): scope the KYC route by URL and report unmapped steps#933
Danswar wants to merge 4 commits into
stagingfrom
fix/kyc-context-and-unmapped-step-report

Conversation

@Danswar

@Danswar Danswar commented Aug 23, 2026

Copy link
Copy Markdown
Collaborator

EN:
The KYC context now travels in the route URL instead of go_router extra, so the flow stays scoped to the context its entry point reported even after a background lock — the restore re-pushes the captured location by bare path and had no extra to give, which rebuilt the flow unscoped. Every route into the unsupported-step handoff is now also reported as a non-fatal event carrying the step's wire identifier, so the next gap in the client's step-mapping table is visible instead of silent. No new screens, no new user-facing strings, no change to what the API is asked or told. Covers items 3 and 5 of DFXswiss/api#4556.

DE:
Der KYC-Context reist neu in der Route-URL statt im go_router-extra, damit der Flow auch nach einem Hintergrund-Lock auf den Context seines Einstiegspunkts eingegrenzt bleibt — der Restore pusht die gemerkte Location als blossen Pfad und hatte kein extra mitzugeben, wodurch der Flow ungescoped neu aufgebaut wurde. Jeder Weg in den Unsupported-Step-Handoff wird zusätzlich als Non-Fatal-Event mit dem Wire-Identifier des Steps gemeldet, damit die nächste Lücke in der Step-Mapping-Tabelle des Clients sichtbar statt still ist. Keine neuen Screens, keine neuen Texte, keine Änderung daran, was die API gefragt oder ihr mitgeteilt wird. Deckt Items 3 und 5 aus DFXswiss/api#4556 ab.

Details

Item 3 — the KYC context on entry

The buy and sell gates already forwarded the context the API attached to KYC_LEVEL_REQUIRED / REGISTRATION_REQUIRED, so those entries were scoped. The one remaining way into /kyc without a context was the post-lock restore: BootNavRestore re-pushes the captured location with router.push(location) — a bare path, no extra — so KycPageManager rebuilt with kycContext: null. Unscoped, the API computes processStatus over every globally required step, including the ones RealunitBuy deliberately excludes, and a user who has finished Ident is reported InProgress for steps that do not gate buying.

Moving the context into the URL fixes that entry without touching the lock/restore machinery: the capture already stores effectiveLocation, which includes the query, and isGateLocation / isRestorableLocation compare Uri.parse(loc).path only, so the allowlist match is unaffected.

  • kycRouteQuery(String?) in app_routes.dart builds the query; a null context yields an empty map, so a gate the API attached no context to enters exactly as unscoped as before — the app never invents one.
  • The five entry points in payment_action_button.dart and sell_button.dart pass queryParameters: instead of extra:; the route builder reads state.uri.queryParameters['context'].

Item 5 — an unmapped step is no longer silent

The generic handoff page and its routing arm already exist (#883): an unmapped step renders KycUnsupportedStepPage with a refresh and a support link, not a fatal error screen. What was missing is the other half — nothing recorded the occurrence. Every request in the flow returns 200 and nothing is logged above LOG level, so a step name absent from _mapStepName only surfaced through a user report.

  • The four sites that emitted KycUnsupportedStepFailure now funnel through one _emitUnsupportedStep, which reports before emitting.
  • reportNonFatal in crash_reporting.dart logs for an attached developer and, when a DSN was injected at build time, captures an error event. Same channel, same DSN gate and same pinned option surface as the uncaught-error path — no PII, no attachments, no breadcrumb widening. Without a DSN (every local and test build) nothing was started and the call is a pure log line.
  • The payload is UnsupportedKycStepException, whose rendered string is the step's API wire identifier and nothing else. Per CONTRIBUTING it is enumerated in exception_surface_test.dart in this PR.
  • The reporter is an injectable seam on KycCubit (optional named report, defaulting to reportNonFatal), so the cubit tests observe the report and no test reaches the real sink.

Deliberately not in scope

  • Item 4b (dedicated Recommendation / ResidencePermit pages) — the issue itself recommends the generic fallback first and building the individual forms only once a real affected population is measured. That measurement is what item 5 now supplies.
  • Items 1 and 2 are API-side / dropped.

Verification

  • Both pre-push review lanes (conformity/CONTRIBUTING and logic/correctness) reported 0 findings on the full diff in one round.
  • No Flutter SDK is available on the machine this was written on, so flutter analyze and flutter test were not run locally — CI is the first execution of this diff. Any red check gets reworked before this leaves draft.

Tests added or changed

Test Pins
boot_navigation_apply_test.dart a restore of /kyc?context=RealunitBuy rebuilds the flow with that context
kyc_route_query_test.dart the query carries a context and omits the parameter when the API attached none
payment_action_button_test.dart the buy gate delivers the context as a query parameter
kyc_cubit_test.dart the unmapped step and the no-step-named case each report exactly once, with the right step name; a mapped step reports nothing
unsupported_kyc_step_exception_test.dart the reported string names the step by its wire identifier, and stays readable when no step was named
crash_reporting_test.dart reportNonFatal is inert and non-throwing when no reporter was started

Danswar and others added 4 commits August 23, 2026 10:19
Carry the KYC context in the route URL instead of go_router `extra`.
The post-lock restore re-pushes the captured location by bare path and
has no `extra` to give, so the flow rebuilt unscoped after a background
lock: the API then computed `processStatus` over every globally required
step instead of only those the entered flow needs, reporting a user who
had finished Ident as still in progress for steps that do not gate
buying.

Report every route into the unsupported-step handoff as a non-fatal
event. The handoff page already gave the user something to do, but the
gap itself stayed invisible — every call in the flow returns 200, so a
step name missing from the client mapping table only surfaced through a
user report. The event carries the step's wire identifier and nothing
else, through the existing DSN-gated crash-reporting channel.
Rename UnsupportedKycStepException to KycUnsupportedStepException to
match the feature's Kyc-prefixed naming convention (KycUnsupportedStepFailure,
KycUnsupportedStepPage, KycLevelRequiredException). Treat an empty-string
context the same as absent so an API-supplied empty value can never
overwrite a real stored context. Wrap the Sentry call in reportNonFatal
in a try/catch so a reporting-layer failure can never propagate into a
caller, matching the swallow-on-failure contract already used by
initCrashReporting.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Normalize an empty-string context to absent in kycRouteQuery too, not
just in KycCubit, so an API-supplied empty context can never produce an
invented /kyc?context= query. Add sell-side coverage for context
forwarding on kycRequired/registrationRequired, matching the existing
buy-side test. Replace the sync-only try/catch around the Sentry call
in reportNonFatal with Future.ignore(), which also discards an
asynchronous rejection instead of letting it surface as an unhandled
error. Fix an import-order regression introduced by the previous
rename.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
The doc comment described the null case only; the function has treated
empty the same as null since the previous commit.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
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.

1 participant