The last of the audit findings — the small ones. Two turned out to be checks pointing at nothing, and one is a missing index on the table a billing page reads most.
What you need to do
Apply the new migration when convenient. It is optional and additive, and
worth doing before your invoice list gets long. If your code reads the
auditId returned when an operator resolves a stuck billing operation, it is
now a string rather than a number.
Your invoice list had no index
The invoice table shipped with four indexes and every one of them answers a question the background worker asks — which account owns this charge, this payment, this Stripe invoice. Those matter: a dispute arrives carrying a charge and has to reach the right account without asking Stripe.
Nobody indexed the question your customer asks, which is "show me my invoices". Every other table had an account index. This one, the one a billing page reads most, did not.
On 200,000 invoices across 20,000 accounts, listing one account's twenty most recent invoices read the entire table and sorted it: 12.4ms. With the index, 0.04ms.
migrations/0004_billing_invoices_principal_index.sql adds it, and refuses to
run on a table above 50,000 rows — naming the command to run by hand instead,
because building an index under a write lock would block the webhook worker
that writes invoices. Both the refusal and the recovery it describes are
tested, since an instruction that leads back to the error it was meant to
resolve is worse than no instruction.
The requirements map is checked now, not just written
Version 1.0.0 was withdrawn partly because 51 entries in the traceability file named tests that did not exist. They were written, and that was the end of it — nothing stopped the next one from naming nothing.
There is now a test that walks the whole map: every entry must resolve to a real test, every test claiming a requirement must be one the map lists, and every requirement must carry a status the release gate can actually read. An unreadable status is worse than a wrong one, because it disappears from both the complete and the incomplete totals.
Writing it turned up two things about checks rather than about the code. The first version only looked at test files and reported six conformance checks as missing — they exist, in a library file beside them; a check that cannot see where the evidence lives invents problems. And asserting the reverse direction reports 32 entries that are not errors at all, so that assertion was left out deliberately, with a note saying why. A check that fails 32 times on its first day gets switched off rather than obeyed.
Two functions that looked like they contradicted each other
One said a stuck operation could move to "completed"; the other refused to let it. Reported as a bug. It is not one: the move is real, and the operator recovery path makes it — with evidence, an identity, and an audit record written in the same transaction. What is refused is the automatic path taking it, because the entire reason the operation is stuck is that nobody yet knows whether you were charged.
One describes what is possible, the other guards what a worker may do. Both now say so, and a test pins the difference so it cannot be tidied into agreement.
Smaller
A test named "refuses a path that climbs out of the configured origin" asserted the opposite — that such a path is accepted and safely normalised, which is correct behaviour. Renamed, because someone scanning test names for coverage would have counted a protection that does not exist.
And the package description claimed one of its migrations needed special handling from your migration tool. It does not, and never did in the shipped version — that note described a design replaced before release, and was telling you to work around a problem you do not have.
Checksum
production-stripe-billing-1.3.0.zip
sha256 48a41e33a9f9d1ac264cd490d9fb0a9d9d56c383c880d923d7c7d5f9d173b9b9
bytes 441317
Upgrading
Replace the folder, apply 0004 when you are ready, and re-run the suite.
Nothing else changes.

