* fix: stop unexpected NodeNum regeneration from PKI key loss
A node's NodeNum is derived from its PKI public key
(my_node_num == crc32(public_key)), so it changes only when the keypair is
regenerated -- which happens only when a keygen path runs while
config.security.private_key.size != 32. Two paths could trigger that without
the user intending a new identity:
1. Boot: loadFromDisk() collapsed every non-success loadProto() result for the
config file into installDefaultConfig() (preserveKey=false), which memset()s
config and wipes the private key. loadProto() distinguishes DECODE_FAILED
(file present but undecodable/undecryptable) from OTHER_FAILURE (absent), so
a transient/corrupt read silently minted a new identity. A new
configDecodeFailed flag now gates the boot keygen and the boot config-save
directly (not via region, so it survives the userprefs/region overrides that
run later in loadFromDisk): identity is frozen, the unreadable file is left
intact for a later clean boot to recover, and the node boots radio-silent
(region UNSET, TX off). A genuinely-absent config still gets defaults + keygen.
2. AdminModule security SET: config.security = c.payload_variant.security
wholesale-clobbered the keypair, then regenerated whenever the incoming
private_key.size != 32. A client editing an unrelated security field without
round-tripping the private key would re-mint identity. The existing keypair
is now preserved when the incoming config omits it; first provisioning and
key import are unchanged. Intentional reset goes through factory_reset.
Native PlatformIO unit suite (Docker): 499/499 test cases pass.
* test: cover security keypair preservation; clarify load-failure comment
Addresses PR review feedback:
- Add native unit tests (test_admin_radio) asserting handleSetConfig preserves the
existing identity keypair when a security SET omits the private key, and applies a
full supplied keypair (key import). Guards against reintroduced NodeNum/keypair
regeneration. AdminModuleTestShim (now a friend) defers saves so the lightweight
harness doesn't saveToDisk an uninitialized node database.
- Clarify the non-DECODE_FAILED config-load comment: OTHER_FAILURE / NO_FILESYSTEM
cover an absent or unopenable file, not just first boot.