diff --git a/src/backend/services/episode_evaluation_workflow_vontology_service.py b/src/backend/services/episode_evaluation_workflow_vontology_service.py index cc4de43bc..9c01c54cf 100644 --- a/src/backend/services/episode_evaluation_workflow_vontology_service.py +++ b/src/backend/services/episode_evaluation_workflow_vontology_service.py @@ -242,15 +242,25 @@ def _ensure_episode_evaluation_event_bindings() -> dict[str, Any]: updated_count = 0 bindings: list[dict[str, Any]] = [] - for event_type in ( - EVENT_TYPE_TURN_COMPLETION_GATE_FINALISED, - EVENT_TYPE_WORKFLOW_INSTANCE_TERMINAL, - ): + # JVNAUTOSCI-2507: the workflow.instance_terminal trigger is a recursive + # firehose — every workflow instance terminal (including episode_evaluation's + # OWN terminals) re-fires episode evaluation, so a single event self-sustains + # into an unbounded backlog (138k instances observed live, amplified by + # foreign workers on the shared cluster). It is bootstrapped DISABLED until a + # self-exclusion condition + throttling exist; the per-turn completion-gate + # trigger remains the supported entry point. replace_existing keeps the + # disabled state self-healing across restarts (otherwise bootstrap would + # re-enable it and the storm would return). + binding_enablement = { + EVENT_TYPE_TURN_COMPLETION_GATE_FINALISED: True, + EVENT_TYPE_WORKFLOW_INSTANCE_TERMINAL: False, + } + for event_type, binding_enabled in binding_enablement.items(): binding, created, updated = manager.upsert_event_binding( event_type=event_type, workflow_id=EPISODE_EVALUATION_WORKFLOW_ID, input_mapping={}, - enabled=True, + enabled=binding_enabled, actor=_MANAGED_BY, replace_existing=True, ) diff --git a/tests/backend/test_episode_evaluation_workflow_vontology_service.py b/tests/backend/test_episode_evaluation_workflow_vontology_service.py index 0db40a77d..2e26e14c4 100644 --- a/tests/backend/test_episode_evaluation_workflow_vontology_service.py +++ b/tests/backend/test_episode_evaluation_workflow_vontology_service.py @@ -246,18 +246,33 @@ def test_bootstrap_materialises_episode_evaluation_workflow_family( enabled_only=True, limit=10, ) - terminal_bindings = manager.list_event_bindings( + # JVNAUTOSCI-2507: the completion-gate trigger stays enabled, but the + # recursive workflow.instance_terminal trigger is bootstrapped DISABLED, so + # it must NOT appear among enabled bindings — while still existing (disabled) + # so it remains discoverable/re-enableable once properly gated. + enabled_terminal_bindings = manager.list_event_bindings( event_type=EVENT_TYPE_WORKFLOW_INSTANCE_TERMINAL, enabled_only=True, limit=10, ) + all_terminal_bindings = manager.list_event_bindings( + event_type=EVENT_TYPE_WORKFLOW_INSTANCE_TERMINAL, + limit=10, + ) assert any( binding.workflow_id == EPISODE_EVALUATION_WORKFLOW_ID for binding in turn_bindings ) - assert any( + assert not any( binding.workflow_id == EPISODE_EVALUATION_WORKFLOW_ID - for binding in terminal_bindings + for binding in enabled_terminal_bindings ) + episode_terminal_bindings = [ + binding + for binding in all_terminal_bindings + if binding.workflow_id == EPISODE_EVALUATION_WORKFLOW_ID + ] + assert episode_terminal_bindings + assert episode_terminal_bindings[0].enabled is False for predicate_id in _WORKFLOW_EXPERIENCE_GUIDANCE_PREDICATES: doc = concept_service.get_concept_by_concept_id(predicate_id)