Nirnay must not persist a full Aadhaar number or an unmasked Aadhaar document. For newly processed Nirnay data, the database retains only a masked display value and an opaque vault reference. Lead Collection remains unchanged as an explicit forward-only boundary for the later platform-wide remediation.
For an officer, Aadhaar upload and verification remain familiar: upload both
sides, then enter a 12-digit number only when verification is needed. Nirnay
shows XXXX XXXX 1234, never the complete number.
Behind that experience, the system now guarantees that Nirnay stores only a masked document and, where a number is needed, an opaque protected reference. OCR is helpful but not required to produce the number: a provider can return a complete number, a masked number, or no number without changing the officer workflow or weakening the storage rule.
The existing flow did not consistently distinguish complete Aadhaar OCR from an OCR vendor's masked/no-number result. A complete OCR value could continue into Nirnay persistence, and dual-sided uploads could merge and upload the original front/back bytes. The main escape paths were:
card_number as raw_value and
normalized_value in CustomerProfileFieldValue.The protection implementation makes the existing validation engine the single place where vendor OCR, masking, and Aadhaar protection behaviour lives. Documents and Customer Profile use internal capabilities and never import a vendor strategy directly. The decision is based on canonical OCR data, never the OCR vendor: exactly 12 digits follows the protected-candidate path; every other OCR result follows the masked-document-only path.
| Field | Aadhaar value retained |
|---|---|
raw_value |
XXXX XXXX 1234 |
normalized_value |
XXXX XXXX 1234 |
protected_reference |
Aadhaar vault token |
protection_scheme |
aadhar_vault_store |
| candidate/event hashes | Masked value and protected reference only |
protected_reference and protection_scheme are deliberately not included
in Customer Profile API serializers.
| Situation | Document upload | Aadhaar profile value | Next officer action |
|---|---|---|---|
| OCR returns exactly 12 digits | Stores only the masked document | Creates masked, protected candidate | Enter the number to verify it against the protected value |
| OCR returns a mask or no usable number | Stores only the masked document | No Aadhaar candidate is created | Enter the number in Verify Aadhaar; it is protected and verified then |
| Masking provider does not return both masked sides | Nothing is persisted until another eligible provider is tried | No candidate | Retry automatically through the validation engine; show upload failure only if no provider can supply both sides |
| OCR/masking/protection fails | No raw document or Aadhaar candidate is stored | No change | Correct the document or retry later |
| Historical selected value has no protected reference | Existing record is not used | No runtime fallback | Run the one-time remediation, then verify |
The ordered document-plan registry remains the source of truth. Aadhaar now has the following plan while other document plans stay unchanged.
documents.services.verification.steps is the shared handler registry used
by both synchronous dual-sided upload and the asynchronous document runner.
It passes a transient in-memory context between steps. The context may contain
OCR data, masked replacement image bytes, and a protection token; it is
sanitized before Document records or events are saved. vault is successful
but skipped with no_full_aadhaar_from_ocr when canonical OCR does not contain
exactly 12 digits.
The validation engine may route a masking request to more than one approved provider. A successful provider response is accepted for a dual-sided upload only when it contains replacement images for both sides. This is important because some providers can return a valid masked front image without a usable masked back image.
This safeguard lives in Validations V2, not in LOS or Customer Profile. It is therefore reusable for any future document type that requires replacement artifacts from a provider.
Aadhaar is rejected from the ordinary single-file upload path because it would place raw bytes in Nirnay storage before the masking step could complete.
The source Lead Collection record and object are not changed. The Lead Temporal workflow runs OCR and masking in parallel, then persists OCR data on the masked Lead document; Nirnay must not attempt to recover a full Aadhaar by OCRing the already-masked artifact.
The UI does not prefill the stored mask as a value. It accepts a 12-digit
Aadhaar input and sends it only to verify-aadhaar.
The generic single-field and batch manual-field APIs reject
aadhaar_number, preventing bypass of this flow. If vaulting succeeded but a
subsequent manual persistence operation fails, the implementation makes a
best-effort vault-token deletion call.
| Moment | What the officer sees |
|---|---|
| Aadhaar has been uploaded or verified | A masked value such as XXXX XXXX 5398 |
| Officer clicks edit | An empty numeric field; the existing mask is never copied into the input |
| Officer saves Aadhaar | Verify Aadhaar |
| Submitted number is invalid | A request to enter a 12-digit Aadhaar number |
| Submitted number does not match an existing protected candidate | A normal mismatch message; neither value is exposed |
The UI does not mention tokens, vaulting, providers, OCR vendors, or any other backend protection mechanism.
The following data is removed before Document persistence or event recording:
card_number values;Documents retain only safe step status/diagnostic summaries and, for the vault step, a masked display value. Validations continues to own its existing redacted audits. Customer Profile PII redaction also treats vault-token fields as secrets.
| Concern | Location |
|---|---|
| Internal vault service | Validations/engine/services/aadhaar_vault.py |
| Shared document handlers/sanitizer | documents/services/verification/steps.py |
| Aadhaar document plan | documents/services/verification/registry.py |
| Protected field processor registry | customer_profile/services/field_processing.py |
| Candidate persistence | customer_profile/services/bootstrap.py |
| Manual match/verify | customer_profile/services/aadhaar_match_verify.py |
| Orchestration import protection | documents/services/orchestration_ingest.py |
| LOS replacement-only edit behaviour | intra-frontend/components/los/customer-profile/PDCustomerProfilePage.tsx |
This implementation is forward-only for Nirnay. Historical tokenless Nirnay rows are repaired only through the separately supplied, one-time shell remediation; runtime code does not contain a legacy fallback. Lead Collection source data and historical artifacts remain outside this scope.