diff --git a/.api-sync/spec-map.json b/.api-sync/spec-map.json index 7f1f379..dca23cc 100644 --- a/.api-sync/spec-map.json +++ b/.api-sync/spec-map.json @@ -6,267 +6,1889 @@ ], "enums": [ { - "spec": { "schema": "WebhookEndpointIn", "property": "events", "items": true }, - "sdk": { "file": "src/blindpay/resources/webhooks/webhooks.py", "symbol": "WebhookEvents" } + "spec": { + "schema": "BankAccountOut", + "property": "account_class" + }, + "sdk": { + "file": "src/blindpay/types.py", + "symbol": "AccountClass" + } + }, + { + "spec": { + "schema": "BankAccountOut", + "property": "account_type" + }, + "sdk": { + "file": "src/blindpay/types.py", + "symbol": "BankAccountType" + }, + "note": "KNOWN DIVERGENCE, kept mapped on purpose: spec enum is [checking, saving], SDK Literal is [checking, savings]. See unmodeled.json kind=enum entry; do not blindly append, this needs a coordinated rename PR (confirmed live defect, not just typing)." + }, + { + "spec": { + "schema": "BankAccountOut", + "property": "ach_cop_document_type" + }, + "sdk": { + "file": "src/blindpay/resources/bank_accounts/bank_accounts.py", + "symbol": "AchCopDocument" + } + }, + { + "spec": { + "schema": "BankAccountOut", + "property": "country" + }, + "sdk": { + "file": "src/blindpay/types.py", + "symbol": "Country" + } + }, + { + "spec": { + "schema": "BankAccountOut", + "property": "swift_bank_country" + }, + "sdk": { + "file": "src/blindpay/types.py", + "symbol": "Country" + } + }, + { + "spec": { + "schema": "BankAccountOut", + "property": "swift_beneficiary_country" + }, + "sdk": { + "file": "src/blindpay/types.py", + "symbol": "Country" + } + }, + { + "spec": { + "schema": "BankAccountOut", + "property": "swift_intermediary_bank_country" + }, + "sdk": { + "file": "src/blindpay/types.py", + "symbol": "Country" + } + }, + { + "spec": { + "schema": "BankAccountOut", + "property": "transfers_type" + }, + "sdk": { + "file": "src/blindpay/resources/bank_accounts/bank_accounts.py", + "symbol": "ArgentinaTransfers" + } + }, + { + "spec": { + "schema": "BankAccountOut", + "property": "type" + }, + "sdk": { + "file": "src/blindpay/types.py", + "symbol": "Rail" + } + }, + { + "spec": { + "schema": "BlockchainWalletOut", + "property": "network" + }, + "sdk": { + "file": "src/blindpay/types.py", + "symbol": "Network" + } + }, + { + "spec": { + "schema": "CreateBankAccountIn", + "property": "account_class" + }, + "sdk": { + "file": "src/blindpay/types.py", + "symbol": "AccountClass" + } + }, + { + "spec": { + "schema": "CreateBankAccountIn", + "property": "account_type" + }, + "sdk": { + "file": "src/blindpay/types.py", + "symbol": "BankAccountType" + } + }, + { + "spec": { + "schema": "CreateBankAccountIn", + "property": "ach_cop_document_type" + }, + "sdk": { + "file": "src/blindpay/resources/bank_accounts/bank_accounts.py", + "symbol": "AchCopDocument" + } + }, + { + "spec": { + "schema": "CreateBankAccountIn", + "property": "country" + }, + "sdk": { + "file": "src/blindpay/types.py", + "symbol": "Country" + } + }, + { + "spec": { + "schema": "CreateBankAccountIn", + "property": "recipient_relationship" + }, + "sdk": { + "file": "src/blindpay/types.py", + "symbol": "RecipientRelationship" + } + }, + { + "spec": { + "schema": "CreateBankAccountIn", + "property": "sepa_beneficiary_country" + }, + "sdk": { + "file": "src/blindpay/types.py", + "symbol": "Country" + } + }, + { + "spec": { + "schema": "CreateBankAccountIn", + "property": "spei_protocol" + }, + "sdk": { + "file": "src/blindpay/types.py", + "symbol": "SpeiProtocol" + } + }, + { + "spec": { + "schema": "CreateBankAccountIn", + "property": "swift_bank_country" + }, + "sdk": { + "file": "src/blindpay/types.py", + "symbol": "Country" + } + }, + { + "spec": { + "schema": "CreateBankAccountIn", + "property": "swift_beneficiary_country" + }, + "sdk": { + "file": "src/blindpay/types.py", + "symbol": "Country" + } + }, + { + "spec": { + "schema": "CreateBankAccountIn", + "property": "swift_intermediary_bank_country" + }, + "sdk": { + "file": "src/blindpay/types.py", + "symbol": "Country" + } + }, + { + "spec": { + "schema": "CreateBankAccountIn", + "property": "transfers_type" + }, + "sdk": { + "file": "src/blindpay/resources/bank_accounts/bank_accounts.py", + "symbol": "ArgentinaTransfers" + } + }, + { + "spec": { + "schema": "CreateBankAccountIn", + "property": "type" + }, + "sdk": { + "file": "src/blindpay/types.py", + "symbol": "Rail" + } + }, + { + "spec": { + "schema": "CreateBlockchainWalletIn", + "property": "network" + }, + "sdk": { + "file": "src/blindpay/types.py", + "symbol": "Network" + } + }, + { + "spec": { + "schema": "CreateCustomerIn", + "property": "account_purpose" + }, + "sdk": { + "file": "src/blindpay/resources/customers/customers.py", + "symbol": "AccountPurpose" + } + }, + { + "spec": { + "schema": "CreateCustomerIn", + "property": "business_industry" + }, + "sdk": { + "file": "src/blindpay/resources/customers/customers.py", + "symbol": "BusinessIndustry" + } + }, + { + "spec": { + "schema": "CreateCustomerIn", + "property": "business_type" + }, + "sdk": { + "file": "src/blindpay/resources/customers/customers.py", + "symbol": "CustomerBusinessType" + } + }, + { + "spec": { + "schema": "CreateCustomerIn", + "property": "country" + }, + "sdk": { + "file": "src/blindpay/types.py", + "symbol": "Country" + } + }, + { + "spec": { + "schema": "CreateCustomerIn", + "property": "estimated_annual_revenue" + }, + "sdk": { + "file": "src/blindpay/resources/customers/customers.py", + "symbol": "EstimatedAnnualRevenue" + } + }, + { + "spec": { + "schema": "CreateCustomerIn", + "property": "id_doc_country" + }, + "sdk": { + "file": "src/blindpay/types.py", + "symbol": "Country" + } + }, + { + "spec": { + "schema": "CreateCustomerIn", + "property": "id_doc_type" + }, + "sdk": { + "file": "src/blindpay/resources/customers/customers.py", + "symbol": "IdentificationDocument" + } + }, + { + "spec": { + "schema": "CreateCustomerIn", + "property": "proof_of_address_doc_type" + }, + "sdk": { + "file": "src/blindpay/resources/customers/customers.py", + "symbol": "ProofOfAddressDocType" + } + }, + { + "spec": { + "schema": "CreateCustomerIn", + "property": "purpose_of_transactions" + }, + "sdk": { + "file": "src/blindpay/resources/customers/customers.py", + "symbol": "PurposeOfTransactions" + } + }, + { + "spec": { + "schema": "CreateCustomerIn", + "property": "source_of_funds_doc_type" + }, + "sdk": { + "file": "src/blindpay/resources/customers/customers.py", + "symbol": "SourceOfFundsDocType" + } + }, + { + "spec": { + "schema": "CreateCustomerIn", + "property": "source_of_wealth" + }, + "sdk": { + "file": "src/blindpay/resources/customers/customers.py", + "symbol": "SourceOfWealth" + } + }, + { + "spec": { + "schema": "CreatePayinOut", + "property": "status" + }, + "sdk": { + "file": "src/blindpay/types.py", + "symbol": "TransactionStatus" + } + }, + { + "spec": { + "schema": "CreatePayinQuoteIn", + "property": "currency_type" + }, + "sdk": { + "file": "src/blindpay/types.py", + "symbol": "CurrencyType" + } + }, + { + "spec": { + "schema": "CreatePayinQuoteIn", + "property": "payment_method" + }, + "sdk": { + "file": "src/blindpay/resources/payins/quotes.py", + "symbol": "PaymentMethod" + }, + "note": "the FIELD ACTUALLY USES this local symbol (CreatePayinQuoteInput.payment_method: PaymentMethod resolves to payins/quotes.py's own Literal, not the shared types.py one) -- mapping must point here, not at types.py, or a future member addition would land in a Literal this field never references. Confirmed against blindpay-v2 packages/reference/src/rails.ts (paymentMethod, 9 values) and the other 4 SDKs (node/go/php/swift all carry all 9; python was the only one stuck at 6). Discovered missing transfers/pse/international_swift during Phase A bootstrap; textbook APPLICABLE case (pure member additions, same risk class as BankingPartner/portage), applied in the Phase A drift-application commit. Follow-up: after this fix, payins/quotes.py's local PaymentMethod is a byte-identical duplicate of types.py's shared PaymentMethod -- deliberately NOT refactored away here (a patcher should not delete symbols); worth a dedicated follow-up to import the shared one instead." + }, + { + "spec": { + "schema": "CreatePayinQuoteIn", + "property": "token" + }, + "sdk": { + "file": "src/blindpay/types.py", + "symbol": "StablecoinToken" + } + }, + { + "spec": { + "schema": "CreateTransferOut", + "property": "status" + }, + "sdk": { + "file": "src/blindpay/types.py", + "symbol": "TransactionStatus" + } + }, + { + "spec": { + "schema": "CreateTransferQuoteIn", + "property": "amount_reference" + }, + "sdk": { + "file": "src/blindpay/resources/transfers/transfers.py", + "symbol": "AmountReference" + } + }, + { + "spec": { + "schema": "CreateTransferQuoteIn", + "property": "customer_network" + }, + "sdk": { + "file": "src/blindpay/types.py", + "symbol": "Network" + } + }, + { + "spec": { + "schema": "CreateTransferQuoteIn", + "property": "customer_token" + }, + "sdk": { + "file": "src/blindpay/types.py", + "symbol": "StablecoinToken" + } + }, + { + "spec": { + "schema": "CreateTransferQuoteIn", + "property": "sender_token" + }, + "sdk": { + "file": "src/blindpay/types.py", + "symbol": "StablecoinToken" + } + }, + { + "spec": { + "schema": "CreateVirtualAccountIn", + "property": "banking_partner" + }, + "sdk": { + "file": "src/blindpay/types.py", + "symbol": "BankingPartner" + } + }, + { + "spec": { + "schema": "CreateVirtualAccountIn", + "property": "token" + }, + "sdk": { + "file": "src/blindpay/types.py", + "symbol": "StablecoinToken" + } + }, + { + "spec": { + "schema": "CreateWalletIn", + "property": "network" + }, + "sdk": { + "file": "src/blindpay/types.py", + "symbol": "Network" + } + }, + { + "spec": { + "schema": "CustomerLimitIncreaseIn", + "property": "supporting_document_type" + }, + "sdk": { + "file": "src/blindpay/resources/customers/customers.py", + "symbol": "LimitIncreaseRequestSupportingDocumentType" + } + }, + { + "spec": { + "schema": "CustomerOut", + "property": "account_purpose" + }, + "sdk": { + "file": "src/blindpay/resources/customers/customers.py", + "symbol": "AccountPurpose" + } + }, + { + "spec": { + "schema": "CustomerOut", + "property": "aml_status" + }, + "sdk": { + "file": "src/blindpay/resources/customers/customers.py", + "symbol": "AmlStatus" + } + }, + { + "spec": { + "schema": "CustomerOut", + "property": "business_industry" + }, + "sdk": { + "file": "src/blindpay/resources/customers/customers.py", + "symbol": "BusinessIndustry" + } + }, + { + "spec": { + "schema": "CustomerOut", + "property": "business_type" + }, + "sdk": { + "file": "src/blindpay/resources/customers/customers.py", + "symbol": "CustomerBusinessType" + } + }, + { + "spec": { + "schema": "CustomerOut", + "property": "country" + }, + "sdk": { + "file": "src/blindpay/types.py", + "symbol": "Country" + } + }, + { + "spec": { + "schema": "CustomerOut", + "property": "estimated_annual_revenue" + }, + "sdk": { + "file": "src/blindpay/resources/customers/customers.py", + "symbol": "EstimatedAnnualRevenue" + } + }, + { + "spec": { + "schema": "CustomerOut", + "property": "id_doc_country" + }, + "sdk": { + "file": "src/blindpay/types.py", + "symbol": "Country" + } + }, + { + "spec": { + "schema": "CustomerOut", + "property": "id_doc_type" + }, + "sdk": { + "file": "src/blindpay/resources/customers/customers.py", + "symbol": "IdentificationDocument" + } + }, + { + "spec": { + "schema": "CustomerOut", + "property": "kyc_status" + }, + "sdk": { + "file": "src/blindpay/resources/customers/customers.py", + "symbol": "KycStatus" + }, + "note": "KNOWN DIVERGENCE, kept mapped on purpose: spec has 8 members, SDK Literal has 2. See unmodeled.json kind=enum entry; needs a human decision on whether this Literal is even the right type for this field before adding members." + }, + { + "spec": { + "schema": "CustomerOut", + "property": "proof_of_address_doc_type" + }, + "sdk": { + "file": "src/blindpay/resources/customers/customers.py", + "symbol": "ProofOfAddressDocType" + } + }, + { + "spec": { + "schema": "CustomerOut", + "property": "purpose_of_transactions" + }, + "sdk": { + "file": "src/blindpay/resources/customers/customers.py", + "symbol": "PurposeOfTransactions" + } + }, + { + "spec": { + "schema": "CustomerOut", + "property": "source_of_wealth" + }, + "sdk": { + "file": "src/blindpay/resources/customers/customers.py", + "symbol": "SourceOfWealth" + } + }, + { + "spec": { + "schema": "CustomerOut", + "property": "type" + }, + "sdk": { + "file": "src/blindpay/types.py", + "symbol": "AccountClass" + } + }, + { + "spec": { + "schema": "GetCustomerLimitIncreaseOut", + "property": "status" + }, + "sdk": { + "file": "src/blindpay/resources/customers/customers.py", + "symbol": "LimitIncreaseRequestStatus" + } + }, + { + "spec": { + "schema": "GetCustomerLimitIncreaseOut", + "property": "supporting_document_type" + }, + "sdk": { + "file": "src/blindpay/resources/customers/customers.py", + "symbol": "LimitIncreaseRequestSupportingDocumentType" + } + }, + { + "spec": { + "schema": "OfframpWallet", + "property": "network" + }, + "sdk": { + "file": "src/blindpay/types.py", + "symbol": "Network" + } + }, + { + "spec": { + "schema": "PayinOut", + "property": "manual_execution_status" + }, + "sdk": { + "file": "src/blindpay/types.py", + "symbol": "ManualExecutionStatus" + } + }, + { + "spec": { + "schema": "PayinOut", + "property": "network" + }, + "sdk": { + "file": "src/blindpay/types.py", + "symbol": "Network" + } + }, + { + "spec": { + "schema": "PayinOut", + "property": "payment_method" + }, + "sdk": { + "file": "src/blindpay/types.py", + "symbol": "PaymentMethod" + }, + "note": "the shared, publicly-exported types.py PaymentMethod (9 values) is not referenced by any TypedDict field today (grep confirms it), but it is exported from blindpay/__init__.py and blindpay.resources.payins, so it is kept reconciled against a real 9-value anchor rather than left un-anchored. No gap: already matches exactly." + }, + { + "spec": { + "schema": "PayinOut", + "property": "status" + }, + "sdk": { + "file": "src/blindpay/types.py", + "symbol": "TransactionStatus" + } + }, + { + "spec": { + "schema": "PayinOut", + "property": "token" + }, + "sdk": { + "file": "src/blindpay/types.py", + "symbol": "StablecoinToken" + } + }, + { + "spec": { + "schema": "PayoutOnEvmOut", + "property": "status" + }, + "sdk": { + "file": "src/blindpay/types.py", + "symbol": "TransactionStatus" + } + }, + { + "spec": { + "schema": "PayoutOut", + "property": "account_class" + }, + "sdk": { + "file": "src/blindpay/types.py", + "symbol": "AccountClass" + } + }, + { + "spec": { + "schema": "PayoutOut", + "property": "account_type" + }, + "sdk": { + "file": "src/blindpay/types.py", + "symbol": "BankAccountType" + } + }, + { + "spec": { + "schema": "PayoutOut", + "property": "country" + }, + "sdk": { + "file": "src/blindpay/types.py", + "symbol": "Country" + } + }, + { + "spec": { + "schema": "PayoutOut", + "property": "currency" + }, + "sdk": { + "file": "src/blindpay/types.py", + "symbol": "Currency" + } + }, + { + "spec": { + "schema": "PayoutOut", + "property": "network" + }, + "sdk": { + "file": "src/blindpay/types.py", + "symbol": "Network" + } + }, + { + "spec": { + "schema": "PayoutOut", + "property": "spei_protocol" + }, + "sdk": { + "file": "src/blindpay/types.py", + "symbol": "SpeiProtocol" + } + }, + { + "spec": { + "schema": "PayoutOut", + "property": "status" + }, + "sdk": { + "file": "src/blindpay/types.py", + "symbol": "TransactionStatus" + }, + "note": "spec is a strict subset per-schema (PayoutOut/PayinOut/TransferOut each expose 4-5 of the SDK's 6 values); SDK Literal already a superset, nothing to reconcile" + }, + { + "spec": { + "schema": "PayoutOut", + "property": "swift_beneficiary_country" + }, + "sdk": { + "file": "src/blindpay/types.py", + "symbol": "Country" + } + }, + { + "spec": { + "schema": "PayoutOut", + "property": "token" + }, + "sdk": { + "file": "src/blindpay/types.py", + "symbol": "StablecoinToken" + } + }, + { + "spec": { + "schema": "PayoutOut", + "property": "transaction_document_type" + }, + "sdk": { + "file": "src/blindpay/types.py", + "symbol": "TransactionDocumentType" + } + }, + { + "spec": { + "schema": "PayoutOut", + "property": "transfers_type" + }, + "sdk": { + "file": "src/blindpay/resources/payouts/payouts.py", + "symbol": "ArgentinaTransfers" + } + }, + { + "spec": { + "schema": "PayoutOut", + "property": "type" + }, + "sdk": { + "file": "src/blindpay/types.py", + "symbol": "Rail" + } + }, + { + "spec": { + "schema": "QuoteIn", + "property": "currency_type" + }, + "sdk": { + "file": "src/blindpay/types.py", + "symbol": "CurrencyType" + } + }, + { + "spec": { + "schema": "QuoteIn", + "property": "network" + }, + "sdk": { + "file": "src/blindpay/types.py", + "symbol": "Network" + } + }, + { + "spec": { + "schema": "QuoteIn", + "property": "token" + }, + "sdk": { + "file": "src/blindpay/types.py", + "symbol": "StablecoinToken" + } + }, + { + "spec": { + "schema": "RfiField", + "property": "aiprise_document_type" + }, + "sdk": { + "file": "src/blindpay/types.py", + "symbol": "AipriseDocumentType" + }, + "note": "the Rfi family itself is in ignore.schemas (unmodeled resource), but the enum values are shared with the modeled onboarding flow so the Literal is kept reconciled" }, { - "spec": { "schema": "QuoteIn", "property": "network" }, - "sdk": { "file": "src/blindpay/types.py", "symbol": "Network" } + "spec": { + "schema": "SubmitPayoutDocumentsIn", + "property": "transaction_document_type" + }, + "sdk": { + "file": "src/blindpay/types.py", + "symbol": "TransactionDocumentType" + } }, { - "spec": { "schema": "QuoteIn", "property": "token" }, - "sdk": { "file": "src/blindpay/types.py", "symbol": "StablecoinToken" } + "spec": { + "schema": "TransferOut", + "property": "customer_network" + }, + "sdk": { + "file": "src/blindpay/types.py", + "symbol": "Network" + } }, { - "spec": { "schema": "PayoutOut", "property": "transaction_document_type" }, - "sdk": { "file": "src/blindpay/types.py", "symbol": "TransactionDocumentType" } + "spec": { + "schema": "TransferOut", + "property": "customer_token" + }, + "sdk": { + "file": "src/blindpay/types.py", + "symbol": "StablecoinToken" + } }, { - "spec": { "schema": "CreateBankAccountIn", "property": "type" }, - "sdk": { "file": "src/blindpay/types.py", "symbol": "Rail" } + "spec": { + "schema": "TransferOut", + "property": "sender_token" + }, + "sdk": { + "file": "src/blindpay/types.py", + "symbol": "StablecoinToken" + } }, { - "spec": { "schema": "QuoteIn", "property": "currency_type" }, - "sdk": { "file": "src/blindpay/types.py", "symbol": "CurrencyType" } + "spec": { + "schema": "TransferOut", + "property": "status" + }, + "sdk": { + "file": "src/blindpay/types.py", + "symbol": "TransactionStatus" + } }, { - "spec": { "schema": "CustomerOut", "property": "type" }, - "sdk": { "file": "src/blindpay/types.py", "symbol": "AccountClass" } + "spec": { + "schema": "UpdateCustomerIn", + "property": "account_purpose" + }, + "sdk": { + "file": "src/blindpay/resources/customers/customers.py", + "symbol": "AccountPurpose" + } }, { - "spec": { "schema": "PayoutOut", "property": "status" }, - "sdk": { "file": "src/blindpay/types.py", "symbol": "TransactionStatus" }, - "note": "spec is a strict subset per-schema (PayoutOut/PayinOut/TransferOut each expose 4-5 of the SDK's 6 values); SDK Literal already a superset, nothing to reconcile" + "spec": { + "schema": "UpdateCustomerIn", + "property": "business_industry" + }, + "sdk": { + "file": "src/blindpay/resources/customers/customers.py", + "symbol": "BusinessIndustry" + } }, { - "spec": { "schema": "CreateBankAccountIn", "property": "spei_protocol" }, - "sdk": { "file": "src/blindpay/types.py", "symbol": "SpeiProtocol" } + "spec": { + "schema": "UpdateCustomerIn", + "property": "business_type" + }, + "sdk": { + "file": "src/blindpay/resources/customers/customers.py", + "symbol": "CustomerBusinessType" + } }, { - "spec": { "schema": "CreateBankAccountIn", "property": "recipient_relationship" }, - "sdk": { "file": "src/blindpay/types.py", "symbol": "RecipientRelationship" } + "spec": { + "schema": "UpdateCustomerIn", + "property": "country" + }, + "sdk": { + "file": "src/blindpay/types.py", + "symbol": "Country" + } }, { - "spec": { "schema": "VirtualAccountOut", "property": "banking_partner" }, - "sdk": { "file": "src/blindpay/types.py", "symbol": "BankingPartner" } + "spec": { + "schema": "UpdateCustomerIn", + "property": "estimated_annual_revenue" + }, + "sdk": { + "file": "src/blindpay/resources/customers/customers.py", + "symbol": "EstimatedAnnualRevenue" + } }, { - "spec": { "schema": "CreatePayinQuoteIn", "property": "payment_method" }, - "sdk": { "file": "src/blindpay/resources/payins/quotes.py", "symbol": "PaymentMethod" }, - "note": "the FIELD ACTUALLY USES this local symbol (CreatePayinQuoteInput.payment_method: PaymentMethod resolves to payins/quotes.py's own Literal, not the shared types.py one) -- mapping must point here, not at types.py, or a future member addition would land in a Literal this field never references. Confirmed against blindpay-v2 packages/reference/src/rails.ts (paymentMethod, 9 values) and the other 4 SDKs (node/go/php/swift all carry all 9; python was the only one stuck at 6). Discovered missing transfers/pse/international_swift during Phase A bootstrap; textbook APPLICABLE case (pure member additions, same risk class as BankingPartner/portage), applied in the Phase A drift-application commit. Follow-up: after this fix, payins/quotes.py's local PaymentMethod is a byte-identical duplicate of types.py's shared PaymentMethod -- deliberately NOT refactored away here (a patcher should not delete symbols); worth a dedicated follow-up to import the shared one instead." + "spec": { + "schema": "UpdateCustomerIn", + "property": "id_doc_country" + }, + "sdk": { + "file": "src/blindpay/types.py", + "symbol": "Country" + } }, { - "spec": { "schema": "PayinOut", "property": "payment_method" }, - "sdk": { "file": "src/blindpay/types.py", "symbol": "PaymentMethod" }, - "note": "the shared, publicly-exported types.py PaymentMethod (9 values) is not referenced by any TypedDict field today (grep confirms it), but it is exported from blindpay/__init__.py and blindpay.resources.payins, so it is kept reconciled against a real 9-value anchor rather than left un-anchored. No gap: already matches exactly." + "spec": { + "schema": "UpdateCustomerIn", + "property": "id_doc_type" + }, + "sdk": { + "file": "src/blindpay/resources/customers/customers.py", + "symbol": "IdentificationDocument" + } }, { - "spec": { "schema": "RfiField", "property": "aiprise_document_type" }, - "sdk": { "file": "src/blindpay/types.py", "symbol": "AipriseDocumentType" }, - "note": "the Rfi family itself is in ignore.schemas (unmodeled resource), but the enum values are shared with the modeled onboarding flow so the Literal is kept reconciled" + "spec": { + "schema": "UpdateCustomerIn", + "property": "proof_of_address_doc_type" + }, + "sdk": { + "file": "src/blindpay/resources/customers/customers.py", + "symbol": "ProofOfAddressDocType" + } }, { - "spec": { "schema": "UploadAnalyzeOut", "property": "approval_rate" }, - "sdk": { "file": "src/blindpay/types.py", "symbol": "ApprovalRate" } + "spec": { + "schema": "UpdateCustomerIn", + "property": "purpose_of_transactions" + }, + "sdk": { + "file": "src/blindpay/resources/customers/customers.py", + "symbol": "PurposeOfTransactions" + } }, { - "spec": { "schema": "BankAccountOut", "property": "account_type" }, - "sdk": { "file": "src/blindpay/types.py", "symbol": "BankAccountType" }, - "note": "KNOWN DIVERGENCE, kept mapped on purpose: spec enum is [checking, saving], SDK Literal is [checking, savings]. See unmodeled.json kind=enum entry; do not blindly append, this needs a coordinated rename PR (confirmed live defect, not just typing)." + "spec": { + "schema": "UpdateCustomerIn", + "property": "source_of_funds_doc_type" + }, + "sdk": { + "file": "src/blindpay/resources/customers/customers.py", + "symbol": "SourceOfFundsDocType" + } }, { - "spec": { "schema": "CustomerOut", "property": "kyc_status" }, - "sdk": { "file": "src/blindpay/resources/customers/customers.py", "symbol": "KycStatus" }, - "note": "KNOWN DIVERGENCE, kept mapped on purpose: spec has 8 members, SDK Literal has 2. See unmodeled.json kind=enum entry; needs a human decision on whether this Literal is even the right type for this field before adding members." + "spec": { + "schema": "UpdateCustomerIn", + "property": "source_of_wealth" + }, + "sdk": { + "file": "src/blindpay/resources/customers/customers.py", + "symbol": "SourceOfWealth" + } + }, + { + "spec": { + "schema": "UpdateVirtualAccountIn", + "property": "token" + }, + "sdk": { + "file": "src/blindpay/types.py", + "symbol": "StablecoinToken" + } + }, + { + "spec": { + "schema": "UploadAnalyzeOut", + "property": "approval_rate" + }, + "sdk": { + "file": "src/blindpay/types.py", + "symbol": "ApprovalRate" + } + }, + { + "spec": { + "schema": "UploadIn", + "property": "bucket" + }, + "sdk": { + "file": "src/blindpay/resources/upload/upload.py", + "symbol": "UploadBucket" + } + }, + { + "spec": { + "schema": "VirtualAccountOut", + "property": "banking_partner" + }, + "sdk": { + "file": "src/blindpay/types.py", + "symbol": "BankingPartner" + } + }, + { + "spec": { + "schema": "VirtualAccountOut", + "property": "token" + }, + "sdk": { + "file": "src/blindpay/types.py", + "symbol": "StablecoinToken" + } + }, + { + "spec": { + "schema": "WalletOut", + "property": "network" + }, + "sdk": { + "file": "src/blindpay/types.py", + "symbol": "Network" + } + }, + { + "spec": { + "schema": "WebhookEndpoint", + "property": "events", + "items": true + }, + "sdk": { + "file": "src/blindpay/resources/webhooks/webhooks.py", + "symbol": "WebhookEvents" + } + }, + { + "spec": { + "schema": "WebhookEndpointIn", + "property": "events", + "items": true + }, + "sdk": { + "file": "src/blindpay/resources/webhooks/webhooks.py", + "symbol": "WebhookEvents" + } } ], "types": [ - { "spec": "AvailableBankDetails", "sdk": [{ "file": "src/blindpay/resources/available/available.py", "symbol": "BankDetail" }] }, - { "spec": "AvailableNaicsList", "sdk": [{ "file": "src/blindpay/resources/available/available.py", "symbol": "NaicsCode" }] }, - { "spec": "AvailableRails", "sdk": [{ "file": "src/blindpay/resources/available/available.py", "symbol": "RailInfo" }] }, - { "spec": ["SwiftCodeItem", "SwiftCodeResponse"], "sdk": [{ "file": "src/blindpay/resources/available/available.py", "symbol": "SwiftCodeBankDetail" }], + { + "spec": "AvailableBankDetails", + "sdk": [ + { + "file": "src/blindpay/resources/available/available.py", + "symbol": "BankDetail" + } + ] + }, + { + "spec": "AvailableNaicsList", + "sdk": [ + { + "file": "src/blindpay/resources/available/available.py", + "symbol": "NaicsCode" + } + ] + }, + { + "spec": "AvailableRails", + "sdk": [ + { + "file": "src/blindpay/resources/available/available.py", + "symbol": "RailInfo" + } + ] + }, + { + "spec": [ + "SwiftCodeItem", + "SwiftCodeResponse" + ], + "sdk": [ + { + "file": "src/blindpay/resources/available/available.py", + "symbol": "SwiftCodeBankDetail" + } + ], "note": "SwiftCodeResponse is the bare-array wrapper (List[SwiftCodeItem]) with no properties of its own; listed so it is not reported as an unaccounted schema" }, - { "spec": "PaginationMetadata", "sdk": [{ "file": "src/blindpay/types.py", "symbol": "PaginationMetadata" }] }, - - { "spec": "BankAccountOut", "sdk": [ - { "file": "src/blindpay/resources/bank_accounts/bank_accounts.py", "symbol": "BankAccount" }, - { "file": "src/blindpay/resources/bank_accounts/bank_accounts.py", "symbol": "GetBankAccountResponse" } + { + "spec": "PaginationMetadata", + "sdk": [ + { + "file": "src/blindpay/types.py", + "symbol": "PaginationMetadata" + } + ] + }, + { + "spec": "BankAccountOut", + "sdk": [ + { + "file": "src/blindpay/resources/bank_accounts/bank_accounts.py", + "symbol": "BankAccount" + }, + { + "file": "src/blindpay/resources/bank_accounts/bank_accounts.py", + "symbol": "GetBankAccountResponse" + } ], "note": "discriminator fan-out target for the response side; BankAccount is the list() shape, GetBankAccountResponse is the get() shape (already carries pre-existing allowlist.json divergences: account_holder_name/is_primary/swift_code/iban)" }, - { "spec": "CreateBankAccountIn", "sdk": [ - { "file": "src/blindpay/resources/bank_accounts/bank_accounts.py", "symbol": "CreatePixInput" }, - { "file": "src/blindpay/resources/bank_accounts/bank_accounts.py", "symbol": "CreateArgentinaTransfersInput" }, - { "file": "src/blindpay/resources/bank_accounts/bank_accounts.py", "symbol": "CreateSpeiInput" }, - { "file": "src/blindpay/resources/bank_accounts/bank_accounts.py", "symbol": "CreateColombiaAchInput" }, - { "file": "src/blindpay/resources/bank_accounts/bank_accounts.py", "symbol": "CreateAchInput" }, - { "file": "src/blindpay/resources/bank_accounts/bank_accounts.py", "symbol": "CreateWireInput" }, - { "file": "src/blindpay/resources/bank_accounts/bank_accounts.py", "symbol": "CreateInternationalSwiftInput" }, - { "file": "src/blindpay/resources/bank_accounts/bank_accounts.py", "symbol": "CreateRtpInput" }, - { "file": "src/blindpay/resources/bank_accounts/bank_accounts.py", "symbol": "CreateSepaInput" }, - { "file": "src/blindpay/resources/bank_accounts/bank_accounts.py", "symbol": "CreatePixSafeInput" }, - { "file": "src/blindpay/resources/bank_accounts/bank_accounts.py", "symbol": "CreateTedInput" } + { + "spec": "CreateBankAccountIn", + "sdk": [ + { + "file": "src/blindpay/resources/bank_accounts/bank_accounts.py", + "symbol": "CreatePixInput" + }, + { + "file": "src/blindpay/resources/bank_accounts/bank_accounts.py", + "symbol": "CreateArgentinaTransfersInput" + }, + { + "file": "src/blindpay/resources/bank_accounts/bank_accounts.py", + "symbol": "CreateSpeiInput" + }, + { + "file": "src/blindpay/resources/bank_accounts/bank_accounts.py", + "symbol": "CreateColombiaAchInput" + }, + { + "file": "src/blindpay/resources/bank_accounts/bank_accounts.py", + "symbol": "CreateAchInput" + }, + { + "file": "src/blindpay/resources/bank_accounts/bank_accounts.py", + "symbol": "CreateWireInput" + }, + { + "file": "src/blindpay/resources/bank_accounts/bank_accounts.py", + "symbol": "CreateInternationalSwiftInput" + }, + { + "file": "src/blindpay/resources/bank_accounts/bank_accounts.py", + "symbol": "CreateRtpInput" + }, + { + "file": "src/blindpay/resources/bank_accounts/bank_accounts.py", + "symbol": "CreateSepaInput" + }, + { + "file": "src/blindpay/resources/bank_accounts/bank_accounts.py", + "symbol": "CreatePixSafeInput" + }, + { + "file": "src/blindpay/resources/bank_accounts/bank_accounts.py", + "symbol": "CreateTedInput" + } ], "note": "the 11-rail discriminator fan-out on the request side (design doc's headline case); `type` is injected by each create_* method, never a field on these TypedDicts" }, - - { "spec": "CreateBlockchainWalletIn", "sdk": [ - { "file": "src/blindpay/resources/wallets/blockchain.py", "symbol": "CreateBlockchainWalletWithAddressInput" }, - { "file": "src/blindpay/resources/wallets/blockchain.py", "symbol": "CreateBlockchainWalletWithHashInput" } + { + "spec": "CreateBlockchainWalletIn", + "sdk": [ + { + "file": "src/blindpay/resources/wallets/blockchain.py", + "symbol": "CreateBlockchainWalletWithAddressInput" + }, + { + "file": "src/blindpay/resources/wallets/blockchain.py", + "symbol": "CreateBlockchainWalletWithHashInput" + } ], "note": "discriminated by is_account_abstraction, which each create_with_* method injects rather than exposing as a field" }, - { "spec": "BlockchainWalletOut", "sdk": [{ "file": "src/blindpay/resources/wallets/blockchain.py", "symbol": "BlockchainWallet" }] }, - { "spec": "BlockchainWalletMessageOut", "sdk": [{ "file": "src/blindpay/resources/wallets/blockchain.py", "symbol": "GetBlockchainWalletMessageResponse" }] }, - - { "spec": "CreateCustomerIn", "sdk": [ - { "file": "src/blindpay/resources/customers/customers.py", "symbol": "CreateIndividualWithStandardKYCInput" }, - { "file": "src/blindpay/resources/customers/customers.py", "symbol": "CreateIndividualWithEnhancedKYCInput" }, - { "file": "src/blindpay/resources/customers/customers.py", "symbol": "CreateBusinessWithStandardKYBInput" } + { + "spec": "BlockchainWalletOut", + "sdk": [ + { + "file": "src/blindpay/resources/wallets/blockchain.py", + "symbol": "BlockchainWallet" + } + ] + }, + { + "spec": "BlockchainWalletMessageOut", + "sdk": [ + { + "file": "src/blindpay/resources/wallets/blockchain.py", + "symbol": "GetBlockchainWalletMessageResponse" + } + ] + }, + { + "spec": "CreateCustomerIn", + "sdk": [ + { + "file": "src/blindpay/resources/customers/customers.py", + "symbol": "CreateIndividualWithStandardKYCInput" + }, + { + "file": "src/blindpay/resources/customers/customers.py", + "symbol": "CreateIndividualWithEnhancedKYCInput" + }, + { + "file": "src/blindpay/resources/customers/customers.py", + "symbol": "CreateBusinessWithStandardKYBInput" + } ], "note": "the 3-way KYC discriminator fan-out on the request side; `type`/`kyc_type` are injected by each create_* method" }, - { "spec": "CustomerOut", "sdk": [ - { "file": "src/blindpay/resources/customers/customers.py", "symbol": "IndividualWithStandardKYC" }, - { "file": "src/blindpay/resources/customers/customers.py", "symbol": "IndividualWithEnhancedKYC" }, - { "file": "src/blindpay/resources/customers/customers.py", "symbol": "BusinessWithStandardKYB" } + { + "spec": "CustomerOut", + "sdk": [ + { + "file": "src/blindpay/resources/customers/customers.py", + "symbol": "IndividualWithStandardKYC" + }, + { + "file": "src/blindpay/resources/customers/customers.py", + "symbol": "IndividualWithEnhancedKYC" + }, + { + "file": "src/blindpay/resources/customers/customers.py", + "symbol": "BusinessWithStandardKYB" + } ], "note": "the 3-way KYC discriminator fan-out on the response side (design doc's headline case)" }, - { "spec": "UpdateCustomerIn", "sdk": [{ "file": "src/blindpay/resources/customers/customers.py", "symbol": "UpdateCustomerInput" }] }, - { "spec": "CustomerLimitIncreaseIn", "sdk": [{ "file": "src/blindpay/resources/customers/customers.py", "symbol": "RequestLimitIncreaseInput" }] }, - { "spec": "CustomerLimitIncreaseOut", "sdk": [{ "file": "src/blindpay/resources/customers/customers.py", "symbol": "RequestLimitIncreaseResponse" }] }, - { "spec": "GetCustomerLimitIncreaseOut", "sdk": [{ "file": "src/blindpay/resources/customers/customers.py", "symbol": "LimitIncreaseRequest" }] }, - { "spec": "GetCustomerLimitsOut", "sdk": [ - { "file": "src/blindpay/resources/customers/customers.py", "symbol": "GetCustomerLimitsResponse" }, - { "file": "src/blindpay/resources/customers/customers.py", "symbol": "Limits" }, - { "file": "src/blindpay/resources/customers/customers.py", "symbol": "PayinLimit" }, - { "file": "src/blindpay/resources/customers/customers.py", "symbol": "PayoutLimit" }, - { "file": "src/blindpay/resources/limits/limits.py", "symbol": "GetCustomerLimitsResponse" }, - { "file": "src/blindpay/resources/limits/limits.py", "symbol": "CustomerLimits" }, - { "file": "src/blindpay/resources/limits/limits.py", "symbol": "PayinLimit" }, - { "file": "src/blindpay/resources/limits/limits.py", "symbol": "PayoutLimit" } + { + "spec": "UpdateCustomerIn", + "sdk": [ + { + "file": "src/blindpay/resources/customers/customers.py", + "symbol": "UpdateCustomerInput" + } + ] + }, + { + "spec": "CustomerLimitIncreaseIn", + "sdk": [ + { + "file": "src/blindpay/resources/customers/customers.py", + "symbol": "RequestLimitIncreaseInput" + } + ] + }, + { + "spec": "CustomerLimitIncreaseOut", + "sdk": [ + { + "file": "src/blindpay/resources/customers/customers.py", + "symbol": "RequestLimitIncreaseResponse" + } + ] + }, + { + "spec": "GetCustomerLimitIncreaseOut", + "sdk": [ + { + "file": "src/blindpay/resources/customers/customers.py", + "symbol": "LimitIncreaseRequest" + } + ] + }, + { + "spec": "GetCustomerLimitsOut", + "sdk": [ + { + "file": "src/blindpay/resources/customers/customers.py", + "symbol": "GetCustomerLimitsResponse" + }, + { + "file": "src/blindpay/resources/customers/customers.py", + "symbol": "Limits" + }, + { + "file": "src/blindpay/resources/customers/customers.py", + "symbol": "PayinLimit" + }, + { + "file": "src/blindpay/resources/customers/customers.py", + "symbol": "PayoutLimit" + }, + { + "file": "src/blindpay/resources/limits/limits.py", + "symbol": "GetCustomerLimitsResponse" + }, + { + "file": "src/blindpay/resources/limits/limits.py", + "symbol": "CustomerLimits" + }, + { + "file": "src/blindpay/resources/limits/limits.py", + "symbol": "PayinLimit" + }, + { + "file": "src/blindpay/resources/limits/limits.py", + "symbol": "PayoutLimit" + } ], "note": "pre-existing duplicate modeling: customers.py and limits.py each declare their own near-identical copy of this shape (survey irregularity #4, out of scope for this project); both are listed so reconciliation checks either copy" }, - - { "spec": "CreatePayinIn", "sdk": [{ "file": "src/blindpay/resources/payins/payins.py", "symbol": "CreatePayinInput" }], - "note": "CreatePayinInput is dead code (create_evm() takes a bare payin_quote_id str, per allowlist.json); mapped anyway so drift on this schema stays visible instead of silently skipped" }, - { "spec": "CreatePayinOut", "sdk": [{ "file": "src/blindpay/resources/payins/payins.py", "symbol": "CreateEvmPayinResponse" }] }, - { "spec": "PayinOut", "sdk": [ - { "file": "src/blindpay/resources/payins/payins.py", "symbol": "Payin" }, - { "file": "src/blindpay/resources/payins/payins.py", "symbol": "GetPayinTrackResponse" } - ] - }, - { "spec": "CreatePayinQuoteIn", "sdk": [{ "file": "src/blindpay/resources/payins/quotes.py", "symbol": "CreatePayinQuoteInput" }] }, - { "spec": "CreatePayinQuoteOut", "sdk": [{ "file": "src/blindpay/resources/payins/quotes.py", "symbol": "CreatePayinQuoteResponse" }] }, - - { "spec": "PayoutOut", "sdk": [{ "file": "src/blindpay/resources/payouts/payouts.py", "symbol": "Payout" }] }, - { "spec": "PayoutOnEvmIn", "sdk": [{ "file": "src/blindpay/resources/payouts/payouts.py", "symbol": "CreateEvmPayoutInput" }] }, - { "spec": "PayoutOnEvmOut", "sdk": [ - { "file": "src/blindpay/resources/payouts/payouts.py", "symbol": "CreateEvmPayoutResponse" }, - { "file": "src/blindpay/resources/payouts/payouts.py", "symbol": "CreateSolanaPayoutResponse" }, - { "file": "src/blindpay/resources/payouts/payouts.py", "symbol": "CreateStellarPayoutResponse" } + { + "spec": "CreatePayinIn", + "sdk": [ + { + "file": "src/blindpay/resources/payins/payins.py", + "symbol": "CreatePayinInput" + } + ], + "note": "CreatePayinInput is dead code (create_evm() takes a bare payin_quote_id str, per allowlist.json); mapped anyway so drift on this schema stays visible instead of silently skipped" + }, + { + "spec": "CreatePayinOut", + "sdk": [ + { + "file": "src/blindpay/resources/payins/payins.py", + "symbol": "CreateEvmPayinResponse" + } + ] + }, + { + "spec": "PayinOut", + "sdk": [ + { + "file": "src/blindpay/resources/payins/payins.py", + "symbol": "Payin" + }, + { + "file": "src/blindpay/resources/payins/payins.py", + "symbol": "GetPayinTrackResponse" + } + ] + }, + { + "spec": "CreatePayinQuoteIn", + "sdk": [ + { + "file": "src/blindpay/resources/payins/quotes.py", + "symbol": "CreatePayinQuoteInput" + } + ] + }, + { + "spec": "CreatePayinQuoteOut", + "sdk": [ + { + "file": "src/blindpay/resources/payins/quotes.py", + "symbol": "CreatePayinQuoteResponse" + } + ] + }, + { + "spec": "PayoutOut", + "sdk": [ + { + "file": "src/blindpay/resources/payouts/payouts.py", + "symbol": "Payout" + } + ] + }, + { + "spec": "PayoutOnEvmIn", + "sdk": [ + { + "file": "src/blindpay/resources/payouts/payouts.py", + "symbol": "CreateEvmPayoutInput" + } + ] + }, + { + "spec": "PayoutOnEvmOut", + "sdk": [ + { + "file": "src/blindpay/resources/payouts/payouts.py", + "symbol": "CreateEvmPayoutResponse" + }, + { + "file": "src/blindpay/resources/payouts/payouts.py", + "symbol": "CreateSolanaPayoutResponse" + }, + { + "file": "src/blindpay/resources/payouts/payouts.py", + "symbol": "CreateStellarPayoutResponse" + } ], "note": "the spec reuses this ONE schema as the 200 response for evm, solana AND stellar payout creation; the SDK models 3 separate response TypedDicts for it" }, - { "spec": "PayoutOnSolanaIn", "sdk": [{ "file": "src/blindpay/resources/payouts/payouts.py", "symbol": "CreateSolanaPayoutInput" }] }, - { "spec": "PayoutOnStellarIn", "sdk": [{ "file": "src/blindpay/resources/payouts/payouts.py", "symbol": "CreateStellarPayoutInput" }] }, - { "spec": "PayoutOnStellarAuthorizeIn", "sdk": [{ "file": "src/blindpay/resources/payouts/payouts.py", "symbol": "AuthorizeStellarTokenInput" }] }, - { "spec": "SubmitPayoutDocumentsIn", "sdk": [{ "file": "src/blindpay/resources/payouts/payouts.py", "symbol": "SubmitPayoutDocumentsInput" }] }, - - { "spec": "QuoteIn", "sdk": [{ "file": "src/blindpay/resources/quotes/quotes.py", "symbol": "CreateQuoteInput" }], - "note": "refund_wallet_address lands here (Phase A delta field)" }, - { "spec": "QuoteOut", "sdk": [ - { "file": "src/blindpay/resources/quotes/quotes.py", "symbol": "CreateQuoteResponse" }, - { "file": "src/blindpay/resources/quotes/quotes.py", "symbol": "Contract" }, - { "file": "src/blindpay/resources/quotes/quotes.py", "symbol": "ContractNetwork" } - ] - }, - - { "spec": "CreateTransferQuoteIn", "sdk": [{ "file": "src/blindpay/resources/transfers/transfers.py", "symbol": "CreateTransferQuoteInput" }] }, - { "spec": "CreateTransferQuoteOut", "sdk": [{ "file": "src/blindpay/resources/transfers/transfers.py", "symbol": "CreateTransferQuoteResponse" }] }, - { "spec": "CreateTransferIn", "sdk": [{ "file": "src/blindpay/resources/transfers/transfers.py", "symbol": "CreateTransferInput" }] }, - { "spec": "CreateTransferOut", "sdk": [{ "file": "src/blindpay/resources/transfers/transfers.py", "symbol": "Transfer" }] }, - { "spec": "TransferOut", "sdk": [{ "file": "src/blindpay/resources/transfers/transfers.py", "symbol": "Transfer" }] }, - - { "spec": "CreateVirtualAccountIn", "sdk": [{ "file": "src/blindpay/resources/virtual_accounts/virtual_accounts.py", "symbol": "CreateVirtualAccountInput" }] }, - { "spec": "VirtualAccountOut", "sdk": [{ "file": "src/blindpay/resources/virtual_accounts/virtual_accounts.py", "symbol": "VirtualAccount" }] }, - { "spec": "UpdateVirtualAccountIn", "sdk": [{ "file": "src/blindpay/resources/virtual_accounts/virtual_accounts.py", "symbol": "UpdateVirtualAccountInput" }] }, - - { "spec": "CreateWalletIn", "sdk": [{ "file": "src/blindpay/resources/custodial_wallets/custodial_wallets.py", "symbol": "CreateCustodialWalletInput" }] }, - { "spec": "WalletOut", "sdk": [{ "file": "src/blindpay/resources/custodial_wallets/custodial_wallets.py", "symbol": "CustodialWallet" }] }, - { "spec": "WalletBalanceOut", "sdk": [{ "file": "src/blindpay/resources/custodial_wallets/custodial_wallets.py", "symbol": "CustodialWalletBalance" }] }, - { "spec": "WalletTokenOut", "sdk": [{ "file": "src/blindpay/resources/custodial_wallets/custodial_wallets.py", "symbol": "CustodialWalletBalanceToken" }] }, - - { "spec": "OfframpWallet", "sdk": [ - { "file": "src/blindpay/resources/bank_accounts/bank_accounts.py", "symbol": "OfframpWallet" }, - { "file": "src/blindpay/resources/wallets/offramp.py", "symbol": "OfframpWallet" } + { + "spec": "PayoutOnSolanaIn", + "sdk": [ + { + "file": "src/blindpay/resources/payouts/payouts.py", + "symbol": "CreateSolanaPayoutInput" + } + ] + }, + { + "spec": "PayoutOnStellarIn", + "sdk": [ + { + "file": "src/blindpay/resources/payouts/payouts.py", + "symbol": "CreateStellarPayoutInput" + } + ] + }, + { + "spec": "PayoutOnStellarAuthorizeIn", + "sdk": [ + { + "file": "src/blindpay/resources/payouts/payouts.py", + "symbol": "AuthorizeStellarTokenInput" + } + ] + }, + { + "spec": "SubmitPayoutDocumentsIn", + "sdk": [ + { + "file": "src/blindpay/resources/payouts/payouts.py", + "symbol": "SubmitPayoutDocumentsInput" + } + ] + }, + { + "spec": "QuoteIn", + "sdk": [ + { + "file": "src/blindpay/resources/quotes/quotes.py", + "symbol": "CreateQuoteInput" + } + ], + "note": "refund_wallet_address lands here (Phase A delta field)" + }, + { + "spec": "QuoteOut", + "sdk": [ + { + "file": "src/blindpay/resources/quotes/quotes.py", + "symbol": "CreateQuoteResponse" + }, + { + "file": "src/blindpay/resources/quotes/quotes.py", + "symbol": "Contract" + }, + { + "file": "src/blindpay/resources/quotes/quotes.py", + "symbol": "ContractNetwork" + } + ] + }, + { + "spec": "CreateTransferQuoteIn", + "sdk": [ + { + "file": "src/blindpay/resources/transfers/transfers.py", + "symbol": "CreateTransferQuoteInput" + } + ] + }, + { + "spec": "CreateTransferQuoteOut", + "sdk": [ + { + "file": "src/blindpay/resources/transfers/transfers.py", + "symbol": "CreateTransferQuoteResponse" + } + ] + }, + { + "spec": "CreateTransferIn", + "sdk": [ + { + "file": "src/blindpay/resources/transfers/transfers.py", + "symbol": "CreateTransferInput" + } + ] + }, + { + "spec": "CreateTransferOut", + "sdk": [ + { + "file": "src/blindpay/resources/transfers/transfers.py", + "symbol": "Transfer" + } + ] + }, + { + "spec": "TransferOut", + "sdk": [ + { + "file": "src/blindpay/resources/transfers/transfers.py", + "symbol": "Transfer" + } + ] + }, + { + "spec": "CreateVirtualAccountIn", + "sdk": [ + { + "file": "src/blindpay/resources/virtual_accounts/virtual_accounts.py", + "symbol": "CreateVirtualAccountInput" + } + ] + }, + { + "spec": "VirtualAccountOut", + "sdk": [ + { + "file": "src/blindpay/resources/virtual_accounts/virtual_accounts.py", + "symbol": "VirtualAccount" + } + ] + }, + { + "spec": "UpdateVirtualAccountIn", + "sdk": [ + { + "file": "src/blindpay/resources/virtual_accounts/virtual_accounts.py", + "symbol": "UpdateVirtualAccountInput" + } + ] + }, + { + "spec": "CreateWalletIn", + "sdk": [ + { + "file": "src/blindpay/resources/custodial_wallets/custodial_wallets.py", + "symbol": "CreateCustodialWalletInput" + } + ] + }, + { + "spec": "WalletOut", + "sdk": [ + { + "file": "src/blindpay/resources/custodial_wallets/custodial_wallets.py", + "symbol": "CustodialWallet" + } + ] + }, + { + "spec": "WalletBalanceOut", + "sdk": [ + { + "file": "src/blindpay/resources/custodial_wallets/custodial_wallets.py", + "symbol": "CustodialWalletBalance" + } + ] + }, + { + "spec": "WalletTokenOut", + "sdk": [ + { + "file": "src/blindpay/resources/custodial_wallets/custodial_wallets.py", + "symbol": "CustodialWalletBalanceToken" + } + ] + }, + { + "spec": "OfframpWallet", + "sdk": [ + { + "file": "src/blindpay/resources/bank_accounts/bank_accounts.py", + "symbol": "OfframpWallet" + }, + { + "file": "src/blindpay/resources/wallets/offramp.py", + "symbol": "OfframpWallet" + } ], "note": "pre-existing duplicate modeling: two separate TypedDicts with the same name and shape in two files (survey irregularity, out of scope for this project); both listed" }, - - { "spec": "WebhookEndpointIn", "sdk": [{ "file": "src/blindpay/resources/webhooks/webhooks.py", "symbol": "CreateWebhookEndpointInput" }] }, - { "spec": "WebhookEndpointOut", "sdk": [{ "file": "src/blindpay/resources/webhooks/webhooks.py", "symbol": "CreateWebhookEndpointResponse" }] }, - { "spec": "WebhookEndpoint", "sdk": [{ "file": "src/blindpay/resources/webhooks/webhooks.py", "symbol": "WebhookEndpoint" }] }, - { "spec": "PortalAccessOut", "sdk": [{ "file": "src/blindpay/resources/webhooks/webhooks.py", "symbol": "GetPortalAccessUrlResponse" }] }, - - { "spec": "InitiateTosIn", "sdk": [{ "file": "src/blindpay/resources/terms_of_service/terms_of_service.py", "symbol": "InitiateInput" }] }, - { "spec": "InitiateTosOut", "sdk": [{ "file": "src/blindpay/resources/terms_of_service/terms_of_service.py", "symbol": "InitiateResponse" }] }, - - { "spec": "UpdateInstanceIn", "sdk": [{ "file": "src/blindpay/resources/instances/instances.py", "symbol": "UpdateInstanceInput" }] }, - { "spec": "UpdateInstanceMemberIn", "sdk": [{ "file": "src/blindpay/resources/instances/instances.py", "symbol": "UpdateInstanceMemberRoleInput" }], - "note": "UpdateInstanceMemberRoleInput is dead code per allowlist.json (update_member_role takes member_id/role as loose args); mapped anyway so drift stays visible" }, - { "spec": "MigrateInstanceOwnershipIn", "sdk": [ - { "file": "src/blindpay/resources/instances/instances.py", "symbol": "MigrateInstanceOwnershipInput" }, - { "file": "src/blindpay/resources/ownership/ownership.py", "symbol": "MigrateInstanceOwnershipInput" } + { + "spec": "WebhookEndpointIn", + "sdk": [ + { + "file": "src/blindpay/resources/webhooks/webhooks.py", + "symbol": "CreateWebhookEndpointInput" + } + ] + }, + { + "spec": "WebhookEndpointOut", + "sdk": [ + { + "file": "src/blindpay/resources/webhooks/webhooks.py", + "symbol": "CreateWebhookEndpointResponse" + } + ] + }, + { + "spec": "WebhookEndpoint", + "sdk": [ + { + "file": "src/blindpay/resources/webhooks/webhooks.py", + "symbol": "WebhookEndpoint" + } + ] + }, + { + "spec": "PortalAccessOut", + "sdk": [ + { + "file": "src/blindpay/resources/webhooks/webhooks.py", + "symbol": "GetPortalAccessUrlResponse" + } + ] + }, + { + "spec": "InitiateTosIn", + "sdk": [ + { + "file": "src/blindpay/resources/terms_of_service/terms_of_service.py", + "symbol": "InitiateInput" + } + ] + }, + { + "spec": "InitiateTosOut", + "sdk": [ + { + "file": "src/blindpay/resources/terms_of_service/terms_of_service.py", + "symbol": "InitiateResponse" + } + ] + }, + { + "spec": "UpdateInstanceIn", + "sdk": [ + { + "file": "src/blindpay/resources/instances/instances.py", + "symbol": "UpdateInstanceInput" + } + ] + }, + { + "spec": "UpdateInstanceMemberIn", + "sdk": [ + { + "file": "src/blindpay/resources/instances/instances.py", + "symbol": "UpdateInstanceMemberRoleInput" + } + ], + "note": "UpdateInstanceMemberRoleInput is dead code per allowlist.json (update_member_role takes member_id/role as loose args); mapped anyway so drift stays visible" + }, + { + "spec": "MigrateInstanceOwnershipIn", + "sdk": [ + { + "file": "src/blindpay/resources/instances/instances.py", + "symbol": "MigrateInstanceOwnershipInput" + }, + { + "file": "src/blindpay/resources/ownership/ownership.py", + "symbol": "MigrateInstanceOwnershipInput" + } ], "note": "pre-existing duplicate: instances.py and ownership.py both declare this (survey irregularity #4); both listed" }, - { "spec": "Success", "sdk": [ - { "file": "src/blindpay/resources/instances/instances.py", "symbol": "MigrateInstanceOwnershipResponse" }, - { "file": "src/blindpay/resources/ownership/ownership.py", "symbol": "MigrateInstanceOwnershipResponse" } + { + "spec": "Success", + "sdk": [ + { + "file": "src/blindpay/resources/instances/instances.py", + "symbol": "MigrateInstanceOwnershipResponse" + }, + { + "file": "src/blindpay/resources/ownership/ownership.py", + "symbol": "MigrateInstanceOwnershipResponse" + } + ] + }, + { + "spec": "FeeOptions", + "sdk": [ + { + "file": "src/blindpay/resources/fees/fees.py", + "symbol": "FeeOptions" + } + ] + }, + { + "spec": "GetFeesOut", + "sdk": [ + { + "file": "src/blindpay/resources/fees/fees.py", + "symbol": "FeesResponse" + } + ] + }, + { + "spec": "UploadIn", + "sdk": [ + { + "file": "src/blindpay/resources/upload/upload.py", + "symbol": "UploadInput" + } + ] + }, + { + "spec": "UploadOut", + "sdk": [ + { + "file": "src/blindpay/resources/upload/upload.py", + "symbol": "UploadResponse" + } ] }, - - { "spec": "FeeOptions", "sdk": [{ "file": "src/blindpay/resources/fees/fees.py", "symbol": "FeeOptions" }] }, - { "spec": "GetFeesOut", "sdk": [{ "file": "src/blindpay/resources/fees/fees.py", "symbol": "FeesResponse" }] }, - - { "spec": "UploadIn", "sdk": [{ "file": "src/blindpay/resources/upload/upload.py", "symbol": "UploadInput" }] }, - { "spec": "UploadOut", "sdk": [{ "file": "src/blindpay/resources/upload/upload.py", "symbol": "UploadResponse" }] }, - - { "spec": ["PayoutOut", "PayoutOnEvmOut", "PayoutNewWebhookOut", "PayoutCompleteWebhookOut", "PayoutUpdateWebhookOut", "PayoutPartnerFeeWebhookOut"], + { + "spec": [ + "PayoutOut", + "PayoutOnEvmOut", + "PayoutNewWebhookOut", + "PayoutCompleteWebhookOut", + "PayoutUpdateWebhookOut", + "PayoutPartnerFeeWebhookOut" + ], "specPath": "tracking_payment", - "sdk": [{ "file": "src/blindpay/types.py", "symbol": "TrackingPayment" }], + "sdk": [ + { + "file": "src/blindpay/types.py", + "symbol": "TrackingPayment" + } + ], "note": "inline sub-object duplicated across 6 payout-side schemas (never $ref'd, no shared spec component); SDK models one shared TypedDict used for both payout and payin. Payout wire shape has 20 properties, SDK models 6 -- see unmodeled.json" }, - { "spec": ["PayoutOut", "PayoutOnEvmOut", "PayoutNewWebhookOut", "PayoutCompleteWebhookOut", "PayoutUpdateWebhookOut", "PayoutPartnerFeeWebhookOut"], + { + "spec": [ + "PayoutOut", + "PayoutOnEvmOut", + "PayoutNewWebhookOut", + "PayoutCompleteWebhookOut", + "PayoutUpdateWebhookOut", + "PayoutPartnerFeeWebhookOut" + ], "specPath": "tracking_transaction", - "sdk": [{ "file": "src/blindpay/types.py", "symbol": "TrackingTransaction" }] + "sdk": [ + { + "file": "src/blindpay/types.py", + "symbol": "TrackingTransaction" + } + ] }, - { "spec": ["PayoutOut", "PayoutOnEvmOut", "PayoutNewWebhookOut", "PayoutCompleteWebhookOut", "PayoutUpdateWebhookOut", "PayoutPartnerFeeWebhookOut"], + { + "spec": [ + "PayoutOut", + "PayoutOnEvmOut", + "PayoutNewWebhookOut", + "PayoutCompleteWebhookOut", + "PayoutUpdateWebhookOut", + "PayoutPartnerFeeWebhookOut" + ], "specPath": "tracking_complete", - "sdk": [{ "file": "src/blindpay/types.py", "symbol": "TrackingComplete" }] + "sdk": [ + { + "file": "src/blindpay/types.py", + "symbol": "TrackingComplete" + } + ] }, - { "spec": ["PayoutOut", "PayoutOnEvmOut", "PayoutNewWebhookOut", "PayoutCompleteWebhookOut", "PayoutUpdateWebhookOut", "PayoutPartnerFeeWebhookOut"], + { + "spec": [ + "PayoutOut", + "PayoutOnEvmOut", + "PayoutNewWebhookOut", + "PayoutCompleteWebhookOut", + "PayoutUpdateWebhookOut", + "PayoutPartnerFeeWebhookOut" + ], "specPath": "tracking_liquidity", - "sdk": [{ "file": "src/blindpay/types.py", "symbol": "TrackingLiquidity" }], + "sdk": [ + { + "file": "src/blindpay/types.py", + "symbol": "TrackingLiquidity" + } + ], "note": "no gaps: SDK's 5-field TrackingLiquidity already matches this sub-object exactly" }, - { "spec": ["PayinOut", "CreatePayinOut", "PayinNewWebhookOut", "PayinCompleteWebhookOut", "PayinUpdateWebhookOut", "PayinPartnerFeeWebhookOut"], + { + "spec": [ + "PayinOut", + "CreatePayinOut", + "PayinNewWebhookOut", + "PayinCompleteWebhookOut", + "PayinUpdateWebhookOut", + "PayinPartnerFeeWebhookOut" + ], "specPath": "tracking_payment", - "sdk": [{ "file": "src/blindpay/types.py", "symbol": "TrackingPayment" }], + "sdk": [ + { + "file": "src/blindpay/types.py", + "symbol": "TrackingPayment" + } + ], "note": "payin-side tracking_payment shape (6 properties) differs entirely from payout-side (20 properties) despite sharing the same SDK TypedDict; see unmodeled.json" }, - { "spec": ["PayinOut", "CreatePayinOut", "PayinNewWebhookOut", "PayinCompleteWebhookOut", "PayinUpdateWebhookOut", "PayinPartnerFeeWebhookOut"], + { + "spec": [ + "PayinOut", + "CreatePayinOut", + "PayinNewWebhookOut", + "PayinCompleteWebhookOut", + "PayinUpdateWebhookOut", + "PayinPartnerFeeWebhookOut" + ], "specPath": "tracking_transaction", - "sdk": [{ "file": "src/blindpay/types.py", "symbol": "TrackingTransaction" }], + "sdk": [ + { + "file": "src/blindpay/types.py", + "symbol": "TrackingTransaction" + } + ], "note": "payin-side wire shape has ~15 properties; SDK's generic 4-field TrackingTransaction covers only step/status/transaction_hash/completed_at. A fuller GetPayinTrackingTransaction TypedDict already exists in payins.py but is dead code (never referenced) -- see unmodeled.json" }, - { "spec": ["PayinOut", "CreatePayinOut", "PayinNewWebhookOut", "PayinCompleteWebhookOut", "PayinUpdateWebhookOut", "PayinPartnerFeeWebhookOut"], + { + "spec": [ + "PayinOut", + "CreatePayinOut", + "PayinNewWebhookOut", + "PayinCompleteWebhookOut", + "PayinUpdateWebhookOut", + "PayinPartnerFeeWebhookOut" + ], "specPath": "tracking_complete", - "sdk": [{ "file": "src/blindpay/types.py", "symbol": "TrackingComplete" }], + "sdk": [ + { + "file": "src/blindpay/types.py", + "symbol": "TrackingComplete" + } + ], "note": "no gaps: payin-side tracking_complete (3 properties) is a subset of the SDK's 4-field TrackingComplete" } ], @@ -280,9 +1902,18 @@ "schema": "Rfi", "reason": "the RFI (request-for-information) resource family is entirely unmodeled by this SDK: no resource file, no client method. GET /v1/instances/{instance_id}/rfi and GET .../customers/{customer_id}/rfi are real, reachable operations with zero SDK coverage. Adding it is additive, non-breaking, human-scoped work (Phase C per the design doc), not a field-level patch." }, - { "schema": "RfiField", "reason": "nested under Rfi/InstanceRfi/RfiSection; same Rfi-family gap as Rfi." }, - { "schema": "RfiSection", "reason": "nested under Rfi/InstanceRfi; same Rfi-family gap as Rfi." }, - { "schema": "InstanceRfi", "reason": "instance-level counterpart of Rfi (GET /v1/instances/{instance_id}/rfi); same Rfi-family gap." }, + { + "schema": "RfiField", + "reason": "nested under Rfi/InstanceRfi/RfiSection; same Rfi-family gap as Rfi." + }, + { + "schema": "RfiSection", + "reason": "nested under Rfi/InstanceRfi; same Rfi-family gap as Rfi." + }, + { + "schema": "InstanceRfi", + "reason": "instance-level counterpart of Rfi (GET /v1/instances/{instance_id}/rfi); same Rfi-family gap." + }, { "schema": "CreateRfiBody", "reason": "unreferenced by any public operation (0 $ref); the public POST .../rfi endpoints take an untyped object body, not this schema. Orphan, and also part of the unmodeled Rfi family." @@ -293,11 +1924,11 @@ }, { "schema": "UploadAnalyzeIn", - "reason": "reachable (POST /v1/upload/analyze) but the SDK's UploadAnalyzeInput/analyze() predates the current spec shape: `type` is required by the API and never sent by the SDK, so calling analyze() as currently typed does not produce a valid request. A known, real, broken integration needing dedicated human work (Phase C), not an incremental field-add." + "reason": "reachable (POST /v1/upload/analyze); UploadAnalyzeInput now declares `type` and `metadata` (fixed), but the request body is multipart/form-data and the SDK's analyze()/create() still send it via the same JSON post() path as every other resource call, with `file` typed as a bare str. Whether that JSON path actually produces a valid multipart upload against the live API needs dedicated human verification (Phase C), not a field-level patch." }, { "schema": "UploadAnalyzeOut", - "reason": "response counterpart of UploadAnalyzeIn; same broken/incomplete-integration reasoning." + "reason": "response counterpart of UploadAnalyzeIn; same open multipart-transport question." }, { "schema": "Error", @@ -311,28 +1942,70 @@ "schema": "BankAccountWebhookOut", "reason": "webhook payload; the SDK does not parse webhook payloads at all, only verify_webhook_signature() (HMAC check). Callers deserialize the body themselves." }, - { "schema": "BlockchainWalletWebhookOut", "reason": "webhook payload; same as BankAccountWebhookOut above." }, - { "schema": "CustomerNewWebhookOut", "reason": "webhook payload; same as BankAccountWebhookOut above." }, - { "schema": "CustomerUpdateWebhookOut", "reason": "webhook payload; same as BankAccountWebhookOut above." }, - { "schema": "CustomerDeleteWebhookOut", "reason": "webhook payload; same as BankAccountWebhookOut above." }, - { "schema": "LimitIncreaseNewWebhookOut", "reason": "webhook payload; same as BankAccountWebhookOut above." }, - { "schema": "LimitIncreaseUpdateWebhookOut", "reason": "webhook payload; same as BankAccountWebhookOut above." }, - { "schema": "TosAcceptWebhookOut", "reason": "webhook payload; same as BankAccountWebhookOut above." }, - { "schema": "WalletInboundSchemaOut", "reason": "webhook payload; same as BankAccountWebhookOut above." }, + { + "schema": "BlockchainWalletWebhookOut", + "reason": "webhook payload; same as BankAccountWebhookOut above." + }, + { + "schema": "CustomerNewWebhookOut", + "reason": "webhook payload; same as BankAccountWebhookOut above." + }, + { + "schema": "CustomerUpdateWebhookOut", + "reason": "webhook payload; same as BankAccountWebhookOut above." + }, + { + "schema": "CustomerDeleteWebhookOut", + "reason": "webhook payload; same as BankAccountWebhookOut above." + }, + { + "schema": "LimitIncreaseNewWebhookOut", + "reason": "webhook payload; same as BankAccountWebhookOut above." + }, + { + "schema": "LimitIncreaseUpdateWebhookOut", + "reason": "webhook payload; same as BankAccountWebhookOut above." + }, + { + "schema": "TosAcceptWebhookOut", + "reason": "webhook payload; same as BankAccountWebhookOut above." + }, + { + "schema": "WalletInboundSchemaOut", + "reason": "webhook payload; same as BankAccountWebhookOut above." + }, { "schema": "PayoutNewWebhookOut", "reason": "webhook envelope; same as BankAccountWebhookOut above for its own top-level fields (type, timestamp, key, ...). Its nested tracking_payment/tracking_transaction/tracking_complete/tracking_liquidity sub-objects ARE separately reconciled via the specPath entries in `types` above, since those are reused verbatim from the modeled GET/POST payout responses." }, - { "schema": "PayoutCompleteWebhookOut", "reason": "webhook envelope; same split treatment as PayoutNewWebhookOut above." }, - { "schema": "PayoutUpdateWebhookOut", "reason": "webhook envelope; same split treatment as PayoutNewWebhookOut above." }, - { "schema": "PayoutPartnerFeeWebhookOut", "reason": "webhook envelope; same split treatment as PayoutNewWebhookOut above." }, + { + "schema": "PayoutCompleteWebhookOut", + "reason": "webhook envelope; same split treatment as PayoutNewWebhookOut above." + }, + { + "schema": "PayoutUpdateWebhookOut", + "reason": "webhook envelope; same split treatment as PayoutNewWebhookOut above." + }, + { + "schema": "PayoutPartnerFeeWebhookOut", + "reason": "webhook envelope; same split treatment as PayoutNewWebhookOut above." + }, { "schema": "PayinNewWebhookOut", "reason": "webhook envelope; same split treatment as PayoutNewWebhookOut above, but for the payin-side tracking_payment/tracking_transaction/tracking_complete specPath entries." }, - { "schema": "PayinCompleteWebhookOut", "reason": "webhook envelope; same split treatment as PayinNewWebhookOut above." }, - { "schema": "PayinUpdateWebhookOut", "reason": "webhook envelope; same split treatment as PayinNewWebhookOut above." }, - { "schema": "PayinPartnerFeeWebhookOut", "reason": "webhook envelope; same split treatment as PayinNewWebhookOut above." } + { + "schema": "PayinCompleteWebhookOut", + "reason": "webhook envelope; same split treatment as PayinNewWebhookOut above." + }, + { + "schema": "PayinUpdateWebhookOut", + "reason": "webhook envelope; same split treatment as PayinNewWebhookOut above." + }, + { + "schema": "PayinPartnerFeeWebhookOut", + "reason": "webhook envelope; same split treatment as PayinNewWebhookOut above." + } ] } } diff --git a/.api-sync/sync.py b/.api-sync/sync.py index 29c85a5..df7d351 100644 --- a/.api-sync/sync.py +++ b/.api-sync/sync.py @@ -85,8 +85,15 @@ def load_unmodeled() -> list[dict]: required = {"schema", "field", "reason", "owner"} elif kind == "enum": required = {"enum", "missing_values", "reason", "owner"} + elif kind == "enum_coverage": + required = {"schema", "property", "reason", "owner"} + elif kind == "nested_object": + required = {"schema", "path", "reason", "owner"} else: - raise SystemExit(f"{UNMODELED_PATH}[{i}]: unknown or missing 'kind' (expected 'property' or 'enum')") + raise SystemExit( + f"{UNMODELED_PATH}[{i}]: unknown or missing 'kind' " + f"(expected 'property', 'enum', 'enum_coverage', or 'nested_object')" + ) missing = required - e.keys() if missing: raise SystemExit(f"{UNMODELED_PATH}[{i}]: missing required key(s) {sorted(missing)}") @@ -178,6 +185,50 @@ def get_enum_values(spec: dict, locator: dict) -> Optional[set[str]]: return set(enum) +def find_enum_locator(prop_schema: dict) -> Optional[tuple[bool, list]]: + """Detects an enum constraint in any of the shapes the public spec uses: + a bare `enum`, an array's `items.enum`, or either wrapped in `anyOf`/`oneOf` + (the usual nullable-enum pattern). Returns (is_items_form, enum_values) or + None if the property carries no enum constraint at all.""" + if not isinstance(prop_schema, dict): + return None + if "enum" in prop_schema: + return False, prop_schema["enum"] + items = prop_schema.get("items") + if isinstance(items, dict) and "enum" in items: + return True, items["enum"] + for key in ("anyOf", "oneOf"): + variants = prop_schema.get(key) + if not isinstance(variants, list): + continue + for variant in variants: + if not isinstance(variant, dict): + continue + if "enum" in variant: + return False, variant["enum"] + v_items = variant.get("items") + if isinstance(v_items, dict) and "enum" in v_items: + return True, v_items["enum"] + return None + + +def inline_object_shapes(prop_schema: dict) -> list[tuple[str, dict]]: + """Returns (path_suffix, shape) for every inline (non-$ref) object shape + implied directly by this property: the property itself if it is an inline + object, and/or its array items if those are an inline object. A $ref'd + object is a named schema and out of scope here -- it is reachable (or not) + and mappable (or ignored) on its own terms via the normal schema machinery.""" + shapes: list[tuple[str, dict]] = [] + if not isinstance(prop_schema, dict): + return shapes + if prop_schema.get("type") == "object" and "$ref" not in prop_schema and "properties" in prop_schema: + shapes.append(("", prop_schema)) + items = prop_schema.get("items") + if isinstance(items, dict) and "$ref" not in items and items.get("type") == "object" and "properties" in items: + shapes.append((".items", items)) + return shapes + + def coarse_type(prop_schema: dict) -> tuple[Optional[str], bool]: """Returns (single non-null JSON type or None if ambiguous/multi-type, nullable).""" t = prop_schema.get("type") @@ -500,6 +551,131 @@ def reconcile_types(spec: dict, map_data: dict, unmodeled: list[dict], index: Sd return gaps +def unmodeled_enum_coverage_allowed(unmodeled: list[dict], schema: str, path: Optional[str], prop: str) -> bool: + return any( + e.get("kind") == "enum_coverage" and e["schema"] == schema and e.get("path") == path and e["property"] == prop + for e in unmodeled + ) + + +def unmodeled_nested_object_allowed(unmodeled: list[dict], schema: str, path: str) -> bool: + return any(e.get("kind") == "nested_object" and e["schema"] == schema and e["path"] == path for e in unmodeled) + + +@dataclass +class EnumCoverageGap: + schema: str + path: Optional[str] + property: str + + +def reconcile_enum_coverage( + spec: dict, map_data: dict, unmodeled: list[dict], index: SdkIndex +) -> list[EnumCoverageGap]: + """Every enum-constrained property on a schema this SDK actually models + the field for must resolve to a mapped Literal (via spec-map.json's + `enums` list) or a recorded (kind=enum_coverage) exclusion. Unlike + reconcile_enums, which only checks the *members* of enums someone already + chose to map, this walks every mapped schema and flags enum-constrained + properties nobody mapped at all.""" + reachable = compute_reachable_schemas(spec) + covered = { + (e["spec"]["schema"], e["spec"].get("path"), e["spec"]["property"], bool(e["spec"].get("items"))) + for e in map_data.get("enums", []) + } + + gaps: list[EnumCoverageGap] = [] + seen: set[tuple[str, Optional[str], str]] = set() + for e in map_data.get("types", []): + spec_names = e["spec"] if isinstance(e["spec"], list) else [e["spec"]] + path = e.get("specPath") + sdk_fields: set[str] = set() + for site in e["sdk"]: + sdk_fields |= resolve_typeddict_fields(site["symbol"], site["file"], index) + + for name in spec_names: + if name not in reachable: + continue + schema_obj = get_schema(spec, name) + if schema_obj is None: + continue + node = resolve_path(schema_obj, path) + for prop_name, prop_schema in get_properties(node).items(): + if prop_name not in sdk_fields: + continue # unmapped field entirely -- reconcile_types's concern, not this check's + locator = find_enum_locator(prop_schema) + if locator is None: + continue + is_items, _values = locator + key = (name, path, prop_name) + if key in seen: + continue + if (name, path, prop_name, is_items) in covered: + continue + if unmodeled_enum_coverage_allowed(unmodeled, name, path, prop_name): + continue + seen.add(key) + gaps.append(EnumCoverageGap(schema=name, path=path, property=prop_name)) + + gaps.sort(key=lambda g: (g.schema, g.path or "", g.property)) + return gaps + + +@dataclass +class NestedObjectGap: + schema: str + path: str + + +def reconcile_nested_coverage(spec: dict, map_data: dict, unmodeled: list[dict]) -> list[NestedObjectGap]: + """Recursively enumerates inline object and array-item-object shapes + reachable under every mapped schema's mapped path. Each such shape must + itself have a map entry (specPath pointing at it) or a recorded + (kind=nested_object) omission -- otherwise its own properties (which may + include further enums, or fields drifting out from under it) are + invisible to every other check in this file.""" + reachable = compute_reachable_schemas(spec) + mapped_paths: set[tuple[str, Optional[str]]] = set() + for e in map_data.get("types", []): + spec_names = e["spec"] if isinstance(e["spec"], list) else [e["spec"]] + path = e.get("specPath") + for name in spec_names: + mapped_paths.add((name, path)) + + gaps: list[NestedObjectGap] = [] + seen: set[tuple[str, str]] = set() + + def walk(schema_name: str, base_path: Optional[str], node: Optional[dict]) -> None: + for prop_name, prop_schema in get_properties(node).items(): + child_path = f"{base_path}.{prop_name}" if base_path else prop_name + for suffix, shape in inline_object_shapes(prop_schema): + full_path = child_path + suffix + key = (schema_name, full_path) + if key in seen: + continue + if (schema_name, full_path) in mapped_paths: + walk(schema_name, full_path, shape) + continue + if unmodeled_nested_object_allowed(unmodeled, schema_name, full_path): + continue + seen.add(key) + gaps.append(NestedObjectGap(schema=schema_name, path=full_path)) + # do not recurse into an unresolved shape -- its own nested + # shapes are not in scope until this one is mapped or excused + + for name, path in sorted(mapped_paths, key=lambda t: (t[0], t[1] or "")): + if name not in reachable: + continue + schema_obj = get_schema(spec, name) + if schema_obj is None: + continue + node = resolve_path(schema_obj, path) + walk(name, path, node) + + gaps.sort(key=lambda g: (g.schema, g.path)) + return gaps + + # --------------------------------------------------------------------------- # # Old-vs-new diff (apply-only): removals, required/type changes, new operations/schemas # --------------------------------------------------------------------------- # @@ -920,6 +1096,8 @@ def cmd_check(report_path: Optional[Path]) -> int: spec = load_json(SNAPSHOT_PATH) enum_gaps = reconcile_enums(spec, map_data, unmodeled, index) if not map_errors else [] prop_gaps = reconcile_types(spec, map_data, unmodeled, index) if not map_errors else [] + enum_coverage_gaps = reconcile_enum_coverage(spec, map_data, unmodeled, index) if not map_errors else [] + nested_gaps = reconcile_nested_coverage(spec, map_data, unmodeled) if not map_errors else [] report = { "mode": "check", @@ -928,11 +1106,15 @@ def cmd_check(report_path: Optional[Path]) -> int: "property_gaps": [ {"schema": g.schema_names, "path": g.path, "missing": sorted(g.missing.keys())} for g in prop_gaps ], + "enum_coverage_gaps": [ + {"schema": g.schema, "path": g.path, "property": g.property} for g in enum_coverage_gaps + ], + "nested_object_gaps": [{"schema": g.schema, "path": g.path} for g in nested_gaps], } if report_path: report_path.write_text(json.dumps(report, indent=2, sort_keys=True) + "\n") - if not (map_errors or enum_gaps or prop_gaps): + if not (map_errors or enum_gaps or prop_gaps or enum_coverage_gaps or nested_gaps): return 0 if map_errors: @@ -951,6 +1133,19 @@ def cmd_check(report_path: Optional[Path]) -> int: f"{[s['symbol'] for s in g.sdk_sites]}; add the field(s) or record in " f".api-sync/unmodeled.json (kind=property) with a reason and owner." ) + for g in enum_coverage_gaps: + ctx = g.schema + (f"#{g.path}" if g.path else "") + print( + f"ENUM COVERAGE GAP: {ctx}.{g.property} is enum-constrained in the spec but is not mapped " + f"to an SDK Literal via spec-map.json's `enums` list; map it or record it in " + f".api-sync/unmodeled.json (kind=enum_coverage) with a reason and owner." + ) + for g in nested_gaps: + print( + f"NESTED OBJECT COVERAGE GAP: {g.schema}#{g.path} is an inline object/array-item shape with no " + f"map entry; add a specPath map entry for it or record it in .api-sync/unmodeled.json " + f"(kind=nested_object) with a reason and owner." + ) return 1 diff --git a/.api-sync/unmodeled.json b/.api-sync/unmodeled.json index 6421283..3726ccc 100644 --- a/.api-sync/unmodeled.json +++ b/.api-sync/unmodeled.json @@ -1009,5 +1009,980 @@ ], "reason": "CustomerOut.kyc_status carries the full 8-value customer state machine on the wire; the SDK's KycStatus Literal used for that exact field only has 2 members (awaiting_contract, compliance_request). Needs a human decision on whether KycStatus should simply gain the 6 missing members, or whether this field was wired to the wrong Literal from the start (customers.py also declares an unrelated 5-value CustomerStatus used only for a list-filter param).", "owner": "eric@blindpay.com" + }, + { + "kind": "enum", + "enum": "BusinessIndustry", + "missing_values": [ + "446120" + ], + "reason": "Found by the new enum-coverage check when CreateCustomerIn/CustomerOut/UpdateCustomerIn.business_industry was first wired into spec-map.json's `enums` list. The wire enum has one more NAICS-style code than the SDK's BusinessIndustry Literal. Needs a human to confirm '446120' and add it.", + "owner": "eric@blindpay.com" + }, + { + "kind": "enum", + "enum": "LimitIncreaseRequestSupportingDocumentType", + "missing_values": [ + "business_accounts_receivable", + "business_contract", + "business_merchant_processor_statement", + "business_shareholder_loan", + "individual_blockchain_wallet_statement", + "individual_crypto_exchange_statement", + "individual_employment_letter", + "individual_investment_statement", + "individual_pay_stub" + ], + "reason": "Found by the new enum-coverage check when CustomerLimitIncreaseIn/GetCustomerLimitIncreaseOut.supporting_document_type was first wired into spec-map.json's `enums` list. The wire enum has grown to cover business-side supporting documents; the SDK's Literal only has the original individual-side subset. Needs a human pass to add the missing members.", + "owner": "eric@blindpay.com" + }, + { + "kind": "enum", + "enum": "Currency", + "missing_values": [ + "EUR" + ], + "reason": "Found by the new enum-coverage check when PayoutOut.currency was first wired into spec-map.json's `enums` list. The wire enum now includes EUR; the SDK's Currency Literal does not. Needs a human to add it.", + "owner": "eric@blindpay.com" + }, + { + "kind": "enum", + "enum": "ManualExecutionStatus", + "missing_values": [ + "concluded", + "pending" + ], + "reason": "Found by the new enum-coverage check when PayinOut.manual_execution_status was first wired into spec-map.json's `enums` list. The SDK's ManualExecutionStatus Literal is missing two wire values. Needs a human to add them.", + "owner": "eric@blindpay.com" + }, + { + "kind": "enum_coverage", + "schema": "CustomerOut", + "property": "kyc_type", + "reason": "CustomerOut is a union of IndividualWithStandardKYC/IndividualWithEnhancedKYC/BusinessWithStandardKYB, and each variant pins kyc_type to its OWN singleton Literal (StandardKycType='standard', EnhancedKycType='enhanced') rather than sharing one Literal across all three -- correct per-variant, but this check maps one enum locator per (schema, property), so it cannot express 'verified per discriminated-union variant, not as a single shared enum'. A human should decide whether to add per-variant specPath-scoped enum entries or fold this into the existing 3-member KycType Literal instead.", + "owner": "eric@blindpay.com" + }, + { + "kind": "enum_coverage", + "schema": "BankAccountOut", + "property": "spei_protocol", + "reason": "BankAccountOut.spei_protocol is typed as a bare str even though the SDK already has a SpeiProtocol Literal (now mapped for PayoutOut.spei_protocol via this same PR). Needs a human to retype this field to SpeiProtocol and verify member parity.", + "owner": "eric@blindpay.com" + }, + { + "kind": "enum_coverage", + "schema": "CreateBankAccountIn", + "property": "business_industry", + "reason": "CreateBankAccountIn.business_industry is typed as a bare str even though the SDK already has a BusinessIndustry Literal (used by customers.py). Needs a human to retype this field and verify member parity between the bank-account and customer domains.", + "owner": "eric@blindpay.com" + }, + { + "kind": "enum_coverage", + "schema": "CreateBankAccountIn", + "property": "swift_payment_code", + "reason": "CreateBankAccountIn.swift_payment_code is typed as a bare str; the wire constrains it to an enum with no existing SDK Literal counterpart. Needs a human to design the right domain Literal.", + "owner": "eric@blindpay.com" + }, + { + "kind": "enum_coverage", + "schema": "CreatePayinOut", + "property": "step", + "reason": "TrackingComplete/TrackingPayment/TrackingTransaction/TrackingLiquidity in types.py type this field as a bare str; the wire constrains it to an enum. These sub-objects are shared verbatim across every Payin/Payout GET response and webhook envelope schema, so this same gap repeats once per host schema. Needs a human to design the right domain Literal(s) for the tracking sub-object family -- not a mechanical single-field patch, and not safe to guess at from one call site alone.", + "owner": "eric@blindpay.com", + "path": "tracking_complete" + }, + { + "kind": "enum_coverage", + "schema": "CreatePayinOut", + "property": "step", + "reason": "TrackingComplete/TrackingPayment/TrackingTransaction/TrackingLiquidity in types.py type this field as a bare str; the wire constrains it to an enum. These sub-objects are shared verbatim across every Payin/Payout GET response and webhook envelope schema, so this same gap repeats once per host schema. Needs a human to design the right domain Literal(s) for the tracking sub-object family -- not a mechanical single-field patch, and not safe to guess at from one call site alone.", + "owner": "eric@blindpay.com", + "path": "tracking_payment" + }, + { + "kind": "enum_coverage", + "schema": "CreatePayinOut", + "property": "status", + "reason": "TrackingComplete/TrackingPayment/TrackingTransaction/TrackingLiquidity in types.py type this field as a bare str; the wire constrains it to an enum. These sub-objects are shared verbatim across every Payin/Payout GET response and webhook envelope schema, so this same gap repeats once per host schema. Needs a human to design the right domain Literal(s) for the tracking sub-object family -- not a mechanical single-field patch, and not safe to guess at from one call site alone.", + "owner": "eric@blindpay.com", + "path": "tracking_transaction" + }, + { + "kind": "enum_coverage", + "schema": "CreatePayinOut", + "property": "step", + "reason": "TrackingComplete/TrackingPayment/TrackingTransaction/TrackingLiquidity in types.py type this field as a bare str; the wire constrains it to an enum. These sub-objects are shared verbatim across every Payin/Payout GET response and webhook envelope schema, so this same gap repeats once per host schema. Needs a human to design the right domain Literal(s) for the tracking sub-object family -- not a mechanical single-field patch, and not safe to guess at from one call site alone.", + "owner": "eric@blindpay.com", + "path": "tracking_transaction" + }, + { + "kind": "enum_coverage", + "schema": "CreateVirtualAccountIn", + "property": "sole_proprietor_doc_type", + "reason": "CreateVirtualAccountIn.sole_proprietor_doc_type is typed as a bare str; the wire constrains it to an enum with no existing SDK Literal counterpart. Needs a human to design the right domain Literal.", + "owner": "eric@blindpay.com" + }, + { + "kind": "enum_coverage", + "schema": "CustomerOut", + "property": "source_of_funds_doc_type", + "reason": "CustomerOut.source_of_funds_doc_type is typed as a bare str on the IndividualWithEnhancedKYC variant even though the SDK already has a SourceOfFundsDocType Literal used for the equivalent CreateCustomerIn/UpdateCustomerIn field. Needs a human to retype this field.", + "owner": "eric@blindpay.com" + }, + { + "kind": "enum_coverage", + "schema": "PayinCompleteWebhookOut", + "property": "step", + "reason": "TrackingComplete/TrackingPayment/TrackingTransaction/TrackingLiquidity in types.py type this field as a bare str; the wire constrains it to an enum. These sub-objects are shared verbatim across every Payin/Payout GET response and webhook envelope schema, so this same gap repeats once per host schema. Needs a human to design the right domain Literal(s) for the tracking sub-object family -- not a mechanical single-field patch, and not safe to guess at from one call site alone.", + "owner": "eric@blindpay.com", + "path": "tracking_complete" + }, + { + "kind": "enum_coverage", + "schema": "PayinCompleteWebhookOut", + "property": "step", + "reason": "TrackingComplete/TrackingPayment/TrackingTransaction/TrackingLiquidity in types.py type this field as a bare str; the wire constrains it to an enum. These sub-objects are shared verbatim across every Payin/Payout GET response and webhook envelope schema, so this same gap repeats once per host schema. Needs a human to design the right domain Literal(s) for the tracking sub-object family -- not a mechanical single-field patch, and not safe to guess at from one call site alone.", + "owner": "eric@blindpay.com", + "path": "tracking_payment" + }, + { + "kind": "enum_coverage", + "schema": "PayinCompleteWebhookOut", + "property": "status", + "reason": "TrackingComplete/TrackingPayment/TrackingTransaction/TrackingLiquidity in types.py type this field as a bare str; the wire constrains it to an enum. These sub-objects are shared verbatim across every Payin/Payout GET response and webhook envelope schema, so this same gap repeats once per host schema. Needs a human to design the right domain Literal(s) for the tracking sub-object family -- not a mechanical single-field patch, and not safe to guess at from one call site alone.", + "owner": "eric@blindpay.com", + "path": "tracking_transaction" + }, + { + "kind": "enum_coverage", + "schema": "PayinCompleteWebhookOut", + "property": "step", + "reason": "TrackingComplete/TrackingPayment/TrackingTransaction/TrackingLiquidity in types.py type this field as a bare str; the wire constrains it to an enum. These sub-objects are shared verbatim across every Payin/Payout GET response and webhook envelope schema, so this same gap repeats once per host schema. Needs a human to design the right domain Literal(s) for the tracking sub-object family -- not a mechanical single-field patch, and not safe to guess at from one call site alone.", + "owner": "eric@blindpay.com", + "path": "tracking_transaction" + }, + { + "kind": "enum_coverage", + "schema": "PayinNewWebhookOut", + "property": "step", + "reason": "TrackingComplete/TrackingPayment/TrackingTransaction/TrackingLiquidity in types.py type this field as a bare str; the wire constrains it to an enum. These sub-objects are shared verbatim across every Payin/Payout GET response and webhook envelope schema, so this same gap repeats once per host schema. Needs a human to design the right domain Literal(s) for the tracking sub-object family -- not a mechanical single-field patch, and not safe to guess at from one call site alone.", + "owner": "eric@blindpay.com", + "path": "tracking_complete" + }, + { + "kind": "enum_coverage", + "schema": "PayinNewWebhookOut", + "property": "step", + "reason": "TrackingComplete/TrackingPayment/TrackingTransaction/TrackingLiquidity in types.py type this field as a bare str; the wire constrains it to an enum. These sub-objects are shared verbatim across every Payin/Payout GET response and webhook envelope schema, so this same gap repeats once per host schema. Needs a human to design the right domain Literal(s) for the tracking sub-object family -- not a mechanical single-field patch, and not safe to guess at from one call site alone.", + "owner": "eric@blindpay.com", + "path": "tracking_payment" + }, + { + "kind": "enum_coverage", + "schema": "PayinNewWebhookOut", + "property": "status", + "reason": "TrackingComplete/TrackingPayment/TrackingTransaction/TrackingLiquidity in types.py type this field as a bare str; the wire constrains it to an enum. These sub-objects are shared verbatim across every Payin/Payout GET response and webhook envelope schema, so this same gap repeats once per host schema. Needs a human to design the right domain Literal(s) for the tracking sub-object family -- not a mechanical single-field patch, and not safe to guess at from one call site alone.", + "owner": "eric@blindpay.com", + "path": "tracking_transaction" + }, + { + "kind": "enum_coverage", + "schema": "PayinNewWebhookOut", + "property": "step", + "reason": "TrackingComplete/TrackingPayment/TrackingTransaction/TrackingLiquidity in types.py type this field as a bare str; the wire constrains it to an enum. These sub-objects are shared verbatim across every Payin/Payout GET response and webhook envelope schema, so this same gap repeats once per host schema. Needs a human to design the right domain Literal(s) for the tracking sub-object family -- not a mechanical single-field patch, and not safe to guess at from one call site alone.", + "owner": "eric@blindpay.com", + "path": "tracking_transaction" + }, + { + "kind": "enum_coverage", + "schema": "PayinOut", + "property": "currency", + "reason": "PayinOut.currency is typed as a bare str; the wire constrains it to an enum with no existing SDK Literal counterpart. Needs a human to design the right domain Literal.", + "owner": "eric@blindpay.com" + }, + { + "kind": "enum_coverage", + "schema": "PayinOut", + "property": "pse_document_type", + "reason": "PayinOut.pse_document_type is typed as a bare str; the wire constrains it to an enum with no existing SDK Literal counterpart. Needs a human to design the right domain Literal.", + "owner": "eric@blindpay.com" + }, + { + "kind": "enum_coverage", + "schema": "PayinOut", + "property": "type", + "reason": "PayinOut.type is typed as a bare str; the wire constrains it to an enum with no existing SDK Literal counterpart. Needs a human to design the right domain Literal.", + "owner": "eric@blindpay.com" + }, + { + "kind": "enum_coverage", + "schema": "PayinOut", + "property": "step", + "reason": "TrackingComplete/TrackingPayment/TrackingTransaction/TrackingLiquidity in types.py type this field as a bare str; the wire constrains it to an enum. These sub-objects are shared verbatim across every Payin/Payout GET response and webhook envelope schema, so this same gap repeats once per host schema. Needs a human to design the right domain Literal(s) for the tracking sub-object family -- not a mechanical single-field patch, and not safe to guess at from one call site alone.", + "owner": "eric@blindpay.com", + "path": "tracking_complete" + }, + { + "kind": "enum_coverage", + "schema": "PayinOut", + "property": "step", + "reason": "TrackingComplete/TrackingPayment/TrackingTransaction/TrackingLiquidity in types.py type this field as a bare str; the wire constrains it to an enum. These sub-objects are shared verbatim across every Payin/Payout GET response and webhook envelope schema, so this same gap repeats once per host schema. Needs a human to design the right domain Literal(s) for the tracking sub-object family -- not a mechanical single-field patch, and not safe to guess at from one call site alone.", + "owner": "eric@blindpay.com", + "path": "tracking_payment" + }, + { + "kind": "enum_coverage", + "schema": "PayinOut", + "property": "status", + "reason": "TrackingComplete/TrackingPayment/TrackingTransaction/TrackingLiquidity in types.py type this field as a bare str; the wire constrains it to an enum. These sub-objects are shared verbatim across every Payin/Payout GET response and webhook envelope schema, so this same gap repeats once per host schema. Needs a human to design the right domain Literal(s) for the tracking sub-object family -- not a mechanical single-field patch, and not safe to guess at from one call site alone.", + "owner": "eric@blindpay.com", + "path": "tracking_transaction" + }, + { + "kind": "enum_coverage", + "schema": "PayinOut", + "property": "step", + "reason": "TrackingComplete/TrackingPayment/TrackingTransaction/TrackingLiquidity in types.py type this field as a bare str; the wire constrains it to an enum. These sub-objects are shared verbatim across every Payin/Payout GET response and webhook envelope schema, so this same gap repeats once per host schema. Needs a human to design the right domain Literal(s) for the tracking sub-object family -- not a mechanical single-field patch, and not safe to guess at from one call site alone.", + "owner": "eric@blindpay.com", + "path": "tracking_transaction" + }, + { + "kind": "enum_coverage", + "schema": "PayinPartnerFeeWebhookOut", + "property": "step", + "reason": "TrackingComplete/TrackingPayment/TrackingTransaction/TrackingLiquidity in types.py type this field as a bare str; the wire constrains it to an enum. These sub-objects are shared verbatim across every Payin/Payout GET response and webhook envelope schema, so this same gap repeats once per host schema. Needs a human to design the right domain Literal(s) for the tracking sub-object family -- not a mechanical single-field patch, and not safe to guess at from one call site alone.", + "owner": "eric@blindpay.com", + "path": "tracking_complete" + }, + { + "kind": "enum_coverage", + "schema": "PayinPartnerFeeWebhookOut", + "property": "step", + "reason": "TrackingComplete/TrackingPayment/TrackingTransaction/TrackingLiquidity in types.py type this field as a bare str; the wire constrains it to an enum. These sub-objects are shared verbatim across every Payin/Payout GET response and webhook envelope schema, so this same gap repeats once per host schema. Needs a human to design the right domain Literal(s) for the tracking sub-object family -- not a mechanical single-field patch, and not safe to guess at from one call site alone.", + "owner": "eric@blindpay.com", + "path": "tracking_payment" + }, + { + "kind": "enum_coverage", + "schema": "PayinPartnerFeeWebhookOut", + "property": "status", + "reason": "TrackingComplete/TrackingPayment/TrackingTransaction/TrackingLiquidity in types.py type this field as a bare str; the wire constrains it to an enum. These sub-objects are shared verbatim across every Payin/Payout GET response and webhook envelope schema, so this same gap repeats once per host schema. Needs a human to design the right domain Literal(s) for the tracking sub-object family -- not a mechanical single-field patch, and not safe to guess at from one call site alone.", + "owner": "eric@blindpay.com", + "path": "tracking_transaction" + }, + { + "kind": "enum_coverage", + "schema": "PayinPartnerFeeWebhookOut", + "property": "step", + "reason": "TrackingComplete/TrackingPayment/TrackingTransaction/TrackingLiquidity in types.py type this field as a bare str; the wire constrains it to an enum. These sub-objects are shared verbatim across every Payin/Payout GET response and webhook envelope schema, so this same gap repeats once per host schema. Needs a human to design the right domain Literal(s) for the tracking sub-object family -- not a mechanical single-field patch, and not safe to guess at from one call site alone.", + "owner": "eric@blindpay.com", + "path": "tracking_transaction" + }, + { + "kind": "enum_coverage", + "schema": "PayinUpdateWebhookOut", + "property": "step", + "reason": "TrackingComplete/TrackingPayment/TrackingTransaction/TrackingLiquidity in types.py type this field as a bare str; the wire constrains it to an enum. These sub-objects are shared verbatim across every Payin/Payout GET response and webhook envelope schema, so this same gap repeats once per host schema. Needs a human to design the right domain Literal(s) for the tracking sub-object family -- not a mechanical single-field patch, and not safe to guess at from one call site alone.", + "owner": "eric@blindpay.com", + "path": "tracking_complete" + }, + { + "kind": "enum_coverage", + "schema": "PayinUpdateWebhookOut", + "property": "step", + "reason": "TrackingComplete/TrackingPayment/TrackingTransaction/TrackingLiquidity in types.py type this field as a bare str; the wire constrains it to an enum. These sub-objects are shared verbatim across every Payin/Payout GET response and webhook envelope schema, so this same gap repeats once per host schema. Needs a human to design the right domain Literal(s) for the tracking sub-object family -- not a mechanical single-field patch, and not safe to guess at from one call site alone.", + "owner": "eric@blindpay.com", + "path": "tracking_payment" + }, + { + "kind": "enum_coverage", + "schema": "PayinUpdateWebhookOut", + "property": "status", + "reason": "TrackingComplete/TrackingPayment/TrackingTransaction/TrackingLiquidity in types.py type this field as a bare str; the wire constrains it to an enum. These sub-objects are shared verbatim across every Payin/Payout GET response and webhook envelope schema, so this same gap repeats once per host schema. Needs a human to design the right domain Literal(s) for the tracking sub-object family -- not a mechanical single-field patch, and not safe to guess at from one call site alone.", + "owner": "eric@blindpay.com", + "path": "tracking_transaction" + }, + { + "kind": "enum_coverage", + "schema": "PayinUpdateWebhookOut", + "property": "step", + "reason": "TrackingComplete/TrackingPayment/TrackingTransaction/TrackingLiquidity in types.py type this field as a bare str; the wire constrains it to an enum. These sub-objects are shared verbatim across every Payin/Payout GET response and webhook envelope schema, so this same gap repeats once per host schema. Needs a human to design the right domain Literal(s) for the tracking sub-object family -- not a mechanical single-field patch, and not safe to guess at from one call site alone.", + "owner": "eric@blindpay.com", + "path": "tracking_transaction" + }, + { + "kind": "enum_coverage", + "schema": "PayoutCompleteWebhookOut", + "property": "status", + "reason": "TrackingComplete/TrackingPayment/TrackingTransaction/TrackingLiquidity in types.py type this field as a bare str; the wire constrains it to an enum. These sub-objects are shared verbatim across every Payin/Payout GET response and webhook envelope schema, so this same gap repeats once per host schema. Needs a human to design the right domain Literal(s) for the tracking sub-object family -- not a mechanical single-field patch, and not safe to guess at from one call site alone.", + "owner": "eric@blindpay.com", + "path": "tracking_complete" + }, + { + "kind": "enum_coverage", + "schema": "PayoutCompleteWebhookOut", + "property": "step", + "reason": "TrackingComplete/TrackingPayment/TrackingTransaction/TrackingLiquidity in types.py type this field as a bare str; the wire constrains it to an enum. These sub-objects are shared verbatim across every Payin/Payout GET response and webhook envelope schema, so this same gap repeats once per host schema. Needs a human to design the right domain Literal(s) for the tracking sub-object family -- not a mechanical single-field patch, and not safe to guess at from one call site alone.", + "owner": "eric@blindpay.com", + "path": "tracking_complete" + }, + { + "kind": "enum_coverage", + "schema": "PayoutCompleteWebhookOut", + "property": "estimated_time_of_arrival", + "reason": "TrackingComplete/TrackingPayment/TrackingTransaction/TrackingLiquidity in types.py type this field as a bare str; the wire constrains it to an enum. These sub-objects are shared verbatim across every Payin/Payout GET response and webhook envelope schema, so this same gap repeats once per host schema. Needs a human to design the right domain Literal(s) for the tracking sub-object family -- not a mechanical single-field patch, and not safe to guess at from one call site alone.", + "owner": "eric@blindpay.com", + "path": "tracking_liquidity" + }, + { + "kind": "enum_coverage", + "schema": "PayoutCompleteWebhookOut", + "property": "provider_status", + "reason": "TrackingComplete/TrackingPayment/TrackingTransaction/TrackingLiquidity in types.py type this field as a bare str; the wire constrains it to an enum. These sub-objects are shared verbatim across every Payin/Payout GET response and webhook envelope schema, so this same gap repeats once per host schema. Needs a human to design the right domain Literal(s) for the tracking sub-object family -- not a mechanical single-field patch, and not safe to guess at from one call site alone.", + "owner": "eric@blindpay.com", + "path": "tracking_liquidity" + }, + { + "kind": "enum_coverage", + "schema": "PayoutCompleteWebhookOut", + "property": "step", + "reason": "TrackingComplete/TrackingPayment/TrackingTransaction/TrackingLiquidity in types.py type this field as a bare str; the wire constrains it to an enum. These sub-objects are shared verbatim across every Payin/Payout GET response and webhook envelope schema, so this same gap repeats once per host schema. Needs a human to design the right domain Literal(s) for the tracking sub-object family -- not a mechanical single-field patch, and not safe to guess at from one call site alone.", + "owner": "eric@blindpay.com", + "path": "tracking_liquidity" + }, + { + "kind": "enum_coverage", + "schema": "PayoutCompleteWebhookOut", + "property": "estimated_time_of_arrival", + "reason": "TrackingComplete/TrackingPayment/TrackingTransaction/TrackingLiquidity in types.py type this field as a bare str; the wire constrains it to an enum. These sub-objects are shared verbatim across every Payin/Payout GET response and webhook envelope schema, so this same gap repeats once per host schema. Needs a human to design the right domain Literal(s) for the tracking sub-object family -- not a mechanical single-field patch, and not safe to guess at from one call site alone.", + "owner": "eric@blindpay.com", + "path": "tracking_payment" + }, + { + "kind": "enum_coverage", + "schema": "PayoutCompleteWebhookOut", + "property": "provider_name", + "reason": "TrackingComplete/TrackingPayment/TrackingTransaction/TrackingLiquidity in types.py type this field as a bare str; the wire constrains it to an enum. These sub-objects are shared verbatim across every Payin/Payout GET response and webhook envelope schema, so this same gap repeats once per host schema. Needs a human to design the right domain Literal(s) for the tracking sub-object family -- not a mechanical single-field patch, and not safe to guess at from one call site alone.", + "owner": "eric@blindpay.com", + "path": "tracking_payment" + }, + { + "kind": "enum_coverage", + "schema": "PayoutCompleteWebhookOut", + "property": "provider_status", + "reason": "TrackingComplete/TrackingPayment/TrackingTransaction/TrackingLiquidity in types.py type this field as a bare str; the wire constrains it to an enum. These sub-objects are shared verbatim across every Payin/Payout GET response and webhook envelope schema, so this same gap repeats once per host schema. Needs a human to design the right domain Literal(s) for the tracking sub-object family -- not a mechanical single-field patch, and not safe to guess at from one call site alone.", + "owner": "eric@blindpay.com", + "path": "tracking_payment" + }, + { + "kind": "enum_coverage", + "schema": "PayoutCompleteWebhookOut", + "property": "step", + "reason": "TrackingComplete/TrackingPayment/TrackingTransaction/TrackingLiquidity in types.py type this field as a bare str; the wire constrains it to an enum. These sub-objects are shared verbatim across every Payin/Payout GET response and webhook envelope schema, so this same gap repeats once per host schema. Needs a human to design the right domain Literal(s) for the tracking sub-object family -- not a mechanical single-field patch, and not safe to guess at from one call site alone.", + "owner": "eric@blindpay.com", + "path": "tracking_payment" + }, + { + "kind": "enum_coverage", + "schema": "PayoutCompleteWebhookOut", + "property": "status", + "reason": "TrackingComplete/TrackingPayment/TrackingTransaction/TrackingLiquidity in types.py type this field as a bare str; the wire constrains it to an enum. These sub-objects are shared verbatim across every Payin/Payout GET response and webhook envelope schema, so this same gap repeats once per host schema. Needs a human to design the right domain Literal(s) for the tracking sub-object family -- not a mechanical single-field patch, and not safe to guess at from one call site alone.", + "owner": "eric@blindpay.com", + "path": "tracking_transaction" + }, + { + "kind": "enum_coverage", + "schema": "PayoutCompleteWebhookOut", + "property": "step", + "reason": "TrackingComplete/TrackingPayment/TrackingTransaction/TrackingLiquidity in types.py type this field as a bare str; the wire constrains it to an enum. These sub-objects are shared verbatim across every Payin/Payout GET response and webhook envelope schema, so this same gap repeats once per host schema. Needs a human to design the right domain Literal(s) for the tracking sub-object family -- not a mechanical single-field patch, and not safe to guess at from one call site alone.", + "owner": "eric@blindpay.com", + "path": "tracking_transaction" + }, + { + "kind": "enum_coverage", + "schema": "PayoutNewWebhookOut", + "property": "status", + "reason": "TrackingComplete/TrackingPayment/TrackingTransaction/TrackingLiquidity in types.py type this field as a bare str; the wire constrains it to an enum. These sub-objects are shared verbatim across every Payin/Payout GET response and webhook envelope schema, so this same gap repeats once per host schema. Needs a human to design the right domain Literal(s) for the tracking sub-object family -- not a mechanical single-field patch, and not safe to guess at from one call site alone.", + "owner": "eric@blindpay.com", + "path": "tracking_complete" + }, + { + "kind": "enum_coverage", + "schema": "PayoutNewWebhookOut", + "property": "step", + "reason": "TrackingComplete/TrackingPayment/TrackingTransaction/TrackingLiquidity in types.py type this field as a bare str; the wire constrains it to an enum. These sub-objects are shared verbatim across every Payin/Payout GET response and webhook envelope schema, so this same gap repeats once per host schema. Needs a human to design the right domain Literal(s) for the tracking sub-object family -- not a mechanical single-field patch, and not safe to guess at from one call site alone.", + "owner": "eric@blindpay.com", + "path": "tracking_complete" + }, + { + "kind": "enum_coverage", + "schema": "PayoutNewWebhookOut", + "property": "estimated_time_of_arrival", + "reason": "TrackingComplete/TrackingPayment/TrackingTransaction/TrackingLiquidity in types.py type this field as a bare str; the wire constrains it to an enum. These sub-objects are shared verbatim across every Payin/Payout GET response and webhook envelope schema, so this same gap repeats once per host schema. Needs a human to design the right domain Literal(s) for the tracking sub-object family -- not a mechanical single-field patch, and not safe to guess at from one call site alone.", + "owner": "eric@blindpay.com", + "path": "tracking_liquidity" + }, + { + "kind": "enum_coverage", + "schema": "PayoutNewWebhookOut", + "property": "provider_status", + "reason": "TrackingComplete/TrackingPayment/TrackingTransaction/TrackingLiquidity in types.py type this field as a bare str; the wire constrains it to an enum. These sub-objects are shared verbatim across every Payin/Payout GET response and webhook envelope schema, so this same gap repeats once per host schema. Needs a human to design the right domain Literal(s) for the tracking sub-object family -- not a mechanical single-field patch, and not safe to guess at from one call site alone.", + "owner": "eric@blindpay.com", + "path": "tracking_liquidity" + }, + { + "kind": "enum_coverage", + "schema": "PayoutNewWebhookOut", + "property": "step", + "reason": "TrackingComplete/TrackingPayment/TrackingTransaction/TrackingLiquidity in types.py type this field as a bare str; the wire constrains it to an enum. These sub-objects are shared verbatim across every Payin/Payout GET response and webhook envelope schema, so this same gap repeats once per host schema. Needs a human to design the right domain Literal(s) for the tracking sub-object family -- not a mechanical single-field patch, and not safe to guess at from one call site alone.", + "owner": "eric@blindpay.com", + "path": "tracking_liquidity" + }, + { + "kind": "enum_coverage", + "schema": "PayoutNewWebhookOut", + "property": "estimated_time_of_arrival", + "reason": "TrackingComplete/TrackingPayment/TrackingTransaction/TrackingLiquidity in types.py type this field as a bare str; the wire constrains it to an enum. These sub-objects are shared verbatim across every Payin/Payout GET response and webhook envelope schema, so this same gap repeats once per host schema. Needs a human to design the right domain Literal(s) for the tracking sub-object family -- not a mechanical single-field patch, and not safe to guess at from one call site alone.", + "owner": "eric@blindpay.com", + "path": "tracking_payment" + }, + { + "kind": "enum_coverage", + "schema": "PayoutNewWebhookOut", + "property": "provider_name", + "reason": "TrackingComplete/TrackingPayment/TrackingTransaction/TrackingLiquidity in types.py type this field as a bare str; the wire constrains it to an enum. These sub-objects are shared verbatim across every Payin/Payout GET response and webhook envelope schema, so this same gap repeats once per host schema. Needs a human to design the right domain Literal(s) for the tracking sub-object family -- not a mechanical single-field patch, and not safe to guess at from one call site alone.", + "owner": "eric@blindpay.com", + "path": "tracking_payment" + }, + { + "kind": "enum_coverage", + "schema": "PayoutNewWebhookOut", + "property": "provider_status", + "reason": "TrackingComplete/TrackingPayment/TrackingTransaction/TrackingLiquidity in types.py type this field as a bare str; the wire constrains it to an enum. These sub-objects are shared verbatim across every Payin/Payout GET response and webhook envelope schema, so this same gap repeats once per host schema. Needs a human to design the right domain Literal(s) for the tracking sub-object family -- not a mechanical single-field patch, and not safe to guess at from one call site alone.", + "owner": "eric@blindpay.com", + "path": "tracking_payment" + }, + { + "kind": "enum_coverage", + "schema": "PayoutNewWebhookOut", + "property": "step", + "reason": "TrackingComplete/TrackingPayment/TrackingTransaction/TrackingLiquidity in types.py type this field as a bare str; the wire constrains it to an enum. These sub-objects are shared verbatim across every Payin/Payout GET response and webhook envelope schema, so this same gap repeats once per host schema. Needs a human to design the right domain Literal(s) for the tracking sub-object family -- not a mechanical single-field patch, and not safe to guess at from one call site alone.", + "owner": "eric@blindpay.com", + "path": "tracking_payment" + }, + { + "kind": "enum_coverage", + "schema": "PayoutNewWebhookOut", + "property": "status", + "reason": "TrackingComplete/TrackingPayment/TrackingTransaction/TrackingLiquidity in types.py type this field as a bare str; the wire constrains it to an enum. These sub-objects are shared verbatim across every Payin/Payout GET response and webhook envelope schema, so this same gap repeats once per host schema. Needs a human to design the right domain Literal(s) for the tracking sub-object family -- not a mechanical single-field patch, and not safe to guess at from one call site alone.", + "owner": "eric@blindpay.com", + "path": "tracking_transaction" + }, + { + "kind": "enum_coverage", + "schema": "PayoutNewWebhookOut", + "property": "step", + "reason": "TrackingComplete/TrackingPayment/TrackingTransaction/TrackingLiquidity in types.py type this field as a bare str; the wire constrains it to an enum. These sub-objects are shared verbatim across every Payin/Payout GET response and webhook envelope schema, so this same gap repeats once per host schema. Needs a human to design the right domain Literal(s) for the tracking sub-object family -- not a mechanical single-field patch, and not safe to guess at from one call site alone.", + "owner": "eric@blindpay.com", + "path": "tracking_transaction" + }, + { + "kind": "enum_coverage", + "schema": "PayoutOnEvmOut", + "property": "status", + "reason": "TrackingComplete/TrackingPayment/TrackingTransaction/TrackingLiquidity in types.py type this field as a bare str; the wire constrains it to an enum. These sub-objects are shared verbatim across every Payin/Payout GET response and webhook envelope schema, so this same gap repeats once per host schema. Needs a human to design the right domain Literal(s) for the tracking sub-object family -- not a mechanical single-field patch, and not safe to guess at from one call site alone.", + "owner": "eric@blindpay.com", + "path": "tracking_complete" + }, + { + "kind": "enum_coverage", + "schema": "PayoutOnEvmOut", + "property": "step", + "reason": "TrackingComplete/TrackingPayment/TrackingTransaction/TrackingLiquidity in types.py type this field as a bare str; the wire constrains it to an enum. These sub-objects are shared verbatim across every Payin/Payout GET response and webhook envelope schema, so this same gap repeats once per host schema. Needs a human to design the right domain Literal(s) for the tracking sub-object family -- not a mechanical single-field patch, and not safe to guess at from one call site alone.", + "owner": "eric@blindpay.com", + "path": "tracking_complete" + }, + { + "kind": "enum_coverage", + "schema": "PayoutOnEvmOut", + "property": "estimated_time_of_arrival", + "reason": "TrackingComplete/TrackingPayment/TrackingTransaction/TrackingLiquidity in types.py type this field as a bare str; the wire constrains it to an enum. These sub-objects are shared verbatim across every Payin/Payout GET response and webhook envelope schema, so this same gap repeats once per host schema. Needs a human to design the right domain Literal(s) for the tracking sub-object family -- not a mechanical single-field patch, and not safe to guess at from one call site alone.", + "owner": "eric@blindpay.com", + "path": "tracking_liquidity" + }, + { + "kind": "enum_coverage", + "schema": "PayoutOnEvmOut", + "property": "provider_status", + "reason": "TrackingComplete/TrackingPayment/TrackingTransaction/TrackingLiquidity in types.py type this field as a bare str; the wire constrains it to an enum. These sub-objects are shared verbatim across every Payin/Payout GET response and webhook envelope schema, so this same gap repeats once per host schema. Needs a human to design the right domain Literal(s) for the tracking sub-object family -- not a mechanical single-field patch, and not safe to guess at from one call site alone.", + "owner": "eric@blindpay.com", + "path": "tracking_liquidity" + }, + { + "kind": "enum_coverage", + "schema": "PayoutOnEvmOut", + "property": "step", + "reason": "TrackingComplete/TrackingPayment/TrackingTransaction/TrackingLiquidity in types.py type this field as a bare str; the wire constrains it to an enum. These sub-objects are shared verbatim across every Payin/Payout GET response and webhook envelope schema, so this same gap repeats once per host schema. Needs a human to design the right domain Literal(s) for the tracking sub-object family -- not a mechanical single-field patch, and not safe to guess at from one call site alone.", + "owner": "eric@blindpay.com", + "path": "tracking_liquidity" + }, + { + "kind": "enum_coverage", + "schema": "PayoutOnEvmOut", + "property": "estimated_time_of_arrival", + "reason": "TrackingComplete/TrackingPayment/TrackingTransaction/TrackingLiquidity in types.py type this field as a bare str; the wire constrains it to an enum. These sub-objects are shared verbatim across every Payin/Payout GET response and webhook envelope schema, so this same gap repeats once per host schema. Needs a human to design the right domain Literal(s) for the tracking sub-object family -- not a mechanical single-field patch, and not safe to guess at from one call site alone.", + "owner": "eric@blindpay.com", + "path": "tracking_payment" + }, + { + "kind": "enum_coverage", + "schema": "PayoutOnEvmOut", + "property": "provider_name", + "reason": "TrackingComplete/TrackingPayment/TrackingTransaction/TrackingLiquidity in types.py type this field as a bare str; the wire constrains it to an enum. These sub-objects are shared verbatim across every Payin/Payout GET response and webhook envelope schema, so this same gap repeats once per host schema. Needs a human to design the right domain Literal(s) for the tracking sub-object family -- not a mechanical single-field patch, and not safe to guess at from one call site alone.", + "owner": "eric@blindpay.com", + "path": "tracking_payment" + }, + { + "kind": "enum_coverage", + "schema": "PayoutOnEvmOut", + "property": "provider_status", + "reason": "TrackingComplete/TrackingPayment/TrackingTransaction/TrackingLiquidity in types.py type this field as a bare str; the wire constrains it to an enum. These sub-objects are shared verbatim across every Payin/Payout GET response and webhook envelope schema, so this same gap repeats once per host schema. Needs a human to design the right domain Literal(s) for the tracking sub-object family -- not a mechanical single-field patch, and not safe to guess at from one call site alone.", + "owner": "eric@blindpay.com", + "path": "tracking_payment" + }, + { + "kind": "enum_coverage", + "schema": "PayoutOnEvmOut", + "property": "step", + "reason": "TrackingComplete/TrackingPayment/TrackingTransaction/TrackingLiquidity in types.py type this field as a bare str; the wire constrains it to an enum. These sub-objects are shared verbatim across every Payin/Payout GET response and webhook envelope schema, so this same gap repeats once per host schema. Needs a human to design the right domain Literal(s) for the tracking sub-object family -- not a mechanical single-field patch, and not safe to guess at from one call site alone.", + "owner": "eric@blindpay.com", + "path": "tracking_payment" + }, + { + "kind": "enum_coverage", + "schema": "PayoutOnEvmOut", + "property": "status", + "reason": "TrackingComplete/TrackingPayment/TrackingTransaction/TrackingLiquidity in types.py type this field as a bare str; the wire constrains it to an enum. These sub-objects are shared verbatim across every Payin/Payout GET response and webhook envelope schema, so this same gap repeats once per host schema. Needs a human to design the right domain Literal(s) for the tracking sub-object family -- not a mechanical single-field patch, and not safe to guess at from one call site alone.", + "owner": "eric@blindpay.com", + "path": "tracking_transaction" + }, + { + "kind": "enum_coverage", + "schema": "PayoutOnEvmOut", + "property": "step", + "reason": "TrackingComplete/TrackingPayment/TrackingTransaction/TrackingLiquidity in types.py type this field as a bare str; the wire constrains it to an enum. These sub-objects are shared verbatim across every Payin/Payout GET response and webhook envelope schema, so this same gap repeats once per host schema. Needs a human to design the right domain Literal(s) for the tracking sub-object family -- not a mechanical single-field patch, and not safe to guess at from one call site alone.", + "owner": "eric@blindpay.com", + "path": "tracking_transaction" + }, + { + "kind": "enum_coverage", + "schema": "PayoutOut", + "property": "ach_cop_document_type", + "reason": "PayoutOut.ach_cop_document_type is typed as a bare str even though the SDK already has an AchCopDocument Literal (used by bank_accounts.py). Needs a human to retype this field and verify member parity.", + "owner": "eric@blindpay.com" + }, + { + "kind": "enum_coverage", + "schema": "PayoutOut", + "property": "status", + "reason": "TrackingComplete/TrackingPayment/TrackingTransaction/TrackingLiquidity in types.py type this field as a bare str; the wire constrains it to an enum. These sub-objects are shared verbatim across every Payin/Payout GET response and webhook envelope schema, so this same gap repeats once per host schema. Needs a human to design the right domain Literal(s) for the tracking sub-object family -- not a mechanical single-field patch, and not safe to guess at from one call site alone.", + "owner": "eric@blindpay.com", + "path": "tracking_complete" + }, + { + "kind": "enum_coverage", + "schema": "PayoutOut", + "property": "step", + "reason": "TrackingComplete/TrackingPayment/TrackingTransaction/TrackingLiquidity in types.py type this field as a bare str; the wire constrains it to an enum. These sub-objects are shared verbatim across every Payin/Payout GET response and webhook envelope schema, so this same gap repeats once per host schema. Needs a human to design the right domain Literal(s) for the tracking sub-object family -- not a mechanical single-field patch, and not safe to guess at from one call site alone.", + "owner": "eric@blindpay.com", + "path": "tracking_complete" + }, + { + "kind": "enum_coverage", + "schema": "PayoutOut", + "property": "estimated_time_of_arrival", + "reason": "TrackingComplete/TrackingPayment/TrackingTransaction/TrackingLiquidity in types.py type this field as a bare str; the wire constrains it to an enum. These sub-objects are shared verbatim across every Payin/Payout GET response and webhook envelope schema, so this same gap repeats once per host schema. Needs a human to design the right domain Literal(s) for the tracking sub-object family -- not a mechanical single-field patch, and not safe to guess at from one call site alone.", + "owner": "eric@blindpay.com", + "path": "tracking_liquidity" + }, + { + "kind": "enum_coverage", + "schema": "PayoutOut", + "property": "provider_status", + "reason": "TrackingComplete/TrackingPayment/TrackingTransaction/TrackingLiquidity in types.py type this field as a bare str; the wire constrains it to an enum. These sub-objects are shared verbatim across every Payin/Payout GET response and webhook envelope schema, so this same gap repeats once per host schema. Needs a human to design the right domain Literal(s) for the tracking sub-object family -- not a mechanical single-field patch, and not safe to guess at from one call site alone.", + "owner": "eric@blindpay.com", + "path": "tracking_liquidity" + }, + { + "kind": "enum_coverage", + "schema": "PayoutOut", + "property": "step", + "reason": "TrackingComplete/TrackingPayment/TrackingTransaction/TrackingLiquidity in types.py type this field as a bare str; the wire constrains it to an enum. These sub-objects are shared verbatim across every Payin/Payout GET response and webhook envelope schema, so this same gap repeats once per host schema. Needs a human to design the right domain Literal(s) for the tracking sub-object family -- not a mechanical single-field patch, and not safe to guess at from one call site alone.", + "owner": "eric@blindpay.com", + "path": "tracking_liquidity" + }, + { + "kind": "enum_coverage", + "schema": "PayoutOut", + "property": "estimated_time_of_arrival", + "reason": "TrackingComplete/TrackingPayment/TrackingTransaction/TrackingLiquidity in types.py type this field as a bare str; the wire constrains it to an enum. These sub-objects are shared verbatim across every Payin/Payout GET response and webhook envelope schema, so this same gap repeats once per host schema. Needs a human to design the right domain Literal(s) for the tracking sub-object family -- not a mechanical single-field patch, and not safe to guess at from one call site alone.", + "owner": "eric@blindpay.com", + "path": "tracking_payment" + }, + { + "kind": "enum_coverage", + "schema": "PayoutOut", + "property": "provider_name", + "reason": "TrackingComplete/TrackingPayment/TrackingTransaction/TrackingLiquidity in types.py type this field as a bare str; the wire constrains it to an enum. These sub-objects are shared verbatim across every Payin/Payout GET response and webhook envelope schema, so this same gap repeats once per host schema. Needs a human to design the right domain Literal(s) for the tracking sub-object family -- not a mechanical single-field patch, and not safe to guess at from one call site alone.", + "owner": "eric@blindpay.com", + "path": "tracking_payment" + }, + { + "kind": "enum_coverage", + "schema": "PayoutOut", + "property": "provider_status", + "reason": "TrackingComplete/TrackingPayment/TrackingTransaction/TrackingLiquidity in types.py type this field as a bare str; the wire constrains it to an enum. These sub-objects are shared verbatim across every Payin/Payout GET response and webhook envelope schema, so this same gap repeats once per host schema. Needs a human to design the right domain Literal(s) for the tracking sub-object family -- not a mechanical single-field patch, and not safe to guess at from one call site alone.", + "owner": "eric@blindpay.com", + "path": "tracking_payment" + }, + { + "kind": "enum_coverage", + "schema": "PayoutOut", + "property": "step", + "reason": "TrackingComplete/TrackingPayment/TrackingTransaction/TrackingLiquidity in types.py type this field as a bare str; the wire constrains it to an enum. These sub-objects are shared verbatim across every Payin/Payout GET response and webhook envelope schema, so this same gap repeats once per host schema. Needs a human to design the right domain Literal(s) for the tracking sub-object family -- not a mechanical single-field patch, and not safe to guess at from one call site alone.", + "owner": "eric@blindpay.com", + "path": "tracking_payment" + }, + { + "kind": "enum_coverage", + "schema": "PayoutOut", + "property": "status", + "reason": "TrackingComplete/TrackingPayment/TrackingTransaction/TrackingLiquidity in types.py type this field as a bare str; the wire constrains it to an enum. These sub-objects are shared verbatim across every Payin/Payout GET response and webhook envelope schema, so this same gap repeats once per host schema. Needs a human to design the right domain Literal(s) for the tracking sub-object family -- not a mechanical single-field patch, and not safe to guess at from one call site alone.", + "owner": "eric@blindpay.com", + "path": "tracking_transaction" + }, + { + "kind": "enum_coverage", + "schema": "PayoutOut", + "property": "step", + "reason": "TrackingComplete/TrackingPayment/TrackingTransaction/TrackingLiquidity in types.py type this field as a bare str; the wire constrains it to an enum. These sub-objects are shared verbatim across every Payin/Payout GET response and webhook envelope schema, so this same gap repeats once per host schema. Needs a human to design the right domain Literal(s) for the tracking sub-object family -- not a mechanical single-field patch, and not safe to guess at from one call site alone.", + "owner": "eric@blindpay.com", + "path": "tracking_transaction" + }, + { + "kind": "enum_coverage", + "schema": "PayoutPartnerFeeWebhookOut", + "property": "status", + "reason": "TrackingComplete/TrackingPayment/TrackingTransaction/TrackingLiquidity in types.py type this field as a bare str; the wire constrains it to an enum. These sub-objects are shared verbatim across every Payin/Payout GET response and webhook envelope schema, so this same gap repeats once per host schema. Needs a human to design the right domain Literal(s) for the tracking sub-object family -- not a mechanical single-field patch, and not safe to guess at from one call site alone.", + "owner": "eric@blindpay.com", + "path": "tracking_complete" + }, + { + "kind": "enum_coverage", + "schema": "PayoutPartnerFeeWebhookOut", + "property": "step", + "reason": "TrackingComplete/TrackingPayment/TrackingTransaction/TrackingLiquidity in types.py type this field as a bare str; the wire constrains it to an enum. These sub-objects are shared verbatim across every Payin/Payout GET response and webhook envelope schema, so this same gap repeats once per host schema. Needs a human to design the right domain Literal(s) for the tracking sub-object family -- not a mechanical single-field patch, and not safe to guess at from one call site alone.", + "owner": "eric@blindpay.com", + "path": "tracking_complete" + }, + { + "kind": "enum_coverage", + "schema": "PayoutPartnerFeeWebhookOut", + "property": "estimated_time_of_arrival", + "reason": "TrackingComplete/TrackingPayment/TrackingTransaction/TrackingLiquidity in types.py type this field as a bare str; the wire constrains it to an enum. These sub-objects are shared verbatim across every Payin/Payout GET response and webhook envelope schema, so this same gap repeats once per host schema. Needs a human to design the right domain Literal(s) for the tracking sub-object family -- not a mechanical single-field patch, and not safe to guess at from one call site alone.", + "owner": "eric@blindpay.com", + "path": "tracking_liquidity" + }, + { + "kind": "enum_coverage", + "schema": "PayoutPartnerFeeWebhookOut", + "property": "provider_status", + "reason": "TrackingComplete/TrackingPayment/TrackingTransaction/TrackingLiquidity in types.py type this field as a bare str; the wire constrains it to an enum. These sub-objects are shared verbatim across every Payin/Payout GET response and webhook envelope schema, so this same gap repeats once per host schema. Needs a human to design the right domain Literal(s) for the tracking sub-object family -- not a mechanical single-field patch, and not safe to guess at from one call site alone.", + "owner": "eric@blindpay.com", + "path": "tracking_liquidity" + }, + { + "kind": "enum_coverage", + "schema": "PayoutPartnerFeeWebhookOut", + "property": "step", + "reason": "TrackingComplete/TrackingPayment/TrackingTransaction/TrackingLiquidity in types.py type this field as a bare str; the wire constrains it to an enum. These sub-objects are shared verbatim across every Payin/Payout GET response and webhook envelope schema, so this same gap repeats once per host schema. Needs a human to design the right domain Literal(s) for the tracking sub-object family -- not a mechanical single-field patch, and not safe to guess at from one call site alone.", + "owner": "eric@blindpay.com", + "path": "tracking_liquidity" + }, + { + "kind": "enum_coverage", + "schema": "PayoutPartnerFeeWebhookOut", + "property": "estimated_time_of_arrival", + "reason": "TrackingComplete/TrackingPayment/TrackingTransaction/TrackingLiquidity in types.py type this field as a bare str; the wire constrains it to an enum. These sub-objects are shared verbatim across every Payin/Payout GET response and webhook envelope schema, so this same gap repeats once per host schema. Needs a human to design the right domain Literal(s) for the tracking sub-object family -- not a mechanical single-field patch, and not safe to guess at from one call site alone.", + "owner": "eric@blindpay.com", + "path": "tracking_payment" + }, + { + "kind": "enum_coverage", + "schema": "PayoutPartnerFeeWebhookOut", + "property": "provider_name", + "reason": "TrackingComplete/TrackingPayment/TrackingTransaction/TrackingLiquidity in types.py type this field as a bare str; the wire constrains it to an enum. These sub-objects are shared verbatim across every Payin/Payout GET response and webhook envelope schema, so this same gap repeats once per host schema. Needs a human to design the right domain Literal(s) for the tracking sub-object family -- not a mechanical single-field patch, and not safe to guess at from one call site alone.", + "owner": "eric@blindpay.com", + "path": "tracking_payment" + }, + { + "kind": "enum_coverage", + "schema": "PayoutPartnerFeeWebhookOut", + "property": "provider_status", + "reason": "TrackingComplete/TrackingPayment/TrackingTransaction/TrackingLiquidity in types.py type this field as a bare str; the wire constrains it to an enum. These sub-objects are shared verbatim across every Payin/Payout GET response and webhook envelope schema, so this same gap repeats once per host schema. Needs a human to design the right domain Literal(s) for the tracking sub-object family -- not a mechanical single-field patch, and not safe to guess at from one call site alone.", + "owner": "eric@blindpay.com", + "path": "tracking_payment" + }, + { + "kind": "enum_coverage", + "schema": "PayoutPartnerFeeWebhookOut", + "property": "step", + "reason": "TrackingComplete/TrackingPayment/TrackingTransaction/TrackingLiquidity in types.py type this field as a bare str; the wire constrains it to an enum. These sub-objects are shared verbatim across every Payin/Payout GET response and webhook envelope schema, so this same gap repeats once per host schema. Needs a human to design the right domain Literal(s) for the tracking sub-object family -- not a mechanical single-field patch, and not safe to guess at from one call site alone.", + "owner": "eric@blindpay.com", + "path": "tracking_payment" + }, + { + "kind": "enum_coverage", + "schema": "PayoutPartnerFeeWebhookOut", + "property": "status", + "reason": "TrackingComplete/TrackingPayment/TrackingTransaction/TrackingLiquidity in types.py type this field as a bare str; the wire constrains it to an enum. These sub-objects are shared verbatim across every Payin/Payout GET response and webhook envelope schema, so this same gap repeats once per host schema. Needs a human to design the right domain Literal(s) for the tracking sub-object family -- not a mechanical single-field patch, and not safe to guess at from one call site alone.", + "owner": "eric@blindpay.com", + "path": "tracking_transaction" + }, + { + "kind": "enum_coverage", + "schema": "PayoutPartnerFeeWebhookOut", + "property": "step", + "reason": "TrackingComplete/TrackingPayment/TrackingTransaction/TrackingLiquidity in types.py type this field as a bare str; the wire constrains it to an enum. These sub-objects are shared verbatim across every Payin/Payout GET response and webhook envelope schema, so this same gap repeats once per host schema. Needs a human to design the right domain Literal(s) for the tracking sub-object family -- not a mechanical single-field patch, and not safe to guess at from one call site alone.", + "owner": "eric@blindpay.com", + "path": "tracking_transaction" + }, + { + "kind": "enum_coverage", + "schema": "PayoutUpdateWebhookOut", + "property": "status", + "reason": "TrackingComplete/TrackingPayment/TrackingTransaction/TrackingLiquidity in types.py type this field as a bare str; the wire constrains it to an enum. These sub-objects are shared verbatim across every Payin/Payout GET response and webhook envelope schema, so this same gap repeats once per host schema. Needs a human to design the right domain Literal(s) for the tracking sub-object family -- not a mechanical single-field patch, and not safe to guess at from one call site alone.", + "owner": "eric@blindpay.com", + "path": "tracking_complete" + }, + { + "kind": "enum_coverage", + "schema": "PayoutUpdateWebhookOut", + "property": "step", + "reason": "TrackingComplete/TrackingPayment/TrackingTransaction/TrackingLiquidity in types.py type this field as a bare str; the wire constrains it to an enum. These sub-objects are shared verbatim across every Payin/Payout GET response and webhook envelope schema, so this same gap repeats once per host schema. Needs a human to design the right domain Literal(s) for the tracking sub-object family -- not a mechanical single-field patch, and not safe to guess at from one call site alone.", + "owner": "eric@blindpay.com", + "path": "tracking_complete" + }, + { + "kind": "enum_coverage", + "schema": "PayoutUpdateWebhookOut", + "property": "estimated_time_of_arrival", + "reason": "TrackingComplete/TrackingPayment/TrackingTransaction/TrackingLiquidity in types.py type this field as a bare str; the wire constrains it to an enum. These sub-objects are shared verbatim across every Payin/Payout GET response and webhook envelope schema, so this same gap repeats once per host schema. Needs a human to design the right domain Literal(s) for the tracking sub-object family -- not a mechanical single-field patch, and not safe to guess at from one call site alone.", + "owner": "eric@blindpay.com", + "path": "tracking_liquidity" + }, + { + "kind": "enum_coverage", + "schema": "PayoutUpdateWebhookOut", + "property": "provider_status", + "reason": "TrackingComplete/TrackingPayment/TrackingTransaction/TrackingLiquidity in types.py type this field as a bare str; the wire constrains it to an enum. These sub-objects are shared verbatim across every Payin/Payout GET response and webhook envelope schema, so this same gap repeats once per host schema. Needs a human to design the right domain Literal(s) for the tracking sub-object family -- not a mechanical single-field patch, and not safe to guess at from one call site alone.", + "owner": "eric@blindpay.com", + "path": "tracking_liquidity" + }, + { + "kind": "enum_coverage", + "schema": "PayoutUpdateWebhookOut", + "property": "step", + "reason": "TrackingComplete/TrackingPayment/TrackingTransaction/TrackingLiquidity in types.py type this field as a bare str; the wire constrains it to an enum. These sub-objects are shared verbatim across every Payin/Payout GET response and webhook envelope schema, so this same gap repeats once per host schema. Needs a human to design the right domain Literal(s) for the tracking sub-object family -- not a mechanical single-field patch, and not safe to guess at from one call site alone.", + "owner": "eric@blindpay.com", + "path": "tracking_liquidity" + }, + { + "kind": "enum_coverage", + "schema": "PayoutUpdateWebhookOut", + "property": "estimated_time_of_arrival", + "reason": "TrackingComplete/TrackingPayment/TrackingTransaction/TrackingLiquidity in types.py type this field as a bare str; the wire constrains it to an enum. These sub-objects are shared verbatim across every Payin/Payout GET response and webhook envelope schema, so this same gap repeats once per host schema. Needs a human to design the right domain Literal(s) for the tracking sub-object family -- not a mechanical single-field patch, and not safe to guess at from one call site alone.", + "owner": "eric@blindpay.com", + "path": "tracking_payment" + }, + { + "kind": "enum_coverage", + "schema": "PayoutUpdateWebhookOut", + "property": "provider_name", + "reason": "TrackingComplete/TrackingPayment/TrackingTransaction/TrackingLiquidity in types.py type this field as a bare str; the wire constrains it to an enum. These sub-objects are shared verbatim across every Payin/Payout GET response and webhook envelope schema, so this same gap repeats once per host schema. Needs a human to design the right domain Literal(s) for the tracking sub-object family -- not a mechanical single-field patch, and not safe to guess at from one call site alone.", + "owner": "eric@blindpay.com", + "path": "tracking_payment" + }, + { + "kind": "enum_coverage", + "schema": "PayoutUpdateWebhookOut", + "property": "provider_status", + "reason": "TrackingComplete/TrackingPayment/TrackingTransaction/TrackingLiquidity in types.py type this field as a bare str; the wire constrains it to an enum. These sub-objects are shared verbatim across every Payin/Payout GET response and webhook envelope schema, so this same gap repeats once per host schema. Needs a human to design the right domain Literal(s) for the tracking sub-object family -- not a mechanical single-field patch, and not safe to guess at from one call site alone.", + "owner": "eric@blindpay.com", + "path": "tracking_payment" + }, + { + "kind": "enum_coverage", + "schema": "PayoutUpdateWebhookOut", + "property": "step", + "reason": "TrackingComplete/TrackingPayment/TrackingTransaction/TrackingLiquidity in types.py type this field as a bare str; the wire constrains it to an enum. These sub-objects are shared verbatim across every Payin/Payout GET response and webhook envelope schema, so this same gap repeats once per host schema. Needs a human to design the right domain Literal(s) for the tracking sub-object family -- not a mechanical single-field patch, and not safe to guess at from one call site alone.", + "owner": "eric@blindpay.com", + "path": "tracking_payment" + }, + { + "kind": "enum_coverage", + "schema": "PayoutUpdateWebhookOut", + "property": "status", + "reason": "TrackingComplete/TrackingPayment/TrackingTransaction/TrackingLiquidity in types.py type this field as a bare str; the wire constrains it to an enum. These sub-objects are shared verbatim across every Payin/Payout GET response and webhook envelope schema, so this same gap repeats once per host schema. Needs a human to design the right domain Literal(s) for the tracking sub-object family -- not a mechanical single-field patch, and not safe to guess at from one call site alone.", + "owner": "eric@blindpay.com", + "path": "tracking_transaction" + }, + { + "kind": "enum_coverage", + "schema": "PayoutUpdateWebhookOut", + "property": "step", + "reason": "TrackingComplete/TrackingPayment/TrackingTransaction/TrackingLiquidity in types.py type this field as a bare str; the wire constrains it to an enum. These sub-objects are shared verbatim across every Payin/Payout GET response and webhook envelope schema, so this same gap repeats once per host schema. Needs a human to design the right domain Literal(s) for the tracking sub-object family -- not a mechanical single-field patch, and not safe to guess at from one call site alone.", + "owner": "eric@blindpay.com", + "path": "tracking_transaction" + }, + { + "kind": "enum_coverage", + "schema": "TransferOut", + "property": "network", + "reason": "TransferOut.network is typed as a bare str even though the SDK already has a Network Literal (used elsewhere on the same schema for customer_network/sender token contexts). Needs a human to retype this field.", + "owner": "eric@blindpay.com" + }, + { + "kind": "enum_coverage", + "schema": "VirtualAccountOut", + "property": "kyc_status", + "reason": "VirtualAccountOut.kyc_status is typed as a bare str; the wire constrains it to an enum with no existing SDK Literal counterpart. Needs a human to design the right domain Literal.", + "owner": "eric@blindpay.com" + }, + { + "kind": "nested_object", + "schema": "BankAccountOut", + "path": "offramp_wallets.items", + "reason": "Found by the new nested-object coverage check: BankAccountOut#offramp_wallets.items is an inline object/array-item shape reachable under a mapped schema. It is already modeled by the OfframpWallet TypedDict, but that mapping is not registered as a specPath map entry, so this check cannot verify field parity. Needs a human pass to add the proper specPath map entry (or entries, if the shape recurs across multiple host schemas) rather than a blind mechanical add, since the right host TypedDict per specPath needs manual confirmation.", + "owner": "eric@blindpay.com" + }, + { + "kind": "nested_object", + "schema": "CreateCustomerIn", + "path": "owners.items", + "reason": "Found by the new nested-object coverage check: CreateCustomerIn#owners.items is an inline object/array-item shape reachable under a mapped schema. It is already modeled by the Owner TypedDict, but that mapping is not registered as a specPath map entry, so this check cannot verify field parity. Needs a human pass to add the proper specPath map entry (or entries, if the shape recurs across multiple host schemas) rather than a blind mechanical add, since the right host TypedDict per specPath needs manual confirmation.", + "owner": "eric@blindpay.com" + }, + { + "kind": "nested_object", + "schema": "CreatePayinOut", + "path": "blindpay_bank_details", + "reason": "Found by the new nested-object coverage check: CreatePayinOut#blindpay_bank_details is an inline object/array-item shape reachable under a mapped schema. It is already modeled by the BankDetails TypedDict, but that mapping is not registered as a specPath map entry, so this check cannot verify field parity. Needs a human pass to add the proper specPath map entry (or entries, if the shape recurs across multiple host schemas) rather than a blind mechanical add, since the right host TypedDict per specPath needs manual confirmation.", + "owner": "eric@blindpay.com" + }, + { + "kind": "nested_object", + "schema": "CreateTransferOut", + "path": "tracking_bridge_swap", + "reason": "Found by the new nested-object coverage check: CreateTransferOut#tracking_bridge_swap is an inline object/array-item shape reachable under a mapped schema. It is already modeled by the TransferTrackingStep TypedDict, but that mapping is not registered as a specPath map entry, so this check cannot verify field parity. Needs a human pass to add the proper specPath map entry (or entries, if the shape recurs across multiple host schemas) rather than a blind mechanical add, since the right host TypedDict per specPath needs manual confirmation.", + "owner": "eric@blindpay.com" + }, + { + "kind": "nested_object", + "schema": "CreateTransferOut", + "path": "tracking_complete", + "reason": "Found by the new nested-object coverage check: CreateTransferOut#tracking_complete is an inline object/array-item shape reachable under a mapped schema. It is already modeled inline on the Transfer/CreateTransferOut TypedDict, but that mapping is not registered as a specPath map entry, so this check cannot verify field parity. Needs a human pass to add the proper specPath map entry (or entries, if the shape recurs across multiple host schemas) rather than a blind mechanical add, since the right host TypedDict per specPath needs manual confirmation.", + "owner": "eric@blindpay.com" + }, + { + "kind": "nested_object", + "schema": "CreateTransferOut", + "path": "tracking_paymaster", + "reason": "Found by the new nested-object coverage check: CreateTransferOut#tracking_paymaster is an inline object/array-item shape reachable under a mapped schema. It is already modeled by the TransferTrackingStep TypedDict, but that mapping is not registered as a specPath map entry, so this check cannot verify field parity. Needs a human pass to add the proper specPath map entry (or entries, if the shape recurs across multiple host schemas) rather than a blind mechanical add, since the right host TypedDict per specPath needs manual confirmation.", + "owner": "eric@blindpay.com" + }, + { + "kind": "nested_object", + "schema": "CreateTransferOut", + "path": "tracking_transaction_monitoring", + "reason": "Found by the new nested-object coverage check: CreateTransferOut#tracking_transaction_monitoring is an inline object/array-item shape reachable under a mapped schema. It is already modeled by the TransferTrackingStep TypedDict, but that mapping is not registered as a specPath map entry, so this check cannot verify field parity. Needs a human pass to add the proper specPath map entry (or entries, if the shape recurs across multiple host schemas) rather than a blind mechanical add, since the right host TypedDict per specPath needs manual confirmation.", + "owner": "eric@blindpay.com" + }, + { + "kind": "nested_object", + "schema": "CustomerOut", + "path": "fraud_warnings.items", + "reason": "Found by the new nested-object coverage check: CustomerOut#fraud_warnings.items is an inline object/array-item shape reachable under a mapped schema. It is already modeled by the FraudWarning TypedDict, but that mapping is not registered as a specPath map entry, so this check cannot verify field parity. Needs a human pass to add the proper specPath map entry (or entries, if the shape recurs across multiple host schemas) rather than a blind mechanical add, since the right host TypedDict per specPath needs manual confirmation.", + "owner": "eric@blindpay.com" + }, + { + "kind": "nested_object", + "schema": "CustomerOut", + "path": "kyc_warnings.items", + "reason": "Found by the new nested-object coverage check: CustomerOut#kyc_warnings.items is an inline object/array-item shape reachable under a mapped schema. It is already modeled by the KycWarning TypedDict, but that mapping is not registered as a specPath map entry, so this check cannot verify field parity. Needs a human pass to add the proper specPath map entry (or entries, if the shape recurs across multiple host schemas) rather than a blind mechanical add, since the right host TypedDict per specPath needs manual confirmation.", + "owner": "eric@blindpay.com" + }, + { + "kind": "nested_object", + "schema": "CustomerOut", + "path": "limit", + "reason": "Found by the new nested-object coverage check: CustomerOut#limit is an inline object/array-item shape reachable under a mapped schema. It is already modeled by the TransactionLimit TypedDict, but that mapping is not registered as a specPath map entry, so this check cannot verify field parity. Needs a human pass to add the proper specPath map entry (or entries, if the shape recurs across multiple host schemas) rather than a blind mechanical add, since the right host TypedDict per specPath needs manual confirmation.", + "owner": "eric@blindpay.com" + }, + { + "kind": "nested_object", + "schema": "CustomerOut", + "path": "owners.items", + "reason": "Found by the new nested-object coverage check: CustomerOut#owners.items is an inline object/array-item shape reachable under a mapped schema. It is already modeled by the Owner TypedDict, but that mapping is not registered as a specPath map entry, so this check cannot verify field parity. Needs a human pass to add the proper specPath map entry (or entries, if the shape recurs across multiple host schemas) rather than a blind mechanical add, since the right host TypedDict per specPath needs manual confirmation.", + "owner": "eric@blindpay.com" + }, + { + "kind": "nested_object", + "schema": "GetCustomerLimitsOut", + "path": "limits", + "reason": "Found by the new nested-object coverage check: GetCustomerLimitsOut#limits is an inline object/array-item shape reachable under a mapped schema. It is already modeled by the Limits/CustomerLimits TypedDict, but that mapping is not registered as a specPath map entry, so this check cannot verify field parity. Needs a human pass to add the proper specPath map entry (or entries, if the shape recurs across multiple host schemas) rather than a blind mechanical add, since the right host TypedDict per specPath needs manual confirmation.", + "owner": "eric@blindpay.com" + }, + { + "kind": "nested_object", + "schema": "TransferOut", + "path": "tracking_bridge_swap", + "reason": "Found by the new nested-object coverage check: TransferOut#tracking_bridge_swap is an inline object/array-item shape reachable under a mapped schema. It is already modeled by the TransferTrackingStep TypedDict, but that mapping is not registered as a specPath map entry, so this check cannot verify field parity. Needs a human pass to add the proper specPath map entry (or entries, if the shape recurs across multiple host schemas) rather than a blind mechanical add, since the right host TypedDict per specPath needs manual confirmation.", + "owner": "eric@blindpay.com" + }, + { + "kind": "nested_object", + "schema": "TransferOut", + "path": "tracking_complete", + "reason": "Found by the new nested-object coverage check: TransferOut#tracking_complete is an inline object/array-item shape reachable under a mapped schema. It is already modeled inline on the Transfer TypedDict, but that mapping is not registered as a specPath map entry, so this check cannot verify field parity. Needs a human pass to add the proper specPath map entry (or entries, if the shape recurs across multiple host schemas) rather than a blind mechanical add, since the right host TypedDict per specPath needs manual confirmation.", + "owner": "eric@blindpay.com" + }, + { + "kind": "nested_object", + "schema": "TransferOut", + "path": "tracking_paymaster", + "reason": "Found by the new nested-object coverage check: TransferOut#tracking_paymaster is an inline object/array-item shape reachable under a mapped schema. It is already modeled by the TransferTrackingStep TypedDict, but that mapping is not registered as a specPath map entry, so this check cannot verify field parity. Needs a human pass to add the proper specPath map entry (or entries, if the shape recurs across multiple host schemas) rather than a blind mechanical add, since the right host TypedDict per specPath needs manual confirmation.", + "owner": "eric@blindpay.com" + }, + { + "kind": "nested_object", + "schema": "TransferOut", + "path": "tracking_transaction_monitoring", + "reason": "Found by the new nested-object coverage check: TransferOut#tracking_transaction_monitoring is an inline object/array-item shape reachable under a mapped schema. It is already modeled by the TransferTrackingStep TypedDict, but that mapping is not registered as a specPath map entry, so this check cannot verify field parity. Needs a human pass to add the proper specPath map entry (or entries, if the shape recurs across multiple host schemas) rather than a blind mechanical add, since the right host TypedDict per specPath needs manual confirmation.", + "owner": "eric@blindpay.com" + }, + { + "kind": "nested_object", + "schema": "UpdateCustomerIn", + "path": "owners.items", + "reason": "Found by the new nested-object coverage check: UpdateCustomerIn#owners.items is an inline object/array-item shape reachable under a mapped schema. It is already modeled by the OwnerUpdate TypedDict, but that mapping is not registered as a specPath map entry, so this check cannot verify field parity. Needs a human pass to add the proper specPath map entry (or entries, if the shape recurs across multiple host schemas) rather than a blind mechanical add, since the right host TypedDict per specPath needs manual confirmation.", + "owner": "eric@blindpay.com" + }, + { + "kind": "nested_object", + "schema": "VirtualAccountOut", + "path": "us", + "reason": "Found by the new nested-object coverage check: VirtualAccountOut#us is an inline object/array-item shape reachable under a mapped schema. It is already modeled by the USBankDetails TypedDict, but that mapping is not registered as a specPath map entry, so this check cannot verify field parity. Needs a human pass to add the proper specPath map entry (or entries, if the shape recurs across multiple host schemas) rather than a blind mechanical add, since the right host TypedDict per specPath needs manual confirmation.", + "owner": "eric@blindpay.com" } ] diff --git a/.github/workflows/api-sync.yml b/.github/workflows/api-sync.yml index 9af1525..e046308 100644 --- a/.github/workflows/api-sync.yml +++ b/.github/workflows/api-sync.yml @@ -21,10 +21,54 @@ jobs: with: python-version: "3.12" - - name: Fetch the newly delivered spec from api-sync-data + - name: Fetch the newly delivered spec + manifest from api-sync-data run: | git fetch origin api-sync-data git show origin/api-sync-data:.api-sync/spec-current.json > .api-sync/spec-current.json + git show origin/api-sync-data:.api-sync/delivery.json > /tmp/delivery.json + + - name: Verify delivery manifest + env: + DISPATCH_SPEC_SHA256: ${{ github.event.client_payload.spec_sha256 }} + run: | + set -euo pipefail + + if [ ! -s .api-sync/spec-current.json ]; then + echo "::error::spec-current.json is missing or empty on api-sync-data. Aborting." + exit 1 + fi + if [ ! -s /tmp/delivery.json ]; then + echo "::error::delivery.json is missing or empty on api-sync-data. Aborting." + exit 1 + fi + + MANIFEST_SHA256=$(jq -r '.spec_sha256 // empty' /tmp/delivery.json) + MANIFEST_SOURCE_REPO=$(jq -r '.source_repository // empty' /tmp/delivery.json) + MANIFEST_SOURCE_SHA=$(jq -r '.source_sha // empty' /tmp/delivery.json) + MANIFEST_GENERATED_AT=$(jq -r '.generated_at // empty' /tmp/delivery.json) + + if [ -z "$MANIFEST_SHA256" ] || [ -z "$MANIFEST_SOURCE_REPO" ] || [ -z "$MANIFEST_SOURCE_SHA" ] || [ -z "$MANIFEST_GENERATED_AT" ]; then + echo "::error::delivery.json is missing one or more required fields (source_repository, source_sha, spec_sha256, generated_at)." + exit 1 + fi + + if [ "$MANIFEST_SOURCE_REPO" != "blindpaylabs/blindpay-v2" ]; then + echo "::error::delivery.json source_repository is '$MANIFEST_SOURCE_REPO', expected 'blindpaylabs/blindpay-v2'." + exit 1 + fi + + ACTUAL_SHA256=$(sha256sum .api-sync/spec-current.json | cut -d' ' -f1) + if [ "$ACTUAL_SHA256" != "$MANIFEST_SHA256" ]; then + echo "::error::spec-current.json sha256 ($ACTUAL_SHA256) does not match delivery.json's spec_sha256 ($MANIFEST_SHA256). The delivered spec is corrupt or does not match its manifest." + exit 1 + fi + + if [ -n "${DISPATCH_SPEC_SHA256:-}" ] && [ "$DISPATCH_SPEC_SHA256" != "$MANIFEST_SHA256" ]; then + echo "::error::Stale delivery: the repository_dispatch payload's spec_sha256 ($DISPATCH_SPEC_SHA256) does not match the manifest currently on api-sync-data ($MANIFEST_SHA256). A newer delivery has landed since this dispatch fired -- re-trigger." + exit 1 + fi + + echo "Delivery manifest verified: source_sha=$MANIFEST_SOURCE_SHA generated_at=$MANIFEST_GENERATED_AT spec_sha256=$MANIFEST_SHA256" - name: Create or reset the api-sync branch from main run: | diff --git a/tests/test_api_sync.py b/tests/test_api_sync.py index e97a3a5..9e55471 100644 --- a/tests/test_api_sync.py +++ b/tests/test_api_sync.py @@ -443,6 +443,264 @@ def test_nested_specpath_sub_object_reconciled_independently_of_root(self, repo: assert "unrelated_top_level_field" not in gaps[0].missing +# --------------------------------------------------------------------------- # +# Enum locator detection (bare, items, anyOf/oneOf wrapper) +# --------------------------------------------------------------------------- # + + +class TestFindEnumLocator: + def test_bare_enum(self): + sync = load_sync(Path("/nonexistent")) + assert sync.find_enum_locator({"type": "string", "enum": ["a", "b"]}) == (False, ["a", "b"]) + + def test_items_enum(self): + sync = load_sync(Path("/nonexistent")) + prop = {"type": "array", "items": {"type": "string", "enum": ["a", "b"]}} + assert sync.find_enum_locator(prop) == (True, ["a", "b"]) + + def test_anyof_wrapped_enum(self): + sync = load_sync(Path("/nonexistent")) + prop = {"anyOf": [{"type": "string", "enum": ["a", "b"]}, {"type": "null"}]} + assert sync.find_enum_locator(prop) == (False, ["a", "b"]) + + def test_anyof_wrapped_items_enum(self): + sync = load_sync(Path("/nonexistent")) + prop = {"anyOf": [{"type": "array", "items": {"enum": ["a", "b"]}}, {"type": "null"}]} + assert sync.find_enum_locator(prop) == (True, ["a", "b"]) + + def test_no_enum_returns_none(self): + sync = load_sync(Path("/nonexistent")) + assert sync.find_enum_locator({"type": "string"}) is None + + +# --------------------------------------------------------------------------- # +# Enum coverage: every enum-constrained property on a mapped schema must +# resolve to a mapped Literal or a recorded exclusion. +# --------------------------------------------------------------------------- # + + +class TestEnumCoverage: + def test_gap_detected_for_unmapped_enum_property(self, repo: Path): + _write_fixture_repo(repo) + map_data: dict[str, Any] = { + "enums": [], + "types": [{"spec": "PlainOut", "sdk": [{"file": "src/blindpay/resources/sample.py", "symbol": "Plain"}]}], + "ignore": {"schemas": []}, + } + _write_map_and_unmodeled(repo, map_data) + sync = load_sync(repo) + index = sync.build_sdk_index(sync.SRC_ROOT) + spec = base_schema( + { + "PlainOut": { + "properties": { + "id": {"type": "string"}, + "name": {"type": "string", "enum": ["red", "blue"]}, + } + } + }, + {"/x": {"get": op(ref_out="PlainOut")}}, + ) + gaps = sync.reconcile_enum_coverage(spec, map_data, [], index) + assert len(gaps) == 1 + assert (gaps[0].schema, gaps[0].path, gaps[0].property) == ("PlainOut", None, "name") + + def test_gap_suppressed_when_property_itself_is_unmapped(self, repo: Path): + """A property absent from the SDK entirely is reconcile_types's concern, + not this check's -- it must not double-report the same gap.""" + _write_fixture_repo(repo) + map_data: dict[str, Any] = { + "enums": [], + "types": [{"spec": "PlainOut", "sdk": [{"file": "src/blindpay/resources/sample.py", "symbol": "Plain"}]}], + "ignore": {"schemas": []}, + } + _write_map_and_unmodeled(repo, map_data) + sync = load_sync(repo) + index = sync.build_sdk_index(sync.SRC_ROOT) + spec = base_schema( + { + "PlainOut": { + "properties": { + "id": {"type": "string"}, + "not_on_sdk": {"type": "string", "enum": ["red", "blue"]}, + } + } + }, + {"/x": {"get": op(ref_out="PlainOut")}}, + ) + gaps = sync.reconcile_enum_coverage(spec, map_data, [], index) + assert gaps == [] + + def test_gap_suppressed_by_enums_map_entry(self, repo: Path): + _write_fixture_repo(repo) + map_data: dict[str, Any] = { + "enums": [ + { + "spec": {"schema": "PlainOut", "property": "name"}, + "sdk": {"file": "src/blindpay/types.py", "symbol": "Color"}, + } + ], + "types": [{"spec": "PlainOut", "sdk": [{"file": "src/blindpay/resources/sample.py", "symbol": "Plain"}]}], + "ignore": {"schemas": []}, + } + _write_map_and_unmodeled(repo, map_data) + sync = load_sync(repo) + index = sync.build_sdk_index(sync.SRC_ROOT) + spec = base_schema( + { + "PlainOut": { + "properties": { + "id": {"type": "string"}, + "name": {"type": "string", "enum": ["red", "blue"]}, + } + } + }, + {"/x": {"get": op(ref_out="PlainOut")}}, + ) + gaps = sync.reconcile_enum_coverage(spec, map_data, [], index) + assert gaps == [] + + def test_gap_suppressed_by_unmodeled_enum_coverage_entry(self, repo: Path): + _write_fixture_repo(repo) + map_data: dict[str, Any] = { + "enums": [], + "types": [{"spec": "PlainOut", "sdk": [{"file": "src/blindpay/resources/sample.py", "symbol": "Plain"}]}], + "ignore": {"schemas": []}, + } + unmodeled = [ + {"kind": "enum_coverage", "schema": "PlainOut", "property": "name", "reason": "test", "owner": "x"} + ] + _write_map_and_unmodeled(repo, map_data, unmodeled) + sync = load_sync(repo) + index = sync.build_sdk_index(sync.SRC_ROOT) + spec = base_schema( + { + "PlainOut": { + "properties": { + "id": {"type": "string"}, + "name": {"type": "string", "enum": ["red", "blue"]}, + } + } + }, + {"/x": {"get": op(ref_out="PlainOut")}}, + ) + gaps = sync.reconcile_enum_coverage(spec, map_data, unmodeled, index) + assert gaps == [] + + +# --------------------------------------------------------------------------- # +# Nested-object coverage: inline object/array-item shapes under a mapped +# schema must themselves have a map entry or a recorded omission. +# --------------------------------------------------------------------------- # + + +class TestNestedObjectCoverage: + def test_gap_detected_for_unmapped_inline_object(self, repo: Path): + _write_fixture_repo(repo) + map_data: dict[str, Any] = { + "enums": [], + "types": [{"spec": "PlainOut", "sdk": [{"file": "src/blindpay/resources/sample.py", "symbol": "Plain"}]}], + "ignore": {"schemas": []}, + } + _write_map_and_unmodeled(repo, map_data) + sync = load_sync(repo) + spec = base_schema( + { + "PlainOut": { + "properties": { + "id": {"type": "string"}, + "meta": {"type": "object", "properties": {"note": {"type": "string"}}}, + } + } + }, + {"/x": {"get": op(ref_out="PlainOut")}}, + ) + gaps = sync.reconcile_nested_coverage(spec, map_data, []) + assert len(gaps) == 1 + assert (gaps[0].schema, gaps[0].path) == ("PlainOut", "meta") + + def test_gap_detected_for_inline_array_item_object(self, repo: Path): + _write_fixture_repo(repo) + map_data: dict[str, Any] = { + "enums": [], + "types": [{"spec": "PlainOut", "sdk": [{"file": "src/blindpay/resources/sample.py", "symbol": "Plain"}]}], + "ignore": {"schemas": []}, + } + _write_map_and_unmodeled(repo, map_data) + sync = load_sync(repo) + spec = base_schema( + { + "PlainOut": { + "properties": { + "id": {"type": "string"}, + "items_list": { + "type": "array", + "items": {"type": "object", "properties": {"note": {"type": "string"}}}, + }, + } + } + }, + {"/x": {"get": op(ref_out="PlainOut")}}, + ) + gaps = sync.reconcile_nested_coverage(spec, map_data, []) + assert len(gaps) == 1 + assert (gaps[0].schema, gaps[0].path) == ("PlainOut", "items_list.items") + + def test_gap_suppressed_when_nested_shape_has_its_own_map_entry(self, repo: Path): + _write_fixture_repo(repo) + map_data: dict[str, Any] = { + "enums": [], + "types": [ + {"spec": "PlainOut", "sdk": [{"file": "src/blindpay/resources/sample.py", "symbol": "Plain"}]}, + { + "spec": ["PlainOut"], + "specPath": "meta", + "sdk": [{"file": "src/blindpay/resources/sample.py", "symbol": "Plain"}], + }, + ], + "ignore": {"schemas": []}, + } + _write_map_and_unmodeled(repo, map_data) + sync = load_sync(repo) + spec = base_schema( + { + "PlainOut": { + "properties": { + "id": {"type": "string"}, + "meta": {"type": "object", "properties": {"note": {"type": "string"}}}, + } + } + }, + {"/x": {"get": op(ref_out="PlainOut")}}, + ) + gaps = sync.reconcile_nested_coverage(spec, map_data, []) + assert gaps == [] + + def test_gap_suppressed_by_unmodeled_nested_object_entry(self, repo: Path): + _write_fixture_repo(repo) + map_data: dict[str, Any] = { + "enums": [], + "types": [{"spec": "PlainOut", "sdk": [{"file": "src/blindpay/resources/sample.py", "symbol": "Plain"}]}], + "ignore": {"schemas": []}, + } + unmodeled = [{"kind": "nested_object", "schema": "PlainOut", "path": "meta", "reason": "test", "owner": "x"}] + _write_map_and_unmodeled(repo, map_data, unmodeled) + sync = load_sync(repo) + spec = base_schema( + { + "PlainOut": { + "properties": { + "id": {"type": "string"}, + "meta": {"type": "object", "properties": {"note": {"type": "string"}}}, + } + } + }, + {"/x": {"get": op(ref_out="PlainOut")}}, + ) + gaps = sync.reconcile_nested_coverage(spec, map_data, unmodeled) + assert gaps == [] + + # --------------------------------------------------------------------------- # # Map validity # --------------------------------------------------------------------------- # @@ -948,6 +1206,18 @@ def test_unknown_kind_rejected(self, repo: Path): with pytest.raises(SystemExit): sync.load_unmodeled() + def test_enum_coverage_entry_requires_all_keys(self, repo: Path): + write(repo, ".api-sync/unmodeled.json", json.dumps([{"kind": "enum_coverage", "schema": "X"}])) + sync = load_sync(repo) + with pytest.raises(SystemExit): + sync.load_unmodeled() + + def test_nested_object_entry_requires_all_keys(self, repo: Path): + write(repo, ".api-sync/unmodeled.json", json.dumps([{"kind": "nested_object", "schema": "X"}])) + sync = load_sync(repo) + with pytest.raises(SystemExit): + sync.load_unmodeled() + def test_valid_entries_load(self, repo: Path): write( repo, @@ -956,11 +1226,25 @@ def test_valid_entries_load(self, repo: Path): [ {"kind": "property", "schema": "X", "field": "y", "reason": "r", "owner": "eric@blindpay.com"}, {"kind": "enum", "enum": "X", "missing_values": ["a"], "reason": "r", "owner": "eric@blindpay.com"}, + { + "kind": "enum_coverage", + "schema": "X", + "property": "y", + "reason": "r", + "owner": "eric@blindpay.com", + }, + { + "kind": "nested_object", + "schema": "X", + "path": "y", + "reason": "r", + "owner": "eric@blindpay.com", + }, ] ), ) sync = load_sync(repo) - assert len(sync.load_unmodeled()) == 2 + assert len(sync.load_unmodeled()) == 4 # --------------------------------------------------------------------------- #