@@ -1674,6 +1674,48 @@ async def run_until_cancelled() -> None:
16741674 assert progress .run .status is ScenarioRunState .CANCELLED
16751675 assert [result .attack_result_id for result in progress .results ] == [delta .attack_result_id ]
16761676
1677+ async def test_cancelled_readback_reports_terminal_state_and_cleans_up_active_task (
1678+ self , mock_all_registries
1679+ ) -> None :
1680+ """Cancelled runs read back terminal DB state and drop the active-task entry exactly once."""
1681+ mock_memory = mock_all_registries ["memory" ]
1682+ started = asyncio .Event ()
1683+
1684+ async def run_until_cancelled () -> None :
1685+ started .set ()
1686+ await asyncio .Event ().wait ()
1687+
1688+ mock_all_registries ["scenario_instance" ].run_async .side_effect = run_until_cancelled
1689+ service = ScenarioRunService ()
1690+ response = await service .start_run_async (request = _make_request ())
1691+ await asyncio .wait_for (started .wait (), timeout = 5 )
1692+ rid = response .scenario_result_id
1693+ # Narrowly scoped internal invariant: the live run stays tracked until readback.
1694+ assert rid in service ._active_tasks
1695+
1696+ running_result = mock_all_registries ["db_result" ]
1697+ cancelled_result = _make_db_scenario_result (
1698+ result_id = rid ,
1699+ run_state = ScenarioRunState .CANCELLED ,
1700+ )
1701+ cancelled_result .metadata = {}
1702+ mock_memory .get_scenario_results .side_effect = [
1703+ [running_result ],
1704+ [cancelled_result ],
1705+ [cancelled_result ],
1706+ [cancelled_result ],
1707+ ]
1708+
1709+ await service .cancel_run_async (scenario_result_id = rid )
1710+ fetched = service .get_run (scenario_result_id = rid )
1711+ assert fetched is not None
1712+ assert fetched .status is ScenarioRunState .CANCELLED
1713+ assert rid not in service ._active_tasks
1714+
1715+ fetched_again = service .get_run (scenario_result_id = rid )
1716+ assert fetched_again is not None
1717+ assert fetched_again .status is ScenarioRunState .CANCELLED
1718+
16771719 async def test_cancel_completed_run_raises_value_error (self , mock_memory ) -> None :
16781720 """Test that cancelling a completed run raises ValueError."""
16791721 db_result = _make_db_scenario_result (result_id = "sr-done" , run_state = ScenarioRunState .COMPLETED )
0 commit comments