refactor(tooling): T-1273 — the Blender carve-out, and a guard that keeps it carved
35 payloads move to tooling/scripts/blender/ and stay outside package scope. They run under Blender's bundled Python, which cannot see the repo venv, so they physically cannot import tooling.core — holding them to the D-263 contract would either fail the gate forever or force the contract to be weakened for everyone, and the second is how a gate stops meaning anything. Count verified by import rather than filename: 33 import bpy/bmesh directly, and the two that do not are still payloads per their own usage lines. garment-fit/make_logo.py is the one genuine non-payload and stays for T-1290. The bash wrapper is retired rather than kept. Keeping it would have put the install-resolution logic in two places, which is the duplication T-1286 had just finished collapsing three copies of. domains/blender/service.py owns the decisions — resolve_blender (native beats flatpak, ordering preserved), resolve_payload, absolutise — and only run_payload performs. test_blender.py pins all of them without launching Blender, which matters here more than usual: the thing being launched is a 200 MB GUI application that writes GLBs. `reach blender run` takes a registered payload name OR a path to any script, because the wrapper served both — the spikes and the glb-gen skill hand it one-off scripts of their own. An unknown name enumerates all 35 and exits 2. The exclusion now defends itself. check_carve_out_stays_carved fails if `scripts` is added to PACKAGE_ROOTS, if the payload directory empties (an empty exclusion proves nothing), or if an __init__.py appears there (which would make the payloads importable — the coupling the carve-out exists to prevent). All three arms mutation-proved. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -58,6 +58,18 @@ FORBIDDEN_IMPORTS = {"typer", "click"}
|
||||
# needing to be widened by hand.
|
||||
PACKAGE_ROOTS = ("main.py", "__init__.py", "core", "domains")
|
||||
|
||||
# tooling/scripts/blender/ is DELIBERATELY not in that tuple, and must never be
|
||||
# added (T-1273). Those 35 files run under Blender's BUNDLED Python, which has
|
||||
# no access to the repo venv — they physically cannot `import tooling.core`, so
|
||||
# they cannot carry `@command` or print through `console`. Holding them to the
|
||||
# contract would either fail this gate forever or force the contract to be
|
||||
# weakened for everyone, and the second is how a gate stops meaning anything.
|
||||
#
|
||||
# It reads like an oversight, so `check_carve_out_stays_carved` below asserts
|
||||
# the exclusion on purpose: widening PACKAGE_ROOTS to cover them fails loudly
|
||||
# instead of quietly redefining what conformance means.
|
||||
PAYLOAD_ROOTS = ("scripts",)
|
||||
|
||||
|
||||
def _package_files() -> list[Path]:
|
||||
"""Every .py that is part of the reach package — not the legacy scripts."""
|
||||
@@ -303,6 +315,42 @@ def check_exit_codes(failures: list[str]) -> None:
|
||||
)
|
||||
|
||||
|
||||
def check_carve_out_stays_carved(failures: list[str]) -> None:
|
||||
"""(7) The Blender payloads stay outside package scope, and stay populated.
|
||||
|
||||
Two failure modes, opposite directions:
|
||||
|
||||
- Someone widens PACKAGE_ROOTS to cover `scripts/` because the exclusion
|
||||
looks like an oversight. Every payload then fails five invariants it
|
||||
cannot satisfy, and the likely repair is to weaken the invariants.
|
||||
- The payload directory quietly empties — a move, a bad merge — and the
|
||||
exclusion goes on passing because excluding nothing is trivially fine.
|
||||
An exception that guards nothing should not read as healthy.
|
||||
"""
|
||||
for root in PAYLOAD_ROOTS:
|
||||
if root in PACKAGE_ROOTS:
|
||||
failures.append(
|
||||
f"[carve-out] '{root}' was added to PACKAGE_ROOTS — the Blender "
|
||||
"payloads run under Blender's bundled Python and cannot import "
|
||||
"tooling.core; see the comment above PAYLOAD_ROOTS (T-1273)"
|
||||
)
|
||||
|
||||
payloads = list((PACKAGE / "scripts" / "blender").glob("*.py"))
|
||||
if not payloads:
|
||||
failures.append(
|
||||
"[carve-out] tooling/scripts/blender/ holds no payloads — either the "
|
||||
"carve-out was undone or they moved; an empty exclusion proves nothing"
|
||||
)
|
||||
|
||||
# The payloads must not be reachable as modules either: an __init__.py would
|
||||
# make them importable and invite exactly the coupling the carve-out prevents.
|
||||
if (PACKAGE / "scripts" / "__init__.py").exists():
|
||||
failures.append(
|
||||
"[carve-out] tooling/scripts/__init__.py exists — that makes the "
|
||||
"payload tree an importable package, which is what the carve-out avoids"
|
||||
)
|
||||
|
||||
|
||||
def main() -> int:
|
||||
if shutil.which("reach") is None:
|
||||
print("test_conformance: `reach` is not on PATH.\n Fix: make install-reach", file=sys.stderr)
|
||||
@@ -316,6 +364,7 @@ def main() -> int:
|
||||
check_commands_decorated(failures)
|
||||
check_errors_name_a_remedy(failures)
|
||||
check_exit_codes(failures)
|
||||
check_carve_out_stays_carved(failures)
|
||||
|
||||
if failures:
|
||||
print("test_conformance: FAIL", file=sys.stderr)
|
||||
|
||||
Reference in New Issue
Block a user