Files
baya-monorepo/archive/build-chain/working-context/backend/handoff/after-backend-phase-6.md
T
2026-08-02 18:48:32 +03:30

7.2 KiB

After backend-phase-6 — nurse verification & credentials are live (vendors mocked)

The trust engine now exists. A nurse opens a verification checklist, clears automated checks (identity-KYC, Shahkar, bank-ownership) and uploads documents for manual credential steps; an admin reviews, decides, and — when every required step passes — the platform flips nurse_profiles.is_verified inside a single transaction, making the nurse bookable. Credential numbers are encrypted and never leave the API; the public trust badge exposes only the credential types a nurse holds. Contract: dev/contracts/domains/verification.md; machine schema: dev/contracts/openapi/swagger.v1.json (refreshed for b6 — all 15 endpoints). All vendor/money calls are mocked behind deterministic DI seams.

What the frontend (f5-b6) can now build

  • The nurse verification checklist screenGET api/v1/nurse_verification returns status, isBookable, blockingSteps[], and steps[] (each with code, displayName, status, isAutomated, expiresAt?, failureReason?). POST api/v1/nurse_verification/submit opens/seeds it (idempotent). Requires a nurse profile first (b3 nurse_profiles/upsert). Render the checklist off steps, gate the "you're bookable" state on isBookable, and surface blockingSteps as the to-do list.
  • Identity submit (automated)POST api/v1/nurse_verification/steps/identity_kyc/run { nationalId, livenessPayload? }RunStepResult (stepStatus, failureReason?). A pass populates the nurse's national id; drive the next steps off it.
  • Shahkar & bank runs (automated)POST .../steps/shahkar_match/run (no body; needs KYC passed) and POST .../steps/bank_account_verification/run (no body; needs KYC + a primary bank account from b3). Both return RunStepResult. Shared-SIM comes back as a clean failed + reason (not an error) — show it.
  • Document upload for manual steps — two-step: POST .../steps/{stepId}/upload_url { contentType, fileName? }{ objectStorageKey, uploadUrl }; PUT the file to uploadUrl; then POST .../steps/{stepId}/documents { objectStorageKey, integrityHash, contentType, fileSizeBytes, originalFileName? } → the step moves to in_review. Manual steps only (an automated step 400s the upload).
  • The credentials / under-review / rejected states — render each step.status (not_started/pending/in_review/passed/failed/expired) with the right affordance; in_review = "with our team", failed/expired = show failureReason + a redo path, passed = done.
  • The public trust badge (f6)GET api/v1/nurses/{nurseId}/trust_badge (anonymous) → isVerified, approvedAt?, credentialTypes[]. Types only — never a credential number. Back the nurse-profile trust chip / verified marker with it.

Live endpoints (all under api/v1, action-style, camelCase bodies)

Nurse ([Authorize], nurse-scoped): nurse_verification/submit, GET nurse_verification, nurse_verification/steps/{stepId}/upload_url, nurse_verification/steps/{stepId}/documents, nurse_verification/steps/identity_kyc/run, nurse_verification/steps/shahkar_match/run, nurse_verification/steps/bank_account_verification/run. Admin step-types (dynamic-permission): GET admin_verification_step_types, POST admin_verification_step_types, DELETE admin_verification_step_types/{id}. Admin review (dynamic-permission): GET admin_verifications, GET admin_verifications/{nurseVerificationId}, admin_verifications/steps/{stepId}/decide, admin_verifications/{nurseVerificationId}/suspend, admin_verifications/scan_expiring. Public ([AllowAnonymous]): GET nurses/{nurseId}/trust_badge.

Rules baked into the API (don't fight them client-side)

  • isBookable / isVerified are read-only, server-derived. Never infer verification client-side — read VerificationStatusDto.isBookable (nurse view) or TrustBadgeDto.isVerified (public view). The aggregate nurse_verifications.status is the single source of truth; is_verified is flipped only inside the finalize transaction (and reversed on suspend/expiry).
  • Credential numbers never cross the wire. NurseCredentialDto / TrustBadgeDto carry types and metadata only. Don't build a UI that expects to display a number.
  • Automated vs manual steps drive different UI. step.isAutomated=true → a /run button; false → the upload flow (upload_url → PUT → documents). The list tells you which.
  • Ordering / prerequisites — Shahkar needs identity-KYC passed; bank verification needs KYC and a primary bank account (b3). Calling out of order → 400; disable/guide accordingly.
  • Shared-SIM and vendor fails are 200 with stepStatus:"failed" + failureReason, not HTTP errors — surface the reason, don't treat as a crash. (Shared-SIM also raises an internal support alert.)
  • Documents come back as short-lived signed URLs (VerificationDocumentDto.url) — fetch/display fresh; don't cache the URL.
  • Refresh after select_role still applies (nurse scoping reads the role claim in the token).
  • Routes are action-style POST (nurse_verification/submit, admin_verifications/steps/{id}/decide, …), ids from the route, camelCase bodies — see the contract for the full list.

Schema / migration

New verif schema, one additive migration, 5 tables: nurse_verifications (header; status = single source of truth), verification_step_types (seeded catalog — six stable codes), verification_steps (one per required step-type; snapshots is_automated), verification_documents (metadata only — bytes never in the DB), nurse_credentials (credential_number encrypted, never serialized). The only derived boolean is nurse_profiles.is_verified, flipped/reversed transactionally.

What's mocked

All vendor/money calls (deterministic seams; test values in the contract):

  • IShahkarVerifierMockShahkarVerifier (pass unless shared-SIM 09120000000 / mismatch id 1111111111).
  • IIdentityKycProviderMockIdentityKycProvider (passes any well-formed 10-digit id except 0000000000).
  • ICredentialVerifierMockCredentialVerifier (manual-admin default; verification_method=manual).
  • Reused: IBankAccountOwnershipVerifier (b3; mismatch IBAN IR000000000000000000000000), IObjectStorage (b0; local-disk signed URLs), IFieldEncryptor (b0; encrypts credential_number).

See reports/mocks-registry.md for the make-it-real steps.

Deferred to later phases (do not build against these yet)

  • Scheduled expiry cron (CredentialExpiryScannerJob) → the scan logic ships as ScanExpiringCredentialsCommand; today only the admin scan_expiring endpoint triggers it.
  • Automated MoH/INO license lookup (ICredentialVerifier, verification_method=api) → deferred.
  • fraud_flags / ML fraud scoring → deferred.
  • Professional-liability-insurance step → addable later as a step-type row (no schema change).
  • b7 (search & matching) reads nurse_profiles.is_verified for nurse_search_index.is_searchable — b6 owns the flip, b7 owns the index (not built here).