> ## Documentation Index
> Fetch the complete documentation index at: https://docs.vinmake.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Why a ledger, not a spreadsheet

> The failure class GreatBook exists to remove: a stored balance that drifts from reality, and nobody can tell.

# Why a ledger, not a spreadsheet

Most small and mid-size businesses do their accounting in a workbook.
The workbook works, right up until it silently stops working, and the way it stops is always the same.

## The paste-over-value failure class

A workbook computes a balance in a formula, then somebody pastes the computed number over the formula.
Perhaps to freeze a month.
Perhaps because the formula broke and the number looked right.

From that moment the cell is no longer a balance.
It is a **claim** about a balance, and nothing in the workbook can tell the difference.
The reconciliation tab that checks that cell against itself still returns zero, because it is now comparing a pasted number to a pasted number.
The check passes forever, and it is checking nothing.

That is the failure class, and it is worse than an error, because an error is visible.

<Note>
  This is not a hypothetical.
  GreatBook's Close list is a reproduction of a real v12 workbook's reconciliation dashboard.
  Several of its rows turned out to be exactly this: a check of a derived column against itself.
</Note>

## What GreatBook does instead

**Nothing that can be derived is stored.**

Every balance the workbook derived is computed on demand from the sub-ledger rows and the GL: bill due amounts, unapplied payments, AP and AR control totals, advance pools, rail balances, float and deposit balances.
There is no column to paste over, so there is nothing to drift.

That has a consequence for the Close list that is worth stating plainly, because it is the most credible thing in the finance layer.
Where a workbook check was a paste-over-value tautology, it was **not** reproduced as a fake zero-check.
The derived value cannot drift, so the check has nothing to do.
A version with real teeth was implemented instead:

| Instead of the tautology                 | The check that replaced it                                                                              |
| ---------------------------------------- | ------------------------------------------------------------------------------------------------------- |
| a derived column compared to itself      | the sub-ledger against the GL control account, which catches a rogue posting straight to the control    |
| book-vs-evidence recorded from one event | reconciliation against **external** bank evidence, which catches a booked payment the bank never showed |
| an allocation implied by construction    | cross-foot, which catches an allocation with no settlement                                              |
| an unstated deposit position             | Σ\|advance pool\|, which catches an unapplied deposit                                                   |

The result is a set of **104 Close checksums** that report on the whole book across seven Books families and the Wave E period-end controls.
They are separate from, and weaker than, the **18 GL invariants**, which refuse a bad write before it happens.
The difference between the two is explained in [The seven guarantees](/greatbook/guarantees).

## Two failures a spreadsheet cannot see, worked through

The audit that produced GreatBook's finance layer found both of these in a running system.
Each is now a control with a test behind it.

<AccordionGroup>
  <Accordion title="Finding #4 · FX as of now, applied to all of history" icon="banknote">
    Every conversion in the running floor picked the latest available rate: `order by as_of_date desc limit 1`.
    With a single seed snapshot that is **today's guess applied to every transaction ever recorded**.
    A purchase from three months ago was revalued at this morning's rate, and the books moved because
    somebody loaded a rate file.

    A book of record must value a historical transaction at its own date's rate.
    The rule is now `where as_of_date <= txn_date order by as_of_date desc limit 1`: the most recent rate
    that was in effect on or before the transaction date.

    And when no rate exists on or before that date, the system raises `RateUnavailableError` and the
    caller **quarantines the transaction**. It does not post at par.
    Posting at par is a made-up number that balances, which is the worst kind: it looks correct.
  </Accordion>

  <Accordion title="Finding #5 · money crossing a float at the posting boundary" icon="calculator">
    The posting path cast base amounts, native amounts and rates through Python `float()`.

    Floating point cannot represent decimal cents exactly.
    A ledger that does arithmetic in float will, given enough entries, fail its own balance check by a
    sub-cent residue, and **a book of record cannot be "balanced except for rounding"**.

    Money is now one type in one place: `Decimal`, `NUMERIC(20,4)`, `ROUND_HALF_UP`, coerced through
    `Decimal(str(x))` so a value that originated as a float lands on its decimal representation rather
    than the float's binary artefact.

    A float reaching the posting path is **rejected outright**, not rounded away.
    Values that genuinely arrive as floats, such as JSON numbers from a connector, are converted once,
    explicitly, at the ingestion boundary, and are `Decimal` from then on.
  </Accordion>
</AccordionGroup>

## Why an LLM writing directly into accounts is a liability

The obvious version of AI accounting is an assistant with database access.
It is fast to build and it fails in a way you cannot audit.

A ledger's value is not that the numbers are usually right.
It is that specific things are **impossible**:

* an entry cannot be unbalanced, because the writer refuses it;
* a posted entry cannot be edited, because database triggers block the update;
* re-running the same source cannot double-post, because an exact replay returns the prior entry and changed immutable facts conflict;
* an entry cannot land in a closed period, because the period lock is checked before the write;
* a change cannot go unnoticed, because every entry is chained to the one before it.

None of those survive contact with a model that writes.
A model that is right 99% of the time still puts a wrong number in an immutable book once every hundred entries, and the whole point of the book is that you cannot take it back out.

So GreatBook does not give the model a write path.
The model proposes.
A person approves.
One function writes, and it validates 18 invariants first.

<Note>
  The interesting claim is not that this makes the AI safe.
  It is that it makes the AI **useful**: because the engine is provably correct, the agent is free to be
  genuinely good at judgment, which is the part it is actually better at than a rules engine.
</Note>

## Related

* [The seven guarantees](/greatbook/guarantees) - each promise, the code that enforces it, and the test that proves it
* [How it fits together](/greatbook/architecture) - the layers, the graphs and the gates
* [The golden path](/greatbook/guides/golden-path) - what all of this looks like from a reviewer's chair
