@@ -1298,15 +1298,16 @@ public Scenario convertToManual(String runId, ConvertToManualMode mode) {
12981298 }
12991299
13001300 /**
1301- * Tears down the autonomous run owning {@code scenarioId} together with its single underlying
1302- * simulation (attack-path rows included) and its timeline/directives. An autonomous scenario and
1303- * its simulation are one unit, so deleting the scenario must delete the simulation too -
1304- * otherwise an orphan run keeps driving a simulation whose scenario is gone.
1301+ * Tears down the autonomous run owning {@code scenarioId} - its coordination row, timeline and
1302+ * directives - and halts the orchestration, so deleting the scenario never leaves an orphan run
1303+ * driving a simulation whose scenario is gone. It does NOT delete a finished LIVE simulation:
1304+ * that is history, detached by the scenario delete like any other simulation (see {@link
1305+ * #tearDownRun}). Only a non-executing plan-mode substrate simulation is removed with the run.
13051306 *
13061307 * <p>Deliberately a best-effort no-op for manual scenarios (and when the preview feature is off),
13071308 * so the generic scenario-delete endpoint can call it unconditionally. A still-active run
1308- * (created / running / paused / waiting-input) is refused with 409: the operator must stop it
1309- * first, mirroring the UI's disabled Delete entry.
1309+ * (created / planning / running / paused / waiting-input) is refused with 409: the operator must
1310+ * stop it first, mirroring the UI's disabled Delete entry.
13101311 */
13111312 @ Transactional (rollbackFor = Exception .class )
13121313 public void deleteForScenario (String scenarioId ) {
@@ -1321,7 +1322,10 @@ public void deleteForScenario(String scenarioId) {
13211322 // treated as terminal so a stale "still running" status can't wrongly block the delete.
13221323 run = reconcileWithSimulation (run );
13231324 AutonomousRunStatus status = run .getStatus ();
1325+ // Same active set as supersedePriorRun (and the frontend's isActive): PLANNING counts - the
1326+ // orchestrator is still designing the plan, so the delete must be refused mid-design too.
13241327 if (status == AutonomousRunStatus .CREATED
1328+ || status == AutonomousRunStatus .PLANNING
13251329 || status == AutonomousRunStatus .RUNNING
13261330 || status == AutonomousRunStatus .PAUSED
13271331 || status == AutonomousRunStatus .WAITING_INPUT ) {
@@ -1355,20 +1359,29 @@ public void deleteForScenarioForce(String scenarioId) {
13551359 }
13561360
13571361 /**
1358- * Tears an autonomous run down together with its underlying simulation, decision timeline, and
1359- * steering directives, and halts the XTM One orchestration. Shared by both scenario-delete paths
1360- * (single and bulk) so an autonomous run is cleaned up the same way however its scenario is
1361- * deleted - never leaving an orphaned run row or a self-resuming durable execution behind.
1362+ * Tears an autonomous run's COORDINATION down - the run row, its decision timeline and steering
1363+ * directives - and halts the XTM One orchestration. Shared by both scenario-delete paths (single
1364+ * and bulk) so a run is cleaned up the same way however its scenario is deleted, never leaving an
1365+ * orphaned run row or a self-resuming durable execution behind.
1366+ *
1367+ * <p>It does NOT delete a real simulation: a finished LIVE simulation is history and is left for
1368+ * the scenario delete to detach (scenarios_exercises SET_REFERENCE_NULL) like any other
1369+ * simulation - consistent with "a scenario can carry many simulations", and matching {@link
1370+ * #supersedePriorRun}. Only a non-executing plan-mode substrate simulation (a throwaway with no
1371+ * results) is deleted with the run.
13621372 */
13631373 private void tearDownRun (AutonomousRun run ) {
13641374 // Halt the XTM One orchestration first: once the run row is gone OpenAEV can no longer be
1365- // driven, but a still-live durable execution would keep self-resuming and dispatching injects
1366- // against the deleted simulation. Fired after commit (the run id is captured now) so the
1367- // upstream cancel resolves the same execution by its stable dedup key. Purge so no orphaned
1368- // shared state / work items linger after the scenario and its simulation are gone.
1375+ // driven, but a still-live durable execution would keep self-resuming and dispatching injects.
1376+ // Fired after commit (the run id is captured now) so the upstream cancel resolves the same
1377+ // execution by its stable dedup key. Purge so no orphaned shared state / work items linger.
13691378 String runId = run .getId ();
13701379 cancelOrchestratorAfterCommit (runId , "autonomous scenario deleted" , true );
1371- if (hasText (run .getSimulationId ())) {
1380+ // A plan-mode substrate simulation never executed and holds no results - delete it. A finished
1381+ // LIVE simulation is real history: keep it (the scenario delete detaches it) so deleting the
1382+ // scenario never destroys a simulation, which is the legacy 1:1 scenario<->simulation coupling
1383+ // we no longer want.
1384+ if (run .isPlanMode () && hasText (run .getSimulationId ())) {
13721385 exerciseService .deleteById (run .getSimulationId ());
13731386 }
13741387 directiveRepository .deleteByRunId (runId );
@@ -2879,10 +2892,11 @@ public AutonomousRun getBySimulation(String simulationId) {
28792892 }
28802893
28812894 /**
2882- * Returns the run driving a given scenario, if any. An autonomous run owns exactly one scenario
2883- * (and its single simulation), so this is the scenario-side twin of {@link #getBySimulation}: it
2884- * lets the scenario detail page render the same AI-driven cockpit and steer the underlying
2885- * simulation. 404 when the scenario is not autonomous.
2895+ * Returns the CURRENT autonomous run of a given scenario, if any. A scenario keeps at most one
2896+ * live run at a time (a rebuild or relaunch supersedes the prior run row, though its finished
2897+ * simulation stays as history - see {@link #supersedePriorRun}), so this is the scenario-side
2898+ * twin of {@link #getBySimulation}: it lets the scenario detail page render the AI-driven cockpit
2899+ * and steer the current run's simulation. 404 when the scenario has no autonomous run.
28862900 */
28872901 @ Transactional (rollbackFor = Exception .class )
28882902 public AutonomousRun getByScenario (String scenarioId ) {
0 commit comments