backend phase 6: nurse verification & credentials (mocked vendors)

The trust engine. New `verif` schema (5 tables) + a data-driven verification
pipeline: steps are rows (6 seeded step-types), not a code enum.

- nurse_verifications.status is the single source of verification truth;
  nurse_profiles.is_verified is flipped ONLY inside the finalize transaction
  (VerificationAggregator: tracked verification + tracked profile -> one commit)
  and reversed on suspension/expiry — no in-between state.
- is_automated snapshotted onto each step at submit; steps seeded from active
  required step-types; automated runs (identity-KYC, Shahkar, IBAN ownership)
  find their step by code.
- users.national_id populated only on identity-KYC pass; Shahkar + IBAN owner
  compare against it (money-mule guard); shared-SIM -> shared_sim support alert.
- Documents are metadata-only behind signed URLs; credential_number encrypted
  and never serialized; public trust badge exposes credential TYPES, not numbers;
  holder-name cross-checked against the verified identity before recording.
- Admin-triggered credential-expiry scan reverts lapsed steps, re-gates
  bookability, raises a verification_expired alert + verification_expiry_prompt
  notification (scheduled cron deferred; config key
  verification_expiry_scan_cadence_hours).

Three new mock vendor seams (IShahkarVerifier / IIdentityKycProvider /
ICredentialVerifier) behind DI; reuses b3 IBankAccountOwnershipVerifier and
b0 IObjectStorage/IFieldEncryptor. 15 endpoints across 4 controllers.

Two migrations (tables + step-type seed). 154 tests pass, zero new warnings.
Contract dev/contracts/domains/verification.md + swagger snapshot refreshed;
handoff/report/mocks-registry updated.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
hamid
2026-07-05 14:39:32 +03:30
parent 687fbfc6d9
commit 1c266523bc
105 changed files with 13938 additions and 10 deletions
@@ -43,6 +43,14 @@
<li><strong>MVP:</strong> all six steps; data-driven <code>verification_step_types</code>; structured <code>nurse_credentials</code> registry; manual MoH/INO verification; nurse-uploaded عدم سوء پیشینه with expiry; automated identity + Shahkar + IBAN-ownership via one KYC vendor; expiry-driven re-verification alerts; transactional <code>is_verified</code>.</li>
<li><strong>DEFERRED:</strong> automated MoH/INO license lookup (pending a B2B API); ML-driven fraud scoring (<code>fraud_flags</code> is modeled but inactive); professional-liability-insurance step (addable as a row when required).</li>
</ul>
<h2 id="c-bis-as-built-clarifications-backend-phase-6">(c-bis) As-built clarifications (backend-phase-6) <a class="anchor" href="#c-bis-as-built-clarifications-backend-phase-6" aria-hidden="true">#</a></h2>
<p>Rules confirmed while building the pipeline (implementation in the <code>verif</code> schema):</p>
<ul>
<li><strong>Step state <code>pending</code> vs <code>in_review</code>:</strong> <code>pending</code> = awaiting submission/automation (a seeded or automated step not yet run); <code>in_review</code> = a manual step with evidence uploaded, awaiting the admin decision. The aggregate is <code>in_review</code> while any required step is <code>in_review</code>, <code>rejected</code> if any required step failed, and <code>approved</code> only when <strong>every</strong> required step is <code>passed</code> (the same transaction flips <code>is_verified</code>).</li>
<li><strong>Renewal is an in-app notification, not only an alert:</strong> an expiry re-scan raises a <code>verification_expired</code> <strong>support alert</strong> (staff worklist) <em>and</em> sends the nurse a <code>verification_expiry_prompt</code> <strong>notification</strong>; the required step reverts to <code>expired</code> and bookability is re-gated.</li>
<li><strong>The public trust badge exposes credential _types_ held, never the numbers</strong> (e.g. "MoH license · INO member"); <code>credential_number</code> is encrypted and never serialized.</li>
<li><strong>Shared-SIM</strong> is surfaced as a distinct <code>shared_sim</code> support-alert type (non-accusatory), separate from a plain phone↔national-id mismatch.</li>
</ul>
<h2 id="d-supporting-database-entities">(d) Supporting database entities <a class="anchor" href="#d-supporting-database-entities" aria-hidden="true">#</a></h2>
<p><code>nurse_verifications</code>, <code>verification_step_types</code>, <code>verification_steps</code>, <code>verification_documents</code>, <strong><code>nurse_credentials</code></strong> (structured license registry), <code>nurse_bank_accounts</code> (IBAN ownership), <code>support_alerts</code> (expiry/renewal), <code>audit_logs</code>.</p>
<blockquote><p><strong>Related:</strong> Data model — <a href="../data-model/04-verification-and-credentials.html">Verification &amp; Credentials</a>; Research — <a href="../research/verification.html">Verification</a>.</p>
@@ -30,6 +30,13 @@ The verification steps:
- **MVP:** all six steps; data-driven `verification_step_types`; structured `nurse_credentials` registry; manual MoH/INO verification; nurse-uploaded عدم سوء پیشینه with expiry; automated identity + Shahkar + IBAN-ownership via one KYC vendor; expiry-driven re-verification alerts; transactional `is_verified`.
- **DEFERRED:** automated MoH/INO license lookup (pending a B2B API); ML-driven fraud scoring (`fraud_flags` is modeled but inactive); professional-liability-insurance step (addable as a row when required).
## (c-bis) As-built clarifications (backend-phase-6)
Rules confirmed while building the pipeline (implementation in the `verif` schema):
- **Step state `pending` vs `in_review`:** `pending` = awaiting submission/automation (a seeded or automated step not yet run); `in_review` = a manual step with evidence uploaded, awaiting the admin decision. The aggregate is `in_review` while any required step is `in_review`, `rejected` if any required step failed, and `approved` only when **every** required step is `passed` (the same transaction flips `is_verified`).
- **Renewal is an in-app notification, not only an alert:** an expiry re-scan raises a `verification_expired` **support alert** (staff worklist) *and* sends the nurse a `verification_expiry_prompt` **notification**; the required step reverts to `expired` and bookability is re-gated.
- **The public trust badge exposes credential _types_ held, never the numbers** (e.g. "MoH license · INO member"); `credential_number` is encrypted and never serialized.
- **Shared-SIM** is surfaced as a distinct `shared_sim` support-alert type (non-accusatory), separate from a plain phone↔national-id mismatch.
## (d) Supporting database entities
`nurse_verifications`, `verification_step_types`, `verification_steps`, `verification_documents`, **`nurse_credentials`** (structured license registry), `nurse_bank_accounts` (IBAN ownership), `support_alerts` (expiry/renewal), `audit_logs`.