From 6ba3bbeb12c1e3eff5ff696486aa8aeca8f80182 Mon Sep 17 00:00:00 2001 From: RickZ <121943721+rickisba@users.noreply.github.com> Date: Fri, 31 Jul 2026 09:30:23 +0800 Subject: [PATCH] packaging: remove repository-only wheel namespaces --- doc/architecture/package-architecture-rfc.md | 7 ++++ doc/package_migration_phase_a.md | 15 +++++++++ pyproject.toml | 14 +++----- test/test_repository_governance.py | 17 +++++++--- test/test_wheel_install.py | 34 ++++++++++++++++++-- 5 files changed, 72 insertions(+), 15 deletions(-) diff --git a/doc/architecture/package-architecture-rfc.md b/doc/architecture/package-architecture-rfc.md index 95f4a41..3087d72 100644 --- a/doc/architecture/package-architecture-rfc.md +++ b/doc/architecture/package-architecture-rfc.md @@ -315,6 +315,13 @@ Approve this RFC as the target architecture before implementation phases begin. Clean packaging so only canonical packages and approved compatibility shims enter the wheel. Add the unified CLI and application factories. +The first packaging-boundary step removes the repository-only `doc`, `env`, +`log`, `scripts`, and `test` namespaces (including their descendants) from the +wheel. Transitional root runtime packages remain explicitly installed until +their focused migrations; `data` remains installed pending a separate consumer +and package-data audit. This interim boundary does not authorize entrypoint or +service migration. + ### Phase 2 — contracts and dependency-light domains Move versioned contracts, Runtime Profile, lifecycle models, capability models, and topology/resource models. diff --git a/doc/package_migration_phase_a.md b/doc/package_migration_phase_a.md index ffcaf16..56bfd6e 100644 --- a/doc/package_migration_phase_a.md +++ b/doc/package_migration_phase_a.md @@ -220,6 +220,21 @@ dropping root packages. The final package-migration phase removes the explicit transition and enables src-only discovery after every listed root runtime package is migrated or intentionally deprecated through review. +### Interim repository-only wheel boundary + +The first substantive packaging-boundary step removes `doc`, `doc.blog`, +`doc.integrations`, `env`, `env.config`, `env.docker`, `env.docker.cu130`, +`env.docker.cu130.scripts`, `log`, `log.scheduler`, `scripts`, `test`, and +`test.kdn` from the explicit setuptools package list. These directories remain +available in source checkouts but are not application packages supplied by the +wheel. + +The exact interim wheel top-level package set is `UI`, `cacheroute`, +`cacheroute_compat`, `client`, `core`, `data`, `instance`, `kdn_server`, +`model`, `proxy`, `scheduler`, `store`, and `util`. These root runtime packages +remain transitional. In particular, `data` and its existing descendants remain +installed and unchanged until a separate consumer and package-data audit. + ## Later phases and risks 1. PR #156 performs the observability implementation migration described above. diff --git a/pyproject.toml b/pyproject.toml index cc7d7dd..ff9eb21 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -37,9 +37,8 @@ dev = [ ] [tool.setuptools] -# Transitional Phase A package map. Root packages remain installed until their -# later reviewed migrations; the final migration phase replaces this explicit -# list with src-only discovery. +# Transitional package map. Repository-only namespaces are excluded, while +# root runtime packages remain explicitly installed pending focused migrations. package-dir = {cacheroute = "src/cacheroute", cacheroute_compat = "src/cacheroute_compat"} packages = [ "cacheroute", "cacheroute.compat", "cacheroute.observability", @@ -47,20 +46,17 @@ packages = [ "UI", "UI.client_ui", "UI.client_ui.static", "UI.client_ui.templates", "UI.kdn_ui", "UI.proxy_ui", "UI.proxy_ui.static", "client", "client.taskset", "core", "data", "data.CacheRoute_dataset", - "data.CacheRoute_dataset.knowledge_document", "doc", "doc.blog", - "doc.integrations", "env", "env.config", "env.docker", "env.docker.cu130", - "env.docker.cu130.scripts", "instance", "instance.TPOT_predictor", + "data.CacheRoute_dataset.knowledge_document", "instance", "instance.TPOT_predictor", "instance.TTFT_predictor", "instance.TTFT_predictor.data", "instance.pclient", "instance.resource_agent", "instance.resource_agent.src", "instance.resource_dashboard", "instance.resource_dashboard.static", "kdn_server", "kdn_server.KV_database", "kdn_server.contracts", "kdn_server.domain", "kdn_server.gateway", "kdn_server.sclient", "kdn_server.text_database", "kdn_server.text_database.blocks", - "kdn_server.util", "log", "log.scheduler", "model", "proxy", + "kdn_server.util", "model", "proxy", "proxy.metrics", "proxy.metrics.data", "proxy.queue", "proxy.resource", "proxy.sclient", "proxy.strategy", "scheduler", "scheduler.knowledge", - "scheduler.resource", "scheduler.strategy", "scripts", "store", "test", - "test.kdn", "util", + "scheduler.resource", "scheduler.strategy", "store", "util", ] [tool.setuptools.package-data] diff --git a/test/test_repository_governance.py b/test/test_repository_governance.py index ebbf93b..ace4721 100644 --- a/test/test_repository_governance.py +++ b/test/test_repository_governance.py @@ -33,6 +33,9 @@ "cacheroute", "cacheroute.compat", "cacheroute.observability", "cacheroute_compat", } +REPOSITORY_ONLY_PACKAGE_PREFIXES = { + "doc", "env", "log", "scripts", "test", +} SOURCE_BOOTSTRAP_ENTRYPOINTS = { Path("client/client.py"), Path("client/kv_timing_sender.py"), @@ -70,11 +73,15 @@ "*.__pycache__", "*.__pycache__.*", ".mypy_cache", ".mypy_cache.*", ".ruff_cache", ".ruff_cache.*", "tests", "tests.*", "docs", "docs.*", - # Repository-only Markdown architecture documents are not wheel packages. - "doc.architecture", "doc.architecture.*", + *REPOSITORY_ONLY_PACKAGE_PREFIXES, + *(f"{prefix}.*" for prefix in REPOSITORY_ONLY_PACKAGE_PREFIXES), ] +def _has_package_prefix(package, prefixes): + return any(package == prefix or package.startswith(f"{prefix}.") for prefix in prefixes) + + def test_no_unreviewed_functional_root_directories(): tracked = { path.relative_to(ROOT).parts[0] @@ -86,8 +93,10 @@ def test_no_unreviewed_functional_root_directories(): def test_transitional_explicit_packages_match_root_discovery(): configuration = tomllib.loads((ROOT / "pyproject.toml").read_text(encoding="utf-8")) configured = set(configuration["tool"]["setuptools"]["packages"]) - assert "doc.architecture" not in configured - assert not any(package.startswith("doc.architecture.") for package in configured) + assert not any( + _has_package_prefix(package, REPOSITORY_ONLY_PACKAGE_PREFIXES) + for package in configured + ) discovered_root = _discover_root_namespace_packages() assert CANONICAL_PACKAGES <= configured assert configured - CANONICAL_PACKAGES == discovered_root diff --git a/test/test_wheel_install.py b/test/test_wheel_install.py index 2f161e4..aa91b3d 100644 --- a/test/test_wheel_install.py +++ b/test/test_wheel_install.py @@ -13,9 +13,9 @@ EXPECTED_TOP_LEVEL_PACKAGES = { "UI", "cacheroute", "cacheroute_compat", "client", "core", "data", - "env", "instance", "kdn_server", "model", "proxy", "scheduler", - "scripts", "store", "test", "util", + "instance", "kdn_server", "model", "proxy", "scheduler", "store", "util", } +FORBIDDEN_TOP_LEVEL_PACKAGES = {"doc", "env", "log", "scripts", "test"} REQUIRED_PACKAGE_DATA = { "UI/client_ui/static/app.js", "UI/client_ui/static/style.css", @@ -80,6 +80,12 @@ def test_wheel_preserves_packages_and_runtime_data(built_wheel): if "/" in name and not name.partition("/")[0].endswith(".dist-info") } assert packaged == EXPECTED_TOP_LEVEL_PACKAGES + assert packaged.isdisjoint(FORBIDDEN_TOP_LEVEL_PACKAGES) + for prefix in FORBIDDEN_TOP_LEVEL_PACKAGES: + assert not any( + member == prefix or member.startswith(f"{prefix}/") + for member in members + ) assert REQUIRED_PACKAGE_DATA <= members assert "instance/TTFT_predictor/prompt_length_validation.log" not in members @@ -112,6 +118,30 @@ def test_dependency_light_clean_wheel_imports(built_wheel, tmp_path): print(result.stdout, end="") +def test_repository_only_namespaces_are_not_importable_from_clean_wheel(built_wheel, tmp_path): + python = _create_isolated_environment(tmp_path / "negative-venv") + subprocess.run([python, "-m", "pip", "install", "--no-deps", str(built_wheel)], check=True) + result = _run_outside_repo(python, tmp_path / "outside-negative", """ +import importlib.util +from pathlib import Path +import sys + +for name in ("doc", "env", "log", "scripts", "test"): + spec = importlib.util.find_spec(name) + if name == "test": + # CPython may supply its own stdlib test package. It must not resolve + # from this environment's wheel installation directory. + if spec is not None: + assert Path(spec.origin).resolve().is_relative_to(Path(sys.base_prefix).resolve()) + assert "site-packages" not in Path(spec.origin).parts + else: + assert spec is None, name +print("repository-only clean-wheel imports: absent") +""") + assert "repository-only clean-wheel imports: absent" in result.stdout + print(result.stdout, end="") + + @pytest.mark.network @pytest.mark.skipif( os.getenv("CACHEROUTE_RUN_NETWORK_TESTS") != "1",