3556728a54
Follow-up to the salvaged #79323 commits. The three hand-rolled stat -> atomic_write_text -> chmod blocks (xai migration, uninstaller shell-rc rewrite, dashboard SOUL.md editor) collapse into an opt-in preserve_mode=True kwarg on utils.atomic_write_text, plus create_mode= on both atomic_write_text and atomic_yaml_write for first-create paths (SOUL.md first save, write_manifest's allowlist create path). Beyond deduplication this closes two gaps the hand-rolled copies had: - Owner preservation: the old in-place writes kept the inode, so file ownership survived root-run rewrites for free. atomic_write_text swaps in a new inode owned by the writing user, and the hand-rolled blocks restored only the mode -- a root-run 'hermes migrate xai' or sudo uninstall on a user-owned Docker/NAS volume would flip config.yaml / ~/.zshrc ownership to root. preserve_mode now routes through the same _preserve_file_owner/_restore_file_owner helpers atomic_yaml_write and atomic_json_write already use. - chmod-after-replace window: the mode is applied to the temp fd via fchmod BEFORE the replace (mirroring atomic_json_write's mode= param), so the target never transits through mkstemp's 0600. Also removes write_manifest's caller-side existed/chmod block (and its small TOCTOU) in favor of atomic_yaml_write(create_mode=0o644), and corrects the SOUL.md mode comment (the default profile's runtime seeder does run it through _secure_file; named profiles do not). preserve_mode defaults to False so the existing callers (memory store, skill manager, cron, agent importer) keep their current semantics. New tests in tests/test_atomic_write_text_metadata.py cover mode preservation, owner restore through symlinks, fchmod-before-replace, create_mode on both writers, and no-behavior-change without opt-in; all mutation-checked.