Package books is a per-owner general ledger: an address opens a chart of accounts and posts balanced double-entry entries against it.
The one idea
Every other ledger-like realm in this portfolio is SINGLE-PURPOSE and its state shape IS its question. invoices tracks receivables. payroll tracks accrued wages. budgets tracks allocations. fee_split tracks shares. Each is a correct answer to one question and structurally cannot answer a second, because there is nowhere to put a transaction that is not already one of the modelled kinds.
This realm has no opinion about what the accounts mean. The owner defines the chart; the realm enforces that every entry balances, that it cannot be altered after the fact, and that only the owner may write it.
NO CUSTODY — and refusing coins is not the same as not holding them
This realm holds nothing. There is no coinio import, no payable path, no fee, no withdrawal, no admin.
So every mutating entrypoint REFUSES a transaction that arrives carrying coins. A realm with no withdrawal path that silently accepts ugnot strands it permanently, and "we don't hold funds" would then be true of the design and false of the balance. Having no custody is not the same as being safe to send coins to, and the difference is one guard.
A BOOK IS A SET OF CLAIMS BY ITS OWNER
Read this before trusting any number this realm returns.
With no custody there is no solvency invariant tying "Cash 5000" to 5000 real ugnot anywhere. A book can be internally perfect and describe nothing. IsBalanced proves arithmetic, never truth.
Consequently this realm exposes NO CROSS-OWNER AGGREGATE. Summing every book's debits would be summing every stranger's self-reported assertion, and the getter's existence would invite reading it as a signal. What it exposes instead is owner-scoped, which is the only question a consumer can act on — and a consumer acting on its OWN book is trusting only itself.
The account code DETERMINES the account kind
An account's code's leading digit fixes its kind, by the standard chart-of-accounts convention:
Example
11xxx -> Asset 2xxx -> Liability 3xxx -> Equity
24xxx -> Revenue 5xxx -> Expense
There is no kind parameter. This is deliberate and it is the realm's answer to p/journal consumer obligation 7, which exists because of that package's audit finding Y1: NO CHECK IN p/journal VALIDATES AN ACCOUNT'S KIND. An account opened Asset that should have been Liability reports a sign-flipped Balance() forever while IsBalanced, RederiveFromJournal and AccountingEquation all three stay healthy — every one of them is built on Debits and Credits, which the kind does not touch.
Deriving the kind does not eliminate that hazard. It MOVES it: after the derivation the kind cannot disagree with the code, so the unauditable question "is this kind right?" becomes the visible question "is this code right?", which any reader of the chart can check against the convention above. That is the whole claim and it is deliberately not stated as more.
The cost is a mandatory numbering convention: a code whose first character is not 1-5 is refused. For a realm whose purpose is auditable books, that is the feature.
Entries are append-only and are corrected by REVERSAL
There is no edit and no delete. A wrong entry is corrected by posting its mirror image, which leaves both the error and the correction readable. Each entry may be reversed at most once; a reversal is itself an entry and is itself reversible once.
Periods
An owner may seal everything up to a height with CloseThrough, after which no entry may be dated at or before it. The seal only moves forward. This is the owner's own discipline, not a protocol rule — nothing forces an owner to close anything.
The clock is the chain's
Entries are dated with runtime.ChainHeight() and never with a caller- supplied value, satisfying p/journal obligations 2 and 3. Heights throughout, never seconds. A realm that let a caller choose the date would have no period discipline at all.
Caps, and why they are far below the engine's
p/journal permits 10 000 accounts and 1 000 000 entries. Its AUDIT.md names the gas ceiling on RederiveFromJournal at full cap — a worst case of 64M posting visits in one call — as UNMEASURED and does not claim it. So this realm sets its own caps far below, and exposes Rederive as an explicit owner-callable rather than running it inside a getter where another party would pay for it.
The caps are per-owner, never global. A global cap on books would be a griefing vector: one address could fill it and lock everyone else out. Per-owner, an owner can exhaust their own capacity and nobody else's, and the cost of doing so is gas plus a storage deposit per record.
Realms may keep books too, for free
The owner is cur.Previous().Address(), so a realm that calls in keeps books under its own address with no extra authorization code. That is the realm-to-realm integration surface, and it arrived as a property of the owner-only rule rather than as a feature.
What this realm does NOT do
- It does not hold, move, or price any asset.
- It does not verify that an entry describes a real event.
- It does not validate that the right transaction went in the right account. The derived kind makes the CODE auditable, not the judgement.
- It has no admin. The deploying namespace has no privilege here at all: no setter, no pause, no ability to post, reverse, or read anything a stranger cannot read.