The only way I could think of to implement #1 is to continually CRC the program memory against a known value and if it fails the CRC check retrieve a known good copy of the program from another source. There's many gotchas to this: the stored CRC value might get corrupted somehow or there may be no 'other source' of your program readily available - user intervention might be required. Also, since the corruption might be anywhere in program memory it might affect the CRC code so it doesn't run or the bootloader so it won't function (you can go as far down the rabbit hole as you like in this regard).
You could combine the CRC check with a watchdog timer or external health monitor such that if the CRC code fails to run or fails to produce the correct result the microcontroller will reset and run a special recovery bootloader instead of the application. What the recovery bootloader would do depends on your application: it could somehow alert the users that a new program load is needed or if you designed for it attempt to retrieve a pristine copy of your program from external memory if available. The same rabbit hole as above applies: how do you know that the external memory hasn't been corrupted? Or, if the CRC is corrupted your program would be right but always fail the check.
At some point your device can't handle this type of error by itself and if you want the thing to keep running you'll have to bring a development system and programmer to it to bring it back up. This type of scheme will probably add a few 9's to your reliability though even if it's not perfect.