Both my and gspeed's solutions are provably brick safe
I guess this assumes the latest firmware update contains a flawless app, and although the bootloader is choosing the latest valid version like is should, if that firmware for example had a flaw which prevented loading the next firmware update then you are stuck running that version and there is now no way to update firmware.
You are right, I was fixated on the safety of the update process itself, e.g. against power loss at any timing, corruption while writing.
That is easy to prove. Old app always stays bootable, but if new app
also becomes bootable and prevents entering the update process, the the device is effectively bricked, even if it technically runs a valid application.
To protect from this, some sort of "boot the old app" signal to the bootloader helps, but doesn't guarantee anything - a more subtle flaw that allows one update more to succeed would wipe the old, working app. So would a new update operation which starts (erases) but never finishes.
The only flawless version is that bootloader itself is the one which
receives and does the update, which doesn't happen in either mine or gpseed's or nctnico's examples. If the update interface is simple, say, UART, then it's obvious to do it like this. But if it's like in our case, that the firmware connects to the production server through TLS authentication&encryption, and updates are served through the same path, then that would mean bootloader needs to do all of
that. Fair enough, it could do it, and the app would reuse the same networking code to avoid wasting flash in duplicated code. But then the question of updating the bootloader arises. Sooner or later the TLS / networking code needs an update, and that means the bootloader needs to be updateable, which means same A/B concept for the bootloader, which means the same risk of putting technically correct but functionally broken code in place we just discussed. We just changed names of parts (app -> bootloader), not the actual structure.
There is silver lining through: this risk of "broken update" can be mitigated by good
testing. Test well before releasing. Release in waves with canaries. This is what we do. Final simple test that after update and wait of a few days, another new update passes, in different device configurations.
Testing doesn't help if the flashing process physically is sensitive to power loss or data corruption, though. Which makes these two failure modes different. It's inexcusable to have an update process which bricks the device on power loss in 2026, in my mind.