Quality review found the Airwallex payload shape in the original plan was wrong on several points, verified against Airwallex's live public API reference:
- individual_documents[].document_type does not exist; identity documents belong under identifications.primary (passport/personal_id/drivers_license, with front/back file ids), not in the supporting-documents array. Added primary/liveSelfieFileId/supporting routing plus the Sumsub->Airwallex mapping tables in constants/identity-documents.ts.
- residential_address field names were wrong: street_address/street_address2/ city -> address_line1/address_line2/suburb.
- the upload budget's pre-upload deadline check could not bound total runtime (measured: 3 x 120s docs elapse 360s against a 240s budget without tripping). Replaced with a renewLease callback invoked after each upload, which Task 6 will use to keep the provisioning lease alive; kept the old deadline only as a coarse backstop with a corrected comment.
- added per-call timeouts to SumsubService (getApplicant/getApplicantImages/ getDocumentImage), which had none, mirroring the airwallex file-upload fix.
- reviewAnswer !== 'RED' admitted unreviewed images; changed to === 'GREEN' and added a regression test.
- guarded a missing addedDate instead of crashing, and required kyc fields (first/last name, dob, address) instead of sending Airwallex nulls.
- nationality now goes through toAlpha2 instead of passing through raw.
- added PermanentProvisioningError for input that cannot become valid on retry (unmappable country/nationality, missing kyc fields, no usable identity document), separate from the NotFoundException races (no kyc row, no inspectionId yet) that should still retry.
- buildIndividualDetails/uploadDocuments now take KycInfo as a parameter instead of each querying kyc_info independently, closing a race where a concurrent recordId rebind could split one call from the other.
Co-Authored-By: Claude Opus 5 (1M context) noreply@anthropic.com