The cross-customer transfer arm (method = TRANSFER_NETWORK) of the
transaction search, as a view.
Same motivation as depositTransactionView (see schema/deposit.ts): the
inline 12-arm UNION serialises past the RDS Proxy 16KB parse limit and pins
connections; behind a view the runtime statement is a short SELECT.
Source and target wallets are owned by DIFFERENT customers — the
ws.customer_id <> wt.customer_id predicate in the t CTE is business
logic, not a caller filter: it is what splits this arm from the
intra-customer TRANSFER arm, whose predicate is the complement. It stays.
Two sub-arms, one per side:
DEBIT — audience: the source customer. Counterparty is the target CUSTOMER.
CREDIT — audience: the target customer. Mirrored.
Filtering is entirely the shared transactionScalarConditions, applied on
this view's own projected columns because a view takes no parameters.
customer_id is what scopes each side: it is source_customer_external_id on
the debit arm and target_customer_external_id on the credit arm, so neither
side of the transfer can see the other's row. Both arms project the same
receipt ->> 'id' as signature.
bankIds never reaches this arm — buildTransactionSelects skips it when
bankIds is set, since a network transfer never touches a bank rail (bank_id
/ bank_transaction_id are unconditionally NULL).
securityInvoker is required: every underlying table has RLS enabled, and
without it the view would run with the owner's row visibility.
The cross-customer transfer arm (method =
TRANSFER_NETWORK) of the transaction search, as a view.Same motivation as
depositTransactionView(seeschema/deposit.ts): the inline 12-arm UNION serialises past the RDS Proxy 16KB parse limit and pins connections; behind a view the runtime statement is a short SELECT.Source and target wallets are owned by DIFFERENT customers — the
ws.customer_id <> wt.customer_idpredicate in thetCTE is business logic, not a caller filter: it is what splits this arm from the intra-customerTRANSFERarm, whose predicate is the complement. It stays.Two sub-arms, one per side:
Filtering is entirely the shared
transactionScalarConditions, applied on this view's own projected columns because a view takes no parameters.customer_idis what scopes each side: it issource_customer_external_idon the debit arm andtarget_customer_external_idon the credit arm, so neither side of the transfer can see the other's row. Both arms project the samereceipt ->> 'id'assignature.bankIdsnever reaches this arm —buildTransactionSelectsskips it whenbankIdsis set, since a network transfer never touches a bank rail (bank_id / bank_transaction_id are unconditionally NULL).securityInvokeris required: every underlying table has RLS enabled, and without it the view would run with the owner's row visibility.