fix(dist): stop USER_OWNED_EXCLUDE from filtering nested directories

The copytree ignore lambda in _copy_dist_payload applied USER_OWNED_EXCLUDE
recursively at every directory depth. This caused nested directories whose
names matched exclude entries (bin, logs, cache, etc.) to be silently dropped
during distribution install/update.

Fix: only apply USER_OWNED_EXCLUDE filtering at the root of the staged tree,
matching the two-tier pattern used by _clone_all_copytree_ignore and
_default_export_ignore in profiles.py.

Add 5 tests covering nested bin/logs/cache preservation and top-level
filtering still working.

Fixes #37954
This commit is contained in:
islam666 2026-06-03 08:58:58 +00:00 committed by Teknium
parent 09a5548628
commit e53b74c394
2 changed files with 77 additions and 1 deletions

View file

@ -573,10 +573,15 @@ def _copy_dist_payload(
if entry.is_dir():
if dest.exists():
shutil.rmtree(dest)
staged_resolved = staged.resolve()
shutil.copytree(
entry,
dest,
ignore=lambda d, names: [n for n in names if n in USER_OWNED_EXCLUDE],
ignore=lambda d, names: (
[n for n in names if n in USER_OWNED_EXCLUDE]
if Path(d).resolve() == staged_resolved
else []
),
)
else:
shutil.copy2(entry, dest)

View file

@ -497,6 +497,77 @@ class TestSecurity:
assert not (target / "skills" / "demo" / "leak.txt").exists()
# ===========================================================================
# Nested directories whose names match USER_OWNED_EXCLUDE must survive install
# ===========================================================================
class TestNestedUserOwnedExcludeNotFiltered:
def test_nested_bin_dir_is_preserved(self, profile_env):
""""A distribution shipping tools/bin/ must not have tools/bin/ dropped
during install even though 'bin' is in USER_OWNED_EXCLUDE."""
staged = _make_staging_dir(profile_env, "src")
(staged / "tools" / "bin").mkdir(parents=True)
(staged / "tools" / "bin" / "tool.py").write_text("# tool\n")
plan = install_distribution(str(staged), name="nested_bin")
assert (plan.target_dir / "tools" / "bin").is_dir(), "nested bin/ was dropped"
assert (plan.target_dir / "tools" / "bin" / "tool.py").exists()
def test_nested_logs_dir_is_preserved(self, profile_env):
staged = _make_staging_dir(profile_env, "src")
(staged / "scripts" / "logs").mkdir(parents=True)
(staged / "scripts" / "logs" / "run.log").write_text("ok\n")
plan = install_distribution(str(staged), name="nested_logs")
assert (plan.target_dir / "scripts" / "logs").is_dir()
assert (plan.target_dir / "scripts" / "logs" / "run.log").read_text() == "ok\n"
def test_nested_cache_dir_is_preserved(self, profile_env):
staged = _make_staging_dir(profile_env, "src")
(staged / "control-plane" / "cache").mkdir(parents=True)
(staged / "control-plane" / "cache" / "data.json").write_text("{}\n")
plan = install_distribution(str(staged), name="nested_cache")
assert (plan.target_dir / "control-plane" / "cache").is_dir()
assert (plan.target_dir / "control-plane" / "cache" / "data.json").exists()
def test_top_level_user_owned_still_skipped(self, profile_env):
"""Top-level entries in USER_OWNED_EXCLUDE must still be skipped —
only nested (deeper) directories should be preserved.
Note: _bootstrap_user_dirs creates some of these (logs/, sessions/,
memories/) in every fresh profile, so we check that the *staged content*
did not leak through rather than asserting the directory doesn't exist."""
staged = _make_staging_dir(profile_env, "src")
# Add top-level excluded entries alongside the legit ones
(staged / "bin").mkdir(exist_ok=True)
(staged / "bin" / "shipped_binary").write_text("x")
(staged / "logs").mkdir(exist_ok=True)
(staged / "logs" / "shipped.log").write_text("y\n")
plan = install_distribution(str(staged), name="top_filter")
# bin/ is not created by _bootstrap_user_dirs so absence means filtered
assert not (plan.target_dir / "bin").exists(), "top-level bin/ should be filtered"
# logs/ is created by _bootstrap_user_dirs even on a clean profile,
# so check that the staged file did NOT land there.
assert not (plan.target_dir / "logs" / "shipped.log").exists(), \
"staged logs/ content should not leak into target"
def test_both_nested_and_top_level_coexist(self, profile_env):
"""Top-level bin/ filtered, but tools/bin/ kept."""
staged = _make_staging_dir(profile_env, "src")
(staged / "bin").mkdir(exist_ok=True)
(staged / "bin" / "top.sh").write_text("# top\n")
(staged / "tools" / "bin").mkdir(parents=True)
(staged / "tools" / "bin" / "helper.py").write_text("# helper\n")
plan = install_distribution(str(staged), name="coexist")
assert not (plan.target_dir / "bin").exists()
assert (plan.target_dir / "tools" / "bin" / "helper.py").exists()
# ===========================================================================
# Install-time metadata (installed_at stamp)
# ===========================================================================