Skip to content

feat(bank): wire Bank Frick custody assets into equity, liquidity and accounting#4252

Merged
TaprootFreak merged 2 commits into
developfrom
feat/frick-asset-wiring
Jul 17, 2026
Merged

feat(bank): wire Bank Frick custody assets into equity, liquidity and accounting#4252
TaprootFreak merged 2 commits into
developfrom
feat/frick-asset-wiring

Conversation

@TaprootFreak

Copy link
Copy Markdown
Collaborator

What

Bank Frick is active as a bank rail (EUR/CHF payout accounts, receive+send) but had no linked
custody Asset
. Because every bank+currency is modeled as a dedicated Custody Asset distinguished
by a per-bank Blockchain member and linked via the unique bank.assetId FK, Frick's cash was
invisible to:

  • equity / FinanceLog and the safety kill-switch,
  • the liquidity-management balance refresh (which iterates LM rules),
  • ledger custody classification — Frick bank_tx booked to SUSPENSE,
  • pendingInputAmount liability matching — returned 0 for Frick credits.

Changes

  • Add Blockchain.FRICK and two dedicated Frick custody Assets (EUR/CHF).
  • Prod-only, idempotent data migration: creates the assets (price reused from the incumbent EUR/CHF
    custody assets), links bank.assetId on the active Frick rows, and registers 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. Fails loud on a missing
    price source or an unlinked active Frick row.
  • Add a Frick arm to every blockchain switch / exhaustive map that previously listed only
    Olkypay/Yapeal: pendingInputAmount (×4), blockchainToBankName, BANK_BLOCKCHAINS (×2), the
    EUR-bank log aggregation, and the exhaustive Blockchain maps.
  • Tests: unit coverage for each Frick arm plus a real-Postgres migration spec (asset creation, bank
    link, observe-only rules, idempotency, fail-loud post-condition, down()).

Safety / scope

  • Observe-only LM rules: minimal/maximal/optimal and both action FKs are NULL, so
    LiquidityManagementRule.verify() always returns action: null and no order/transfer can result;
    status='Active' only keeps the balance refresh running.
  • No customer-facing change: the customer deposit-bank selector guard and Frick's fallback
    sendPriority are untouched.
  • Local/dev obtain the Frick custody assets from the seed (asset.csv); the migration is a no-op
    outside prod.

… 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().
@TaprootFreak

Copy link
Copy Markdown
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
TaprootFreak marked this pull request as ready for review July 16, 2026 21:57
@TaprootFreak
TaprootFreak merged commit 2863922 into develop Jul 17, 2026
8 checks passed
@TaprootFreak
TaprootFreak deleted the feat/frick-asset-wiring branch July 17, 2026 20:55
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.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant