diff --git a/LEARNINGS.md b/LEARNINGS.md index 56de2aa..acfe722 100644 --- a/LEARNINGS.md +++ b/LEARNINGS.md @@ -7,6 +7,9 @@ ## Learnings +### 2026-07-22 | Architecture | Cinematic Output Type existierte bereits mit falschem workflow_rollout_mode +Der "Cinematic Highlight" Output-Type war seit März 2026 in der DB mit `workflow_rollout_mode = shadow` und einer `workflow_definition_id` gesetzt. Da es keinen `BLENDER_CINEMATIC`-Node im Workflow-Graph gibt, hätte jeder Cinematic-Render eine Shadow-Graph-Execution ausgelöst, die still scheitert. Fix via Migration 071: alle cinematic Output-Types auf `legacy_only` + `workflow_definition_id = NULL` patchen. Zusätzlich API-Guard in POST/PATCH `output_types.py` eingebaut: cinematic + workflow_definition_id → 400 Error. Defense-in-depth im `dispatch_service.py`: frühzeitiger Legacy-Exit wenn `render_settings.cinematic = true`. + ### 2026-03-15 | Architecture | Per-order-line render overrides via JSONB Render overrides (JSONB) on OrderLine allow overriding any output type render setting (format, resolution, samples, engine, etc.) at order time without duplicating output types. Applied AFTER output type render_settings AND after transparent_bg/cycles_device_val assignment, so they take final priority. Also affects dispatch queue routing (width/height overrides change light vs heavy queue routing). diff --git a/backend/alembic/versions/070_seed_cinematic_output_type.py b/backend/alembic/versions/070_seed_cinematic_output_type.py new file mode 100644 index 0000000..e87e51b --- /dev/null +++ b/backend/alembic/versions/070_seed_cinematic_output_type.py @@ -0,0 +1,59 @@ +"""Seed 'Cinematic Highlight' output type. + +Revision ID: 070 +Revises: 069 +""" +from alembic import op +import sqlalchemy as sa + + +revision = "070" +down_revision = "069" +branch_labels = None +depends_on = None + +_NAME = "Cinematic Highlight" + + +def upgrade() -> None: + op.execute( + sa.text( + f""" + INSERT INTO output_types ( + id, name, description, renderer, render_settings, + output_format, sort_order, compatible_categories, render_backend, + is_animation, transparent_bg, workflow_family, artifact_kind, + invocation_overrides, cycles_device, pricing_tier_id, is_active, + tenant_id, material_override, workflow_definition_id, + workflow_rollout_mode, created_at, updated_at + ) VALUES ( + gen_random_uuid(), + '{_NAME}', + 'Cinematic highlight animation: 4-segment camera orbit, depth-of-field, ' + '250 frames @ 25 fps (10 s), 1920x1080 MP4. ' + 'Runs legacy-only — no workflow graph node exists for cinematic.', + 'blender', + '{{"cinematic": true, "samples": 128, "width": 1920, "height": 1080}}'::jsonb, + 'mp4', + 50, + '[]'::jsonb, + 'celery', + true, + false, + 'order_line', + 'turntable_video', + '{{}}'::jsonb, + NULL, NULL, true, NULL, NULL, NULL, + 'legacy_only', + now(), now() + ) + ON CONFLICT (name) DO NOTHING + """ + ) + ) + + +def downgrade() -> None: + op.execute( + sa.text("DELETE FROM output_types WHERE name = :name").bindparams(name=_NAME) + ) diff --git a/backend/alembic/versions/071_fix_cinematic_output_type_rollout.py b/backend/alembic/versions/071_fix_cinematic_output_type_rollout.py new file mode 100644 index 0000000..0c7abcd --- /dev/null +++ b/backend/alembic/versions/071_fix_cinematic_output_type_rollout.py @@ -0,0 +1,35 @@ +"""Fix cinematic output types: force legacy_only rollout, clear workflow link. + +No BLENDER_CINEMATIC node exists in the workflow graph system. Any cinematic +output type with a workflow_definition_id set would cause the shadow/graph +execution path to attempt a graph run and fail silently. + +Revision ID: 071 +Revises: 070 +""" +from alembic import op +import sqlalchemy as sa + + +revision = "071" +down_revision = "070" +branch_labels = None +depends_on = None + + +def upgrade() -> None: + op.execute( + sa.text( + """ + UPDATE output_types + SET + workflow_rollout_mode = 'legacy_only', + workflow_definition_id = NULL + WHERE (render_settings->>'cinematic')::boolean IS TRUE + """ + ) + ) + + +def downgrade() -> None: + pass diff --git a/backend/app/api/routers/output_types.py b/backend/app/api/routers/output_types.py index 7d744fa..1330cf0 100644 --- a/backend/app/api/routers/output_types.py +++ b/backend/app/api/routers/output_types.py @@ -271,6 +271,13 @@ async def create_output_type( if body.workflow_definition_id is None: data["workflow_rollout_mode"] = "legacy_only" + if data.get("render_settings", {}).get("cinematic") and body.workflow_definition_id is not None: + raise HTTPException( + 400, + detail="Cinematic output types cannot be linked to a workflow definition. " + "No BLENDER_CINEMATIC node exists in the workflow graph; cinematic renders use the legacy path only.", + ) + ot = OutputType(**data) db.add(ot) await db.commit() @@ -387,6 +394,14 @@ async def update_output_type( if candidate_workflow_definition_id is None: data["workflow_rollout_mode"] = "legacy_only" + candidate_render_settings = data.get("render_settings", ot.render_settings) or {} + if candidate_render_settings.get("cinematic") and candidate_workflow_definition_id is not None: + raise HTTPException( + 400, + detail="Cinematic output types cannot be linked to a workflow definition. " + "No BLENDER_CINEMATIC node exists in the workflow graph; cinematic renders use the legacy path only.", + ) + for field_name, value in data.items(): setattr(ot, field_name, value) await db.commit() diff --git a/backend/app/domains/rendering/dispatch_service.py b/backend/app/domains/rendering/dispatch_service.py index 4821dc9..f717745 100644 --- a/backend/app/domains/rendering/dispatch_service.py +++ b/backend/app/domains/rendering/dispatch_service.py @@ -193,6 +193,30 @@ def dispatch_render_with_workflow(order_line_id: str) -> dict: ) return legacy_result + # Cinematic output types have no BLENDER_CINEMATIC graph node — force legacy regardless + # of what workflow_rollout_mode or workflow_definition_id say. + if output_type and isinstance(output_type.render_settings, dict) and output_type.render_settings.get("cinematic"): + logger.warning( + "order_line %s: output_type %s is cinematic but has a workflow_definition_id set; " + "forcing legacy dispatch (no BLENDER_CINEMATIC node exists in the workflow graph)", + order_line_id, + output_type.id, + ) + legacy_result = _legacy_dispatch(order_line_id) + legacy_result.update( + _build_rollout_signal( + gate_status="cinematic_legacy_only", + ready=False, + reasons=[ + "Cinematic output types always use the legacy dispatch path.", + "Remove the workflow_definition_id link to silence this warning.", + ], + workflow_def_id=wf_def.id, + output_type_id=output_type.id, + ) + ) + return legacy_result + configured_execution_mode = get_workflow_execution_mode(canonical_config, default="legacy") workflow_rollout_mode = _normalize_workflow_rollout_mode( getattr(output_type, "workflow_rollout_mode", None)