Disconnecting a bank left its cash_accounts rows pointing at the revoked
connection, so the BAS slot (e.g. 1930) looked taken forever: reconnecting
the same bank was shunted to 1939 and the picker save was rejected with a
400 the user never saw. Four coordinated fixes:
- DELETE /disconnect now demotes the connection's cash_accounts rows to
manual (bank_connection_id = null) after marking the connection revoked.
Rows are never deleted: transactions.cash_account_id and ledger history
reference them, and upsertFromPsd2 promotes manual holders in place on
reconnect.
- findFreeLedgerAccount and the PATCH /accounts collision guard no longer
count claims held by revoked connections (new getRevokedConnectionIds
helper). This is the self-heal path for rows orphaned before this fix:
no manual data repair needed.
- upsertFromPsd2 promotes a holder row owned by a revoked connection in
place (same as the manual seed row), keeping the row id stable so the
ledger's transaction history stays attached. A duplicate row for the
same connection+uid on an overflow slot (mirrored there by the callback
while the slot was wrongly blocked) is merged: deleted when it has no
linked transactions, demoted to manual otherwise. Either way a primary
duplicate hands the flag to the promoted row, so the __PRIMARY_SEK__
sentinel never resolves to a deleted or stale manual row. The
linked-transactions probe is company-scoped (defense in depth on the
service-role client).
- AccountPickerDialog surfaces rejected saves inline in the picker with
the picks intact instead of routing them into the sync-progress modal.
It also stops signaling the parent to close before the request resolves:
the parent unmounts the whole component on close, which tore down the
progress modal mid-flight and made every save outcome (including the
400) invisible.
Tests: allocator revoked-exclusion + promote/merge unit tests in
lib/cash-accounts, PATCH self-heal case in accounts-route.test.ts, and a
new disconnect-route.test.ts covering claim release and its failure mode.
Fixes#916
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>