fix(folio): use sum-of-added-rows in balanceMatchesAddedTxn

The old predicate (every row's signed amount equals delta) silently passed
the double-submit bug because two same-amount rows each match the delta in
isolation. Switching to the sum check (addedSum === delta) catches both the
double-submit case and any future multi-row append whose total drifts from
the balance change.
This commit is contained in:
pj committed 2026-05-31 15:49:51 +05:30
1 parent 35fce05b65
commit b88b6c239d
1 file changed
+14 -19
+14 -19
View File
@@ -12,17 +12,13 @@ import {
whenRoute,
} from "@sanderling/spec";
import { defaultActions } from "@sanderling/spec/defaults";
import { balanceMatchesAddedSum, type LedgerRow } from "./predicates";
interface Account {
name: string;
balance: number;
}
interface LedgerRow {
key: string;
signed: number;
}
// Parses formatCents output like "$5.00", "-$1,234.56", "+$0.50" back to integer cents.
function parseDollarCents(text: string | undefined): number {
if (!text) return 0;
@@ -93,22 +89,21 @@ const newAccountBalanceIsZero = always(
})
);
// Property 2: when new ledger rows appear, the ledger balance delta equals
// the sum of the new rows' signed amounts. A double-submit lands two rows
// whose individual amounts cannot both equal the aggregate delta, so each
// such row fires the property.
// Property 2: when new ledger rows appear, the balance delta equals the SUM
// of the new rows' signed amounts. Single-row and multi-row sums both must
// match; a double-submit lands two rows whose total is twice the balance
// change and trips the sum check.
const balanceMatchesAddedTxn = always(
now(() => route.current === "ledger").implies(
next(() => {
const prev = ledgerRows.previous ?? [];
const curr = ledgerRows.current;
const prevKeys = new Set(prev.map(r => r.key));
const added = curr.filter(r => !prevKeys.has(r.key));
if (added.length === 0) return true;
const delta = ledgerBalance.current - (ledgerBalance.previous ?? 0);
return added.every(r => r.signed === delta);
})
)
next(() =>
balanceMatchesAddedSum(
ledgerRows.previous ?? [],
ledgerRows.current,
ledgerBalance.previous ?? 0,
ledgerBalance.current,
),
),
),
);
const DEMO_EMAIL = "[email protected]";