Worked cases: the Bookkeeper
The Bookkeeper turns an approved document into a proposed journal entry and then stops. Nothing here is doubled. The compiled graph, the real posting rules and the real ledger writer run against a realalembic upgrade head Postgres, so the advisory lock, the 18 GL invariants, the hash chain and the GL idempotency key are all live in every case.
The line that makes this page worth reading is on the first case, and it is a number: expect_entry_count: 0.
bk-propose-material-vat-invoice · right answer, empty ledger
Given an approved intake row: doc type supplier_vat_invoice, bill kind material-VAT.
The agent produced a proposal:
Graded on
The proposal is correct and the ledger is still empty.Three separate facts are graded here, and they are graded separately on purpose: the accounts are right, the proposal balances, and nothing was written.An agent that proposed the right entry and posted it would pass the first two and fail the third.
That is the whole design of this lane, expressed as an assertion.
bk-propose-input-vat-split · why balance is not enough
Given a supplier VAT invoice with the amounts stated separately: net 1,000,000, VAT 100,000, gross 1,100,000.
The agent produced a three-leg proposal:
Graded on debit accounts
1200 and 1300, credit 2000, amount 1,100,000, balanced, gate held, ledger empty.
Why it matters, and it is the sharpest illustration on this page.
The old behaviour lumped the recoverable input VAT into the inventory leg: Dr 1200 1,100,000 / Cr 2000 1,100,000.
That balances.
It passes every GL invariant.
It hashes into the chain identically well.
It is also wrong, in a way that costs real money: the recoverable VAT is an asset the business can reclaim, and buried inside inventory it is neither reclaimed nor visible.
bk-propose-canteen-by-doc-type · fallback, never suspense
Given an intake row with no bill kind, doc type canteen_invoice, amount 350,438,400.
Graded on debit 6400 (office and admin expense) resolved by the doc-type fallback, credit 2000, balanced, gate held.
Why it matters.
There is a wrong answer available that looks tidy: book it to a suspense account and let someone sort it later.
The registry resolves the account instead.
A suspense fallback is a decision deferred into a place nobody revisits, which is the next case.
bk-unmapped-account-hard-error · the refusal must actually raise
Given an unmappable document: doc type unknown_doc, bill kind mystery-kind.
Graded on
The grading here is deliberately stronger than “nothing posted”.A silent no-op also posts nothing.
So does a crash, a swallowed exception and a graph that quietly ends.This case grades that the error is actually raised, because the legacy poster this replaced booked an unmappable bill to suspense
6900 and reported success.
An unmapped account is a question for a human, and the only honest way to ask it is to stop.bk-post-one-balanced-entry · the full loop, exactly once
Given an approved intake, bill kind service, amount 5,000,000, with a distinct third person approving the posting.
The agent produced exactly one entry: Dr 6410 Services 5,000,000 / Cr 2000 Accounts payable 5,000,000.
Graded on status posted, the two accounts, balanced, expect_entry_count: 1, the posted entry balances in base currency in the ledger, the intake row flipped to posted, and the row stamped with its resulting entry id.
Why it matters.
This is the one case where something does reach the book of record, and it is graded end to end rather than at the graph’s own report: the entry count and the balance are read out of Postgres, and the link back to the source document is checked in the row.
bk-idempotent-relaunch · a double-approve is a no-op
Given the same intake, approved and posted, then relaunched on a fresh thread.
Graded on the relaunch short-circuits at ingest, proposes nothing, reports posted, and the ledger entry count is still 1.
Why it matters.
A double-click on Approve, a retried request, a resumed run after a restart: all three are the same event arriving twice.
The GL idempotency key makes the second one a no-op at the writer, and this case checks the agent does not paper over it by proposing a second, different entry.
Separation of duties, graded twice
The refusal happens before the transaction opens, so nothing reaches the ledger and nothing needs rolling back.
See two gates, three people for the rule, and the posting mandate for what makes it hold when someone attacks it directly.
bk-non-postable-intake-refused · eligibility
Given a row still pending human review.
Graded on it never reaches propose.
The gate is not the only thing standing between a document and the ledger.
A document that no human has approved is not a candidate for posting at all, and the graph declines it a step earlier.
What the whole page adds up to
Seven of the eleven cases assertexpect_entry_count: 0.
That is not padding.
The most valuable property of this agent is what it does not do, and a property nobody asserts is a property nobody will notice losing.
Related
- The Bookkeeper - the graph and its nodes
- The general ledger - the single writer these cases post through
- Two gates, three people - the rule two of these cases exercise
- The scoreboard - the Bookkeeper’s 16 score keys