ouroboros/tests/test_git_ops_default_roots.py
Ouroboros 32800622a0 test: hermetic data-root class pins (red on 8827fd2c) + lane ledger
tests/test_git_ops_default_roots.py is the D13 hook suite (pre-init roots
follow the env per call; pinned roots win; the _log_supervisor writer
lands in the env root; the issue #455 subprocess repro under a throwaway
HOME). tests/test_hermetic_data_root.py pins the class: append_jsonl
fails closed on a live-root write, and the serial E2E pin runs the exact
leaking suites under throwaway HOME + full env isolation with an empty
pre/post inventory delta of the live-shaped root. All six pins verified
RED on a git-archive copy of base 8827fd2c. D13 flips pending->done;
lane section appended to LEDGER_CORRECTIONS.
2026-09-01 15:56:39 +00:00

102 lines
4.2 KiB
Python

"""D13 pin: supervisor/git_ops pre-init roots follow the OUROBOROS_* env.
The defect class (issue #455): ``git_ops.REPO_DIR`` / ``DRIVE_ROOT`` were
hardcoded ``~/Ouroboros`` defaults, resolved once and blind to the env, so a
fully env-isolated process still wrote ``managed_update_stash_restored
(context=test)`` into the LIVE ``data/logs/supervisor.jsonl`` through
``update_merge._log_supervisor``. Pre-init the roots now resolve PER CALL from
the env via the config path SSOT (module ``__getattr__``); ``init()``, worker
rebinds and test monkeypatches pin them as real module attributes.
"""
import os
import pathlib
import subprocess
import sys
import pytest
import supervisor.git_ops as git_ops
import supervisor.update_merge as update_merge
REPO_ROOT = pathlib.Path(__file__).resolve().parent.parent
_UNSET = object()
def _unpin(request, name: str) -> None:
"""Remove a pinned root attribute for the test, restoring it afterwards.
``monkeypatch.delattr`` cannot do this: module ``__getattr__`` makes
``hasattr`` true while the attribute is absent from ``__dict__``.
"""
saved = git_ops.__dict__.get(name, _UNSET)
git_ops.__dict__.pop(name, None)
def _restore():
git_ops.__dict__.pop(name, None)
if saved is not _UNSET:
setattr(git_ops, name, saved)
request.addfinalizer(_restore)
def test_unpinned_roots_follow_env_per_call(request, monkeypatch, tmp_path):
_unpin(request, "DRIVE_ROOT")
_unpin(request, "REPO_DIR")
monkeypatch.setenv("OUROBOROS_DATA_DIR", str(tmp_path / "iso-data"))
monkeypatch.setenv("OUROBOROS_REPO_DIR", str(tmp_path / "iso-repo"))
assert git_ops.DRIVE_ROOT == tmp_path / "iso-data"
assert git_ops.REPO_DIR == tmp_path / "iso-repo"
# PER CALL: a later env change is followed, not cached.
monkeypatch.setenv("OUROBOROS_DATA_DIR", str(tmp_path / "late-data"))
assert git_ops.DRIVE_ROOT == tmp_path / "late-data"
assert git_ops.current_drive_root() == tmp_path / "late-data"
def test_pinned_roots_win_over_env(monkeypatch, tmp_path):
monkeypatch.setattr(git_ops, "DRIVE_ROOT", tmp_path / "pinned")
monkeypatch.setenv("OUROBOROS_DATA_DIR", str(tmp_path / "env-root"))
assert git_ops.DRIVE_ROOT == tmp_path / "pinned"
assert git_ops.current_drive_root() == tmp_path / "pinned"
def test_log_supervisor_writes_into_env_root_when_unpinned(request, monkeypatch, tmp_path):
"""The issue #455 writer path itself: _log_supervisor lands in the env root."""
_unpin(request, "DRIVE_ROOT")
monkeypatch.setenv("OUROBOROS_DATA_DIR", str(tmp_path / "iso"))
update_merge._log_supervisor({"type": "hermetic_probe", "context": "test"})
probe = tmp_path / "iso" / "logs" / "supervisor.jsonl"
assert probe.is_file()
assert "hermetic_probe" in probe.read_text(encoding="utf-8")
@pytest.mark.serial
def test_pre_isolation_import_still_writes_isolated_root(tmp_path):
"""Red on the pre-fix tree: all four OUROBOROS_* set, module imported, and
the write STILL went to the (fake) home live root instead of the isolated
one — the exact issue #455 reproduction, hermetic via a throwaway HOME."""
fake_home = tmp_path / "home"
iso = tmp_path / "iso"
fake_home.mkdir()
env = {**os.environ,
"HOME": str(fake_home),
"OUROBOROS_APP_ROOT": str(iso),
"OUROBOROS_REPO_DIR": str(iso / "repo"),
"OUROBOROS_DATA_DIR": str(iso / "data"),
"OUROBOROS_SETTINGS_PATH": str(iso / "data" / "settings.json")}
env.pop("OUROBOROS_PYTEST_ACTIVE", None)
env.pop("OUROBOROS_TEST_LIVE_DATA_ROOT", None)
script = (
"import supervisor.update_merge as um\n"
"um._log_supervisor({'type': 'hermetic_probe', 'context': 'test'})\n"
)
proc = subprocess.run(
[sys.executable, "-c", script], cwd=str(REPO_ROOT), env=env,
capture_output=True, text=True, timeout=120,
)
assert proc.returncode == 0, proc.stderr[-2000:]
live_shaped = fake_home / "Ouroboros" / "data"
leaked = [str(p) for p in live_shaped.rglob("*") if p.is_file()] if live_shaped.exists() else []
assert not leaked, f"write leaked into the live-shaped root: {leaked}"
assert (iso / "data" / "logs" / "supervisor.jsonl").is_file()