An NDI AI Employee. Routine bookkeeping across one client engagement — a bank feed read against the documents held for it, every attachment judged on whether it actually stands, postings proposed only where a written rule covers them, and the chases and client replies drafted.
It prepares and never acts. No journal is posted. No document is attached in anybody's accounting system. Nothing is sent to the client. No period is closed. No coding question is decided.
The catalogue for this employee asks for the first two. That deviation is deliberate, and it is named in the reports, in the workbook, in the skill and here — rather than discovered later by whoever was holding the wrong figures.
A bank line is not a transaction, and the firm's own numbers get better when it is booked as if it were
The feed gives an amount, a date and a string somebody's payment system wrote for its own reasons. That string is not a supplier, a category or a tax treatment. What was bought, and whether the tax on it can be reclaimed, come from a document — and until one is attached the line is an obligation rather than an entry.
The harm runs the way it runs in every desk measured on speed. A line booked on a guess closes. It leaves the unbooked list, stops being an exception, moves turnaround in the right direction and costs fewer hours than the one that had to be chased. Nobody complains, because nobody outside the firm can tell a line booked from evidence from a line booked from a plausible counterparty string.
So the figures are printed with that sentence attached. A report giving turnaround, exception count and hours per client on their own would be reporting the reward for the behaviour this package exists to prevent.
Second half: a rule is something a person wrote, on a day, under their name. The specification asks for confident matches to be posted and low-confidence ones queued. This package has no confidence. A posting is proposed because a named rule written on a known day says so, or it is not proposed at all — there is no third category in which this tool's opinion of its own guess counts as evidence. A score between nought and one cannot be asked what it meant, and there is nobody to ask.
And the date that governs is the date the transaction belongs to. Not the day the bank recorded it, and not the day somebody typed the row. Three dates sit on every line; confusing them is how a figure in a March report changes in May.
Amount is the whole of most automatic matching, and two documents for the same amount in the same month is not a coincidence worth remarking on — it is a Tuesday. Three things have to agree:
| the amount | within this firm's own tolerance, which is allowed to be nought and at a firm reclaiming tax on every line should be |
| the counterparty | the supplier's name has to appear in the string the bank wrote, not the other way round |
| the direction of time | a document cannot evidence a payment that happened before the document existed |
And one thing has to not be true: the document must not already be attached to something else. A document attached twice is a cost recorded twice and a tax claim made twice — which is the duplicate invoicing the specification escalates, seen from the other end. Both lines then have evidence and both reconcile, which is precisely why nothing else finds it.
Second half: tax reclaimed is a claim against the state, and a document from a supplier with no registration number on file is not evidence of one. The claim can still be entered, it still reduces what is owed, and the person assessed for it years later is the client rather than the firm. Each is named one document at a time and never as a percentage, because a percentage cannot be withdrawn.
A posting dated into it changes a figure a client has already used — quoted to a bank, put in a board pack, paid tax on. Both dates decide it: the same posting entered before the report date is ordinary work done early, and the entry date is the only thing that tells the two apart.
The unit is the engagement, not the firm.
A bookkeeping desk works a portfolio, and the catastrophic failure is not a leak to the outside world. It is one client's figures appearing in another client's report. Two clients of one firm may be competitors, and neither has agreed to the other seeing anything.
So segregation is structural. One run is one engagement. Every row of every table carries an engagement reference, and a row belonging to a different engagement is refused — naming the table, the row and both references — rather than filtered out. A filter discards silently, and working papers built from whatever survived one would be a partial set of somebody's books with nothing on them to say so.
Inside one engagement, the firm's own reasoning stays in the firm. No rule, no rule note and no account code reaches a chase or an answer: a chase naming the account tells the client which treatment was assumed, and a client who knows the assumption can produce a document that fits it. And a suspicion goes to the engagement partner and never to the person it is about — a chase reads exactly the same whether the document is late or does not exist.
No contact point appears anywhere in this repository, and no partial one either. Two of the four kinds of file here are drafts addressed to a client, so an address in the fixture would be a real address inside a file whose entire purpose is to be sent.
papers/working-papers.xlsx — the deliverable. Seven sheets,
opening on a note about what the workbook is not, then on the lines
with no evidence before any total:
Read me first what this is and what it is not
Nothing stands printed first, before any total
Questions every coding decision, with no proposed answer
Proposed postings each with its rule, its author and its date
Tax claims named per document, never as a rate
Closed periods what would change a figure already sent
The figures with the sentence attached
Every total is a formula over exactly the rows above it, never a number this package computed. A hardcoded total is right on the morning it is written and wrong, silently, the first time anybody deletes a row.
Four kinds of text file, each holding what the others must not:
chases/ to the client · the payment quoted back, the name of
who will be in touch, and nothing else
answers/ to the client · their own words verbatim, and a blank
where a person writes the answer
papers/ the firm's own file · the rules, the account codes,
the questions, and everything for the partner
proposals/ one per named person · only the work against their
own name
plus register.txt, which is an output and never an input.
mandate_report.py can this engagement be worked at all M01–M07
line_report.py what the bank says, and what is unbooked L01–L07
document_report.py what evidence is held, and what it backs E01–E08
match_report.py what attaches to what, and whether it X01–X08
should
posting_report.py what is booked, and on whose authority P01–P08
period_report.py what is closed, and the figures C01–C08
build_papers.py the workbook, the chases, the answers, D01–D09
the firm's file and the proposals
55 codes · 35 refused switches · 94 named refusals. Exit 0 clean · 1 findings · 3 refused.
python3 scripts/build_papers.py \
--mandate engagement-mandate.yaml \
--team firm-team.yaml \
--policy booking-policy.yaml \
--out papers/Eleven tables — people, accounts, rules, suppliers,
bank_lines, documents, matches, postings, periods,
messages, chases — described in
references/the-eleven-tables.md, which is generated from the
fixture's own headers so it cannot drift from what the scripts read.
Three governing files, all three of which ship empty. The first run of a fresh install refuses once with 28 gaps across all three listed together. Three refusals in a row teaches a reader to fill in one line and run again, and a person doing that never sees the shape of what they are being asked.
what_happens_to_a_line_with_no_document has no default:
leave-it-unbooked |
nothing enters the books without evidence, and the ledger stops agreeing with the bank by a growing amount every month. A reconciliation that never reconciles is one nobody reads. |
book-it-to-suspense |
the ledger agrees and the unexplained amount sits in one visible place. Suspense then becomes the permanent home of everything nobody chased. |
Both cost something. They are not the same firm, and which of those two failures a firm lives with is not this tool's to choose.
Seven settings ship reading no, written out so that switching one on
is a deliberate edit somebody made to a file: post_a_journal,
attach_a_document_in_the_clients_system, send_the_client_anything,
close_a_period, treat_this_tools_own_confidence_as_a_rule,
book_into_a_period_already_reported_on,
reclaim_tax_with_no_registration_number.
Sample Input/ — 14 files, an invented firm (Threnholm & Vaize)
working an invented client (Calderwick Marine Fittings) over
February and March 2026, as at 10 April.
Every person, supplier, account, bank line, document, rule and registration number is invented. That matters more here than it looks: a real fixture in this shape would be one company's bank account for two months, the suppliers they buy from, what they spend on each, the tax they reclaim and the periods they have already reported. Published, it is a competitor's homework.
Sample Output/ — 141 files, 88,801 bytes, including 123
refusals, every one produced by a real run against the committed
input.
python3 "Build Tools/fixture/verify.py"722 checks, no failures, deterministic across runs, in both layouts — the build tree and this repository. Eight passes:
| COVERAGE | every code, every refused switch and every named refusal reached, or recorded with the reason |
| CONTACT | no contact point and no partial one, over 8 probes with 10 planted examples proving the patterns match |
| PUBLICATION | 127 product names blocked, 47 candidates written down as deliberately absent with the reason |
| MANIFEST | the description inside 1024 in characters and in bytes |
| INVARIANTS | read off the written files and off the cells of the workbook, each with a check that the case was not vacuous |
| NEGATIVE | 30 findings go quiet on a variant, 25 have a written reason, 2 start firing on one |
| MUTATION | 11 gone and 8 appears — the second sense is the one that tests a guard the package enforces on itself |
| HYGIENE | width in characters, nothing idle, every variant proved to change something, every workbook total proved to cover exactly the rows above it |
117 variants, each with a written purpose and each proved to change what some script prints.
New Digital Intelligence · gp-02-bookkeeping-assistant