A capability list is worth nothing if the labels are marketing. So these four labels have exact meanings, and each card carries the source file the claim was read in rather than asking you to take it on trust. Built means the function is in the engine and a real application route calls it. API only means it is built and persisted and no screen renders it. Built, not switched on means the function is written and tested and nothing calls it — that is a real gap, and there are 2 of them below. Honest-off means deliberately not connected, and not connectable without new code being written. Of the 12 capabilities here, 5 are built and reached; the rest carry a weaker label on purpose.
The rule the whole ledger is built on
An entry that does not balance does not post. There is no second path.
The posting function walks every line of a journal entry once. A line whose amount is not a positive exact-cents integer fails. A line pointing at an account that does not exist in the chart fails. A line in a currency the entry is not denominated in fails. Then it compares the debit total to the credit total, and if those two numbers are not identical it returns “not_balanced” and writes nothing at all. There is no override, no suspense account it quietly parks the difference in, and no warning state that lets an unbalanced entry sit in the books until somebody notices at close. Every rejection is a named machine reason a caller can branch on, not a string a human has to read: no_lines, invalid_amount, unknown_account, currency_mismatch, not_balanced. The whole rule is about thirty lines of arithmetic with no database, no clock, and no network call in it, which is why it can be read in full by a business manager who wants to know exactly what the software will and will not accept. And because the rule itself touches no database, the place it matters is the write path, so here is that path exactly. There is one piece of code in the whole system that inserts a journal entry into the database, and the insert sits INSIDE a function that runs the posting rule first and returns before reaching the insert if the rule refused. A rejected entry does not write a row and then get cleaned up; it writes zero rows, because the write is downstream of the rule rather than beside it. That is the one claim on this page we will not soften.
Built and persisted · the only write path runs the rule first
Read in back-office-general-ledger.ts:208 · the not_balanced return at :234 · the sole insert path at finance-gl-repo.ts:309, :336
Why the pennies stay put
Exact integer cents, end to end. No floating-point dollar anywhere.
Every amount in this ledger is an integer number of cents. Not a dollar figure with two decimal places, which is a floating-point number wearing a costume and which will, over a year of thousands of postings, quietly invent or destroy money in the last digit. A line for four hundred and ninety-nine cents is four hundred and ninety-nine cents when it posts, when it folds into an account balance, when it rolls into a trial balance, and when it closes. This is not a preference. Rounding drift is not a small bug in a ledger — it is the bug, the one that turns a clean reconciliation into three hours of hunting for eleven cents — so the type system refuses the shape that causes it rather than correcting for it later.
Built · enforced in the posting guard
Read in back-office-general-ledger.ts:218 (positive exact-cents guard)
The thing general bookkeeping cannot do
A fund is a real dimension, not a tag you remember to apply
Schools do not have one pile of money. They have a general fund, a food-service fund, a capital-projects fund, restricted grant money that may only be spent on the thing it was given for, and activity money that belongs to a club rather than to the district. A trial balance can be pulled for the whole entity or for a single fund, from the same posted entries, because the fund is carried on the transaction rather than reconstructed from a label afterwards. Fund balance is classified the way governmental accounting actually asks for it — nonspendable, restricted, committed, assigned, unassigned — rather than collapsed into one equity line that a board has to take on faith. To be exact about what that sentence claims: we implement the classification. We are not certified by anyone, no auditor has issued an opinion on this software, and this page does not imply otherwise.
Built · per-fund trial balance and fund-balance classification reached
Read in finance-trial-balance.ts:136 · finance-fund-compliance.ts:177
Month end
Trial balance over the stored books. Period close and budget-to-actual are calculators.
The trial balance has both halves, and the difference between them is worth knowing before you buy. Over the STORED books it is a real report: the persisted route reads the chart and the posted entries back out of the database and folds the trial balance from them, entity-wide or for one fund. And the account-balance fold defensively skips any entry whose debits and credits do not match, so even a row corrupted by something outside this engine cannot push a wrong number into a balance. Period close and budget-to-actual are a different thing, and calling them the same thing is what this card used to do. Both are CALCULATORS: you send the chart, the entries, and the budgets in the request, and you get the arithmetic back. Period close does not close anything in a database — it returns the balanced closing entries for you to post. Budget-to-actual ties a budget you supply against actuals you supply. The arithmetic is right and it is tested. Nothing about it is stored, and there is no month-end state anywhere that remembers you ran it.
Trial balance persists · period close and budget-to-actual store nothing
Read in finance-trial-balance.ts:106 · finance-period-close.ts:169 · persisted read via finance-gl-repo.ts readTrialBalance · stateless via finance-ledger.ts:25-31
Committed money, before it is spent
Over-budget is blocked when the commitment is made, not discovered at close
Budget authority, minus what is already encumbered, minus what has already been expended, is the available balance. When an approval encumbers against a budget, the check is fail-closed: a commitment that would push the account past its authority is refused with a named reason rather than recorded and reconciled later. This one has the most real machinery behind it of anything on the page, so here is what is actually durable. The APPROVAL RUN is stored: a spend request needs three separate sign-offs — department, admin, and budget owner — they must be three different people and none of them may be the person who filed the request, and that is enforced in the engine rather than by a settings screen. Every step reloads the request from the database and drives the decision from the stored row, so a client cannot forge somebody else’s sign-off or replay a stale state. When there is no database the transition fails closed instead of trusting the client. The encumbrance itself is appended to an audit table. What is NOT stored is the budget. You send the budget on the encumbrance call and we hand back the new encumbered budget for you to save wherever you keep it. So the over-budget refusal is real arithmetic against the authority you supplied, and the running balance between calls is yours to hold, not ours. And the shape of the lifecycle matters. All five budget event kinds — appropriate, deappropriate, encumber, disencumber, expend — are modelled, are accepted as caller-supplied events, and are folded correctly. But only encumbrance is ever constructed by the server. The other four are foldable, not driven: nothing in the running system today raises an appropriation, releases an encumbrance, or books an expenditure on its own. That is a real gap and it is written on the card below, not buried.
Approval run persists · the budget is caller-held; encumbrance is server-driven
Read in back-office-general-ledger.ts:398, :491 · mosaic-budget-approval.ts:464 (durable instance + three-party SoD) · the caller-owned budget at its header :39-42
Stated plainly, because it would be easy to hide
Appropriate, disencumber, and expend are written and tested — and nothing calls them
The four functions exist in the engine: appropriate, deappropriate, disencumber, expend. They are unit-tested. Searching the entire application tree for a call to any of them returns nothing — and that search was run in the same pass in which the searches for postJournalEntry, encumber, and the finance authorization gate all returned dozens of hits, so the zero is a real absence rather than a search that was pointed at the wrong place. What this means in practice: the budget lifecycle is complete as arithmetic and incomplete as a workflow. A school could supply those events today through the API and the balances would fold correctly. No screen in the product constructs one. Calling this “built” would be a true statement with the scope taken off it, so we do not.
Built, not switched on · zero production callers, measured
Read in back-office-general-ledger.ts:449, :467, :513, :536
The other gap, and the bigger one
The school purchasing and requisition chain is written and is not wired
There is a separation-of-duties purchasing chain in the codebase: record a fund transaction, request a purchase, approve a purchase, receive a purchase. Four functions, written, tested, and with zero production callers between them — measured the same way, in the same pass, against the same positive controls. The module they live in is not dead, which is why this needs saying carefully. Its READ half is genuinely live: school fund balances and budget variance are computed from it and are reached by the finance command centre today. Its WRITE half is not. Nothing in the running system files a requisition through this chain or drives one through approval and receipt. So: do not buy MyBooks today for school purchasing workflow. Buy it for the books underneath, and hold us to a date for the rest.
Built, not switched on · write half has zero callers; read half is live
Read in sch-fund-accounting.ts:230, :349, :412, :485
Payables and receivables
Aging and never-pay-unapproved — correct arithmetic, and no invoice store behind it
Send a set of invoices and a date and you get back each one’s outstanding balance, its aging bucket, the approved-payable versus unapproved-held split, and the total outstanding and overdue on the receivable side. All exact cents, no clock of its own — you supply the as-of date, so the same input always gives the same answer. Never-pay-unapproved is a real function and it does refuse: an unapproved payable returns blocked_not_approved and a zero payable amount, and there is no setting that turns that off because it is not a setting. Here is the scope on it, which the earlier version of this card left off. The function reads an approved flag on the invoice you send it. There is no invoice table in this product — not a thin one, none — so it is not verifying your approval against a stored record of who approved what and when. It refuses to call an unapproved invoice payable. It cannot tell you whether the approval you asserted actually happened. That is a genuine control at the arithmetic layer and it is not a substitute for an approval system of record, and we would rather you know which one you are buying.
Calc reached · no invoice store; approval is an input, not a stored fact
Read in back-office-ap-ar.ts canPay:153 (branches on inv.approved:163) · caller back-office-erp.ts:226 CanPayBody · :247 aging · no *invoice* store in apps/api/src/store
The unglamorous half of the job
Bank reconciliation and a 1099 vendor year-to-date worksheet — both run per request
Bank reconciliation produces an audit record rather than a checkbox: send the book entries and the bank statement lines and it matches them deterministically, oldest first, each line matched once, then reports what matched, what is outstanding on each side, and the remaining difference. It passes only on an exact zero difference with every amount an integer. The 1099 worksheet sums vendor year-to-date reportable payments per box, flags who crosses the reporting floor, and lists the excluded rows with reasons. It emits no form and files nothing. Both are per-request calculations and neither keeps a record. There is no reconciliation you can come back to next month and no stored vendor year-to-date that accumulates as you go — you send the year’s payments each time you want the total. That makes these two a strong worksheet replacement and not yet a filing system, and January is still a review you drive.
Calc reached · nothing stored between requests
Read in cbuild-bank-reconciliation.ts · cbuild-1099-vendor-ytd-accumulation.ts · callers back-office-erp.ts:277, :292 (both stateless, per :61-66)
Payroll, and exactly how far it goes
Gross-to-net is computed. Nothing is disbursed, and nothing can be.
The payroll engine computes gross-to-net and builds a pay stub: withholding, contributions, deductions, net pay, all in exact cents. Eight endpoints sit on it — preview, taxable basis, federal withholding, FICA, stub compute, a NACHA file preview, filing reconciliation, and a four-eyes approval decision. It never pays anybody. The disbursement check is called against a partner that is not wired, so the verdict it returns is always “honest off, no partner” and the disburses flag it hands back is always false. That is not a configuration left switched off — there is no wired partner to switch on, and the route’s own test asserts the false. The eight are also stateless: you send the earnings and deduction lines each time and no pay run is stored, so there is no payroll history here to look back at. Read this as a calculation and preview surface that a payroll bureau’s numbers can be checked against, not as a replacement for the bureau. We say more about that below, under the heading where we lose.
Calc built · disbursement structurally off, not flag-off
Read in back-office-payroll-calc.ts:242 · back-office-erp.test.ts:286
Restricted money
A grant fund that keeps its own append-only record
Restricted grant money gets its own fund ledger with an append-only audit trail, so the question an auditor actually asks — show me that this grant paid only for what it was given for — is answered from the record rather than reconstructed from memory and email. One caveat, measured: there is a spend-gate function in that module with zero production callers. The audit trail and the fund append are reached; an automatic gate that refuses an out-of-scope spend is not wired. The record will show you the violation. It will not currently stop it.
Built · audit and append reached; the spend gate has zero callers
Read in cbuild-grants-fund-accounting.ts:399, :491 · the unwired gate at :351
Club and activity money
A per-club activity-funds ledger, persisted — with no screen in front of it yet
Student activity funds are the money most likely to be kept in a spreadsheet on one person’s laptop, and the money a district is most likely to be embarrassed by later. There is a real per-club ledger here: post an entry, read a club balance, pull an audit trail, and it persists through a repository rather than living in memory. It has no user interface. No console renders it, and that is a measured absence rather than an oversight in this description. Today it is an API. If a club treasurer is meant to touch it, somebody has to build the screen first, and that somebody is us, on a date we will give you rather than a quarter we will gesture at.
Built and persisted · API only, no console
Read in cbuild-activity-funds-ledger.ts:309, :399, :528 · store/activity-fund-repo.ts