Skip to content

fix(accounting): key buy-fiat owed cutover marks by the payout bank asset#4246

Closed
Danswar wants to merge 1 commit into
developfrom
fix/cutover-fiat-mark-keyspace
Closed

fix(accounting): key buy-fiat owed cutover marks by the payout bank asset#4246
Danswar wants to merge 1 commit into
developfrom
fix/cutover-fiat-mark-keyspace

Conversation

@Danswar

@Danswar Danswar commented Jul 16, 2026

Copy link
Copy Markdown
Collaborator

Problem

Since the #4239 deploy the prod cutover advances past openAssets and now aborts every 5-minute run in openBuyFiatOwed:

ERROR [LedgerCutoverService] Error during run: Error: Cutover opening 1657688:buy_fiat-owed:67894 without a mark would silently drop the value (CHF liability, never revalued by mark-to-market) — retry when the mark feed is available

The retry can never succeed. The mark cache is keyed by asset-table ids (FinanceLog.assets), but openBuyFiatOwed resolved the non-CHF output mark with the Fiat-table id:

const fiatMark = row.outputAsset?.name === CHF ? 1 : this.fiatMark(row.outputAsset?.id, date, marks);

BuyFiat.outputAsset is a Fiat — for buy_fiat 67894 (EUR output) that id is 2, and asset id 2 is dBTC (DeFiChain), a feedless token that never appears in the FinancialDataLog. EUR only exists in the asset table as the per-bank Custody entries (Olkypay 269, MaerkiBaumann 267, …). So every non-CHF open buy_fiat row (currently 6 EUR rows in the 90d lookback) hits the m6 fail-loud throw on every run and the cutover wedges permanently.

It fails loud only by luck: had the fiat id collided with a fed asset id, the opening would have been silently valued at a wrong price.

Fix

Resolve the output-currency mark from the correct keyspace, exactly like the forward consumer's outputMark (buy-fiat.consumer §4.7) already does:

  1. the row's assigned fiatOutput.bank.asset first (same basis the eventual seq3 settlement uses), and
  2. any bank in the output currency from the bankByIban map the cutover already loads once per run, as fallback for rows without an assigned payout bank yet.

CHF outputs keep mark 1; a genuinely feedless currency still fails loud (m6) — unchanged semantics.

Tests

  • rewrote the EUR owed-opening test: it previously encoded the bug by keying the mocked cache with the fiat id; it now keys by bank asset id, with the fiat id deliberately absent from the cache
  • new: assigned fiatOutput bank wins over the same-currency fallback (opening and settlement share the valuation basis)
  • updated both m6 fail-loud tests to the new resolution path (no banks / no currency key)
  • full accounting suite green (34 suites, 754 tests), prettier/eslint/tsc clean

…sset

The mark cache is keyed by asset-table ids (FinanceLog.assets), but
openBuyFiatOwed looked up the outputAsset Fiat id — a different keyspace
that never matches (fiat 2/EUR reads asset 2/dBTC, a feedless DeFiChain
token). Every non-CHF open buy_fiat row therefore hit the m6 fail-loud
throw on each run and the cutover could never complete, with no retry
able to heal it.

Resolve the mark the way the forward consumer's outputMark does: the
row's assigned fiatOutput bank asset first, falling back to any bank in
the output currency from the bankByIban map already loaded per cutover.
@TaprootFreak

Copy link
Copy Markdown
Collaborator

Heads-up: develop already fixes this same wedge via a different route — so #4246 is now a design choice, not a release blocker.

While reviewing the develop → main release (#4256) I checked why this PR conflicts. develop already resolves the buy_fiat-owed mark wedge (the 2026-07-17 openBuyFiatOwed … without a mark fail-loud, fiat-id → feedless dBTC) — via currencyMark() / markAssetByCurrency in ledger-cutover.service.ts (comment there: "never via the fiat's own id"). The wedge is fixed in the release without this PR.

So the two are two solutions to one problem, which is why they conflict (5 hunks in the service + 4 in the spec — not a mechanical merge):

  • develop (currencyMark) values a foreign-currency owed opening at the currency's representative bank asset (markAssetByCurrency.get(currency)).
  • fix(accounting): key buy-fiat owed cutover marks by the payout bank asset #4246 (buyFiatOutputMark) values it at the row's assigned fiatOutput.bank.asset first (currency fallback only if unassigned) — mirroring the forward outputMark at settlement, i.e. no opening↔settlement FX-basis drift. Your added test captures exactly this: assigned asset 267-940 vs. representative 269-950.

Owner's call:

  • If the assigned-bank precision is intended (avoid the opening↔settlement basis drift), rebase onto develop so it lands as a refinement that supersedes currencyMark — CI is green; the conflict is just the two approaches meeting.
  • If the currency-representative valuation is good enough, this can be closed.

Either way #4256 is safe to ship as-is w.r.t. the wedge — flagging so this isn't merged as a "must-include" or force-resolved without the accounting context.

@Danswar

Danswar commented Jul 20, 2026

Copy link
Copy Markdown
Collaborator Author

Closing — superseded by #4253, which resolves the same buy_fiat-owed fiat-vs-asset keyspace bug via currencyMark / markAssetByCurrency and shipped in release #4250. The one-shot ledger cutover has since completed and the deadlock signature no longer occurs; this branch now conflicts with develop. The only remaining delta is valuing owed openings at the row-assigned bank asset rather than the currency-representative asset — will reopen with a clean rebase if that precision is worth a follow-up.

@Danswar Danswar closed this Jul 20, 2026
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.

2 participants