8711f2c005
The #86687 self-lock preflight fired on every Windows `hermes update`: bitwarden.py's module-level cryptography import (fixed in #86782 / #86826-class change) meant cryptography._rust was ALWAYS mapped by the time the preflight ran, so the update exited 2 before even fetching and looped forever — including the Desktop in-app update (#86780). Two structural fixes so the guard can never re-brick the flow it protects: 1. Version-gated detection: _detect_self_loaded_native_modules() now consults _dependency_sync_would_rewrite(dist) — installed version vs the on-disk pyproject pins (base deps + all extras, env markers honored). A loaded module whose distribution the sync will not touch is no lock risk and is not reported. Unknown → fail closed. 2. Relocated deferral: the check no longer runs pre-fetch. It runs via _abort_dependency_sync_if_self_locked() immediately before each venv rewrite (git-path dep sync, ZIP-path dep sync, current-checkout venv repair) — AFTER the code swap. A deferral now leaves the user on NEW code with only the dependency install pending (completed by the next launch's marker recovery), instead of stranding them on the old checkout in an exit-2 loop. PyYAML's _yaml extension (loaded by every CLI process) joins the registry — with version gating it is now safe to list. Tests: version-gate unit coverage (no-change skip, stale pin, missing dist, extras, markers, fail-closed None), deferral wiring (marker + gateway resume + exit 2), placement guards (no detector call pre-fetch; guard present at git/ZIP sync), and subprocess-verified import hygiene (import hermes_cli.main and the update --check dispatch never load cryptography._rust). Follow-up to #86687 (Halldrix's #83590 salvage — the preflight's intent stands as defence-in-depth; this makes it fire only when true). Fixes #86735 Fixes #86780 Fixes #86781