Address review feedback:
- store() now maps the failure to the right message (invalid credentials vs
rate-limited vs statement-still-generating) instead of always reporting
'invalid token or query ID'.
- Add connect/read timeouts to the Flex HTTP calls so a slow IB can't hang the
synchronous connect past the gateway timeout.
- Classify an invalid/deleted Flex Query ID as an auth failure so the user is
prompted to fix credentials instead of retrying 'temporarily unavailable'
forever.
- Skip NAV rows without a total so a malformed row can't store a 0 balance.
- connections.tsx isApiKeyProvider derives from CONNECT_PROVIDERS instead of a
hardcoded list; drop an orphaned es.json key.
Each provider's credential inputs (request field, encrypted column, validation
rules) now live once in BankingProvider::credentialFields(). The update request
rules, the update controller's column mapping, and every connect controller
derive from it via credentialColumns(), so adding a provider no longer means
editing parallel match arms in several files. Client validation stays a match
(behavior dispatch, like the syncer factory).
Connect/update-credentials endpoints validate the Flex token + query ID by
pulling a statement, then build pending accounts from it. Gated by a Pennant
feature (InteractiveBrokers, off by default) so it can be enabled per beta
tester until validated against a live account.
Credentials reuse the encrypted api_token (Flex token) and api_secret (Flex
query ID) columns, so no migration is needed.