* fix(enable-banking): connector-hop failures no longer park a connection in error
On 2026-09-04 the daily cron flipped four canary companies (bank sync routed
through Accounted Connect, PR #2205) to 'error' with "Banksynkningen
misslyckades ... forny anslutningen" because the service answered 200 with a
body that fails bankSyncResponseSchema. The PSD2 sessions were fine; users
re-authorized Danske, SEB and Revolut for nothing, and the bare "unexpected
shape" message left the contract mismatch undiagnosable.
- New ConnectorSyncError (status, code, body, Zod issue paths) thrown for
every connector-hop failure: transport, timeout, error envelope other than
a dead session, and wire-contract mismatch. The failing field paths are
logged at the throw site.
- Cron: AspspUnavailableError and ConnectorSyncError are transient. The row
is left untouched (no status flip, no renewal advice) and logged at warn
level; the health probe on the same run still catches a dead session.
- Manual sync (POST /sync) and agent sync (triggerConnectionSync) answer
retryable with CONNECTOR_UNAVAILABLE_MESSAGE, which says explicitly that
the connection does not need renewing.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MSet8McjShABUeoATNnh9L
* fix(enable-banking): keep the connector response body out of the sync log line
Superagent P2 and CodeRabbit: POST /sync logged up to 500 chars of the raw
connector body next to user and connection ids. A connector response can
carry transaction and personal data, so the log line now carries only code,
status, the Zod issue paths and the body length. The BAD_SHAPE message no
longer falls back to the body either.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MSet8McjShABUeoATNnh9L
---------
Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
* fix(enable-banking): recover error-state connections, respect PSD2 balance quota, clean error surface
Three defects from the 2026-07-09 production log triage, all in how the
enable-banking extension handles upstream (Enable Banking / ASPSP) failures:
1. Retry dead-end: a non-session sync failure parked the connection in
status='error', but POST /sync rejected anything not 'active' with 400,
so the UI's "Försök igen" button could never succeed and the connection
stayed stranded until a full re-auth. /sync now accepts 'error' (while
still rejecting 'expired': a dead consent needs re-authorization), and a
successful sync restores status='active' and clears error_message.
2. Balance quota burn: every sync (manual or cron) called the BALANCES
endpoint although PSD2 unattended consents allow only 4 calls/day
(observed 429 "Consent daily limit 4 is exceeded"), and the retry
wrapper retried those 429s twice against a daily quota. The sync now
skips the balance call while the stored balance_updated_at is fresher
than 12 hours, and authenticatedFetchWithRetry fails fast on a 429
whose body signals a daily limit.
3. Raw JSON in UI: sync failures persisted the raw English Enable Banking
error body into bank_connections.error_message, which the settings
panel renders verbatim. Failures are now mapped to short Swedish user
messages (shared constants in api-client.ts); the raw body stays in
server logs only.
Also ratchets the eslint baseline down by 1: the no-explicit-any disable
in the cron route was on the wrong line and never suppressed anything.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(enable-banking): treat future balance timestamps as stale (CodeRabbit)
A future balance_updated_at yielded a negative age that always passed the freshness check, suppressing balance refreshes indefinitely; only 0 <= age < BALANCE_MAX_AGE_MS now counts as fresh.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>