From 146640f736492b65e74c90927e3eb940e3b54937 Mon Sep 17 00:00:00 2001 From: Pigbibi <20649888+Pigbibi@users.noreply.github.com> Date: Mon, 25 May 2026 16:37:51 +0800 Subject: [PATCH 1/2] Separate Google Voice alert config --- .github/workflows/sync-cloud-run-env.yml | 80 ++++++++--------------- README.md | 32 +++------ requirements.txt | 2 +- runtime_config_support.py | 48 ++++---------- tests/conftest.py | 11 +--- tests/test_runtime_config_support.py | 33 +++------- tests/test_sync_cloud_run_env_workflow.sh | 39 ++++------- 7 files changed, 75 insertions(+), 170 deletions(-) diff --git a/.github/workflows/sync-cloud-run-env.yml b/.github/workflows/sync-cloud-run-env.yml index 1804d05..a4e3b1f 100644 --- a/.github/workflows/sync-cloud-run-env.yml +++ b/.github/workflows/sync-cloud-run-env.yml @@ -34,14 +34,9 @@ jobs: IBKR_MIN_RESERVED_CASH_USD: ${{ vars.IBKR_MIN_RESERVED_CASH_USD }} IBKR_RESERVED_CASH_RATIO: ${{ vars.IBKR_RESERVED_CASH_RATIO }} IBKR_SAFE_HAVEN_CASH_SUBSTITUTE_THRESHOLD_USD: ${{ vars.IBKR_SAFE_HAVEN_CASH_SUBSTITUTE_THRESHOLD_USD }} - CRISIS_ALERT_GOOGLE_VOICE_TO: ${{ vars.CRISIS_ALERT_GOOGLE_VOICE_TO }} - CRISIS_ALERT_SMTP_FROM: ${{ vars.CRISIS_ALERT_SMTP_FROM }} - CRISIS_ALERT_SMTP_HOST: ${{ vars.CRISIS_ALERT_SMTP_HOST }} - CRISIS_ALERT_SMTP_PORT: ${{ vars.CRISIS_ALERT_SMTP_PORT }} - CRISIS_ALERT_SMTP_USERNAME: ${{ vars.CRISIS_ALERT_SMTP_USERNAME }} - CRISIS_ALERT_SMTP_PASSWORD_SECRET_NAME: ${{ vars.CRISIS_ALERT_SMTP_PASSWORD_SECRET_NAME }} - CRISIS_ALERT_SMTP_STARTTLS: ${{ vars.CRISIS_ALERT_SMTP_STARTTLS }} - CRISIS_ALERT_SMTP_SSL: ${{ vars.CRISIS_ALERT_SMTP_SSL }} + CRISIS_ALERT_GOOGLE_VOICE_GATEWAY: ${{ vars.CRISIS_ALERT_GOOGLE_VOICE_GATEWAY }} + CRISIS_ALERT_GOOGLE_VOICE_GMAIL_USER: ${{ vars.CRISIS_ALERT_GOOGLE_VOICE_GMAIL_USER }} + CRISIS_ALERT_GOOGLE_VOICE_GMAIL_APP_PASSWORD_SECRET_NAME: ${{ vars.CRISIS_ALERT_GOOGLE_VOICE_GMAIL_APP_PASSWORD_SECRET_NAME }} # Strategy-owned defaults continue to come from UsEquityStrategies; this workflow only syncs platform/runtime inputs. EXECUTION_REPORT_GCS_URI: ${{ vars.EXECUTION_REPORT_GCS_URI }} IB_GATEWAY_ZONE: ${{ vars.IB_GATEWAY_ZONE }} @@ -49,7 +44,7 @@ jobs: GLOBAL_TELEGRAM_CHAT_ID: ${{ vars.GLOBAL_TELEGRAM_CHAT_ID }} NOTIFY_LANG: ${{ vars.NOTIFY_LANG }} TELEGRAM_TOKEN: ${{ secrets.TELEGRAM_TOKEN }} - CRISIS_ALERT_SMTP_PASSWORD: ${{ secrets.CRISIS_ALERT_SMTP_PASSWORD }} + CRISIS_ALERT_GOOGLE_VOICE_GMAIL_APP_PASSWORD: ${{ secrets.CRISIS_ALERT_GOOGLE_VOICE_GMAIL_APP_PASSWORD }} steps: - name: Check whether env sync is enabled id: config @@ -272,8 +267,16 @@ jobs: "IB_CLIENT_ID" "IB_GATEWAY_INSTANCE_NAME" "IB_GATEWAY_MODE" + "CRISIS_ALERT_GOOGLE_VOICE_TO" + "CRISIS_ALERT_SMTP_FROM" + "CRISIS_ALERT_SMTP_HOST" + "CRISIS_ALERT_SMTP_PORT" + "CRISIS_ALERT_SMTP_USERNAME" + "CRISIS_ALERT_SMTP_PASSWORD" + "CRISIS_ALERT_SMTP_STARTTLS" + "CRISIS_ALERT_SMTP_SSL" ) - remove_secret_vars=() + remove_secret_vars=("CRISIS_ALERT_SMTP_PASSWORD") if [ -n "${TELEGRAM_TOKEN_SECRET_NAME:-}" ]; then secret_pairs+=("TELEGRAM_TOKEN=${TELEGRAM_TOKEN_SECRET_NAME}:latest") @@ -282,14 +285,15 @@ jobs: remove_secret_vars+=("TELEGRAM_TOKEN") fi - if [ -n "${CRISIS_ALERT_SMTP_PASSWORD_SECRET_NAME:-}" ]; then - secret_pairs+=("CRISIS_ALERT_SMTP_PASSWORD=${CRISIS_ALERT_SMTP_PASSWORD_SECRET_NAME}:latest") - elif [ -n "${CRISIS_ALERT_SMTP_PASSWORD:-}" ]; then - env_pairs+=("CRISIS_ALERT_SMTP_PASSWORD=${CRISIS_ALERT_SMTP_PASSWORD}") - remove_secret_vars+=("CRISIS_ALERT_SMTP_PASSWORD") + if [ -n "${CRISIS_ALERT_GOOGLE_VOICE_GMAIL_APP_PASSWORD_SECRET_NAME:-}" ]; then + secret_pairs+=("CRISIS_ALERT_GOOGLE_VOICE_GMAIL_APP_PASSWORD=${CRISIS_ALERT_GOOGLE_VOICE_GMAIL_APP_PASSWORD_SECRET_NAME}:latest") + remove_env_vars+=("CRISIS_ALERT_GOOGLE_VOICE_GMAIL_APP_PASSWORD") + elif [ -n "${CRISIS_ALERT_GOOGLE_VOICE_GMAIL_APP_PASSWORD:-}" ]; then + env_pairs+=("CRISIS_ALERT_GOOGLE_VOICE_GMAIL_APP_PASSWORD=${CRISIS_ALERT_GOOGLE_VOICE_GMAIL_APP_PASSWORD}") + remove_secret_vars+=("CRISIS_ALERT_GOOGLE_VOICE_GMAIL_APP_PASSWORD") else - remove_env_vars+=("CRISIS_ALERT_SMTP_PASSWORD") - remove_secret_vars+=("CRISIS_ALERT_SMTP_PASSWORD") + remove_env_vars+=("CRISIS_ALERT_GOOGLE_VOICE_GMAIL_APP_PASSWORD") + remove_secret_vars+=("CRISIS_ALERT_GOOGLE_VOICE_GMAIL_APP_PASSWORD") fi if [ -n "${IB_GATEWAY_ZONE:-}" ]; then @@ -364,46 +368,16 @@ jobs: remove_env_vars+=("IBKR_RESERVED_CASH_RATIO") fi - if [ -n "${CRISIS_ALERT_GOOGLE_VOICE_TO:-}" ]; then - env_pairs+=("CRISIS_ALERT_GOOGLE_VOICE_TO=${CRISIS_ALERT_GOOGLE_VOICE_TO}") + if [ -n "${CRISIS_ALERT_GOOGLE_VOICE_GATEWAY:-}" ]; then + env_pairs+=("CRISIS_ALERT_GOOGLE_VOICE_GATEWAY=${CRISIS_ALERT_GOOGLE_VOICE_GATEWAY}") else - remove_env_vars+=("CRISIS_ALERT_GOOGLE_VOICE_TO") + remove_env_vars+=("CRISIS_ALERT_GOOGLE_VOICE_GATEWAY") fi - if [ -n "${CRISIS_ALERT_SMTP_FROM:-}" ]; then - env_pairs+=("CRISIS_ALERT_SMTP_FROM=${CRISIS_ALERT_SMTP_FROM}") + if [ -n "${CRISIS_ALERT_GOOGLE_VOICE_GMAIL_USER:-}" ]; then + env_pairs+=("CRISIS_ALERT_GOOGLE_VOICE_GMAIL_USER=${CRISIS_ALERT_GOOGLE_VOICE_GMAIL_USER}") else - remove_env_vars+=("CRISIS_ALERT_SMTP_FROM") - fi - - if [ -n "${CRISIS_ALERT_SMTP_HOST:-}" ]; then - env_pairs+=("CRISIS_ALERT_SMTP_HOST=${CRISIS_ALERT_SMTP_HOST}") - else - remove_env_vars+=("CRISIS_ALERT_SMTP_HOST") - fi - - if [ -n "${CRISIS_ALERT_SMTP_PORT:-}" ]; then - env_pairs+=("CRISIS_ALERT_SMTP_PORT=${CRISIS_ALERT_SMTP_PORT}") - else - remove_env_vars+=("CRISIS_ALERT_SMTP_PORT") - fi - - if [ -n "${CRISIS_ALERT_SMTP_USERNAME:-}" ]; then - env_pairs+=("CRISIS_ALERT_SMTP_USERNAME=${CRISIS_ALERT_SMTP_USERNAME}") - else - remove_env_vars+=("CRISIS_ALERT_SMTP_USERNAME") - fi - - if [ -n "${CRISIS_ALERT_SMTP_STARTTLS:-}" ]; then - env_pairs+=("CRISIS_ALERT_SMTP_STARTTLS=${CRISIS_ALERT_SMTP_STARTTLS}") - else - remove_env_vars+=("CRISIS_ALERT_SMTP_STARTTLS") - fi - - if [ -n "${CRISIS_ALERT_SMTP_SSL:-}" ]; then - env_pairs+=("CRISIS_ALERT_SMTP_SSL=${CRISIS_ALERT_SMTP_SSL}") - else - remove_env_vars+=("CRISIS_ALERT_SMTP_SSL") + remove_env_vars+=("CRISIS_ALERT_GOOGLE_VOICE_GMAIL_USER") fi if [ -n "${EXECUTION_REPORT_GCS_URI:-}" ]; then diff --git a/README.md b/README.md index de5d67a..5810606 100644 --- a/README.md +++ b/README.md @@ -112,14 +112,9 @@ For IBKR, keep `paper` as a single account-group entry. If you later add live ac | `TELEGRAM_TOKEN` | Yes | Telegram bot token. For Cloud Run, prefer a Secret Manager reference instead of a literal env var. | | `GLOBAL_TELEGRAM_CHAT_ID` | Yes | Telegram chat ID used by this service. | | `NOTIFY_LANG` | No | `en` (default) or `zh` | -| `CRISIS_ALERT_GOOGLE_VOICE_TO` | No | Comma/semicolon/newline-separated Google Voice SMS gateway recipients, usually ending in `@txt.voice.google.com`. | -| `CRISIS_ALERT_SMTP_FROM` | No | SMTP sender address for Google Voice alerts. | -| `CRISIS_ALERT_SMTP_HOST` | No | SMTP host for Google Voice alerts. | -| `CRISIS_ALERT_SMTP_PORT` | No | SMTP port; defaults to `587`. | -| `CRISIS_ALERT_SMTP_USERNAME` | No | Optional SMTP username. | -| `CRISIS_ALERT_SMTP_PASSWORD` | No | Optional SMTP password. For Cloud Run, prefer `CRISIS_ALERT_SMTP_PASSWORD_SECRET_NAME` in env sync. | -| `CRISIS_ALERT_SMTP_STARTTLS` | No | Whether to use STARTTLS; defaults to `true`. | -| `CRISIS_ALERT_SMTP_SSL` | No | Whether to use implicit SMTP SSL; defaults to `false`. | +| `CRISIS_ALERT_GOOGLE_VOICE_GATEWAY` | No | Comma/semicolon/newline-separated Google Voice SMS gateway recipients, usually ending in `@txt.voice.google.com`. | +| `CRISIS_ALERT_GOOGLE_VOICE_GMAIL_USER` | No | Gmail address used to send Google Voice gateway alerts. | +| `CRISIS_ALERT_GOOGLE_VOICE_GMAIL_APP_PASSWORD` | No | Gmail App Password for Google Voice gateway alerts. For Cloud Run, prefer `CRISIS_ALERT_GOOGLE_VOICE_GMAIL_APP_PASSWORD_SECRET_NAME` in env sync. | The selected account-group entry must provide at least: @@ -215,7 +210,7 @@ Current behavior is fail-fast: - missing account-group config source → startup error - missing key fields in the selected group (`ib_gateway_instance_name`, `ib_gateway_mode`, `ib_client_id`) → startup error -When `IBKR_STRATEGY_PLUGIN_MOUNTS_JSON` includes the `crisis_response_shadow` plugin, the normal strategy-cycle Telegram message still includes the compact plugin line. If the plugin signal escalates beyond `no_action` (for example `canonical_route=true_crisis`, `suggested_action=defend`/`blocked`, or `would_trade_if_enabled=true`), the service also sends an independent crisis Google Voice notification when the `CRISIS_ALERT_*` SMTP settings are complete. +When `IBKR_STRATEGY_PLUGIN_MOUNTS_JSON` includes the `crisis_response_shadow` plugin, the normal strategy-cycle Telegram message still includes the compact plugin line. If the plugin signal escalates beyond `no_action` (for example `canonical_route=true_crisis`, `suggested_action=defend`/`blocked`, or `would_trade_if_enabled=true`), the service also sends an independent crisis Google Voice notification when the `CRISIS_ALERT_GOOGLE_VOICE_*` settings are complete. Google Voice alert results are written into the runtime report. Duplicate suppression uses stable plugin alert keys and stores markers under `STRATEGY_PLUGIN_ALERT_STATE_GCS_URI` when set, otherwise `EXECUTION_REPORT_GCS_URI`, with a local `/tmp` marker fallback. ### GitHub-managed Cloud Run env sync @@ -233,12 +228,11 @@ Recommended setup: - `ACCOUNT_GROUP` (recommended: `paper`) - `IB_ACCOUNT_GROUP_CONFIG_SECRET_NAME` - Optional: `IBKR_STRATEGY_PLUGIN_MOUNTS_JSON`, `IBKR_MIN_RESERVED_CASH_USD`, `IBKR_RESERVED_CASH_RATIO`, `IBKR_SAFE_HAVEN_CASH_SUBSTITUTE_THRESHOLD_USD` - - Optional crisis Google Voice alerts: `CRISIS_ALERT_GOOGLE_VOICE_TO`, `CRISIS_ALERT_SMTP_FROM`, `CRISIS_ALERT_SMTP_HOST`, `CRISIS_ALERT_SMTP_PORT`, `CRISIS_ALERT_SMTP_USERNAME`, `CRISIS_ALERT_SMTP_PASSWORD_SECRET_NAME`, `CRISIS_ALERT_SMTP_STARTTLS`, `CRISIS_ALERT_SMTP_SSL` - `GLOBAL_TELEGRAM_CHAT_ID` - `NOTIFY_LANG` - **Repository Secrets** - `TELEGRAM_TOKEN` (fallback only when `TELEGRAM_TOKEN_SECRET_NAME` is not set) - - `CRISIS_ALERT_SMTP_PASSWORD` (fallback only when `CRISIS_ALERT_SMTP_PASSWORD_SECRET_NAME` is not set) + - `CRISIS_ALERT_GOOGLE_VOICE_GMAIL_APP_PASSWORD` (fallback only when `CRISIS_ALERT_GOOGLE_VOICE_GMAIL_APP_PASSWORD_SECRET_NAME` is not set) - **Optional transition Variables** - `IB_GATEWAY_ZONE` - `IB_GATEWAY_IP_MODE` @@ -364,14 +358,9 @@ IBKR 账户 | `TELEGRAM_TOKEN` | 是 | Telegram 机器人 Token。Cloud Run 上更推荐走 Secret Manager 引用,不要直接写成明文 env。 | | `GLOBAL_TELEGRAM_CHAT_ID` | 是 | 这个服务使用的 Telegram Chat ID。 | | `NOTIFY_LANG` | 否 | `en`(默认)或 `zh` | -| `CRISIS_ALERT_GOOGLE_VOICE_TO` | 否 | Google Voice 短信网关收件人,通常以 `@txt.voice.google.com` 结尾,支持逗号、分号或换行分隔。 | -| `CRISIS_ALERT_SMTP_FROM` | 否 | Google Voice 告警的 SMTP 发件人。 | -| `CRISIS_ALERT_SMTP_HOST` | 否 | Google Voice 告警的 SMTP host。 | -| `CRISIS_ALERT_SMTP_PORT` | 否 | SMTP 端口,默认 `587`。 | -| `CRISIS_ALERT_SMTP_USERNAME` | 否 | 可选 SMTP 用户名。 | -| `CRISIS_ALERT_SMTP_PASSWORD` | 否 | 可选 SMTP 密码。Cloud Run env sync 建议配置 `CRISIS_ALERT_SMTP_PASSWORD_SECRET_NAME`。 | -| `CRISIS_ALERT_SMTP_STARTTLS` | 否 | 是否使用 STARTTLS,默认 `true`。 | -| `CRISIS_ALERT_SMTP_SSL` | 否 | 是否使用隐式 SMTP SSL,默认 `false`。 | +| `CRISIS_ALERT_GOOGLE_VOICE_GATEWAY` | 否 | Google Voice 短信网关收件人,通常以 `@txt.voice.google.com` 结尾,支持逗号、分号或换行分隔。 | +| `CRISIS_ALERT_GOOGLE_VOICE_GMAIL_USER` | 否 | Google Voice 通知使用的 Gmail 地址。 | +| `CRISIS_ALERT_GOOGLE_VOICE_GMAIL_APP_PASSWORD` | 否 | Google Voice 通知使用的 Gmail App Password。Cloud Run env sync 建议配置 `CRISIS_ALERT_GOOGLE_VOICE_GMAIL_APP_PASSWORD_SECRET_NAME`。 | 选中的账号组配置里,至少要有: @@ -433,7 +422,7 @@ IB_GATEWAY_IP_MODE=internal - 没有账号组配置来源 → 启动直接报错 - 选中的账号组缺少关键字段(`ib_gateway_instance_name`、`ib_gateway_mode`、`ib_client_id`)→ 启动直接报错 -如果 `IBKR_STRATEGY_PLUGIN_MOUNTS_JSON` 挂载了 `crisis_response_shadow` 插件,常规策略周期 Telegram 仍会包含插件摘要行。当插件信号升级到非 `no_action`(例如 `canonical_route=true_crisis`、`suggested_action=defend`/`blocked`,或 `would_trade_if_enabled=true`)时,只要 `CRISIS_ALERT_*` SMTP 配置完整,服务还会额外发一封独立 Google Voice 危机通知。 +如果 `IBKR_STRATEGY_PLUGIN_MOUNTS_JSON` 挂载了 `crisis_response_shadow` 插件,常规策略周期 Telegram 仍会包含插件摘要行。当插件信号升级到非 `no_action`(例如 `canonical_route=true_crisis`、`suggested_action=defend`/`blocked`,或 `would_trade_if_enabled=true`)时,只要 `CRISIS_ALERT_GOOGLE_VOICE_*` 配置完整,服务还会额外发送一条独立 Google Voice 危机通知。 Google Voice 告警结果会写入 runtime report。重复发送抑制使用稳定的插件告警 key;如配置了 `STRATEGY_PLUGIN_ALERT_STATE_GCS_URI` 则写入该前缀,否则复用 `EXECUTION_REPORT_GCS_URI`,并有本地 `/tmp` marker fallback。 ### GitHub 统一管理 Cloud Run 环境变量 @@ -451,12 +440,11 @@ Google Voice 告警结果会写入 runtime report。重复发送抑制使用稳 - `ACCOUNT_GROUP`(建议设为 `paper`) - `IB_ACCOUNT_GROUP_CONFIG_SECRET_NAME` - 可选:`IBKR_STRATEGY_PLUGIN_MOUNTS_JSON`、`IBKR_MIN_RESERVED_CASH_USD`、`IBKR_RESERVED_CASH_RATIO`、`IBKR_SAFE_HAVEN_CASH_SUBSTITUTE_THRESHOLD_USD` - - 可选危机插件 Google Voice 告警:`CRISIS_ALERT_GOOGLE_VOICE_TO`、`CRISIS_ALERT_SMTP_FROM`、`CRISIS_ALERT_SMTP_HOST`、`CRISIS_ALERT_SMTP_PORT`、`CRISIS_ALERT_SMTP_USERNAME`、`CRISIS_ALERT_SMTP_PASSWORD_SECRET_NAME`、`CRISIS_ALERT_SMTP_STARTTLS`、`CRISIS_ALERT_SMTP_SSL` - `GLOBAL_TELEGRAM_CHAT_ID` - `NOTIFY_LANG` - **仓库级 Secrets** - `TELEGRAM_TOKEN`(仅在没设置 `TELEGRAM_TOKEN_SECRET_NAME` 时作为 fallback) - - `CRISIS_ALERT_SMTP_PASSWORD`(仅在没设置 `CRISIS_ALERT_SMTP_PASSWORD_SECRET_NAME` 时作为 fallback) + - `CRISIS_ALERT_GOOGLE_VOICE_GMAIL_APP_PASSWORD`(仅在没设置 `CRISIS_ALERT_GOOGLE_VOICE_GMAIL_APP_PASSWORD_SECRET_NAME` 时作为 fallback) - **可选过渡 Variables** - `IB_GATEWAY_ZONE` - `IB_GATEWAY_IP_MODE` diff --git a/requirements.txt b/requirements.txt index 4a982d2..e3ce61b 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,6 +1,6 @@ flask gunicorn -quant-platform-kit @ git+https://github.com/QuantStrategyLab/QuantPlatformKit.git@4305a3c01151ced7e78b39519959444309326cd7 +quant-platform-kit @ git+https://github.com/QuantStrategyLab/QuantPlatformKit.git@2de98b250b437ecb2076197b34de291df080e760 us-equity-strategies @ git+https://github.com/QuantStrategyLab/UsEquityStrategies.git@03e23c5d6a620ca9ecb763550452f9ca3870481a pandas numpy diff --git a/runtime_config_support.py b/runtime_config_support.py index f95367e..0594ed3 100644 --- a/runtime_config_support.py +++ b/runtime_config_support.py @@ -72,14 +72,9 @@ class PlatformRuntimeSettings: tg_chat_id: str | None = None notify_lang: str = "en" strategy_plugin_mounts_json: str | None = None - crisis_alert_google_voice_to: tuple[str, ...] = () - crisis_alert_smtp_from: str | None = None - crisis_alert_smtp_host: str | None = None - crisis_alert_smtp_port: int = 587 - crisis_alert_smtp_username: str | None = None - crisis_alert_smtp_password: str | None = None - crisis_alert_smtp_starttls: bool = True - crisis_alert_smtp_ssl: bool = False + crisis_alert_google_voice_gateway: tuple[str, ...] = () + crisis_alert_google_voice_gmail_user: str | None = None + crisis_alert_google_voice_gmail_app_password: str | None = None runtime_target: RuntimeTarget | None = None @@ -188,14 +183,15 @@ def load_platform_runtime_settings( os.getenv("IBKR_STRATEGY_PLUGIN_MOUNTS_JSON") or os.getenv("STRATEGY_PLUGIN_MOUNTS_JSON") ), - crisis_alert_google_voice_to=split_env_list(os.getenv("CRISIS_ALERT_GOOGLE_VOICE_TO")), - crisis_alert_smtp_from=first_non_empty(os.getenv("CRISIS_ALERT_SMTP_FROM")), - crisis_alert_smtp_host=first_non_empty(os.getenv("CRISIS_ALERT_SMTP_HOST")), - crisis_alert_smtp_port=resolve_positive_int_env("CRISIS_ALERT_SMTP_PORT", default=587), - crisis_alert_smtp_username=first_non_empty(os.getenv("CRISIS_ALERT_SMTP_USERNAME")), - crisis_alert_smtp_password=first_non_empty(os.getenv("CRISIS_ALERT_SMTP_PASSWORD")), - crisis_alert_smtp_starttls=resolve_bool_env("CRISIS_ALERT_SMTP_STARTTLS", default=True), - crisis_alert_smtp_ssl=resolve_bool_value(os.getenv("CRISIS_ALERT_SMTP_SSL")), + crisis_alert_google_voice_gateway=split_env_list( + os.getenv("CRISIS_ALERT_GOOGLE_VOICE_GATEWAY") + ), + crisis_alert_google_voice_gmail_user=first_non_empty( + os.getenv("CRISIS_ALERT_GOOGLE_VOICE_GMAIL_USER") + ), + crisis_alert_google_voice_gmail_app_password=first_non_empty( + os.getenv("CRISIS_ALERT_GOOGLE_VOICE_GMAIL_APP_PASSWORD") + ), runtime_target=runtime_target, ) @@ -226,26 +222,6 @@ def resolve_optional_ratio_env(name: str) -> float | None: return value -def resolve_bool_env(name: str, *, default: bool) -> bool: - raw_value = os.getenv(name) - if raw_value is None or str(raw_value).strip() == "": - return bool(default) - return resolve_bool_value(raw_value) - - -def resolve_positive_int_env(name: str, *, default: int) -> int: - raw_value = os.getenv(name) - if raw_value is None or str(raw_value).strip() == "": - return int(default) - try: - value = int(raw_value) - except (TypeError, ValueError): - raise ValueError(f"{name} must be a positive integer, got {raw_value!r}") from None - if value <= 0: - raise ValueError(f"{name} must be a positive integer, got {raw_value!r}") - return value - - def split_env_list(raw_value: str | None) -> tuple[str, ...]: if raw_value is None: return () diff --git a/tests/conftest.py b/tests/conftest.py index 38c55ef..0512f0b 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -30,14 +30,9 @@ def load_strategy_module(**env_overrides): ), "IB_ACCOUNT_GROUP_CONFIG_SECRET_NAME": None, "GLOBAL_TELEGRAM_CHAT_ID": None, - "CRISIS_ALERT_GOOGLE_VOICE_TO": None, - "CRISIS_ALERT_SMTP_FROM": None, - "CRISIS_ALERT_SMTP_HOST": None, - "CRISIS_ALERT_SMTP_PORT": None, - "CRISIS_ALERT_SMTP_USERNAME": None, - "CRISIS_ALERT_SMTP_PASSWORD": None, - "CRISIS_ALERT_SMTP_STARTTLS": None, - "CRISIS_ALERT_SMTP_SSL": None, + "CRISIS_ALERT_GOOGLE_VOICE_GATEWAY": None, + "CRISIS_ALERT_GOOGLE_VOICE_GMAIL_USER": None, + "CRISIS_ALERT_GOOGLE_VOICE_GMAIL_APP_PASSWORD": None, } defaults.update(env_overrides) if "RUNTIME_TARGET_JSON" not in defaults: diff --git a/tests/test_runtime_config_support.py b/tests/test_runtime_config_support.py index cfa20e2..0128bee 100644 --- a/tests/test_runtime_config_support.py +++ b/tests/test_runtime_config_support.py @@ -135,14 +135,9 @@ def test_load_platform_runtime_settings_uses_minimal_group_config(monkeypatch): assert settings.tg_token is None assert settings.tg_chat_id is None assert settings.strategy_plugin_mounts_json is None - assert settings.crisis_alert_google_voice_to == () - assert settings.crisis_alert_smtp_from is None - assert settings.crisis_alert_smtp_host is None - assert settings.crisis_alert_smtp_port == 587 - assert settings.crisis_alert_smtp_username is None - assert settings.crisis_alert_smtp_password is None - assert settings.crisis_alert_smtp_starttls is True - assert settings.crisis_alert_smtp_ssl is False + assert settings.crisis_alert_google_voice_gateway == () + assert settings.crisis_alert_google_voice_gmail_user is None + assert settings.crisis_alert_google_voice_gmail_app_password is None def test_load_platform_runtime_settings_prefers_runtime_target_json(monkeypatch): @@ -212,25 +207,15 @@ def test_load_platform_runtime_settings_reads_crisis_alert_google_voice_config(m monkeypatch.setenv("RUNTIME_TARGET_JSON", runtime_target_json(SAMPLE_STRATEGY_PROFILE)) monkeypatch.setenv("ACCOUNT_GROUP", "paper") monkeypatch.setenv("IB_ACCOUNT_GROUP_CONFIG_JSON", MINIMAL_GROUP_JSON) - monkeypatch.setenv("CRISIS_ALERT_GOOGLE_VOICE_TO", "gateway@txt.voice.google.com") - monkeypatch.setenv("CRISIS_ALERT_SMTP_FROM", "smtp-from@example.com") - monkeypatch.setenv("CRISIS_ALERT_SMTP_HOST", "smtp.example.com") - monkeypatch.setenv("CRISIS_ALERT_SMTP_PORT", "465") - monkeypatch.setenv("CRISIS_ALERT_SMTP_USERNAME", "bot") - monkeypatch.setenv("CRISIS_ALERT_SMTP_PASSWORD", "secret") - monkeypatch.setenv("CRISIS_ALERT_SMTP_STARTTLS", "false") - monkeypatch.setenv("CRISIS_ALERT_SMTP_SSL", "true") + monkeypatch.setenv("CRISIS_ALERT_GOOGLE_VOICE_GATEWAY", "gateway@txt.voice.google.com") + monkeypatch.setenv("CRISIS_ALERT_GOOGLE_VOICE_GMAIL_USER", "sender@gmail.com") + monkeypatch.setenv("CRISIS_ALERT_GOOGLE_VOICE_GMAIL_APP_PASSWORD", "secret") settings = load_platform_runtime_settings(project_id_resolver=lambda: "project-1") - assert settings.crisis_alert_google_voice_to == ("gateway@txt.voice.google.com",) - assert settings.crisis_alert_smtp_from == "smtp-from@example.com" - assert settings.crisis_alert_smtp_host == "smtp.example.com" - assert settings.crisis_alert_smtp_port == 465 - assert settings.crisis_alert_smtp_username == "bot" - assert settings.crisis_alert_smtp_password == "secret" - assert settings.crisis_alert_smtp_starttls is False - assert settings.crisis_alert_smtp_ssl is True + assert settings.crisis_alert_google_voice_gateway == ("gateway@txt.voice.google.com",) + assert settings.crisis_alert_google_voice_gmail_user == "sender@gmail.com" + assert settings.crisis_alert_google_voice_gmail_app_password == "secret" def test_load_platform_runtime_settings_uses_whole_share_quantity_step(monkeypatch): diff --git a/tests/test_sync_cloud_run_env_workflow.sh b/tests/test_sync_cloud_run_env_workflow.sh index f8286c3..409282e 100644 --- a/tests/test_sync_cloud_run_env_workflow.sh +++ b/tests/test_sync_cloud_run_env_workflow.sh @@ -45,20 +45,15 @@ grep -Fq 'IBKR_PAPER_LIQUIDATE_ONLY: ${{ vars.IBKR_PAPER_LIQUIDATE_ONLY }}' "$wo grep -Fq 'IBKR_MIN_RESERVED_CASH_USD: ${{ vars.IBKR_MIN_RESERVED_CASH_USD }}' "$workflow_file" grep -Fq 'IBKR_RESERVED_CASH_RATIO: ${{ vars.IBKR_RESERVED_CASH_RATIO }}' "$workflow_file" grep -Fq 'IBKR_SAFE_HAVEN_CASH_SUBSTITUTE_THRESHOLD_USD: ${{ vars.IBKR_SAFE_HAVEN_CASH_SUBSTITUTE_THRESHOLD_USD }}' "$workflow_file" -grep -Fq 'CRISIS_ALERT_GOOGLE_VOICE_TO: ${{ vars.CRISIS_ALERT_GOOGLE_VOICE_TO }}' "$workflow_file" -grep -Fq 'CRISIS_ALERT_SMTP_FROM: ${{ vars.CRISIS_ALERT_SMTP_FROM }}' "$workflow_file" -grep -Fq 'CRISIS_ALERT_SMTP_HOST: ${{ vars.CRISIS_ALERT_SMTP_HOST }}' "$workflow_file" -grep -Fq 'CRISIS_ALERT_SMTP_PORT: ${{ vars.CRISIS_ALERT_SMTP_PORT }}' "$workflow_file" -grep -Fq 'CRISIS_ALERT_SMTP_USERNAME: ${{ vars.CRISIS_ALERT_SMTP_USERNAME }}' "$workflow_file" -grep -Fq 'CRISIS_ALERT_SMTP_PASSWORD_SECRET_NAME: ${{ vars.CRISIS_ALERT_SMTP_PASSWORD_SECRET_NAME }}' "$workflow_file" -grep -Fq 'CRISIS_ALERT_SMTP_STARTTLS: ${{ vars.CRISIS_ALERT_SMTP_STARTTLS }}' "$workflow_file" -grep -Fq 'CRISIS_ALERT_SMTP_SSL: ${{ vars.CRISIS_ALERT_SMTP_SSL }}' "$workflow_file" +grep -Fq 'CRISIS_ALERT_GOOGLE_VOICE_GATEWAY: ${{ vars.CRISIS_ALERT_GOOGLE_VOICE_GATEWAY }}' "$workflow_file" +grep -Fq 'CRISIS_ALERT_GOOGLE_VOICE_GMAIL_USER: ${{ vars.CRISIS_ALERT_GOOGLE_VOICE_GMAIL_USER }}' "$workflow_file" +grep -Fq 'CRISIS_ALERT_GOOGLE_VOICE_GMAIL_APP_PASSWORD_SECRET_NAME: ${{ vars.CRISIS_ALERT_GOOGLE_VOICE_GMAIL_APP_PASSWORD_SECRET_NAME }}' "$workflow_file" grep -Fq 'IB_GATEWAY_ZONE: ${{ vars.IB_GATEWAY_ZONE }}' "$workflow_file" grep -Fq 'IB_GATEWAY_IP_MODE: ${{ vars.IB_GATEWAY_IP_MODE }}' "$workflow_file" grep -Fq 'GLOBAL_TELEGRAM_CHAT_ID: ${{ vars.GLOBAL_TELEGRAM_CHAT_ID }}' "$workflow_file" grep -Fq 'NOTIFY_LANG: ${{ vars.NOTIFY_LANG }}' "$workflow_file" grep -Fq 'TELEGRAM_TOKEN: ${{ secrets.TELEGRAM_TOKEN }}' "$workflow_file" -grep -Fq 'CRISIS_ALERT_SMTP_PASSWORD: ${{ secrets.CRISIS_ALERT_SMTP_PASSWORD }}' "$workflow_file" +grep -Fq 'CRISIS_ALERT_GOOGLE_VOICE_GMAIL_APP_PASSWORD: ${{ secrets.CRISIS_ALERT_GOOGLE_VOICE_GMAIL_APP_PASSWORD }}' "$workflow_file" grep -Fq "Skipping Cloud Run env sync because ENABLE_GITHUB_ENV_SYNC is not set to true." "$workflow_file" grep -Fq "Cloud Run env sync is enabled, but these values are missing:" "$workflow_file" grep -Fq 'TELEGRAM_TOKEN_SECRET_NAME or TELEGRAM_TOKEN' "$workflow_file" @@ -96,14 +91,9 @@ grep -Fq 'env_pairs+=("IBKR_PAPER_LIQUIDATE_ONLY=${IBKR_PAPER_LIQUIDATE_ONLY}")' grep -Fq 'env_pairs+=("IBKR_MIN_RESERVED_CASH_USD=${IBKR_MIN_RESERVED_CASH_USD}")' "$workflow_file" grep -Fq 'env_pairs+=("IBKR_RESERVED_CASH_RATIO=${IBKR_RESERVED_CASH_RATIO}")' "$workflow_file" grep -Fq 'env_pairs+=("IBKR_SAFE_HAVEN_CASH_SUBSTITUTE_THRESHOLD_USD=${IBKR_SAFE_HAVEN_CASH_SUBSTITUTE_THRESHOLD_USD}")' "$workflow_file" -grep -Fq 'env_pairs+=("CRISIS_ALERT_GOOGLE_VOICE_TO=${CRISIS_ALERT_GOOGLE_VOICE_TO}")' "$workflow_file" -grep -Fq 'env_pairs+=("CRISIS_ALERT_SMTP_FROM=${CRISIS_ALERT_SMTP_FROM}")' "$workflow_file" -grep -Fq 'env_pairs+=("CRISIS_ALERT_SMTP_HOST=${CRISIS_ALERT_SMTP_HOST}")' "$workflow_file" -grep -Fq 'env_pairs+=("CRISIS_ALERT_SMTP_PORT=${CRISIS_ALERT_SMTP_PORT}")' "$workflow_file" -grep -Fq 'env_pairs+=("CRISIS_ALERT_SMTP_USERNAME=${CRISIS_ALERT_SMTP_USERNAME}")' "$workflow_file" -grep -Fq 'env_pairs+=("CRISIS_ALERT_SMTP_PASSWORD=${CRISIS_ALERT_SMTP_PASSWORD}")' "$workflow_file" -grep -Fq 'env_pairs+=("CRISIS_ALERT_SMTP_STARTTLS=${CRISIS_ALERT_SMTP_STARTTLS}")' "$workflow_file" -grep -Fq 'env_pairs+=("CRISIS_ALERT_SMTP_SSL=${CRISIS_ALERT_SMTP_SSL}")' "$workflow_file" +grep -Fq 'env_pairs+=("CRISIS_ALERT_GOOGLE_VOICE_GATEWAY=${CRISIS_ALERT_GOOGLE_VOICE_GATEWAY}")' "$workflow_file" +grep -Fq 'env_pairs+=("CRISIS_ALERT_GOOGLE_VOICE_GMAIL_USER=${CRISIS_ALERT_GOOGLE_VOICE_GMAIL_USER}")' "$workflow_file" +grep -Fq 'env_pairs+=("CRISIS_ALERT_GOOGLE_VOICE_GMAIL_APP_PASSWORD=${CRISIS_ALERT_GOOGLE_VOICE_GMAIL_APP_PASSWORD}")' "$workflow_file" grep -Fq 'remove_env_vars+=("IBKR_FEATURE_SNAPSHOT_PATH")' "$workflow_file" grep -Fq 'remove_env_vars+=("IBKR_FEATURE_SNAPSHOT_MANIFEST_PATH")' "$workflow_file" grep -Fq 'remove_env_vars+=("IBKR_STRATEGY_CONFIG_PATH")' "$workflow_file" @@ -114,16 +104,13 @@ grep -Fq 'remove_env_vars+=("IBKR_PAPER_LIQUIDATE_ONLY")' "$workflow_file" grep -Fq 'remove_env_vars+=("IBKR_MIN_RESERVED_CASH_USD")' "$workflow_file" grep -Fq 'remove_env_vars+=("IBKR_RESERVED_CASH_RATIO")' "$workflow_file" grep -Fq 'remove_env_vars+=("IBKR_SAFE_HAVEN_CASH_SUBSTITUTE_THRESHOLD_USD")' "$workflow_file" -grep -Fq 'remove_env_vars+=("CRISIS_ALERT_GOOGLE_VOICE_TO")' "$workflow_file" -grep -Fq 'remove_env_vars+=("CRISIS_ALERT_SMTP_FROM")' "$workflow_file" -grep -Fq 'remove_env_vars+=("CRISIS_ALERT_SMTP_HOST")' "$workflow_file" -grep -Fq 'remove_env_vars+=("CRISIS_ALERT_SMTP_PORT")' "$workflow_file" -grep -Fq 'remove_env_vars+=("CRISIS_ALERT_SMTP_USERNAME")' "$workflow_file" -grep -Fq 'remove_env_vars+=("CRISIS_ALERT_SMTP_PASSWORD")' "$workflow_file" -grep -Fq 'remove_env_vars+=("CRISIS_ALERT_SMTP_STARTTLS")' "$workflow_file" -grep -Fq 'remove_env_vars+=("CRISIS_ALERT_SMTP_SSL")' "$workflow_file" +grep -Fq 'remove_env_vars+=("CRISIS_ALERT_GOOGLE_VOICE_GATEWAY")' "$workflow_file" +grep -Fq 'remove_env_vars+=("CRISIS_ALERT_GOOGLE_VOICE_GMAIL_USER")' "$workflow_file" +grep -Fq 'remove_env_vars+=("CRISIS_ALERT_GOOGLE_VOICE_GMAIL_APP_PASSWORD")' "$workflow_file" +grep -Fq '"CRISIS_ALERT_GOOGLE_VOICE_TO"' "$workflow_file" +grep -Fq '"CRISIS_ALERT_SMTP_HOST"' "$workflow_file" grep -Fq 'secret_pairs+=("TELEGRAM_TOKEN=${TELEGRAM_TOKEN_SECRET_NAME}:latest")' "$workflow_file" -grep -Fq 'secret_pairs+=("CRISIS_ALERT_SMTP_PASSWORD=${CRISIS_ALERT_SMTP_PASSWORD_SECRET_NAME}:latest")' "$workflow_file" +grep -Fq 'secret_pairs+=("CRISIS_ALERT_GOOGLE_VOICE_GMAIL_APP_PASSWORD=${CRISIS_ALERT_GOOGLE_VOICE_GMAIL_APP_PASSWORD_SECRET_NAME}:latest")' "$workflow_file" grep -Fq 'env_pairs+=("TELEGRAM_TOKEN=${TELEGRAM_TOKEN}")' "$workflow_file" grep -Fq 'remove_env_vars+=("IB_GATEWAY_ZONE")' "$workflow_file" grep -Fq 'remove_env_vars+=("IB_GATEWAY_IP_MODE")' "$workflow_file" From e21aa15d49d4caba2c50048c753506b020a4e593 Mon Sep 17 00:00:00 2001 From: Pigbibi <20649888+Pigbibi@users.noreply.github.com> Date: Mon, 25 May 2026 17:11:23 +0800 Subject: [PATCH 2/2] Sync Google Voice dependency pins --- requirements.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/requirements.txt b/requirements.txt index e3ce61b..927d54b 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,7 +1,7 @@ flask gunicorn -quant-platform-kit @ git+https://github.com/QuantStrategyLab/QuantPlatformKit.git@2de98b250b437ecb2076197b34de291df080e760 -us-equity-strategies @ git+https://github.com/QuantStrategyLab/UsEquityStrategies.git@03e23c5d6a620ca9ecb763550452f9ca3870481a +quant-platform-kit @ git+https://github.com/QuantStrategyLab/QuantPlatformKit.git@34a2c74deb7aafef0f6f4b278444d7b0efb76794 +us-equity-strategies @ git+https://github.com/QuantStrategyLab/UsEquityStrategies.git@861eaedc1caecf1e33ff12f55bbe9af87a221df1 pandas numpy requests