Files
orchestrator/tests/test_git_worktree_perm.py
claude-bot a98d605477 feat(fs): legacy root-owned ownership detect + actionable worktree error (ORCH-057)
Follow-up ORCH-040: legacy root:root files in /repos broke worktree creation
under uid 1000 with a raw "Permission denied" (agent never started, no diagnosis).
Three additive, kill-switch-reversible layers; STAGE_TRANSITIONS / QG_CHECKS /
check_* / machine-verdict keys / DB schema are byte-for-byte unchanged.

- D1: ensure_worktree classifies the permission class and raises an actionable
  RuntimeError (cause + chown command + INFRA.md ref); non-permission errors keep
  the prior raw-stderr contract; kill-switch off -> contract 1:1 as before ORCH-057.
- D2: new never-raise leaf src/fs_normalize.py — scan_ownership (TTL-cached,
  early-exit per root), applies()-first scope (empty CSV -> self-hosting only),
  opt-in normalize() that chowns ONLY when privileged (no-op under uid 1000).
- D3: best-effort startup detect in main.lifespan (WARNING + Telegram on mismatch,
  never-fatal); read-only fs_ownership block in GET /queue; POST /fs-normalize/check.
  Claim is NOT blocked — the clear early outcome is delivered by D1 at launch.
- Docs/config: .env.example flags + CHANGELOG (architecture README / adr-0031 /
  INFRA.md procedure already landed on the branch).
- Tests: test_fs_normalize.py, test_git_worktree_perm.py,
  test_fs_normalize_startup.py, test_api_queue.py (TC-01..TC-12). Full suite green.

Refs: ORCH-057
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-10 03:03:34 +03:00

140 lines
5.2 KiB
Python

"""ORCH-057 D1: actionable worktree error on a legacy-ownership permission failure.
TC-01 / TC-02 (04-test-plan.yaml): a permission-class ``git worktree add`` /
``os.makedirs`` failure must surface an actionable RuntimeError (cause + healing
command + INFRA.md ref), while a NON-permission failure keeps the prior raw-stderr
contract (no meaning substitution). No real chown / no writes to /repos — failures
are simulated via monkeypatched ``subprocess.run`` / ``os.makedirs``.
"""
import os
import tempfile
import pytest
_test_db = os.path.join(tempfile.gettempdir(), "test_orchestrator_wt_perm.db")
os.environ["ORCH_DB_PATH"] = _test_db
os.environ["ORCH_REPOS_DIR"] = tempfile.gettempdir()
os.environ["ORCH_GITEA_TOKEN"] = "test-token"
os.environ["ORCH_PLANE_API_TOKEN"] = "test-token"
from src import git_worktree
from src.git_worktree import ensure_worktree
class _R:
"""Minimal CompletedProcess stand-in."""
def __init__(self, returncode, stderr=""):
self.returncode = returncode
self.stderr = stderr
self.stdout = ""
@pytest.fixture
def main_repo(tmp_path, monkeypatch):
"""A bare-minimum main clone dir so ensure_worktree gets past the existence check.
repos_dir/<repo> must be a directory; worktrees_dir points at a fresh tmp path.
The actual git calls are monkeypatched per-test.
"""
repo = "orchestrator"
repos_dir = tmp_path / "repos"
(repos_dir / repo).mkdir(parents=True)
monkeypatch.setattr(git_worktree.settings, "repos_dir", str(repos_dir))
monkeypatch.setattr(git_worktree.settings, "worktrees_dir", str(tmp_path / "repos" / "_wt"))
monkeypatch.setattr(git_worktree.settings, "fs_normalize_enabled", True)
return repo
def test_tc01_permission_git_fatal_becomes_actionable(main_repo, monkeypatch):
"""TC-01: a git-fatal 'could not create leading directories / Permission denied'
raises an actionable RuntimeError (diagnosis + chown), not the raw git stderr."""
perm_stderr = (
"fatal: could not create leading directories of "
"'/repos/_wt/orchestrator/x': Permission denied"
)
def fake_run(cmd, *a, **k):
# fetch -> ok; worktree add (both forms) -> permission fatal.
if "fetch" in cmd:
return _R(0)
if "worktree" in cmd and "add" in cmd:
return _R(128, perm_stderr)
return _R(0)
monkeypatch.setattr(git_worktree.subprocess, "run", fake_run)
with pytest.raises(RuntimeError) as ei:
ensure_worktree(main_repo, "feature/x")
msg = str(ei.value)
# Actionable: names the cause + the healing command + the INFRA procedure...
assert "legacy root-owned" in msg.lower()
assert "chown" in msg.lower()
assert "INFRA.md" in msg
# ...and is NOT merely the raw "git worktree add failed" passthrough.
assert "git worktree add failed" not in msg
def test_tc01_makedirs_permission_error_becomes_actionable(main_repo, monkeypatch):
"""TC-01 (sibling path): a PermissionError from os.makedirs (creating the leading
worktree dir) is also turned into the actionable RuntimeError."""
def fake_run(cmd, *a, **k):
return _R(0)
monkeypatch.setattr(git_worktree.subprocess, "run", fake_run)
def boom(*a, **k):
raise PermissionError(13, "Permission denied")
monkeypatch.setattr(git_worktree.os, "makedirs", boom)
with pytest.raises(RuntimeError) as ei:
ensure_worktree(main_repo, "feature/x")
assert "chown" in str(ei.value).lower()
assert "legacy root-owned" in str(ei.value).lower()
def test_tc02_non_permission_error_keeps_prior_contract(main_repo, monkeypatch):
"""TC-02: a NON-permission failure (e.g. a real branch conflict) keeps the prior
raw-stderr 'git worktree add failed' message — no meaning substitution."""
conflict = "fatal: 'feature/x' is already checked out at '/repos/_wt/other'"
def fake_run(cmd, *a, **k):
if "fetch" in cmd:
return _R(0)
if "worktree" in cmd and "add" in cmd:
return _R(128, conflict)
return _R(0)
monkeypatch.setattr(git_worktree.subprocess, "run", fake_run)
with pytest.raises(RuntimeError) as ei:
ensure_worktree(main_repo, "feature/x")
msg = str(ei.value)
assert "git worktree add failed" in msg
assert "already checked out" in msg
# The actionable diagnosis must NOT be injected for a non-permission error.
assert "legacy root-owned" not in msg.lower()
def test_tc02_killswitch_off_keeps_raw_contract_even_for_permission(main_repo, monkeypatch):
"""Kill-switch off (fs_normalize_enabled=False) -> the error contract is byte-for-
byte as before ORCH-057 even for a permission failure (raw stderr passthrough)."""
monkeypatch.setattr(git_worktree.settings, "fs_normalize_enabled", False)
perm_stderr = "fatal: ...: Permission denied"
def fake_run(cmd, *a, **k):
if "fetch" in cmd:
return _R(0)
if "worktree" in cmd and "add" in cmd:
return _R(128, perm_stderr)
return _R(0)
monkeypatch.setattr(git_worktree.subprocess, "run", fake_run)
with pytest.raises(RuntimeError) as ei:
ensure_worktree(main_repo, "feature/x")
msg = str(ei.value)
assert "git worktree add failed" in msg
assert "legacy root-owned" not in msg.lower()