CluPilotCloud/database
nexxo 8c3a79e258 fix(billing): hold only what cannot be matched, and let migrations roll back
Two findings from reviewing all five phases together rather than one at a time.

The webhook held every event a handler answered `null` to — but `null` also
means "already recorded" and "deliberately skipped", which is every checkout's
own invoice and every redelivered renewal. Their contract is right there, so
`replayHeldFor()` could never come back for them, and ordinary Stripe traffic
silted up the holding area until the weekly prune. It now holds only what it
genuinely cannot match — and if the contract appeared in the meantime, applies
the event instead of dropping it: the creation race is narrow, but losing a
cancellation to it would leave us serving someone who had left.

Also fixes a rollback that predates this work and blocks `migrate:fresh` on
MariaDB entirely: dropping the unique index on customers.user_id fails while
the foreign key added one migration earlier still depends on it. Verified by
building all 33 migrations from nothing on MariaDB, rolling every one of them
back, and building them again.

458 tests green. Codex clean on the full branch diff.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-26 13:57:11 +02:00
..
factories fix(billing): a paid order opens a contract, and provisioning obeys it 2026-07-26 11:10:00 +02:00
migrations fix(billing): hold only what cannot be matched, and let migrations roll back 2026-07-26 13:57:11 +02:00
seeders feat(admin): staff RBAC (spatie) + admin settings page 2026-07-25 15:47:42 +02:00
.gitignore chore: bootstrap CluPilot control-plane (Laravel 13, Docker stack) 2026-07-25 00:21:30 +02:00