feat(folio-spec): predicate that gates balance check on TxnSubmit tap

Replaces the row-sum predicate (which always held by construction since
balance is derived from rows in Folio) with one that compares the typed
amount to the actual balance delta after a tap on TxnSubmit. Catches the
planted double-submit bug.
This commit is contained in:
pj committed 2026-05-31 18:07:01 +05:30
1 parent 8ffb174c1b
commit 7a2f03e67f
1 file changed
+18 -21
+18 -21
View File
@@ -1,22 +1,19 @@
export interface LedgerRow { // When the last action is a tap (or double-tap) on the transaction Submit
key: string; // button, the absolute change in total balance must equal the amount the
signed: number; // user typed. A double-submit lands two transactions and shifts the balance
} // by 2x the typed amount, tripping this check.
export function submitChangesBalanceByTypedAmount(args: {
// When new ledger rows appear, the balance delta must equal the SUM of the lastAction: { kind?: string; on?: string | object } | null;
// new rows' signed amounts. Single-row sums and multi-row sums BOTH must typedAmount: number;
// match the delta; a double-submit landing two rows whose total is twice prevTotalBalance: number;
// the balance change trips the sum check. currTotalBalance: number;
export function balanceMatchesAddedSum( }): boolean {
previousRows: readonly LedgerRow[], const { lastAction, typedAmount, prevTotalBalance, currTotalBalance } = args;
currentRows: readonly LedgerRow[], if (lastAction == null) return true;
previousBalance: number, if (lastAction.kind !== "Tap" && lastAction.kind !== "DoubleTap") return true;
currentBalance: number, const on = lastAction.on;
): boolean { const onString = typeof on === "string" ? on : on != null ? JSON.stringify(on) : "";
const previousKeys = new Set(previousRows.map(row => row.key)); if (!onString.includes("TxnSubmit")) return true;
const added = currentRows.filter(row => !previousKeys.has(row.key)); if (typedAmount === 0) return true;
if (added.length === 0) return true; return Math.abs(currTotalBalance - prevTotalBalance) === typedAmount;
const delta = currentBalance - previousBalance;
const addedSum = added.reduce((sum, row) => sum + row.signed, 0);
return addedSum === delta;
} }