"""Phase J2 — kernel exposure of the assembly API (SPEC_ASSEMBLY.md §6, §8). A generator may use the injected `asm` helpers; it still cannot import anything outside the allowlist (asm is injected, not imported). """ import sys from pathlib import Path import pytest ROOT = Path(__file__).resolve().parent.parent if str(ROOT) not in sys.path: sys.path.insert(0, str(ROOT)) from geometry import kernel, metrics, repair # noqa: E402 GEN_ASM = ''' def build(p): cyl = asm.cylinder(r=1.0, h=2.0, name="cylinder") cone = asm.cone(r=1.0, h=1.5, name="cone") asm.attach(cone, "base", cyl, "top") return asm.combine([cyl, cone]) ''' # A generator that tries to reach the assembly module by IMPORT (not the injected # asm) must still be rejected by the allowlist. GEN_IMPORT_GEOMETRY = ''' import geometry def build(p): return [[0,0,0],[1,0,0],[0,1,0]], [[0,1,2,"x"]] ''' def test_asm_generator_runs_through_kernel(): V, F = kernel.run(GEN_ASM) assert len(V) > 8 and len(F) > 8 cids = {f[3] for f in F} assert "cylinder" in cids and "cone" in cids # two joined components v, f = repair.repair(V, F, "composite") assert metrics.passes(v, f, "composite")[0] def test_asm_parts_are_joined(): # After mating, the cone base ring sits on the cylinder top — i.e. the meshes # actually connect (not two disjoint blobs floating apart). V, F = kernel.run(GEN_ASM) ys = sorted(v[1] for v in V) # cylinder spans y∈[-1,1]; cone sits above with apex highest, contiguous. assert min(ys) < -0.9 and max(ys) > 1.0 def test_generator_cannot_import_geometry(): with pytest.raises(kernel.KernelError) as e: kernel.run(GEN_IMPORT_GEOMETRY) assert "disallowed import" in str(e.value) def test_plain_generator_still_works(): # asm injection must not break a generator that doesn't use it. code = ("def build(p):\n" " v=[[1,0,0],[-1,0,0],[0,1,0],[0,-1,0],[0,0,1],[0,0,-1]]\n" " return v,[[4,0,2,'f'],[4,2,1,'f'],[4,1,3,'f'],[4,3,0,'f']," "[5,2,0,'f'],[5,1,2,'f'],[5,3,1,'f'],[5,0,3,'f']]\n") V, F = kernel.run(code) assert len(V) == 6 and len(F) == 8