Three layered causes:
1. config_v5_migration had no executor phase — structurally impossible
to mark complete.
2. StateManager.loadConfig unmarshaled into typed config.Config, but
install template writes version as JSON number while Config types
it as string — migration completion never persisted to disk.
3. readConfigVersion only parsed float64, so normalized string read 0
and re-triggered.
Fix: StateManager is map-based (immune to field-type drift). Executor
has real config_v5 phase (bump + mark complete). parseConfigVersion
accepts both number and string. validateMigration uses MkdirAll then
stat (fixes false 'state dir not found' on Windows).