d595e636c8
A user who picked `deepseek-v4.1-flash` on their own custom endpoint kept landing on `deepseek-v4-flash-0731`. Three sites each "helped" by diffing the pick against a catalog and moving it: - hermes_cli/models_validate.py: the shared catalog matcher auto-corrected any id within difflib ratio 0.9 of a listed one (`corrected_model`), and model_switch applied it. Version bumps, dated snapshots and qualifiers all sit inside 0.9 of a sibling, so a newer release the listing lacked was swapped for the older one under the user's label. The matcher now does exact membership -> suggestion text only; the id goes to the wire verbatim and a genuine typo is refused with the listed siblings named. Every branch that carried the correction (live listing, static catalog, curated fallback, MiniMax, Anthropic, custom, OpenRouter preset base) loses it in one place. - hermes_cli/model_switch.py: a `providers.<key>` endpoint reached by its bare key (the slug Desktop picker rows carry) validated as a built-in and hit the hard-rejecting live-listing branch; the same endpoint as `custom:<key>` soft-accepted. Both spellings now validate as the user's custom endpoint. - apps/desktop: `manualPickRemoved` (composer reseed) and `reconcileSelectionAfterCatalogRefresh` (Refresh Models) retargeted a sticky pick to the profile default / the row's first model whenever the provider row did not list it. Rows are hints (discovered, curated, capped); the gateway's switch result is the only authority on a pick. Both helpers are removed; the pick stays put. Tests: change-detectors pinning the swap are rewritten as invariants (never `corrected_model`; unlisted id on a user endpoint is kept and warned; typo is refused with a suggestion); proven red on origin/main.