From 6bb35fcd714630da5d34be09624bca4a729f9896 Mon Sep 17 00:00:00 2001 From: Pigbibi <20649888+Pigbibi@users.noreply.github.com> Date: Sun, 30 Aug 2026 21:39:16 +0800 Subject: [PATCH] fix: normalize nested service target controls Co-Authored-By: Codex --- python/scripts/build_runtime_switch.py | 19 +++++++++ python/tests/test_runtime_settings.py | 55 ++++++++++++++++++++++++++ 2 files changed, 74 insertions(+) diff --git a/python/scripts/build_runtime_switch.py b/python/scripts/build_runtime_switch.py index c06ac88..a73c74d 100644 --- a/python/scripts/build_runtime_switch.py +++ b/python/scripts/build_runtime_switch.py @@ -962,6 +962,25 @@ def _patch_service_targets( current_entry=current_entry, replacement=replacement, ) + current_env = current_entry.get("env") + if isinstance(current_env, dict): + # Preserve the source shape of migrated inventories. A top-level + # override would take precedence over env.* at deploy time while + # leaving a stale nested value for consoles and future edits. + nested_env = dict(current_env) + structural_fields = { + "service", + "service_name", + "cloud_run_service", + "runtime_target", + "ACCOUNT_GROUP", + } + for name, value in tuple(replacement.items()): + if name in structural_fields: + continue + nested_env[name] = value + replacement.pop(name) + current_entry = {**current_entry, "env": nested_env} entries[matched_index] = {**current_entry, **replacement} elif not allow_create: platform_label = "IBKR " if platform == "ibkr" else "" diff --git a/python/tests/test_runtime_settings.py b/python/tests/test_runtime_settings.py index 31e3c12..c111b5d 100644 --- a/python/tests/test_runtime_settings.py +++ b/python/tests/test_runtime_settings.py @@ -3363,6 +3363,61 @@ def test_build_switch_target_patches_ibkr_service_targets_with_soxl_plugin_mount self.assertEqual(selected["runtime_target"]["strategy_profile"], "soxl_soxx_trend_income") self.assertEqual(selected["IBKR_STRATEGY_PLUGIN_MOUNTS_JSON"]["strategy_plugins"], []) + def test_build_switch_target_updates_nested_service_controls_without_stale_overrides(self): + existing = { + "targets": [ + { + "service": "interactive-brokers-demo-ibkr-soxl-service", + "ACCOUNT_GROUP": "demo-ibkr-soxl", + "env": { + "RUNTIME_TARGET_ENABLED": "false", + "IBKR_DRY_RUN_ONLY": "false", + }, + "runtime_target": { + "platform_id": "ibkr", + "strategy_profile": "soxl_soxx_trend_income", + "dry_run_only": False, + "deployment_selector": "demo-ibkr-soxl", + "account_selector": ["DEMO_IBKR_SOXL"], + "account_scope": "demo-ibkr-soxl", + "service_name": "interactive-brokers-demo-ibkr-soxl-service", + "execution_mode": "live", + }, + }, + ], + } + path = ROOT / ".pytest_runtime_service_targets_nested_controls.json" + path.write_text(runtime_settings.compact_json(existing), encoding="utf-8") + self.addCleanup(lambda: path.unlink(missing_ok=True)) + parser = build_runtime_switch.build_parser() + args = parser.parse_args( + [ + "--platform", + "ibkr", + "--target-name", + "demo-ibkr-soxl", + "--strategy-profile", + "soxl_soxx_trend_income", + "--account-selector", + "DEMO_IBKR_SOXL", + "--service-name", + "interactive-brokers-demo-ibkr-soxl-service", + "--existing-service-targets-json-file", + str(path), + "--extra-variables-json", + '{"RUNTIME_TARGET_ENABLED":"true"}', + ] + ) + + target = build_runtime_switch.build_switch_target(args) + assignments = {item.name: item.value for item in runtime_settings.build_assignments(target)} + selected = json.loads(assignments["CLOUD_RUN_SERVICE_TARGETS_JSON"])["targets"][0] + + self.assertEqual(selected["env"]["RUNTIME_TARGET_ENABLED"], "true") + self.assertEqual(selected["env"]["IBKR_DRY_RUN_ONLY"], "false") + self.assertNotIn("RUNTIME_TARGET_ENABLED", selected) + self.assertNotIn("IBKR_DRY_RUN_ONLY", selected) + def test_build_switch_target_rejects_unknown_ibkr_service_target_by_default(self): path = ROOT / ".pytest_runtime_service_targets_unknown.json" path.write_text('{"targets":[]}', encoding="utf-8")