From fd461e5aabe1db380e5231c9b27f327f3d8e54d9 Mon Sep 17 00:00:00 2001 From: Hrishikesh Ballal Date: Wed, 18 Mar 2026 10:17:13 +0000 Subject: [PATCH 01/10] Moved scenarios to folders --- docs/index.md | 1 + .../bayesian_sim_air_traffic_data.yaml | 0 ...ir_traffic_data_varying_refresh_rates.yaml | 0 .../bluesky_sim_air_traffic_data.yaml | 0 ...y_sim_air_traffic_data_latency_issues.yaml | 0 .../opensky_live_data.yaml | 0 .../openutm_sim_air_traffic_data.yaml | 0 .../stream_air_traffic_example.yaml | 0 .../add_flight_declaration.yaml | 0 ...ht_declaration_via_operational_intent.yaml | 0 .../bulk_add_flight_declarations.yaml | 0 ..._declarations_via_operational_intents.yaml | 0 .../{ => geo-fence}/geo_fence_upload.yaml | 0 .../{ => sdsp-f3623}/sdsp_heartbeat.yaml | 0 scenarios/{ => sdsp-f3623}/sdsp_track.yaml | 0 .../sdsp_verify_sensor_failure_report.yaml | 0 .../{ => sdsp-f3623}/verify_sdsp_metrics.yaml | 0 .../F1_flow_no_telemetry_with_user_input.yaml | 0 .../F1_happy_path.yaml | 0 .../F2_contingent_path.yaml | 0 .../F3_non_conforming_path.yaml | 0 .../F5_non_conforming_path.yaml | 0 scripts/generate_docs.py | 10 ++- .../core/execution/scenario_loader.py | 16 +++- src/openutm_verification/server/router.py | 51 ++++++----- tests/test_yaml_scenarios.py | 4 +- .../ScenarioEditor/ScenarioList.tsx | 90 +++++++++++++++---- 27 files changed, 124 insertions(+), 48 deletions(-) rename scenarios/{ => airtraffic-simulations}/bayesian_sim_air_traffic_data.yaml (100%) rename scenarios/{ => airtraffic-simulations}/bayesian_sim_air_traffic_data_varying_refresh_rates.yaml (100%) rename scenarios/{ => airtraffic-simulations}/bluesky_sim_air_traffic_data.yaml (100%) rename scenarios/{ => airtraffic-simulations}/bluesky_sim_air_traffic_data_latency_issues.yaml (100%) rename scenarios/{ => airtraffic-simulations}/opensky_live_data.yaml (100%) rename scenarios/{ => airtraffic-simulations}/openutm_sim_air_traffic_data.yaml (100%) rename scenarios/{ => airtraffic-simulations}/stream_air_traffic_example.yaml (100%) rename scenarios/{ => flight-declarations}/add_flight_declaration.yaml (100%) rename scenarios/{ => flight-declarations}/add_flight_declaration_via_operational_intent.yaml (100%) rename scenarios/{ => flight-declarations}/bulk_add_flight_declarations.yaml (100%) rename scenarios/{ => flight-declarations}/bulk_add_flight_declarations_via_operational_intents.yaml (100%) rename scenarios/{ => geo-fence}/geo_fence_upload.yaml (100%) rename scenarios/{ => sdsp-f3623}/sdsp_heartbeat.yaml (100%) rename scenarios/{ => sdsp-f3623}/sdsp_track.yaml (100%) rename scenarios/{ => sdsp-f3623}/sdsp_verify_sensor_failure_report.yaml (100%) rename scenarios/{ => sdsp-f3623}/verify_sdsp_metrics.yaml (100%) rename scenarios/{ => standard-scenaios}/F1_flow_no_telemetry_with_user_input.yaml (100%) rename scenarios/{ => standard-scenaios}/F1_happy_path.yaml (100%) rename scenarios/{ => standard-scenaios}/F2_contingent_path.yaml (100%) rename scenarios/{ => standard-scenaios}/F3_non_conforming_path.yaml (100%) rename scenarios/{ => standard-scenaios}/F5_non_conforming_path.yaml (100%) diff --git a/docs/index.md b/docs/index.md index 8b25f9c6..c23578af 100644 --- a/docs/index.md +++ b/docs/index.md @@ -20,3 +20,4 @@ All scenarios are defined as YAML files and executed via the runner. See the [sc * [OpenUTM Sim Air Traffic Data](scenarios/openutm_sim_air_traffic_data.md) * [SDSP Heartbeat](scenarios/sdsp_heartbeat.md) * [SDSP Track](scenarios/sdsp_track.md) +* [SDSP Sensor Failure](scenarios/sdsp_verify_sensor_failure_report.md) diff --git a/scenarios/bayesian_sim_air_traffic_data.yaml b/scenarios/airtraffic-simulations/bayesian_sim_air_traffic_data.yaml similarity index 100% rename from scenarios/bayesian_sim_air_traffic_data.yaml rename to scenarios/airtraffic-simulations/bayesian_sim_air_traffic_data.yaml diff --git a/scenarios/bayesian_sim_air_traffic_data_varying_refresh_rates.yaml b/scenarios/airtraffic-simulations/bayesian_sim_air_traffic_data_varying_refresh_rates.yaml similarity index 100% rename from scenarios/bayesian_sim_air_traffic_data_varying_refresh_rates.yaml rename to scenarios/airtraffic-simulations/bayesian_sim_air_traffic_data_varying_refresh_rates.yaml diff --git a/scenarios/bluesky_sim_air_traffic_data.yaml b/scenarios/airtraffic-simulations/bluesky_sim_air_traffic_data.yaml similarity index 100% rename from scenarios/bluesky_sim_air_traffic_data.yaml rename to scenarios/airtraffic-simulations/bluesky_sim_air_traffic_data.yaml diff --git a/scenarios/bluesky_sim_air_traffic_data_latency_issues.yaml b/scenarios/airtraffic-simulations/bluesky_sim_air_traffic_data_latency_issues.yaml similarity index 100% rename from scenarios/bluesky_sim_air_traffic_data_latency_issues.yaml rename to scenarios/airtraffic-simulations/bluesky_sim_air_traffic_data_latency_issues.yaml diff --git a/scenarios/opensky_live_data.yaml b/scenarios/airtraffic-simulations/opensky_live_data.yaml similarity index 100% rename from scenarios/opensky_live_data.yaml rename to scenarios/airtraffic-simulations/opensky_live_data.yaml diff --git a/scenarios/openutm_sim_air_traffic_data.yaml b/scenarios/airtraffic-simulations/openutm_sim_air_traffic_data.yaml similarity index 100% rename from scenarios/openutm_sim_air_traffic_data.yaml rename to scenarios/airtraffic-simulations/openutm_sim_air_traffic_data.yaml diff --git a/scenarios/stream_air_traffic_example.yaml b/scenarios/airtraffic-simulations/stream_air_traffic_example.yaml similarity index 100% rename from scenarios/stream_air_traffic_example.yaml rename to scenarios/airtraffic-simulations/stream_air_traffic_example.yaml diff --git a/scenarios/add_flight_declaration.yaml b/scenarios/flight-declarations/add_flight_declaration.yaml similarity index 100% rename from scenarios/add_flight_declaration.yaml rename to scenarios/flight-declarations/add_flight_declaration.yaml diff --git a/scenarios/add_flight_declaration_via_operational_intent.yaml b/scenarios/flight-declarations/add_flight_declaration_via_operational_intent.yaml similarity index 100% rename from scenarios/add_flight_declaration_via_operational_intent.yaml rename to scenarios/flight-declarations/add_flight_declaration_via_operational_intent.yaml diff --git a/scenarios/bulk_add_flight_declarations.yaml b/scenarios/flight-declarations/bulk_add_flight_declarations.yaml similarity index 100% rename from scenarios/bulk_add_flight_declarations.yaml rename to scenarios/flight-declarations/bulk_add_flight_declarations.yaml diff --git a/scenarios/bulk_add_flight_declarations_via_operational_intents.yaml b/scenarios/flight-declarations/bulk_add_flight_declarations_via_operational_intents.yaml similarity index 100% rename from scenarios/bulk_add_flight_declarations_via_operational_intents.yaml rename to scenarios/flight-declarations/bulk_add_flight_declarations_via_operational_intents.yaml diff --git a/scenarios/geo_fence_upload.yaml b/scenarios/geo-fence/geo_fence_upload.yaml similarity index 100% rename from scenarios/geo_fence_upload.yaml rename to scenarios/geo-fence/geo_fence_upload.yaml diff --git a/scenarios/sdsp_heartbeat.yaml b/scenarios/sdsp-f3623/sdsp_heartbeat.yaml similarity index 100% rename from scenarios/sdsp_heartbeat.yaml rename to scenarios/sdsp-f3623/sdsp_heartbeat.yaml diff --git a/scenarios/sdsp_track.yaml b/scenarios/sdsp-f3623/sdsp_track.yaml similarity index 100% rename from scenarios/sdsp_track.yaml rename to scenarios/sdsp-f3623/sdsp_track.yaml diff --git a/scenarios/sdsp_verify_sensor_failure_report.yaml b/scenarios/sdsp-f3623/sdsp_verify_sensor_failure_report.yaml similarity index 100% rename from scenarios/sdsp_verify_sensor_failure_report.yaml rename to scenarios/sdsp-f3623/sdsp_verify_sensor_failure_report.yaml diff --git a/scenarios/verify_sdsp_metrics.yaml b/scenarios/sdsp-f3623/verify_sdsp_metrics.yaml similarity index 100% rename from scenarios/verify_sdsp_metrics.yaml rename to scenarios/sdsp-f3623/verify_sdsp_metrics.yaml diff --git a/scenarios/F1_flow_no_telemetry_with_user_input.yaml b/scenarios/standard-scenaios/F1_flow_no_telemetry_with_user_input.yaml similarity index 100% rename from scenarios/F1_flow_no_telemetry_with_user_input.yaml rename to scenarios/standard-scenaios/F1_flow_no_telemetry_with_user_input.yaml diff --git a/scenarios/F1_happy_path.yaml b/scenarios/standard-scenaios/F1_happy_path.yaml similarity index 100% rename from scenarios/F1_happy_path.yaml rename to scenarios/standard-scenaios/F1_happy_path.yaml diff --git a/scenarios/F2_contingent_path.yaml b/scenarios/standard-scenaios/F2_contingent_path.yaml similarity index 100% rename from scenarios/F2_contingent_path.yaml rename to scenarios/standard-scenaios/F2_contingent_path.yaml diff --git a/scenarios/F3_non_conforming_path.yaml b/scenarios/standard-scenaios/F3_non_conforming_path.yaml similarity index 100% rename from scenarios/F3_non_conforming_path.yaml rename to scenarios/standard-scenaios/F3_non_conforming_path.yaml diff --git a/scenarios/F5_non_conforming_path.yaml b/scenarios/standard-scenaios/F5_non_conforming_path.yaml similarity index 100% rename from scenarios/F5_non_conforming_path.yaml rename to scenarios/standard-scenaios/F5_non_conforming_path.yaml diff --git a/scripts/generate_docs.py b/scripts/generate_docs.py index 34dc018e..c4f68286 100644 --- a/scripts/generate_docs.py +++ b/scripts/generate_docs.py @@ -52,19 +52,21 @@ def main(): DOCS_DIR.mkdir(parents=True, exist_ok=True) - for yaml_file in SCENARIOS_DIR.glob("*.yaml"): - print(f"Processing {yaml_file.name}...") + for yaml_file in sorted(SCENARIOS_DIR.rglob("*.yaml")): + relative = yaml_file.relative_to(SCENARIOS_DIR) + print(f"Processing {relative}...") try: with open(yaml_file, "r") as f: data = yaml.safe_load(f) if not data: - print(f"Skipping empty file: {yaml_file.name}") + print(f"Skipping empty file: {relative}") continue md_content = generate_markdown(data) - md_filename = DOCS_DIR / yaml_file.with_suffix(".md").name + md_filename = (DOCS_DIR / relative).with_suffix(".md") + md_filename.parent.mkdir(parents=True, exist_ok=True) with open(md_filename, "w") as f: f.write(md_content) diff --git a/src/openutm_verification/core/execution/scenario_loader.py b/src/openutm_verification/core/execution/scenario_loader.py index e279dca9..a1396f3e 100644 --- a/src/openutm_verification/core/execution/scenario_loader.py +++ b/src/openutm_verification/core/execution/scenario_loader.py @@ -19,9 +19,21 @@ def load_yaml_scenario_definition(scenario_id: str, base_dir: Path | None = None """ scenarios_dir = base_dir or get_scenarios_directory() - scenario_path = scenarios_dir / f"{scenario_id}.yaml" + scenario_path = (scenarios_dir / f"{scenario_id}.yaml").resolve() + if not scenario_path.is_relative_to(scenarios_dir.resolve()): + raise FileNotFoundError(f"Scenario YAML not found: {scenario_id}") if not scenario_path.exists(): - raise FileNotFoundError(f"Scenario YAML not found: {scenario_path}") + if "/" not in scenario_id: + # Bare name: search subfolders for a matching file + matches = list(scenarios_dir.rglob(f"{scenario_id}.yaml")) + if len(matches) == 1: + scenario_path = matches[0] + elif len(matches) > 1: + raise FileNotFoundError(f"Ambiguous scenario '{scenario_id}': found in multiple locations: {[str(m) for m in matches]}") + else: + raise FileNotFoundError(f"Scenario YAML not found: {scenario_id}") + else: + raise FileNotFoundError(f"Scenario YAML not found: {scenario_path}") with open(scenario_path, "r", encoding="utf-8") as f: scenario_data = yaml.safe_load(f) diff --git a/src/openutm_verification/server/router.py b/src/openutm_verification/server/router.py index 22f40d58..c21ab252 100644 --- a/src/openutm_verification/server/router.py +++ b/src/openutm_verification/server/router.py @@ -35,27 +35,49 @@ async def execute_step(step: StepDefinition, runner: Any = Depends(get_runner)): @scenario_router.get("/api/scenarios") async def list_scenarios(): - """List all available scenarios.""" + """List all available scenarios, including those in sub-folders.""" path = get_scenarios_directory() if not path.exists(): return [] - return [f.stem for f in path.glob("*.yaml")] + return [str(f.relative_to(path).with_suffix("")) for f in sorted(path.rglob("*.yaml"))] @scenario_router.get("/api/suites") async def list_suites(runner: Any = Depends(get_runner)): - """Return suite-to-scenario mapping from the loaded configuration.""" + """Return suite-to-scenario mapping, resolving bare names to subfolder-relative paths.""" + scenarios_path = get_scenarios_directory() + stem_to_id = {f.stem: str(f.relative_to(scenarios_path).with_suffix("")) for f in scenarios_path.rglob("*.yaml")} config = runner.config result: dict[str, list[str]] = {} for suite_name, suite_config in config.suites.items(): if suite_config.scenarios: - result[suite_name] = [s.name for s in suite_config.scenarios] + result[suite_name] = [stem_to_id.get(s.name, s.name) for s in suite_config.scenarios] else: result[suite_name] = [] return result -@scenario_router.get("/api/scenarios/{scenario}") +@scenario_router.get("/api/scenarios/{scenario:path}/docs") +async def get_scenario_docs(scenario: str): + """Get the documentation for a specific scenario.""" + docs_dir = get_docs_directory() + file_path = (docs_dir / scenario).with_suffix(".md").resolve() + if not file_path.is_relative_to(docs_dir.resolve()): + raise HTTPException(status_code=404, detail="Documentation not found") + if not file_path.exists(): + # Fallback: search by stem for flat doc files not yet reorganised into subfolders + stem = Path(scenario).stem + matches = list(docs_dir.rglob(f"{stem}.md")) + if len(matches) == 1: + file_path = matches[0] + else: + raise HTTPException(status_code=404, detail="Documentation not found") + + with open(file_path, "r") as f: + return PlainTextResponse(f.read()) + + +@scenario_router.get("/api/scenarios/{scenario:path}") async def get_scenario(scenario: str): """Get the content of a specific scenario.""" try: @@ -67,14 +89,14 @@ async def get_scenario(scenario: str): raise HTTPException(status_code=500, detail=f"Invalid YAML: {e}") -@scenario_router.post("/api/scenarios/{name}") +@scenario_router.post("/api/scenarios/{name:path}") async def save_scenario(name: str, scenario: ScenarioDefinition): """Save a scenario to a YAML file.""" path = get_scenarios_directory() file_path = (path / name).with_suffix(".yaml") - # Ensure directory exists - path.mkdir(parents=True, exist_ok=True) + # Ensure directory exists (including any sub-folder) + file_path.parent.mkdir(parents=True, exist_ok=True) try: # Convert Pydantic model to dict, excluding None values to keep YAML clean @@ -88,19 +110,6 @@ async def save_scenario(name: str, scenario: ScenarioDefinition): raise HTTPException(status_code=500, detail=f"Failed to save scenario: {e}") -@scenario_router.get("/api/scenarios/{scenario}/docs") -async def get_scenario_docs(scenario: str): - """Get the documentation for a specific scenario.""" - file_path = (get_docs_directory() / scenario).with_suffix(".md") - - if not file_path.exists(): - raise HTTPException(status_code=404, detail="Documentation not found") - - with open(file_path, "r") as f: - content = f.read() - return PlainTextResponse(content) - - @scenario_router.get("/api/reports/latest") async def get_latest_report(request: Request, scenario: str | None = None): """Redirect to the latest generated report. Optionally filter by scenario name.""" diff --git a/tests/test_yaml_scenarios.py b/tests/test_yaml_scenarios.py index 7b89309c..6ebbceb4 100644 --- a/tests/test_yaml_scenarios.py +++ b/tests/test_yaml_scenarios.py @@ -12,7 +12,7 @@ from openutm_verification.server.runner import SessionManager SCENARIOS_DIR = Path(os.getenv("SCENARIOS_PATH", Path(__file__).parent.parent / "scenarios")) -YAML_FILES = list(SCENARIOS_DIR.glob("*.yaml")) +YAML_FILES = sorted(SCENARIOS_DIR.rglob("*.yaml")) @pytest.fixture @@ -68,7 +68,7 @@ def mock_data_files(): @pytest.mark.asyncio -@pytest.mark.parametrize("yaml_file", YAML_FILES, ids=[f.name for f in YAML_FILES]) +@pytest.mark.parametrize("yaml_file", YAML_FILES, ids=[str(f.relative_to(SCENARIOS_DIR)) for f in YAML_FILES]) async def test_yaml_scenario_execution(yaml_file, mock_clients, mock_data_files): """Verify that each YAML scenario can be loaded and executed with mocked clients.""" diff --git a/web-editor/src/components/ScenarioEditor/ScenarioList.tsx b/web-editor/src/components/ScenarioEditor/ScenarioList.tsx index 3a325c76..0b629e24 100644 --- a/web-editor/src/components/ScenarioEditor/ScenarioList.tsx +++ b/web-editor/src/components/ScenarioEditor/ScenarioList.tsx @@ -54,6 +54,21 @@ export const ScenarioList = ({ onLoadScenario, operations, currentScenarioName, const hasSuites = Object.keys(suites).length > 0; + const folderGroups = useMemo(() => { + const map: Record = {}; + for (const scenario of scenarios) { + const parts = scenario.split('/'); + const folder = parts.length > 1 ? parts.slice(0, -1).join('/') : ''; + if (!map[folder]) map[folder] = []; + map[folder].push(scenario); + } + return Object.entries(map).sort(([a], [b]) => { + if (a === '') return -1; + if (b === '') return 1; + return a.localeCompare(b); + }); + }, [scenarios]); + const groupedScenarios = useMemo(() => { const suiteNames = Object.keys(suites).sort((a, b) => a.localeCompare(b)); const scenarioSet = new Set(scenarios); @@ -109,25 +124,28 @@ export const ScenarioList = ({ onLoadScenario, operations, currentScenarioName, } }; - const renderScenarioItem = (name: string) => ( -
handleLoad(name)} - role="button" - tabIndex={0} - title={name} - style={{ - cursor: 'pointer', - opacity: loading ? 0.5 : 1, - borderColor: name === currentScenarioName ? 'var(--accent-primary)' : 'var(--border-color)', - backgroundColor: name === currentScenarioName ? 'var(--bg-secondary)' : 'var(--bg-primary)' - }} - > - - {name.replace(/_/g, ' ').replace(/\b\w/g, l => l.toUpperCase())} -
- ); + const renderScenarioItem = (name: string) => { + const displayName = name.split('/').pop() ?? name; + return ( +
handleLoad(name)} + role="button" + tabIndex={0} + title={name} + style={{ + cursor: 'pointer', + opacity: loading ? 0.5 : 1, + borderColor: name === currentScenarioName ? 'var(--accent-primary)' : 'var(--border-color)', + backgroundColor: name === currentScenarioName ? 'var(--bg-secondary)' : 'var(--bg-primary)' + }} + > + + {displayName.replace(/_/g, ' ').replace(/\b\w/g, l => l.toUpperCase())} +
+ ); + }; return (
@@ -164,6 +182,40 @@ export const ScenarioList = ({ onLoadScenario, operations, currentScenarioName,
); }) + ) : folderGroups.some(([folder]) => folder !== '') ? ( + folderGroups.map(([folder, items]) => { + const isCollapsed = collapsedSuites.has(`__folder__${folder}`); + const label = folder === '' + ? 'Root' + : folder.replace(/\//g, ' / ').replace(/_/g, ' ').replace(/\b\w/g, l => l.toUpperCase()); + return folder === '' ? ( +
+ {items.map(renderScenarioItem)} +
+ ) : ( +
+ + {!isCollapsed && ( +
+ {items.map(renderScenarioItem)} +
+ )} +
+ ); + }) ) : ( scenarios.map(renderScenarioItem) )} From 6a5e4042f2ad7e19e365569828b65f0ff9c5ca20 Mon Sep 17 00:00:00 2001 From: Hrishikesh Ballal Date: Wed, 18 Mar 2026 10:48:39 +0000 Subject: [PATCH 02/10] Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- src/openutm_verification/server/router.py | 27 +++++++++++++++++++++-- 1 file changed, 25 insertions(+), 2 deletions(-) diff --git a/src/openutm_verification/server/router.py b/src/openutm_verification/server/router.py index c21ab252..d852a1fa 100644 --- a/src/openutm_verification/server/router.py +++ b/src/openutm_verification/server/router.py @@ -46,12 +46,35 @@ async def list_scenarios(): async def list_suites(runner: Any = Depends(get_runner)): """Return suite-to-scenario mapping, resolving bare names to subfolder-relative paths.""" scenarios_path = get_scenarios_directory() - stem_to_id = {f.stem: str(f.relative_to(scenarios_path).with_suffix("")) for f in scenarios_path.rglob("*.yaml")} + # Build a mapping from stem to all matching scenario IDs to detect ambiguities. + stem_to_ids: dict[str, list[str]] = {} + for f in scenarios_path.rglob("*.yaml"): + stem = f.stem + scenario_id = str(f.relative_to(scenarios_path).with_suffix("")) + stem_to_ids.setdefault(stem, []).append(scenario_id) + + def resolve_scenario_name(name: str) -> str: + """Resolve a bare scenario name to its ID if unambiguous. + + If there are no scenarios with the given stem, or if multiple scenarios + share the same stem, return the name unchanged so callers can use a + fully-qualified ID instead. + """ + ids = stem_to_ids.get(name) + if not ids: + # No matching stem; leave as-is. + return name + if len(ids) == 1: + # Unique stem; safe to auto-resolve. + return ids[0] + # Ambiguous stem; do not auto-resolve to avoid silently picking one. + return name + config = runner.config result: dict[str, list[str]] = {} for suite_name, suite_config in config.suites.items(): if suite_config.scenarios: - result[suite_name] = [stem_to_id.get(s.name, s.name) for s in suite_config.scenarios] + result[suite_name] = [resolve_scenario_name(s.name) for s in suite_config.scenarios] else: result[suite_name] = [] return result From 36aa5996b5e41e07d9e2453a32c1c6ccdf313c18 Mon Sep 17 00:00:00 2001 From: Hrishikesh Ballal Date: Wed, 18 Mar 2026 11:01:18 +0000 Subject: [PATCH 03/10] Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- src/openutm_verification/server/router.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/openutm_verification/server/router.py b/src/openutm_verification/server/router.py index d852a1fa..e4469d00 100644 --- a/src/openutm_verification/server/router.py +++ b/src/openutm_verification/server/router.py @@ -39,7 +39,10 @@ async def list_scenarios(): path = get_scenarios_directory() if not path.exists(): return [] - return [str(f.relative_to(path).with_suffix("")) for f in sorted(path.rglob("*.yaml"))] + return [ + f.relative_to(path).with_suffix("").as_posix() + for f in sorted(path.rglob("*.yaml")) + ] @scenario_router.get("/api/suites") @@ -50,7 +53,7 @@ async def list_suites(runner: Any = Depends(get_runner)): stem_to_ids: dict[str, list[str]] = {} for f in scenarios_path.rglob("*.yaml"): stem = f.stem - scenario_id = str(f.relative_to(scenarios_path).with_suffix("")) + scenario_id = f.relative_to(scenarios_path).with_suffix("").as_posix() stem_to_ids.setdefault(stem, []).append(scenario_id) def resolve_scenario_name(name: str) -> str: From 9a560a79bac0703550187ac55f304953aba94c60 Mon Sep 17 00:00:00 2001 From: Hrishikesh Ballal Date: Wed, 18 Mar 2026 11:01:38 +0000 Subject: [PATCH 04/10] Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- docs/index.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/index.md b/docs/index.md index c23578af..595d57cb 100644 --- a/docs/index.md +++ b/docs/index.md @@ -20,4 +20,4 @@ All scenarios are defined as YAML files and executed via the runner. See the [sc * [OpenUTM Sim Air Traffic Data](scenarios/openutm_sim_air_traffic_data.md) * [SDSP Heartbeat](scenarios/sdsp_heartbeat.md) * [SDSP Track](scenarios/sdsp_track.md) -* [SDSP Sensor Failure](scenarios/sdsp_verify_sensor_failure_report.md) +* [SDSP Sensor Failure](scenarios/sdsp-f3623/sdsp_verify_sensor_failure_report.md) From 424d4c2183c025859ba2c6d2dacf79907df12eaf Mon Sep 17 00:00:00 2001 From: Hrishikesh Ballal Date: Wed, 18 Mar 2026 11:01:59 +0000 Subject: [PATCH 05/10] Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- .../src/components/ScenarioEditor/ScenarioList.tsx | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/web-editor/src/components/ScenarioEditor/ScenarioList.tsx b/web-editor/src/components/ScenarioEditor/ScenarioList.tsx index 0b629e24..478f953d 100644 --- a/web-editor/src/components/ScenarioEditor/ScenarioList.tsx +++ b/web-editor/src/components/ScenarioEditor/ScenarioList.tsx @@ -127,15 +127,15 @@ export const ScenarioList = ({ onLoadScenario, operations, currentScenarioName, const renderScenarioItem = (name: string) => { const displayName = name.split('/').pop() ?? name; return ( -
handleLoad(name)} - role="button" - tabIndex={0} title={name} + disabled={loading} style={{ - cursor: 'pointer', + cursor: loading ? 'not-allowed' : 'pointer', opacity: loading ? 0.5 : 1, borderColor: name === currentScenarioName ? 'var(--accent-primary)' : 'var(--border-color)', backgroundColor: name === currentScenarioName ? 'var(--bg-secondary)' : 'var(--bg-primary)' @@ -143,7 +143,7 @@ export const ScenarioList = ({ onLoadScenario, operations, currentScenarioName, > {displayName.replace(/_/g, ' ').replace(/\b\w/g, l => l.toUpperCase())} -
+ ); }; From 36166102d0875c5994585ec334021f5c039fb657 Mon Sep 17 00:00:00 2001 From: Hrishikesh Ballal Date: Wed, 18 Mar 2026 11:02:09 +0000 Subject: [PATCH 06/10] Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- src/openutm_verification/server/router.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/openutm_verification/server/router.py b/src/openutm_verification/server/router.py index e4469d00..f25c7f5a 100644 --- a/src/openutm_verification/server/router.py +++ b/src/openutm_verification/server/router.py @@ -99,7 +99,7 @@ async def get_scenario_docs(scenario: str): else: raise HTTPException(status_code=404, detail="Documentation not found") - with open(file_path, "r") as f: + with open(file_path, "r", encoding="utf-8") as f: return PlainTextResponse(f.read()) From 39eff69cb5c5f38171ebd5a6e247627c0dd96289 Mon Sep 17 00:00:00 2001 From: Hrishikesh Ballal Date: Wed, 18 Mar 2026 11:02:18 +0000 Subject: [PATCH 07/10] Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- src/openutm_verification/server/router.py | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/openutm_verification/server/router.py b/src/openutm_verification/server/router.py index f25c7f5a..2819bd2e 100644 --- a/src/openutm_verification/server/router.py +++ b/src/openutm_verification/server/router.py @@ -118,8 +118,16 @@ async def get_scenario(scenario: str): @scenario_router.post("/api/scenarios/{name:path}") async def save_scenario(name: str, scenario: ScenarioDefinition): """Save a scenario to a YAML file.""" - path = get_scenarios_directory() - file_path = (path / name).with_suffix(".yaml") + base_dir = get_scenarios_directory().resolve() + + # Reject absolute paths in the name to prevent writing outside the scenarios directory + if Path(name).is_absolute(): + raise HTTPException(status_code=400, detail="Invalid scenario name") + + # Normalize and validate the target path to prevent directory traversal + file_path = (base_dir / name).with_suffix(".yaml").resolve() + if not file_path.is_relative_to(base_dir): + raise HTTPException(status_code=400, detail="Invalid scenario name") # Ensure directory exists (including any sub-folder) file_path.parent.mkdir(parents=True, exist_ok=True) From 4de36054bf3797fbc16b7013d45df52fce15c4d7 Mon Sep 17 00:00:00 2001 From: Hrishikesh Ballal Date: Wed, 18 Mar 2026 11:12:44 +0000 Subject: [PATCH 08/10] Moved documentation to sub-folders --- docs/index.md | 31 +++++++++++++------ .../bluesky_sim_air_traffic_data.md | 0 .../opensky_live_data.md | 0 .../openutm_sim_air_traffic_data.md | 0 .../add_flight_declaration.md | 0 ...ight_declaration_via_operational_intent.md | 0 .../{ => geo-fence}/geo_fence_upload.md | 0 .../{ => sdsp-f3623}/sdsp_heartbeat.md | 0 docs/scenarios/{ => sdsp-f3623}/sdsp_track.md | 0 .../sdsp_verify_sensor_failure_report.md | 0 .../{ => sdsp-f3623}/verify_sdsp_metrics.md | 0 .../F1_flow_no_telemetry_with_user_input.md | 0 .../{ => standard-scenarios}/F1_happy_path.md | 0 .../F2_contingent_path.md | 0 .../F3_non_conforming_path.md | 0 .../F5_non_conforming_path.md | 0 16 files changed, 21 insertions(+), 10 deletions(-) rename docs/scenarios/{ => airtraffic-simulations}/bluesky_sim_air_traffic_data.md (100%) rename docs/scenarios/{ => airtraffic-simulations}/opensky_live_data.md (100%) rename docs/scenarios/{ => airtraffic-simulations}/openutm_sim_air_traffic_data.md (100%) rename docs/scenarios/{ => flight-declarations}/add_flight_declaration.md (100%) rename docs/scenarios/{ => flight-declarations}/add_flight_declaration_via_operational_intent.md (100%) rename docs/scenarios/{ => geo-fence}/geo_fence_upload.md (100%) rename docs/scenarios/{ => sdsp-f3623}/sdsp_heartbeat.md (100%) rename docs/scenarios/{ => sdsp-f3623}/sdsp_track.md (100%) rename docs/scenarios/{ => sdsp-f3623}/sdsp_verify_sensor_failure_report.md (100%) rename docs/scenarios/{ => sdsp-f3623}/verify_sdsp_metrics.md (100%) rename docs/scenarios/{ => standard-scenarios}/F1_flow_no_telemetry_with_user_input.md (100%) rename docs/scenarios/{ => standard-scenarios}/F1_happy_path.md (100%) rename docs/scenarios/{ => standard-scenarios}/F2_contingent_path.md (100%) rename docs/scenarios/{ => standard-scenarios}/F3_non_conforming_path.md (100%) rename docs/scenarios/{ => standard-scenarios}/F5_non_conforming_path.md (100%) diff --git a/docs/index.md b/docs/index.md index 595d57cb..0c942f43 100644 --- a/docs/index.md +++ b/docs/index.md @@ -10,14 +10,25 @@ All scenarios are defined as YAML files and executed via the runner. See the [sc ## Scenarios -* [Add Flight Declaration](scenarios/add_flight_declaration.md) -* [F1 Happy Path](scenarios/F1_happy_path.md) -* [F2 Contingent Path](scenarios/F2_contingent_path.md) -* [F3 Non Conforming Path](scenarios/F3_non_conforming_path.md) -* [F5 Non Conforming Path](scenarios/F5_non_conforming_path.md) -* [Geo Fence Upload](scenarios/geo_fence_upload.md) -* [Opensky Live Data](scenarios/opensky_live_data.md) -* [OpenUTM Sim Air Traffic Data](scenarios/openutm_sim_air_traffic_data.md) -* [SDSP Heartbeat](scenarios/sdsp_heartbeat.md) -* [SDSP Track](scenarios/sdsp_track.md) +### Flight Declarations +* [Add Flight Declaration](scenarios/flight-declarations/add_flight_declaration.md) +* [Add Flight Declaration (via Operational Itnent)](scenarios/flight-declarations/add_flight_declaration_via_operational_intent.md) + +### Basic Scenarios +* [F1 Happy Path](scenarios/standard-scenarios/F1_happy_path.md) +* [F2 Contingent Path](scenarios/standard-scenarios/F2_contingent_path.md) +* [F3 Non Conforming Path](scenarios/standard-scenarios/F3_non_conforming_path.md) +* [F5 Non Conforming Path](scenarios/standard-scenarios/F5_non_conforming_path.md) + +### Geo Fence Scenarios +* [Geo Fence Upload](scenarios/geo-fence/geo_fence_upload.md) + +## Air Traffic Simulation Scenarios +* [Opensky Live Data](scenarios/airtraffic-simulations/opensky_live_data.md) +* [OpenUTM Sim Air Traffic Data](scenarios/airtraffic-simulations/openutm_sim_air_traffic_data.md) + +## SDSP Scenarios +* [SDSP Heartbeat](scenarios/sdsp-f3623/sdsp_heartbeat.md) +* [SDSP Track](scenarios/sdsp-f3623/sdsp_track.md) * [SDSP Sensor Failure](scenarios/sdsp-f3623/sdsp_verify_sensor_failure_report.md) +* [SDSP Metrics](scenarios/sdsp-f3623/verify_sdsp_metrics.md) diff --git a/docs/scenarios/bluesky_sim_air_traffic_data.md b/docs/scenarios/airtraffic-simulations/bluesky_sim_air_traffic_data.md similarity index 100% rename from docs/scenarios/bluesky_sim_air_traffic_data.md rename to docs/scenarios/airtraffic-simulations/bluesky_sim_air_traffic_data.md diff --git a/docs/scenarios/opensky_live_data.md b/docs/scenarios/airtraffic-simulations/opensky_live_data.md similarity index 100% rename from docs/scenarios/opensky_live_data.md rename to docs/scenarios/airtraffic-simulations/opensky_live_data.md diff --git a/docs/scenarios/openutm_sim_air_traffic_data.md b/docs/scenarios/airtraffic-simulations/openutm_sim_air_traffic_data.md similarity index 100% rename from docs/scenarios/openutm_sim_air_traffic_data.md rename to docs/scenarios/airtraffic-simulations/openutm_sim_air_traffic_data.md diff --git a/docs/scenarios/add_flight_declaration.md b/docs/scenarios/flight-declarations/add_flight_declaration.md similarity index 100% rename from docs/scenarios/add_flight_declaration.md rename to docs/scenarios/flight-declarations/add_flight_declaration.md diff --git a/docs/scenarios/add_flight_declaration_via_operational_intent.md b/docs/scenarios/flight-declarations/add_flight_declaration_via_operational_intent.md similarity index 100% rename from docs/scenarios/add_flight_declaration_via_operational_intent.md rename to docs/scenarios/flight-declarations/add_flight_declaration_via_operational_intent.md diff --git a/docs/scenarios/geo_fence_upload.md b/docs/scenarios/geo-fence/geo_fence_upload.md similarity index 100% rename from docs/scenarios/geo_fence_upload.md rename to docs/scenarios/geo-fence/geo_fence_upload.md diff --git a/docs/scenarios/sdsp_heartbeat.md b/docs/scenarios/sdsp-f3623/sdsp_heartbeat.md similarity index 100% rename from docs/scenarios/sdsp_heartbeat.md rename to docs/scenarios/sdsp-f3623/sdsp_heartbeat.md diff --git a/docs/scenarios/sdsp_track.md b/docs/scenarios/sdsp-f3623/sdsp_track.md similarity index 100% rename from docs/scenarios/sdsp_track.md rename to docs/scenarios/sdsp-f3623/sdsp_track.md diff --git a/docs/scenarios/sdsp_verify_sensor_failure_report.md b/docs/scenarios/sdsp-f3623/sdsp_verify_sensor_failure_report.md similarity index 100% rename from docs/scenarios/sdsp_verify_sensor_failure_report.md rename to docs/scenarios/sdsp-f3623/sdsp_verify_sensor_failure_report.md diff --git a/docs/scenarios/verify_sdsp_metrics.md b/docs/scenarios/sdsp-f3623/verify_sdsp_metrics.md similarity index 100% rename from docs/scenarios/verify_sdsp_metrics.md rename to docs/scenarios/sdsp-f3623/verify_sdsp_metrics.md diff --git a/docs/scenarios/F1_flow_no_telemetry_with_user_input.md b/docs/scenarios/standard-scenarios/F1_flow_no_telemetry_with_user_input.md similarity index 100% rename from docs/scenarios/F1_flow_no_telemetry_with_user_input.md rename to docs/scenarios/standard-scenarios/F1_flow_no_telemetry_with_user_input.md diff --git a/docs/scenarios/F1_happy_path.md b/docs/scenarios/standard-scenarios/F1_happy_path.md similarity index 100% rename from docs/scenarios/F1_happy_path.md rename to docs/scenarios/standard-scenarios/F1_happy_path.md diff --git a/docs/scenarios/F2_contingent_path.md b/docs/scenarios/standard-scenarios/F2_contingent_path.md similarity index 100% rename from docs/scenarios/F2_contingent_path.md rename to docs/scenarios/standard-scenarios/F2_contingent_path.md diff --git a/docs/scenarios/F3_non_conforming_path.md b/docs/scenarios/standard-scenarios/F3_non_conforming_path.md similarity index 100% rename from docs/scenarios/F3_non_conforming_path.md rename to docs/scenarios/standard-scenarios/F3_non_conforming_path.md diff --git a/docs/scenarios/F5_non_conforming_path.md b/docs/scenarios/standard-scenarios/F5_non_conforming_path.md similarity index 100% rename from docs/scenarios/F5_non_conforming_path.md rename to docs/scenarios/standard-scenarios/F5_non_conforming_path.md From 0997ced0c71761cca59a654a3d3b99ed80f43a8c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Attila=20K=C3=B3bor?= Date: Wed, 18 Mar 2026 21:14:03 +0100 Subject: [PATCH 09/10] Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- docs/index.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/index.md b/docs/index.md index 0c942f43..75054099 100644 --- a/docs/index.md +++ b/docs/index.md @@ -12,7 +12,7 @@ All scenarios are defined as YAML files and executed via the runner. See the [sc ### Flight Declarations * [Add Flight Declaration](scenarios/flight-declarations/add_flight_declaration.md) -* [Add Flight Declaration (via Operational Itnent)](scenarios/flight-declarations/add_flight_declaration_via_operational_intent.md) +* [Add Flight Declaration (via Operational Intent)](scenarios/flight-declarations/add_flight_declaration_via_operational_intent.md) ### Basic Scenarios * [F1 Happy Path](scenarios/standard-scenarios/F1_happy_path.md) From 2f3f0347379906d49902030a443ae057e7451399 Mon Sep 17 00:00:00 2001 From: Attila Kobor Date: Wed, 18 Mar 2026 21:15:26 +0100 Subject: [PATCH 10/10] fix dir name --- .../F1_flow_no_telemetry_with_user_input.yaml | 0 .../{standard-scenaios => standard-scenarios}/F1_happy_path.yaml | 0 .../F2_contingent_path.yaml | 0 .../F3_non_conforming_path.yaml | 0 .../F5_non_conforming_path.yaml | 0 5 files changed, 0 insertions(+), 0 deletions(-) rename scenarios/{standard-scenaios => standard-scenarios}/F1_flow_no_telemetry_with_user_input.yaml (100%) rename scenarios/{standard-scenaios => standard-scenarios}/F1_happy_path.yaml (100%) rename scenarios/{standard-scenaios => standard-scenarios}/F2_contingent_path.yaml (100%) rename scenarios/{standard-scenaios => standard-scenarios}/F3_non_conforming_path.yaml (100%) rename scenarios/{standard-scenaios => standard-scenarios}/F5_non_conforming_path.yaml (100%) diff --git a/scenarios/standard-scenaios/F1_flow_no_telemetry_with_user_input.yaml b/scenarios/standard-scenarios/F1_flow_no_telemetry_with_user_input.yaml similarity index 100% rename from scenarios/standard-scenaios/F1_flow_no_telemetry_with_user_input.yaml rename to scenarios/standard-scenarios/F1_flow_no_telemetry_with_user_input.yaml diff --git a/scenarios/standard-scenaios/F1_happy_path.yaml b/scenarios/standard-scenarios/F1_happy_path.yaml similarity index 100% rename from scenarios/standard-scenaios/F1_happy_path.yaml rename to scenarios/standard-scenarios/F1_happy_path.yaml diff --git a/scenarios/standard-scenaios/F2_contingent_path.yaml b/scenarios/standard-scenarios/F2_contingent_path.yaml similarity index 100% rename from scenarios/standard-scenaios/F2_contingent_path.yaml rename to scenarios/standard-scenarios/F2_contingent_path.yaml diff --git a/scenarios/standard-scenaios/F3_non_conforming_path.yaml b/scenarios/standard-scenarios/F3_non_conforming_path.yaml similarity index 100% rename from scenarios/standard-scenaios/F3_non_conforming_path.yaml rename to scenarios/standard-scenarios/F3_non_conforming_path.yaml diff --git a/scenarios/standard-scenaios/F5_non_conforming_path.yaml b/scenarios/standard-scenarios/F5_non_conforming_path.yaml similarity index 100% rename from scenarios/standard-scenaios/F5_non_conforming_path.yaml rename to scenarios/standard-scenarios/F5_non_conforming_path.yaml