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
19 changes: 10 additions & 9 deletions .github/workflows/publish-hk-snapshot-artifacts.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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'

Expand Down Expand Up @@ -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=""
Expand Down Expand Up @@ -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 }}
Expand All @@ -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 }}
Expand Down
2 changes: 2 additions & 0 deletions docs/hk_snapshot_publish_workflow.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 2 additions & 0 deletions docs/hk_snapshot_publish_workflow.zh-CN.md
Original file line number Diff line number Diff line change
Expand Up @@ -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。
Expand Down
26 changes: 26 additions & 0 deletions tests/test_publish_hk_snapshot_artifacts_workflow.py
Original file line number Diff line number Diff line change
@@ -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():
Expand All @@ -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)