Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .github/workflows/runtime-guard.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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 }}
Expand Down
10 changes: 3 additions & 7 deletions constraints.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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
22 changes: 20 additions & 2 deletions scripts/cloud_run_runtime_guard.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down Expand Up @@ -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)
Expand All @@ -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 = (
Expand All @@ -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)
Expand All @@ -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 "<none configured>"
Expand Down
9 changes: 9 additions & 0 deletions tests/test_cloud_run_runtime_guard.py
Original file line number Diff line number Diff line change
@@ -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)
Expand Down
Loading