diff --git a/tests/tools/test_documentary_governance.py b/tests/tools/test_documentary_governance.py index 8f498c95..8446be40 100644 --- a/tests/tools/test_documentary_governance.py +++ b/tests/tools/test_documentary_governance.py @@ -112,14 +112,33 @@ def test_documentary_renderer_family_maps_to_remotion(): def test_video_compose_surfaces_all_three_runtimes(): """Preflight must see remotion, hyperframes, and ffmpeg as separate engines.""" + import shutil + info = VideoCompose().get_info() engines = info["render_engines"] assert set(engines.keys()) == {"remotion", "hyperframes", "ffmpeg"} - assert engines["ffmpeg"] is True # always true on this machine + assert engines["ffmpeg"] is bool(shutil.which("ffmpeg")) assert "hyperframes_note" in info assert "runtime_governance" in info +def test_video_compose_ffmpeg_engine_reflects_path_availability(monkeypatch): + """Regression: `get_info()["render_engines"]["ffmpeg"]` must actually check + shutil.which("ffmpeg"), not hardcode True. A machine without ffmpeg on PATH + must not have the agent believe render_runtime='ffmpeg' is safe to lock.""" + import shutil + + monkeypatch.setattr(shutil, "which", lambda name: None) + info = VideoCompose().get_info() + assert info["render_engines"]["ffmpeg"] is False + + monkeypatch.setattr( + shutil, "which", lambda name: "/usr/bin/ffmpeg" if name == "ffmpeg" else None + ) + info = VideoCompose().get_info() + assert info["render_engines"]["ffmpeg"] is True + + def test_video_compose_blocks_silent_hyperframes_swap(tmp_path, monkeypatch): """Governance: if render_runtime='hyperframes' is locked but runtime is missing, the tool MUST return a structured blocker and NOT route to diff --git a/tools/video/video_compose.py b/tools/video/video_compose.py index dbbf7401..976a2e55 100644 --- a/tools/video/video_compose.py +++ b/tools/video/video_compose.py @@ -241,6 +241,12 @@ class VideoCompose(BaseTool): return False return True + def _ffmpeg_available(self) -> bool: + """Check if the ffmpeg binary is actually resolvable on PATH.""" + import shutil as _shutil + + return bool(_shutil.which("ffmpeg")) + def _hyperframes_available(self) -> bool: """Check if HyperFrames rendering is available. @@ -261,10 +267,11 @@ class VideoCompose(BaseTool): fallback between runtimes is forbidden. """ info = super().get_info() + ffmpeg_ok = self._ffmpeg_available() remotion_ok = self._remotion_available() hyperframes_ok = self._hyperframes_available() info["render_engines"] = { - "ffmpeg": True, + "ffmpeg": ffmpeg_ok, "remotion": remotion_ok, "hyperframes": hyperframes_ok, }