diff --git a/.github/workflows/runtime-guard.yml b/.github/workflows/runtime-guard.yml index 7ae7d0c..b5e70f6 100644 --- a/.github/workflows/runtime-guard.yml +++ b/.github/workflows/runtime-guard.yml @@ -52,6 +52,8 @@ jobs: RUNTIME_GUARD_FAIL_WORKFLOW_ON_ALERT: ${{ inputs.fail_workflow_on_alert || vars.RUNTIME_GUARD_FAIL_WORKFLOW_ON_ALERT || 'true' }} RUNTIME_GUARD_SCHEDULER_JOB_PATTERN: ${{ vars.RUNTIME_GUARD_SCHEDULER_JOB_PATTERN }} CLOUD_RUN_SERVICE: ${{ vars.CLOUD_RUN_SERVICE }} + CLOUD_RUN_SERVICES: ${{ vars.CLOUD_RUN_SERVICES }} + CLOUD_RUN_SERVICE_TARGETS_JSON: ${{ vars.CLOUD_RUN_SERVICE_TARGETS_JSON }} GLOBAL_TELEGRAM_CHAT_ID: ${{ vars.GLOBAL_TELEGRAM_CHAT_ID }} TELEGRAM_TOKEN: ${{ secrets.TELEGRAM_TOKEN }} TELEGRAM_TOKEN_SECRET_NAME: ${{ vars.TELEGRAM_TOKEN_SECRET_NAME }} diff --git a/constraints.txt b/constraints.txt index dc4783c..2a6081a 100644 --- a/constraints.txt +++ b/constraints.txt @@ -3,12 +3,8 @@ # Generated: 2026-07-01 # Auto-updated by update-qpk-pin.yml on every push to QPK main. -quant-platform-kit @ git+https://github.com/QuantStrategyLab/QuantPlatformKit.git@a9085d6d46147f62b80bbd441d19d85acaa3dda6 -us-equity-strategies @ git+https://github.com/QuantStrategyLab/UsEquityStrategies.git@635f64207a9db8dc7af51e7ad496557a57be5579 -hk-equity-strategies @ git+https://github.com/QuantStrategyLab/HkEquityStrategies.git@5fb48ea102c28deb6dd1c9674553f3c77aba4f2c +quant-platform-kit @ git+https://github.com/QuantStrategyLab/QuantPlatformKit.git@d786c1140967f0e96e35599d057f0655e5a9ba25 +us-equity-strategies @ git+https://github.com/QuantStrategyLab/UsEquityStrategies.git@c8df5f9659340965bd7f53998892ed1018ed4254 +hk-equity-strategies @ git+https://github.com/QuantStrategyLab/HkEquityStrategies.git@700c8b19c46336d3d8fcba687e58553afcf0235f cn-equity-strategies @ git+https://github.com/QuantStrategyLab/CnEquityStrategies.git@e09fd557c2bd9ae5f4d44228915c4e52c4b0dd21 crypto-strategies @ git+https://github.com/QuantStrategyLab/CryptoStrategies.git@565be005e4186fd6d750884e7b0bc59f779f9549 -quantuscombostrategies @ git+https://github.com/QuantStrategyLab/QuantUsComboStrategies.git@d35f4f86b09a7f4e99a1f83c973b21f2420398ef -quanthkcombostrategies @ git+https://github.com/QuantStrategyLab/QuantHkComboStrategies.git@dfc21e923c0f9ba54678a75d0edc96a56e2188e2 -quantcncombostrategies @ git+https://github.com/QuantStrategyLab/QuantCnComboStrategies.git@1172e77e8046088c2bab2880343786ec135539da -quantcryptocombostrategies @ git+https://github.com/QuantStrategyLab/QuantCryptoComboStrategies.git@475c5259d3ba7e2dcd4de30aaa96437142669a1c diff --git a/scripts/cloud_run_runtime_guard.py b/scripts/cloud_run_runtime_guard.py index c3de263..7f471d5 100644 --- a/scripts/cloud_run_runtime_guard.py +++ b/scripts/cloud_run_runtime_guard.py @@ -85,6 +85,19 @@ def _load_services() -> list[str]: return unique +def _scheduler_job_pattern_for_services(services: list[str]) -> str: + candidates: list[str] = [] + for service in services: + service_name = str(service or "").strip() + if not service_name: + continue + candidates.append(service_name) + if service_name.endswith("-service"): + candidates.append(service_name.removesuffix("-service")) + unique = list(dict.fromkeys(candidates)) + return "|".join(re.escape(candidate) for candidate in unique) + + def _run_gcloud(args: list[str]) -> subprocess.CompletedProcess[str]: return subprocess.run(args, text=True, capture_output=True, check=False) @@ -254,7 +267,6 @@ def main() -> int: require_success = _env_bool("RUNTIME_GUARD_REQUIRE_SUCCESS", False) fail_workflow = _env_bool("RUNTIME_GUARD_FAIL_WORKFLOW_ON_ALERT", True) check_scheduler = _env_bool("RUNTIME_GUARD_CHECK_SCHEDULER", True) - scheduler_pattern = os.environ.get("RUNTIME_GUARD_SCHEDULER_JOB_PATTERN") or "" since = ( dt.datetime.now(dt.timezone.utc) - dt.timedelta(minutes=lookback_minutes) @@ -270,6 +282,10 @@ def main() -> int: except RuntimeError as exc: services = [] issues.append(f"service configuration error: {exc}") + scheduler_pattern = ( + os.environ.get("RUNTIME_GUARD_SCHEDULER_JOB_PATTERN") + or _scheduler_job_pattern_for_services(services) + ) for service in services: log_filter = ( @@ -293,7 +309,7 @@ def main() -> int: f"no successful Cloud Run request found for {', '.join(services)} in the last {lookback_minutes} minutes" ) - if check_scheduler: + if check_scheduler and scheduler_pattern: log_filter = f'resource.type="cloud_scheduler_job" AND timestamp >= "{since_text}"' try: entries = _run_gcloud_logging(project, log_filter, limit) @@ -310,6 +326,8 @@ def main() -> int: details.extend(_summarize(entry) for entry in failures[:5]) except RuntimeError as exc: issues.append(f"Cloud Scheduler log query failed: {exc}") + elif check_scheduler: + print("Skipping Cloud Scheduler check because no scheduler job pattern could be derived.", file=sys.stderr) if not issues: service_text = ", ".join(services) if services else "" diff --git a/tests/test_cloud_run_runtime_guard.py b/tests/test_cloud_run_runtime_guard.py index f46e82d..021c4ae 100644 --- a/tests/test_cloud_run_runtime_guard.py +++ b/tests/test_cloud_run_runtime_guard.py @@ -1,10 +1,19 @@ from __future__ import annotations import subprocess +import re from scripts import cloud_run_runtime_guard as guard +def test_scheduler_job_pattern_includes_service_alias(): + pattern = guard._scheduler_job_pattern_for_services(["firstrade-service"]) + + assert re.search(pattern, "firstrade-service-scheduler") + assert re.search(pattern, "firstrade-scheduler") + assert not re.search(pattern, "charles-schwab-scheduler") + + def test_telegram_token_falls_back_to_secret_manager(monkeypatch): monkeypatch.delenv("TELEGRAM_TOKEN", raising=False) monkeypatch.delenv("TG_TOKEN", raising=False)