Zeno runs across two different environments. For QuickBooks Desktop, it runs locally on Windows as a .NET application storing batches in SQLite. For QuickBooks Online, it runs on Linux as a Node application storing batches in Postgres.
Both implementations speak the exact same data model: The Plan Contract. An AI client prepares a batch, a human reviewer reads the table and signs off, and the engine applies the approved work.
The glue holding this governance together is cryptographic: a human approval is pinned to the exact SHA-256 digest of the proposed plan. When the apply step runs, it recomputes the digest. If the hash differs by even one bit, the apply step refuses to post.
Here is why that requirement makes IEEE 754 floating-point math unusable anywhere in the pipeline.
The representation problem
In general-purpose software development, numeric amounts are routinely parsed
into standard floating-point numbers: double in C#, or number in TypeScript.
For rendering a dashboard or computing an average, minor rounding inaccuracies
are harmless.
In an accounting ledger, floating-point representations introduce three distinct failure modes:
1. The classic binary fraction inaccuracy
Computers store floating-point numbers in base 2. In base 2, simple decimal
fractions like 0.1 and 0.2 are repeating fractions that cannot be stored
with exact precision. In JavaScript:
0.1 + 0.2 === 0.30000000000000004
If an engine adds a $0.10 service charge to a $0.20 fee, an unrounded float turns into a 17-digit number. If that value reaches a JSON serialization string, it pollutes the payload.
2. Differing runtime serialization defaults
Even when numbers do not suffer from binary fraction drift, different runtime JSON serializers format numbers differently:
- C#’s
System.Text.JsonorNewtonsoft.Jsonmight serialize a whole decimal as1250.0or1250. - JavaScript’s
JSON.stringify()serializes1250.00as1250. - Locale formatting can convert periods to commas (
1250,00instead of1250.00) if the thread’s culture is set to a European locale.
If a batch is prepared on a Windows workstation running in German locale and reviewed on a cloud console running Node, a float serialized under different rules produces two completely different string representations of the exact same currency value.
3. Digest breakage
In Zeno, the approval verdict records the SHA-256 hash of the canonical plan JSON.
If a reviewer clicks “Approve” on a plan containing:
{"amount": "1250.00"}
and the applying process serializes the verified value through a float parser as:
{"amount": 1250}
the SHA-256 digest fails verification. The apply step has no way of knowing whether the difference was an innocent float truncation or an unauthorized modification of the amount. The apply step aborts, and the firm’s workflow stops.
The rule: exact invariant decimal strings
To prevent this class of failure, the Plan Contract enforces three strict constraints across both codebases:
1. Money is stored as exact decimal strings
All financial amounts are represented and persisted as strings formatted with an explicit period decimal separator and two fractional digits:
// contract/src/plan.ts
export const DecimalAmount = z.string().regex(/^-?\d+\.\d{2}$/);
In C#, amounts map to System.Decimal, but when serialized to the plan JSON or
written to the SQLite journal, they are formatted strictly using
CultureInfo.InvariantCulture:
amount.ToString("F2", CultureInfo.InvariantCulture)
No financial amount is ever cast to float or double. Arithmetic operations
(line summation, balance reconciliation) use fixed-point decimal arithmetic in
both runtimes.
2. Deterministic JSON canonicalization
Before a digest is computed, the JSON object is normalized:
- Object keys are sorted lexicographically at every depth.
- Insignificant whitespace is removed.
- Character encodings are standardized to UTF-8 without byte-order marks.
Because both the .NET desktop client and the Node cloud engine use the same canonicalization rules, both engines compute the identical SHA-256 digest for any given batch.
3. Reversal records exact values
When a run is posted to QuickBooks, the journal logs what was changed so it can be reversed later. If a vendor balance or line item is modified, the previous value is recorded in the same invariant string format.
When reversing an entry, the restoration payload uses the exact recorded string rather than recalculating differences.
Summary
In accounting software, string representations of numbers are not just a display format; they are an integrity boundary. Treating currency as invariant decimal strings avoids floating-point drift, guarantees cross-runtime compatibility between Windows and Linux, and ensures that human approval digests remain rock-solid from review to posting.