bankTransactionReconciliationWorkflow and the activities behind it: the one
place that decides what reconciliation a b2b.bank_transaction row is owed and
carries it out.
The dispatch spans four domains — withdrawals, deposits, virtual accounts and
bank internal transfers — and no existing service library can see all of them.
bank-transaction-services cannot reach withdrawal-services (that edge runs
the other way, and TypeScript project references cannot be circular), and
withdrawal-services and deposit-services are siblings with no edge between
them. This package sits above all four and is imported only by
temporal-worker and backoffice-api.
Every producer of a bank_transaction row, via
startBankTransactionReconciliationWorkflow in @cfxlabsinc/bank-transaction-services:
VictorWebhookEventConsumer (raw.source = "victor_webhook")persistVictorBankTransactions (raw.source = "victor_sync")The workflow id is keyed on the bank transaction, so the two racing is a no-op.
BankTransactionReconciliationOutcome — RECONCILED,
ALREADY_RECONCILED, or NOT_APPLICABLE with the reason recorded.methodData.method, a rail/quote-method mismatch, book-transfer
legs the internal-transfer service rejects (non-retryable, typed with that
service's error code), or a retryable condition that never resolved.Nothing completes silently having reconciled nothing.
Victor completes an inbound ACH entry with its addenda, company fields and
settlement date still null and fills them in hours later, with no second
webhook. The plan marks such a row with awaitAchEntryDetailUntilEpochMs (four
hours after postedAt), and the workflow re-reads Victor's detail until an
addenda or a settlement date arrives or that deadline passes, then reconciles
either way. A row released without the fields says so at the end of its
outcome's reason.
AchEntryDetailAdminService (src/AchEntryDetailAdminService.ts) — refresh
is the hold's read: it fills the entry fields an unreconciled row lacks and
reports whether the late detail has arrived. backfill does the same read for
rows written before the hold, and also fills the deposit quote or withdrawal
reconciled from each. Both are fill-only.scripts/backfillAchEntryDetail.ts — the operator entry point for backfill.
Dry by default, --apply to write. --help lists the flags and environment.Operator procedure: docs/runbooks/ach-addenda-missing.md. Design: docs/BANK_TRANSACTIONS.md § 3.