restore_heartbeat_watches entered _profile_scope_for_source for every routed
session on every poll. Each entry hydrated the profile secret scope and rebuilt
the terminal policy, and both re-parsed the profile config.yaml from disk, so N
routed sessions cost 2N YAML parses per poll even though nothing changed.
- Group entries by resolved profile home and enter the scope once per group.
- Add utils.load_yaml_file_readonly (file_signature-keyed cache) and use it in
env_loader._load_secrets_config and terminal_scope.build_profile_terminal_scope,
which were both open()+fast_safe_load per scope entry. Present-but-unparseable
still fails closed: parse errors propagate and are never cached.
Measured on a 3-profile host: one _profile_runtime_scope enter/exit 1.80 ms -> 0.11 ms.
(cherry picked from commit c6b16629bd38799bbf166206c73a6140a9559a61)