MickaelK is right, the glob ordering isn't perfect but for a small project with precisely 4 patches, this is currently "good enough".

Ben: Transactions would be sensible if the database supported them - although perhaps having each patch make that decision would be more flexible. Each patch inserts its own patch_history row, since they can also be run manually.

I don't have rollback in this solution and that's an active decision on my part that I will take the risk of needing to live fix over the trouble of writing rollback patches. If this project starts averaging more than one hit a day, I might feel differently about it :)

As I say, not perfect but it is practical and it beats 90% of what I see so I thought I'd share. Seeing these comments has made me think and I might make some improvements - thanks everyone!