If a ledger changes its total when reopened, the formula may not be the only place to inspect. I changed one synthetic cash field into a numeric string, exercised the actual restore entrances and found a gap between what validation accepted and what calculation received.
Reopening should preserve the meaning of the same ledger
A business application has to do more than display plausible numbers in the current window. After closing and reopening it, the same saved information should still mean the same thing. A successful save message and permission to use those saved values in another calculation are different promises. I revisited that boundary in a program that stores simulated accounts.
The actual source had one check in the screen storage reader and another in the engine restore path. Having two checks looked reassuring, but it did not establish that they enforced the same rules. I started with the conditions under which an existing ledger was accepted, rather than treating another normal-looking screen as evidence that restoration was sound.
I changed one apparent number into a string
I created synthetic ledgers without using an operating account or a visitor record. Ten accounts containing the number 100000 each should have total cash of 1000000. When I changed just one cash field to the string "100000", both the engine check and the screen-side check accepted it. They also accepted a null cash field and a ledger with the required totals object removed.
Applying one synthetic market mark to the string-cash ledger produced an aggregate near 1e59 instead of one million. That value was the output of a malformed-input reproduction, not an investment outcome. I separately checked that the input object remained unchanged. A function can preserve its input and still produce a bad new result when the input is outside its intended contract.
Convertible input is different from a stored number
The checks used Number conversion. A numeric string can be converted, and null or a boolean can also become a particular number. If validation accepts a value because it is convertible but then returns the original object, the calculation still receives the original string or null. This gap allowed addition to turn into string concatenation.
I changed the persisted-account boundary to require an already finite number. Cash must be nonnegative; starting capital and holding prices must be positive; quantity must be a positive integer. Profit, loss and return fields can legitimately be negative, so a minus sign alone is not a rejection reason. Each of the six required summary fields must also contain a finite number.
Text entry and external data still need parsing. That belongs to an explicit input stage with its own separator, unit and decimal rules. I kept that responsibility separate from silently repairing a stored accounting value while reopening it. The existing quote normalization and trading calculations were left unchanged by this repair.
Explore the implementation
Numeric predicates used in the actual repair
const positiveNumber = (value) => Number.isFinite(value) && value > 0;
const cashIsValid = Number.isFinite(agent.cash) && agent.cash >= 0;
const totalFields = ['equity', 'cash', 'realizedPnl', 'unrealizedPnl', 'netPnl', 'returnPct'];
// Part of the validator, after requiring totals to be a record:
const totalsAreNumeric = totalFields.every((key) => Number.isFinite(value.totals[key]));I connected both entrances to one validator
The two implementations had also drifted. The engine did not sufficiently require the summary and last-price-map structures, while the screen had a different condition for holding marks. Repairing only one copy could leave the other restore entrance accepting the same bad record. The cost of maintaining similar conditional code twice became visible during the fix.
I connected the screen storage reader to the engine's shared validator. That function now makes the common decision about numeric types and required structures. Values in the last-price map and the holding quantity, average price and mark prices are checked on that same path. The change did not introduce a new storage version or key, or rewrite an otherwise valid saved ledger.
A shared function name alone is not evidence that both entrances use it correctly. The tests executed the actual screen reader code against an in-memory storage substitute. Passing an engine test was not counted as proof that the screen reader had been exercised. I checked that the same malformed cases were rejected at both entrances.
Preserving good records mattered more than changing the version label
A record labelled version 2 could still contain invalid values. Conversely, changing the validator should not cause an ordinary numeric version-2 ledger to be replaced with a fresh one. I therefore checked JSON round trips for valid ledgers in both supported regional variants and for ledgers with holdings created by a synthetic fill.
Valid content was retained, and the engine restore function returned the accepted object itself. The negative tests covered 157 malformed numeric, required-structure, price and holding-field cases. The ten existing engine tests plus six new regression tests all passed. Those counts distinguish test functions from the individual counterexamples exercised inside them.
Requiring a numeric summary does not prove that the summary reconciles with the transaction history. This task addressed types and structures at the restore entrance. It did not audit every historical record, establish accounting recognition rules or complete financial reconciliation. A statement that validation passed needs to say which contract was validated.
I kept the existing fallback policy visible
The original application starts a fresh synthetic ledger when its stored format or region is invalid. I retained that policy while closing the hole through which malformed types entered the normal restore path. This repair did not add a quarantine archive or an approval flow for conversion proposals. It also did not open or reset an actual browser's stored accounts.
A company operations system may need a different decision. Refusing to read bad data must not silently destroy the user's only copy. Before adopting the approach, decide whether to preserve an original snapshot and show a restore hold, offer an explicit conversion proposal, or require an authorized person to approve a new ledger. These are application criteria, not features installed in this repair.
Strict type validation and preservation after failure are separate responsibilities. I did not describe a stronger validator as an improved recovery policy. Reading both the acceptance conditions and the failure behavior is necessary to understand the effect on information someone was already using.
Source repair and a running installation are different results
After editing the source, I built a separate copy using its fixed dependency lockfile. I did not replace the original built output or a running installation. Before-and-after source archives included the context needed for testing. I extracted them, compared their bytes, and ran the ten previous tests on the restored old source and sixteen on the restored updated source. Hashes and modification times of 29 protected files remained unchanged.
An independent code review revisited the two scoped defects against the current implementation. I kept that judgment separate from execution evidence. In-memory reader tests, an isolated build and restored-source tests were not described as rendered-browser inspection or live market and order integration. The result was a local repair with an explicit verification boundary.
Two existing modules carry the next questions
There was no need to publish another small validator merely to reject numeric strings. The existing ledger module covers missing and duplicate transaction amounts, settlement limits, integer bounds and withholding an incomplete total. The synthetic restore module checks bytes, hashes and row structure before returning a replacement candidate and change preview. Neither is the saved-account validator repaired here, but they help explain what must be considered after type checks.
I downloaded both existing public archives, checked their bytes and hashes, and reran their tests in isolated extracted folders. The ledger package provides 21 test groups and 861 combinations; the restore package provides 12 test groups. This is not automatic integration into another application or restoration of real records. Start by deciding your own units, versions and preservation policy, then exercise the same malformed input at every actual entrance.