v5.91.0 26 June 2026

Test Harness Teardown Fixes

Two test harnesses were creating scratch organisations in the production database and not cleaning them up. The root cause was the same in both cases, and the fix is the same discipline the erasure service has always used.

What changed

The retention-purge and credit-note allocation race harnesses both had teardown functions that issued a database sanction and a delete as separate statements on an autocommit connection. The sanction is a transaction-local marker that permits the organisation delete to proceed; on an autocommit connection each statement runs in its own implicit transaction, so the marker expired before the delete ran. The delete was silently refused and the scratch organisation stayed in the database.

In the credit-note race harness, the teardown also pre-deleted child rows before attempting the organisation delete. Because the cascade on every organisation-scoped foreign key has been unconditional since migration 119, the pre-deletes were redundant and introduced additional failure surface: a throw on any child delete would abort the teardown before the organisation delete was ever reached.

Both teardowns now wrap the sanction and the delete in a single explicit transaction, matching the pattern the erasure service has used from the start. The credit-note teardown drops the redundant child pre-deletes and relies on the cascade. Both add a catch block that logs failures rather than swallowing them, so a future teardown problem surfaces in the test output rather than accumulating silently.

Why it matters

Leaked scratch organisations were accumulating in the live database on every test run. They were empty and caused no financial or data integrity issue, but they appeared in the admin panel alongside real accounts and cluttered operational views. More importantly, a silent teardown failure is a diagnostic gap: if a guard or a cascade breaks, the failure should be loud, not invisible.

The fix closes the gap. The harnesses now clean up correctly on every run, and a teardown failure produces a console error rather than a missing row.

How it is proven

The retention-purge suite (six cases) was run twice against the live database after the fix landed. After the second run, zero rows matched the scratch organisation name and slug patterns. The same verification was applied to the credit-note race suite (one case). Both passed.

The deployment pipeline ran all 408 executable proof anchors across 36 test harnesses. Zero violations. The erasure guard, the cascade behaviour, and the transaction-local sanction semantics are all covered by existing anchors.

35 scratch organisations accumulated since the harnesses shipped were purged directly, using the same per-organisation sanction pattern, after confirming each had zero transactions, zero invoices, and no company number.

Breaking changes

None.