feat(bank): wire Bank Frick custody assets into equity, liquidity and accounting#4252
Merged
Conversation
… accounting Bank Frick was active as a bank rail but had no linked custody Asset, so its cash was invisible to equity/FinanceLog and the safety kill-switch, its balance was never refreshed by liquidity management, its bank_tx booked to SUSPENSE, and pending-input-amount matching returned zero for Frick credits. Add a Blockchain.FRICK member and two dedicated Frick custody Assets (EUR/CHF), linked to the active Frick bank rows via bank.assetId, and register observe-only liquidity-management rules (context 'Bank Frick') so the existing per-bank balance adapter refreshes Frick balances without ever triggering a fund-moving action. Add a Frick arm to every blockchain switch/exhaustive map that previously listed only Olkypay/Yapeal (pendingInputAmount, blockchainToBankName, BANK_BLOCKCHAINS, the EUR-bank log aggregation, and the exhaustive Blockchain maps). The data migration is prod-only and idempotent (local/dev obtain the Frick custody assets from the seed). It fails loud if a required price source is missing or if an active Frick bank row is left unlinked. Customer-facing deposit routing and Frick's fallback sendPriority are unchanged.
…ne migration docs Bank Frick was added to the EUR-bank asset filter but not to the EUR-bank IBAN set that gates the Scrypt reconciliation, so a Frick<->Scrypt EUR transfer was zeroed on the Frick asset row and never picked up on the aggregate row — it silently vanished from the pending computation. Add the active Frick EUR bank's IBAN to that set and cover it with a regression test. Also cross-reference the operations runbook in the new migration's docblock (this migration performs the custody-asset link retroactively) and scope the idempotency note to up().
Collaborator
Author
|
Reviewed across three rounds — correctness/safety (including a full liquidity-management-pipeline trace confirming the new rules are observe-only and can never trigger a transfer), conformance/completeness, and a final re-review. Fixes applied: Bank Frick was included in the EUR-bank Scrypt reconciliation set (it had been added to the asset filter but not the IBAN set), the migration docblock was clarified, and a test import was regrouped; a real-Postgres migration spec and unit coverage were added for the new Frick arms. The equivalent CHF Scrypt-reconciliation path (currently Yapeal-only) will be generalized to Bank Frick in a dedicated follow-up, to keep that subtle equity-reconciliation change isolated for review. |
TaprootFreak
marked this pull request as ready for review
July 16, 2026 21:57
TaprootFreak
pushed a commit
that referenced
this pull request
Jul 22, 2026
…booking wedge) (#4309) * fix(accounting): re-run the idempotent CoA bootstrap after cutover The Chart-of-Accounts bootstrap only ran inside the one-time ledger cutover. Custody assets created after the cutover (e.g. the Bank Frick assets from #4252, prod-live three days after the cutover completed) never get an ASSET ledger account, so the first bank_tx referencing them wedges the BankTxConsumer fail-loud ('CoA bootstrap missing'), stalls the booking watermark and floods the log (~120 errors/h since 2026-07-22 12:16 UTC on bank_tx 207473, Frick/EUR). Register a recurring 5-minute @DfxCron wrapper (own kill-switch Process.LEDGER_COA_BOOTSTRAP, gated on isLedgerReady) that re-runs the idempotent bootstrap, and add a rename-guard in bootstrapAssetAccounts: UNIQUE is on name only, so after a uniqueName change findOrCreate(name) would create a second account for the same asset and make findByAssetId ambiguous — the existing account wins. * fix(accounting): fail loud on CoA account name collision After a uniqueName rename, the old name can be reused by a new asset; findOrCreate then name-hits the foreign account and returns it without creating anything — the recurring bootstrap would silently no-op on the exact case it exists to heal. Log an error instead (re-fires every run, persistent signal). Also cover the guard-skip loop continuation with a mixed two-asset spec. * test(accounting): restore the DfxLogger prototype spy after the collision spec The prototype-level mock outlives beforeEach's createMock rebuilds and would leak call history into later tests (repo convention restores it, cf. crypto-input.consumer.spec.ts). * fix(accounting): create CoA accounts active and isolate per-asset bootstrap failures Asset.isActive measures user tradability (all custody/bank assets are false), not account liveness - deriving ledger_account.active from it made every bootstrap-created ASSET account invisible to the reconciliation safety net while still being booked. findOrCreate now always creates live accounts; active=false stays a manual marker. A failing asset no longer aborts the whole recurring run: log and continue, so later assets and the transit/named sections still get their accounts. The collision check now also catches name-hits on NULL-assetId rows, and a real creation logs an info line as the post-deploy signal that the recurring bootstrap did its work. * test(accounting): assert cron schedule and lock timeout of the ledger job wrappers * fix(accounting): activate the bootstrap-created ASSET ledger accounts The bootstrap derived ledger_account.active from Asset.isActive, which is false for every custody/bank asset - so ALL asset-backed accounts created at cutover are invisible to the daily reconciliation while still being booked. The code fix stops the derivation for new accounts; this migration repairs the existing rows so the reconciliation safety net actually covers them.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
Bank Frick is active as a bank rail (EUR/CHF payout accounts,
receive+send) but had no linkedcustody
Asset. Because every bank+currency is modeled as a dedicatedCustodyAsset distinguishedby a per-bank
Blockchainmember and linked via the uniquebank.assetIdFK, Frick's cash wasinvisible to:
bank_txbooked toSUSPENSE,pendingInputAmountliability matching — returned0for Frick credits.Changes
Blockchain.FRICKand two dedicated Frick custody Assets (EUR/CHF).custody assets), links
bank.assetIdon the active Frick rows, and registers observe-onlyliquidity-management rules (
context 'Bank Frick') so the existing per-bank balance adapterrefreshes Frick balances without ever triggering a fund-moving action. Fails loud on a missing
price source or an unlinked active Frick row.
Olkypay/Yapeal:
pendingInputAmount(×4),blockchainToBankName,BANK_BLOCKCHAINS(×2), theEUR-bank log aggregation, and the exhaustive
Blockchainmaps.link, observe-only rules, idempotency, fail-loud post-condition,
down()).Safety / scope
minimal/maximal/optimaland both action FKs areNULL, soLiquidityManagementRule.verify()always returnsaction: nulland no order/transfer can result;status='Active'only keeps the balance refresh running.sendPriorityare untouched.asset.csv); the migration is a no-opoutside prod.