The backstop arm: a ledger_account_withdrawal that no other arm represents.
Four producers write this table, and three of them are already on
transaction_view through some other arm:
the blockchain and self-custody withdrawal activities set
withdrawal_id, and the withdrawal arm's own wd_ row IS that movement;
a reward claim's payout is the reward arm's CLEARING sub-arm (and its
RETURN when voided), joined on idempotency_key = reward_claim.external_id.
The fourth has no representation at all: rows minted with neither a parent
withdrawal nor a claim, by Interval's "Create New Ledger Account Withdrawal"
and by the admin dashboard's own createLedgerAccountWithdrawal server action.
Those reach no view, so they are invisible on /transactions and
unsearchable by their redwd_ id — real money out with no read-model row.
Hence more than one exclusion. Guarding only on the parent withdrawal would
emit a SECOND row for every reward payout: those rows carry no withdrawalId
either, so they satisfy that guard, and the reward arm has already emitted
them under a rew_ id. Different keys, so nothing would dedupe the pair.
Both parent guards test the row itself (withdrawal_id IS NULL, data ? 'rewardClaimId'), backed by a partial index carrying the same predicate, so
ownership needs no join. It replaced reading ownership off idempotency_key, whose value is
the result of a precedence rule rather than a declared relationship.
The third exclusion, the reward_claim anti-join, is the ROLLOUT guard
rather than part of the contract. A payout created between the migration
applying and the writer shipping carries neither key, and would otherwise be
double-counted against the reward arm forever. It is retirable once no such
row can exist.
One row per withdrawal, not two. A VOIDED row reads RETURNED on its own
debit rather than growing a paired credit — this arm exists so the movement
is visible and searchable, and the rows it covers are operator-initiated
one-offs rather than a rail with a settlement lifecycle to reconstruct.
securityInvoker is required, not cosmetic: every underlying table has RLS
enabled, and without it the view runs with the owner's row visibility and
silently bypasses the querying role's policies.
The backstop arm: a
ledger_account_withdrawalthat no other arm represents.Four producers write this table, and three of them are already on
transaction_viewthrough some other arm:withdrawal_id, and the withdrawal arm's ownwd_row IS that movement;CLEARINGsub-arm (and itsRETURNwhen voided), joined onidempotency_key = reward_claim.external_id.The fourth has no representation at all: rows minted with neither a parent withdrawal nor a claim, by Interval's "Create New Ledger Account Withdrawal" and by the admin dashboard's own
createLedgerAccountWithdrawalserver action. Those reach no view, so they are invisible on/transactionsand unsearchable by theirredwd_id — real money out with no read-model row.Hence more than one exclusion. Guarding only on the parent withdrawal would emit a SECOND row for every reward payout: those rows carry no
withdrawalIdeither, so they satisfy that guard, and the reward arm has already emitted them under arew_id. Differentkeys, so nothing would dedupe the pair.Both parent guards test the row itself (
withdrawal_id IS NULL,data ? 'rewardClaimId'), backed by a partial index carrying the same predicate, so ownership needs no join. It replaced reading ownership offidempotency_key, whose value is the result of a precedence rule rather than a declared relationship.The third exclusion, the
reward_claimanti-join, is the ROLLOUT guard rather than part of the contract. A payout created between the migration applying and the writer shipping carries neither key, and would otherwise be double-counted against the reward arm forever. It is retirable once no such row can exist.One row per withdrawal, not two. A VOIDED row reads
RETURNEDon its own debit rather than growing a paired credit — this arm exists so the movement is visible and searchable, and the rows it covers are operator-initiated one-offs rather than a rail with a settlement lifecycle to reconstruct.securityInvokeris required, not cosmetic: every underlying table has RLS enabled, and without it the view runs with the owner's row visibility and silently bypasses the querying role's policies.