From f28a56ce690708883ed18ffa14fb86ef63f84726 Mon Sep 17 00:00:00 2001 From: Pigbibi <20649888+Pigbibi@users.noreply.github.com> Date: Sun, 23 Aug 2026 21:31:52 +0800 Subject: [PATCH] ci: scope HK broker credentials to generation step Co-Authored-By: Codex --- .../publish-hk-snapshot-artifacts.yml | 19 +++++++------- docs/hk_snapshot_publish_workflow.md | 2 ++ docs/hk_snapshot_publish_workflow.zh-CN.md | 2 ++ ..._publish_hk_snapshot_artifacts_workflow.py | 26 +++++++++++++++++++ 4 files changed, 40 insertions(+), 9 deletions(-) diff --git a/.github/workflows/publish-hk-snapshot-artifacts.yml b/.github/workflows/publish-hk-snapshot-artifacts.yml index 9264942..f4bce77 100644 --- a/.github/workflows/publish-hk-snapshot-artifacts.yml +++ b/.github/workflows/publish-hk-snapshot-artifacts.yml @@ -129,13 +129,10 @@ jobs: LONGBRIDGE_APP_SECRET_SECRET_NAME: ${{ inputs.longbridge_app_secret_secret_name }} LONGBRIDGE_ACCESS_TOKEN_SECRET_NAME: ${{ inputs.longbridge_access_token_secret_name }} LONGBRIDGE_SECRET_PROJECT_ID: ${{ inputs.longbridge_secret_project_id || vars.LONGBRIDGE_SECRET_PROJECT_ID || vars.GCP_PROJECT_ID || 'longbridgequant' }} - LONG_BRIDGE_APP_KEY_FROM_GITHUB_SECRET: ${{ secrets.LONGBRIDGE_APP_KEY_HK || secrets.LONG_BRIDGE_APP_KEY_HK }} - LONG_BRIDGE_APP_SECRET_FROM_GITHUB_SECRET: ${{ secrets.LONGBRIDGE_APP_SECRET_HK || secrets.LONG_BRIDGE_APP_SECRET_HK }} - LONG_BRIDGE_ACCESS_TOKEN_FROM_GITHUB_SECRET: ${{ secrets.LONGBRIDGE_ACCESS_TOKEN_HK || secrets.LONG_BRIDGE_ACCESS_TOKEN_HK || secrets.LONGPORT_ACCESS_TOKEN_HK }} steps: - name: Checkout - uses: actions/checkout@v6 + uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6 - name: Verify main CI succeeded before publish if: github.event_name == 'workflow_dispatch' && inputs.execute_publish == true @@ -144,7 +141,7 @@ jobs: run: bash .github/scripts/verify_main_ci_success.sh - name: Set up Python - uses: actions/setup-python@v6 + uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6 with: python-version: '3.11' @@ -185,20 +182,24 @@ jobs: - name: Authenticate to Google Cloud if: steps.gcp-auth-check.outputs.needed == 'true' - uses: google-github-actions/auth@v3 + uses: google-github-actions/auth@7c6bc770dae815cd3e89ee6cdf493a5fab2cc093 # v3 with: workload_identity_provider: ${{ env.GCP_WORKLOAD_IDENTITY_PROVIDER }} service_account: ${{ env.GCP_WORKLOAD_IDENTITY_SERVICE_ACCOUNT }} - name: Set up gcloud if: steps.gcp-auth-check.outputs.needed == 'true' - uses: google-github-actions/setup-gcloud@v3 + uses: google-github-actions/setup-gcloud@aa5489c8933f4cc7a4f7d45035b3b1440c9c10db # v3 with: project_id: ${{ env.GCP_PROJECT_ID }} version: '>= 416.0.0' - name: Resolve or generate factor snapshot CSV id: factor-snapshot + env: + LONG_BRIDGE_APP_KEY_FROM_GITHUB_SECRET: ${{ inputs.input_source_mode == 'longbridge_openapi_staging' && inputs.longbridge_credentials_mode == 'github_secrets' && (secrets.LONGBRIDGE_APP_KEY_HK || secrets.LONG_BRIDGE_APP_KEY_HK) || '' }} + LONG_BRIDGE_APP_SECRET_FROM_GITHUB_SECRET: ${{ inputs.input_source_mode == 'longbridge_openapi_staging' && inputs.longbridge_credentials_mode == 'github_secrets' && (secrets.LONGBRIDGE_APP_SECRET_HK || secrets.LONG_BRIDGE_APP_SECRET_HK) || '' }} + LONG_BRIDGE_ACCESS_TOKEN_FROM_GITHUB_SECRET: ${{ inputs.input_source_mode == 'longbridge_openapi_staging' && inputs.longbridge_credentials_mode == 'github_secrets' && (secrets.LONGBRIDGE_ACCESS_TOKEN_HK || secrets.LONG_BRIDGE_ACCESS_TOKEN_HK || secrets.LONGPORT_ACCESS_TOKEN_HK) || '' }} run: | set -euo pipefail SOURCE_INPUT_SUMMARY_PATH="" @@ -387,7 +388,7 @@ jobs: python scripts/publish_hk_snapshot_artifacts.py "${args[@]}" - name: Upload generated artifacts - uses: actions/upload-artifact@v7 + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7 with: name: ${{ steps.build.outputs.artifact_name }} path: ${{ steps.build.outputs.artifact_dir }} @@ -396,7 +397,7 @@ jobs: - name: Upload generated input snapshot if: inputs.input_source_mode == 'longbridge_openapi_staging' || inputs.input_source_mode == 'public_yfinance_staging' - uses: actions/upload-artifact@v7 + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7 with: name: hk-equity-generated-input-${{ github.run_id }} path: data/input/generated/${{ inputs.profile }} diff --git a/docs/hk_snapshot_publish_workflow.md b/docs/hk_snapshot_publish_workflow.md index 3947879..48c1812 100644 --- a/docs/hk_snapshot_publish_workflow.md +++ b/docs/hk_snapshot_publish_workflow.md @@ -88,6 +88,8 @@ If the GCP Workload Identity binding is not ready yet, set `longbridge_credentia In `github_secrets` mode, LongBridge input generation does not require GCP auth unless `universe_path` / `factor_snapshot_path` uses `gs://` or `execute_publish=true` uploads to GCS. +GitHub-hosted LongBridge credentials are scoped to the single factor-snapshot generation step and are only populated when both `input_source_mode=longbridge_openapi_staging` and `longbridge_credentials_mode=github_secrets`. Checkout, dependency installation, public-data generation, artifact validation, upload, and summary steps do not receive those broker credentials. Secret Manager remains the preferred mode. + Important: generated CSVs with `allow_research_defaults=false` can be runtime artifact inputs after artifact validation and stable GCS publishing, similar to the US snapshot publish flow. They are not final live order approval by themselves; that still requires backtest, broker dry-run, notification, rollout, and operator approval evidence. `allow_research_defaults=true` remains research smoke only. ## GCP Workload Identity prerequisites diff --git a/docs/hk_snapshot_publish_workflow.zh-CN.md b/docs/hk_snapshot_publish_workflow.zh-CN.md index 64b5fef..a3312d8 100644 --- a/docs/hk_snapshot_publish_workflow.zh-CN.md +++ b/docs/hk_snapshot_publish_workflow.zh-CN.md @@ -88,6 +88,8 @@ LongBridge 模式会从 Google Secret Manager 读取以下 secret,默认名称 在 `github_secrets` 模式下,只要 `universe_path` / `factor_snapshot_path` 不使用 `gs://`,并且不设置 `execute_publish=true` 上传 GCS,LongBridge 输入生成不需要 GCP auth。 +GitHub 中的 LongBridge 凭据只注入 factor snapshot 生成这一个步骤,而且只有同时选择 `input_source_mode=longbridge_openapi_staging` 与 `longbridge_credentials_mode=github_secrets` 时才会填入。checkout、依赖安装、公开数据生成、artifact 校验、上传与摘要步骤都不会收到这些券商凭据。仍优先推荐 Secret Manager 模式。 + 注意:`allow_research_defaults=false` 的生成 CSV 通过 artifact validation 并发布到稳定 GCS 路径后,可以像美股 snapshot publish flow 一样作为平台接线用的 runtime artifact evidence。 它仍不等于最终实盘下单批准。最终批准还需要回测、券商 dry-run、通知、rollout 和人工审批 evidence。`allow_research_defaults=true` 仍然只能作为 research smoke。 diff --git a/tests/test_publish_hk_snapshot_artifacts_workflow.py b/tests/test_publish_hk_snapshot_artifacts_workflow.py index c112f31..2f58c01 100644 --- a/tests/test_publish_hk_snapshot_artifacts_workflow.py +++ b/tests/test_publish_hk_snapshot_artifacts_workflow.py @@ -1,7 +1,9 @@ from pathlib import Path +import re WORKFLOW_PATH = Path(".github/workflows/publish-hk-snapshot-artifacts.yml") +FULL_SHA_ACTION = re.compile(r"uses:\s+[^\s@]+@[0-9a-f]{40}(?:\s+#\s+v\d+)?$") def test_publish_workflow_supports_public_yfinance_staging_source(): @@ -20,3 +22,27 @@ def test_publish_workflow_keeps_longbridge_staging_source_available(): assert "longbridge_openapi_staging" in workflow assert "python -m pip install -e '.[longbridge]'" in workflow assert "scripts/build_low_vol_dividend_longbridge_factor_snapshot.py" in workflow + + +def test_publish_workflow_scopes_github_broker_secrets_to_generation_step(): + workflow = WORKFLOW_PATH.read_text(encoding="utf-8") + job_env = workflow[workflow.index(" env:\n") : workflow.index(" steps:\n")] + generation_step = workflow[ + workflow.index(" - name: Resolve or generate factor snapshot CSV") : + workflow.index(" - name: Build and validate HK snapshot artifacts") + ] + + assert "secrets." not in job_env + assert "LONG_BRIDGE_APP_KEY_FROM_GITHUB_SECRET" in generation_step + assert "LONG_BRIDGE_APP_SECRET_FROM_GITHUB_SECRET" in generation_step + assert "LONG_BRIDGE_ACCESS_TOKEN_FROM_GITHUB_SECRET" in generation_step + assert "inputs.input_source_mode == 'longbridge_openapi_staging'" in generation_step + assert "inputs.longbridge_credentials_mode == 'github_secrets'" in generation_step + + +def test_publish_workflow_remote_actions_are_pinned_to_full_commit_shas(): + workflow = WORKFLOW_PATH.read_text(encoding="utf-8") + action_lines = [line.strip() for line in workflow.splitlines() if "uses:" in line] + + assert action_lines + assert all(FULL_SHA_ACTION.fullmatch(line) for line in action_lines)