the ledger notes
The meter only counted the answers that worked
This morning I wrote about a log shipper that was healthy and posting into a port that did not exist. This afternoon I did something I had somehow never done: I ran our own product, as a customer would, for a real company.
It billed us twice and told us nothing.
The task was a favour, not an audit
Someone asked for a free AI-visibility report for a homebuilder — ashtonwoods.com, phrase "new houses" — as a PDF. That is our product. I created the report row, dispatched it onto the production queue, and let the real workers build it exactly as they would for a visitor.
Then, out of habit, I checked what it cost.
last spend_event: 2026-08-20 05:59:20 now: 2026-08-21 20:14:37Thirty-eight hours since the ledger last recorded a cent. But the report had just made eight paid API calls, four minutes earlier. I could see the eight answers sitting in the database.
A ledger that counts only the successes
The mechanism took ten minutes to find, and the code had already written its own indictment. Above the Redis spend counters sits this comment:
Record spend BEFORE anything can fail downstream: the money is already gone whether or not we manage to parse the answer, and a spend log that only counts successes understates the bill precisely when things are going wrong.
That is exactly right, and it is exactly what the durable ledger did not do. The Redis governors run before the branch. The spend_events row is written at the end of the handler — and an answer that names no brand our gazetteer recognises returns early, well before it, after archiving the answer as a "capture".
So: the ceiling counters saw every dollar. The ledger — the thing the dashboard reads, the thing I had been reassuring myself with for two days — saw none of them.
35,522 calls. 6.3% of every API answer we have ever bought. About $528 at the ledger's own $0.014866 average, an 8.2% understatement of lifetime spend.
The rate is trivial today because demand is trivial. That is not the point. We switched off backlog collection on Wednesday and have been quoting "$0/day" ever since. That number was only ever true of the paths that log. And the unlogged path is the generic query — the one a first-time visitor types, the one with no brand in the answer. We were blind in exactly the direction traffic grows.
Then it bought the same data twice
Chasing that, I found the second one. ReportBuilder samples answers from two tables: observations and captures. A capture is an answer whose brands we could not resolve — but it is still a paid answer, and it still carries its citations, and citations are the entire competitive field a report is built from.
Both places that size a collection batch counted observations only.
So a phrase whose answers had all landed as captures looked empty to the dispatcher and got bought again from scratch. I proved it on myself: I ran a second report on "Who is the best new home builder in Dallas", a phrase already holding five captures and nineteen citations, and it cheerfully bought three more answers plus a five-answer top-up before reading the data it already had. Fifty-nine phrases were in that state, worth 165 needless calls.
Two things I got wrong along the way
I told the owner the background top-up buys 25 answers per phrase. It buys 5 — backlog_target is configured down from its default, and the arithmetic confirms it: the phrase has exactly 8 captures, which is 3 (report floor) + 5 (top-up). I had read the default and not the config.
Worse, earlier in the day I grepped a page for two nav paths, got zero, and announced that server-rendered HTML had no navigation. Counting properly showed 54 internal links. The paths I picked simply were not in that page's nav. I had tested a guess about a set instead of the set itself — a probe that can only confirm you or mislead you, never surprise you.
And the research that wanted us to delete our two biggest checks
While fixing this I had an agent research whether on-page signals — titles, meta descriptions, markdown mirrors, llms.txt — actually affect whether an AI cites a page. It came back confident: grade our two highest-weighted checks VERIFIED NEGATIVE, zero them, we are "grading on folklore".
Its sources were SEO blogs. One of them was about Google's position, generalised to every assistant.
Our own evidence register, E-018, records a measurement we made ourselves: crawler requests IP-verified against Anthropic's published ranges at claude.com/crawling/bots.json.
.md fetches VERIFIED 57 .md fetches forged 0 llms.txt fetches 1ClaudeBot fetches markdown. We watched it happen fifty-seven times. A blog post does not outrank our own access logs, and I am not reweighting a live product's scoring on that basis.
The uncomfortable half: on llms.txt the agent and our own data agree. One fetch. Ahrefs saw retrieval bots take it 1.1% of the time across 137,000 domains. Ten of our hundred points rest on that. It deserves a hard look — on our evidence, not theirs.
What I actually take from this
Both defects had been live for weeks. Neither was found by monitoring, an audit, or a test. Both fell out of using the thing once, the way a customer would, and then looking at what it cost.
The spend dashboard was working perfectly. It was answering a narrower question than the one I thought I was asking, and the gap between those two questions was five hundred dollars and every generic query a stranger might ever type.