hyperframes: add HTML/CSS/GSAP as a parallel composition runtime

Separates creative grammar (renderer_family) from technical engine
(render_runtime) so HyperFrames can stand alongside Remotion as a
first-class runtime instead of masquerading as a Remotion sub-case.
Locks runtime choice at proposal stage and enforces it end-to-end: the
schemas require it, video_compose routes by it, the reviewer fails
closed on silent swaps, and a parametrized contract test walks every
pipeline manifest to ensure each planning-stage skill explains the
conversation to the user. Adds hyperframes_compose (scaffold/lint/
validate/render/doctor/add_block), a playbook -> CSS style bridge, and
vendored HyperFrames Layer 3 skills from commit d291358, pinned via
PROVENANCE.md for future re-sync. Final_review now records
render_runtime_used and runtime_swap_detected so compose lies are
catchable after the fact.
This commit is contained in:
calesthio
2026-04-18 18:37:00 -07:00
parent 9e17263aa5
commit b4f7ec4eee
118 changed files with 10128 additions and 126 deletions
@@ -108,6 +108,65 @@ def test_documentary_renderer_family_maps_to_remotion():
assert VideoCompose._get_composition_id("documentary-montage") == "CinematicRenderer"
def test_video_compose_surfaces_all_three_runtimes():
"""Preflight must see remotion, hyperframes, and ffmpeg as separate engines."""
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 "hyperframes_note" in info
assert "runtime_governance" in info
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
Remotion or FFmpeg."""
monkeypatch.setattr(
VideoCompose, "_hyperframes_available", lambda self: False, raising=True
)
result = VideoCompose().execute(
{
"operation": "render",
"edit_decisions": {
"version": "1.0",
"renderer_family": "animation-first",
"render_runtime": "hyperframes",
"cuts": [
{"id": "c1", "source": "x", "in_seconds": 0, "out_seconds": 2}
],
},
"asset_manifest": {"assets": [{"id": "x", "path": "missing.png"}]},
"output_path": str(tmp_path / "out.mp4"),
}
)
assert not result.success
err = (result.error or "").lower()
assert "hyperframes" in err
# Error MUST mention it's a blocker, not silently pick a different engine.
assert ("blocker" in err) or ("not available" in err)
def test_video_compose_rejects_unknown_render_runtime(tmp_path):
result = VideoCompose().execute(
{
"operation": "render",
"edit_decisions": {
"version": "1.0",
"renderer_family": "explainer-data",
"render_runtime": "bogus-runtime",
"cuts": [
{"id": "c1", "source": "x", "in_seconds": 0, "out_seconds": 2}
],
},
"asset_manifest": {"assets": []},
"output_path": str(tmp_path / "out.mp4"),
}
)
assert not result.success
assert "unknown render_runtime" in (result.error or "").lower()
def test_provider_menu_preserves_tool_discovery_metadata(monkeypatch):
import tools.video.stock_sources as stock_sources
File diff suppressed because it is too large Load Diff