From 00d6ccc94f0f73b90c895c24da2ff08e5569d1b5 Mon Sep 17 00:00:00 2001 From: Pigbibi <20649888+Pigbibi@users.noreply.github.com> Date: Tue, 8 Sep 2026 04:37:20 +0800 Subject: [PATCH 1/2] fix(runtime): sync LIFECYCLE_PERFORMANCE_BUCKET to Cloud Run Allow store-backed production drift inject to read the shared lifecycle bucket after env sync. Co-Authored-By: Claude Co-authored-by: Cursor --- scripts/build_cloud_run_env_sync_plan.py | 2 ++ tests/test_build_cloud_run_env_sync_plan.py | 4 ++++ 2 files changed, 6 insertions(+) diff --git a/scripts/build_cloud_run_env_sync_plan.py b/scripts/build_cloud_run_env_sync_plan.py index 1c32f57..9d82c42 100644 --- a/scripts/build_cloud_run_env_sync_plan.py +++ b/scripts/build_cloud_run_env_sync_plan.py @@ -62,6 +62,7 @@ def _should_add_local_src(candidate: Path) -> bool: "GLOBAL_TELEGRAM_CHAT_ID", "NOTIFY_LANG", "EXECUTION_REPORT_GCS_URI", + "LIFECYCLE_PERFORMANCE_BUCKET", "LONGBRIDGE_MARKET", "LONGBRIDGE_MARKET_CALENDAR", "LONGBRIDGE_MARKET_TIMEZONE", @@ -113,6 +114,7 @@ def _should_add_local_src(candidate: Path) -> bool: "CASH_ONLY_EXECUTION", "RUNTIME_TARGET_ENABLED", "EXECUTION_REPORT_GCS_URI", + "LIFECYCLE_PERFORMANCE_BUCKET", "LONGBRIDGE_DURABLE_EXECUTION_COMMAND_PAPER_ENABLED", "LONGBRIDGE_DURABLE_EXECUTION_COMMAND_PAPER_CONSUMER_ENABLED", "LONGBRIDGE_EXECUTION_COMMAND_CLOUD_URI", diff --git a/tests/test_build_cloud_run_env_sync_plan.py b/tests/test_build_cloud_run_env_sync_plan.py index 31e2b90..f17e729 100644 --- a/tests/test_build_cloud_run_env_sync_plan.py +++ b/tests/test_build_cloud_run_env_sync_plan.py @@ -54,6 +54,7 @@ def test_build_cloud_run_env_sync_plan_legacy_mode_uses_shared_env(): "LONGBRIDGE_MARKET": "US", "LONGBRIDGE_MARKET_TIMEZONE": "America/New_York", "EXECUTION_REPORT_GCS_URI": "gs://runtime/execution-reports", + "LIFECYCLE_PERFORMANCE_BUCKET": "gs://qsl-runtime-logs-shared/strategy-lifecycle/v1", "LONGBRIDGE_DURABLE_EXECUTION_COMMAND_PAPER_ENABLED": "true", "LONGBRIDGE_DURABLE_EXECUTION_COMMAND_PAPER_CONSUMER_ENABLED": "true", "LONGBRIDGE_EXECUTION_COMMAND_CLOUD_URI": "gs://runtime/execution-commands/paper", @@ -77,6 +78,9 @@ def test_build_cloud_run_env_sync_plan_legacy_mode_uses_shared_env(): assert target["env"]["GLOBAL_TELEGRAM_CHAT_ID"] == "5992562050" assert target["env"]["LONGBRIDGE_MARKET"] == "US" assert target["env"]["EXECUTION_REPORT_GCS_URI"] == "gs://runtime/execution-reports" + assert target["env"]["LIFECYCLE_PERFORMANCE_BUCKET"] == ( + "gs://qsl-runtime-logs-shared/strategy-lifecycle/v1" + ) assert target["env"]["LONGBRIDGE_DURABLE_EXECUTION_COMMAND_PAPER_ENABLED"] == "true" assert target["env"]["LONGBRIDGE_DURABLE_EXECUTION_COMMAND_PAPER_CONSUMER_ENABLED"] == "true" assert target["env"]["LONGBRIDGE_EXECUTION_COMMAND_CLOUD_URI"] == "gs://runtime/execution-commands/paper" From 0f5444e5dc54d2ec75726fd25b96a65951ae32a9 Mon Sep 17 00:00:00 2001 From: Pigbibi <20649888+Pigbibi@users.noreply.github.com> Date: Tue, 8 Sep 2026 05:12:32 +0800 Subject: [PATCH 2/2] fix(runtime): export lifecycle store settings in sync workflow Co-Authored-By: Codex --- .github/workflows/sync-cloud-run-env.yml | 3 ++ scripts/build_cloud_run_env_sync_plan.py | 2 + tests/test_build_cloud_run_env_sync_plan.py | 59 +++++++++++++++++++++ tests/test_sync_cloud_run_env_workflow.sh | 2 + 4 files changed, 66 insertions(+) diff --git a/.github/workflows/sync-cloud-run-env.yml b/.github/workflows/sync-cloud-run-env.yml index 1f79d71..a2a1f34 100644 --- a/.github/workflows/sync-cloud-run-env.yml +++ b/.github/workflows/sync-cloud-run-env.yml @@ -232,6 +232,7 @@ jobs: RUNTIME_TARGET_ENABLED: ${{ vars.RUNTIME_TARGET_ENABLED }} NOTIFY_LANG: ${{ vars.NOTIFY_LANG }} EXECUTION_REPORT_GCS_URI: ${{ vars.EXECUTION_REPORT_GCS_URI }} + LIFECYCLE_PERFORMANCE_BUCKET: ${{ vars.LIFECYCLE_PERFORMANCE_BUCKET }} LONGBRIDGE_DRY_RUN_ONLY: ${{ vars.LONGBRIDGE_DRY_RUN_ONLY }} LONGBRIDGE_PHYSICAL_ACCOUNT_ID: ${{ vars.LONGBRIDGE_PHYSICAL_ACCOUNT_ID }} # Paper-only durable-command staging. These are intentionally opt-in; @@ -495,6 +496,8 @@ jobs: uv sync --frozen --no-dev - name: Resolve Cloud Run sync targets id: strategy_requirements + env: + GOOGLE_CLOUD_PROJECT: ${{ vars.GOOGLE_CLOUD_PROJECT || env.GCP_PROJECT_ID }} if: steps.config.outputs.env_sync_enabled == 'true' run: | set -euo pipefail diff --git a/scripts/build_cloud_run_env_sync_plan.py b/scripts/build_cloud_run_env_sync_plan.py index 9d82c42..8e6b440 100644 --- a/scripts/build_cloud_run_env_sync_plan.py +++ b/scripts/build_cloud_run_env_sync_plan.py @@ -63,6 +63,7 @@ def _should_add_local_src(candidate: Path) -> bool: "NOTIFY_LANG", "EXECUTION_REPORT_GCS_URI", "LIFECYCLE_PERFORMANCE_BUCKET", + "GOOGLE_CLOUD_PROJECT", "LONGBRIDGE_MARKET", "LONGBRIDGE_MARKET_CALENDAR", "LONGBRIDGE_MARKET_TIMEZONE", @@ -115,6 +116,7 @@ def _should_add_local_src(candidate: Path) -> bool: "RUNTIME_TARGET_ENABLED", "EXECUTION_REPORT_GCS_URI", "LIFECYCLE_PERFORMANCE_BUCKET", + "GOOGLE_CLOUD_PROJECT", "LONGBRIDGE_DURABLE_EXECUTION_COMMAND_PAPER_ENABLED", "LONGBRIDGE_DURABLE_EXECUTION_COMMAND_PAPER_CONSUMER_ENABLED", "LONGBRIDGE_EXECUTION_COMMAND_CLOUD_URI", diff --git a/tests/test_build_cloud_run_env_sync_plan.py b/tests/test_build_cloud_run_env_sync_plan.py index f17e729..fd044b2 100644 --- a/tests/test_build_cloud_run_env_sync_plan.py +++ b/tests/test_build_cloud_run_env_sync_plan.py @@ -6,10 +6,67 @@ import sys from pathlib import Path +import pytest + ROOT = Path(__file__).resolve().parents[1] SYNC_PLAN_SCRIPT_PATH = ROOT / "scripts" / "build_cloud_run_env_sync_plan.py" +def test_workflow_exports_lifecycle_store_settings(): + workflow = (ROOT / ".github/workflows/sync-cloud-run-env.yml").read_text() + assert " LIFECYCLE_PERFORMANCE_BUCKET: ${{ vars.LIFECYCLE_PERFORMANCE_BUCKET }}" in workflow + plan_step = workflow.split("id: strategy_requirements", 1)[1].split("\n - name:", 1)[0] + assert " GOOGLE_CLOUD_PROJECT: ${{ vars.GOOGLE_CLOUD_PROJECT || env.GCP_PROJECT_ID }}" in plan_step + + +@pytest.mark.parametrize("source", ["missing", "shared", "defaults", "target"]) +def test_lifecycle_store_per_service_precedence(source): + defaults = {"GLOBAL_TELEGRAM_CHAT_ID": "test-chat", "NOTIFY_LANG": "en"} + target = { + "service": "unit-service", + "account_prefix": "HK", + "runtime_target": json.loads(runtime_target_json( + "tqqq_growth_income", + deployment_selector="HK", + account_scope="HK", + service_name="unit-service", + )), + } + env = { + name: value for name, value in os.environ.items() + if name in {"PATH", "HOME", "TMPDIR", "LANG", "LC_ALL"} + } + env["PLATFORM_CONFIG_JSON"] = "{}" + values = {"LIFECYCLE_PERFORMANCE_BUCKET": "gs://unit-test-lifecycle"} + values["GOOGLE_CLOUD_PROJECT"] = "unit-test-project" + expected = {} + for layer in ("shared", "defaults", "target"): + if source == "missing": + break + destination = env if layer == "shared" else defaults if layer == "defaults" else target + expected = {name: value + "-" + layer for name, value in values.items()} + destination.update(expected) + if source == layer: + break + env["CLOUD_RUN_SERVICE_TARGETS_JSON"] = json.dumps( + {"defaults": defaults, "targets": [target]} + ) + result = subprocess.run( + [sys.executable, str(SYNC_PLAN_SCRIPT_PATH), "--json"], + check=True, capture_output=True, text=True, env=env, timeout=30, + ) + plan = json.loads(result.stdout) + assert plan["mode"] == "per_service" + actual = plan["targets"][0] + for name in values: + if source == "missing": + assert name not in actual["env"] + assert name in actual["remove_env_vars"] + else: + assert actual["env"][name] == expected[name] + assert name not in actual["remove_env_vars"] + + def runtime_target_json( strategy_profile: str, @@ -55,6 +112,7 @@ def test_build_cloud_run_env_sync_plan_legacy_mode_uses_shared_env(): "LONGBRIDGE_MARKET_TIMEZONE": "America/New_York", "EXECUTION_REPORT_GCS_URI": "gs://runtime/execution-reports", "LIFECYCLE_PERFORMANCE_BUCKET": "gs://qsl-runtime-logs-shared/strategy-lifecycle/v1", + "GOOGLE_CLOUD_PROJECT": "unit-test-project", "LONGBRIDGE_DURABLE_EXECUTION_COMMAND_PAPER_ENABLED": "true", "LONGBRIDGE_DURABLE_EXECUTION_COMMAND_PAPER_CONSUMER_ENABLED": "true", "LONGBRIDGE_EXECUTION_COMMAND_CLOUD_URI": "gs://runtime/execution-commands/paper", @@ -78,6 +136,7 @@ def test_build_cloud_run_env_sync_plan_legacy_mode_uses_shared_env(): assert target["env"]["GLOBAL_TELEGRAM_CHAT_ID"] == "5992562050" assert target["env"]["LONGBRIDGE_MARKET"] == "US" assert target["env"]["EXECUTION_REPORT_GCS_URI"] == "gs://runtime/execution-reports" + assert target["env"]["GOOGLE_CLOUD_PROJECT"] == "unit-test-project" assert target["env"]["LIFECYCLE_PERFORMANCE_BUCKET"] == ( "gs://qsl-runtime-logs-shared/strategy-lifecycle/v1" ) diff --git a/tests/test_sync_cloud_run_env_workflow.sh b/tests/test_sync_cloud_run_env_workflow.sh index 01db5b5..cbe437b 100644 --- a/tests/test_sync_cloud_run_env_workflow.sh +++ b/tests/test_sync_cloud_run_env_workflow.sh @@ -128,6 +128,8 @@ grep -Fq 'IBIT_ZSCORE_EXIT_RISK_REDUCED_EXPOSURE: ${{ vars.IBIT_ZSCORE_EXIT_RISK grep -Fq 'IBIT_ZSCORE_EXIT_RISK_OFF_EXPOSURE: ${{ vars.IBIT_ZSCORE_EXIT_RISK_OFF_EXPOSURE }}' "$workflow_file" grep -Fq 'IBIT_ZSCORE_EXIT_ALLOW_OUTSIDE_EXECUTION_WINDOW: ${{ vars.IBIT_ZSCORE_EXIT_ALLOW_OUTSIDE_EXECUTION_WINDOW }}' "$workflow_file" grep -Fq 'LONGBRIDGE_DRY_RUN_ONLY: ${{ vars.LONGBRIDGE_DRY_RUN_ONLY }}' "$workflow_file" +grep -Fq 'LIFECYCLE_PERFORMANCE_BUCKET: ${{ vars.LIFECYCLE_PERFORMANCE_BUCKET }}' "$workflow_file" +grep -Fq 'GOOGLE_CLOUD_PROJECT: ${{ vars.GOOGLE_CLOUD_PROJECT || env.GCP_PROJECT_ID }}' "$workflow_file" grep -Fq 'LONGBRIDGE_DURABLE_EXECUTION_COMMAND_PAPER_ENABLED: ${{ vars.LONGBRIDGE_DURABLE_EXECUTION_COMMAND_PAPER_ENABLED }}' "$workflow_file" grep -Fq 'LONGBRIDGE_DURABLE_EXECUTION_COMMAND_PAPER_CONSUMER_ENABLED: ${{ vars.LONGBRIDGE_DURABLE_EXECUTION_COMMAND_PAPER_CONSUMER_ENABLED }}' "$workflow_file" grep -Fq 'LONGBRIDGE_EXECUTION_COMMAND_CLOUD_URI: ${{ vars.LONGBRIDGE_EXECUTION_COMMAND_CLOUD_URI }}' "$workflow_file"