Files
hermes-agent/tests/hermes_cli/test_home_directory_diagnostics.py
T
Teknium e6df8675f5 fix(config): diagnose unavailable home links without claiming YAML corruption
Keep externally managed directory links and permissions intact during home
initialization. Refuse missing targets rather than creating directories on an
unmounted volume's underlying filesystem. Report link, target, mount and access
guidance through doctor while preserving config.yaml.

Extract the home initialization phase into config_home, and memoize successful
resolved aliases so plugin discovery cannot repeat chmod after losing the
symlink spelling. Live Linux doctor PTY A/B verified directory and root links,
plain paths, missing targets, mount-style missing paths and file conflicts.
Targeted invariant tests are queued under the campaign's shared serial lock;
this checkpoint is not a unit-suite or merge-readiness claim.

Inspired by #104774 and #103735; deliberately does not auto-create external
targets or silently ignore an unavailable sessions directory.

Co-authored-by: ca-shrimp <320556551+ca-shrimp@users.noreply.github.com>
Co-authored-by: Craig Richardson <craigrichardson@Craigs-Mac-mini.local>
2026-09-07 04:46:37 -07:00

67 lines
2.5 KiB
Python

"""Home initialization must respect operator-owned links and diagnose storage."""
from pathlib import Path
import pytest
from hermes_cli import config
@pytest.mark.linux_only
@pytest.mark.parametrize("subdir", (".", *config._HERMES_HOME_SUBDIRS))
def test_unavailable_directory_links_are_diagnosed_without_creating_targets(tmp_path, monkeypatch, subdir):
home = tmp_path / "hermes"
link = home / subdir
link.parent.mkdir(parents=True, exist_ok=True)
target = tmp_path / "unmounted" / "external"
link.symlink_to(target, target_is_directory=True)
monkeypatch.setattr(config, "get_hermes_home", lambda: home)
monkeypatch.setattr(config, "is_managed", lambda: False)
config._HERMES_HOME_ENSURED.discard(str(home))
issues = config.validate_config_structure()
assert issues
text = " ".join(str(issue) for issue in issues)
assert str(link) in text and str(target) in text
assert "mount" in text.lower() and "setup" not in text.lower()
assert link.is_symlink() and link.readlink() == target
assert not target.parent.exists()
assert str(home) not in config._HERMES_HOME_ENSURED
target.mkdir(parents=True, mode=0o750)
config.ensure_hermes_home()
assert link.is_symlink() and target.stat().st_mode & 0o777 == 0o750
assert str(home) in config._HERMES_HOME_ENSURED
@pytest.mark.linux_only
@pytest.mark.parametrize("linked", ("plain", "logs", "home"))
def test_initialization_preserves_external_directory_modes(tmp_path, monkeypatch, linked):
home = tmp_path / "hermes"
target = tmp_path / "shared"
target.mkdir(mode=0o750)
if linked == "home":
home.symlink_to(target, target_is_directory=True)
else:
home.mkdir()
curator = target / "curator"
curator.mkdir(mode=0o750)
if linked == "logs":
(home / "logs").symlink_to(target, target_is_directory=True)
monkeypatch.setattr(config, "get_hermes_home", lambda: home)
monkeypatch.setattr(config, "is_managed", lambda: False)
config._HERMES_HOME_ENSURED.discard(str(home))
config.ensure_hermes_home()
monkeypatch.setattr(config, "get_hermes_home", lambda: home.resolve())
config.ensure_hermes_home()
assert all((home / name).is_dir() for name in config._HERMES_HOME_SUBDIRS)
assert (home / "SOUL.md").is_file()
if linked != "plain":
assert (home if linked == "home" else home / "logs").is_symlink()
assert target.stat().st_mode & 0o777 == 0o750
assert curator.stat().st_mode & 0o777 == 0o750
else:
assert (home / "logs").stat().st_mode & 0o777 == 0o700