diff --git a/.github/workflows/deploy-strategy-switch-console.yml b/.github/workflows/deploy-strategy-switch-console.yml index c42559e..5a722a4 100644 --- a/.github/workflows/deploy-strategy-switch-console.yml +++ b/.github/workflows/deploy-strategy-switch-console.yml @@ -120,6 +120,19 @@ jobs: target.write_text(text, encoding="utf-8") PY + - name: Verify M0 research-ledger ingress token + env: + # Only the Worker deployment receives this copy. The M0 publisher + # reads the same value from its separate main-only Environment, + # never the Cloudflare deployment credentials. + M0_RESEARCH_SYNC_TOKEN: ${{ secrets.M0_RESEARCH_SYNC_TOKEN }} + run: | + set -euo pipefail + if [ -z "${M0_RESEARCH_SYNC_TOKEN:-}" ]; then + echo "M0_RESEARCH_SYNC_TOKEN is required so a deployment cannot retain a stale M0 ingress credential." >&2 + exit 2 + fi + - name: Deploy Worker working-directory: web/strategy-switch-console run: npx wrangler@4.106.0 deploy --config wrangler.toml @@ -152,6 +165,14 @@ jobs: set -euo pipefail printf '%s' "$EXECUTION_EVIDENCE_SYNC_TOKEN" | npx wrangler@4.106.0 secret put EXECUTION_EVIDENCE_SYNC_TOKEN --config wrangler.toml + - name: Sync M0 research-ledger ingress token + env: + M0_RESEARCH_SYNC_TOKEN: ${{ secrets.M0_RESEARCH_SYNC_TOKEN }} + working-directory: web/strategy-switch-console + run: | + set -euo pipefail + printf '%s' "$M0_RESEARCH_SYNC_TOKEN" | npx wrangler@4.106.0 secret put M0_RESEARCH_SYNC_TOKEN --config wrangler.toml + - name: Sync bundled strategy profiles to KV if: github.event_name != 'workflow_dispatch' || inputs.sync_strategy_profiles run: | diff --git a/docs/m0_research_publisher_envelope_contract.md b/docs/m0_research_publisher_envelope_contract.md index cf0fddc..9c243b2 100644 --- a/docs/m0_research_publisher_envelope_contract.md +++ b/docs/m0_research_publisher_envelope_contract.md @@ -125,6 +125,15 @@ run ID 和 artifact ID 都作为 `source_artifact` metadata 显式传给构建 必须是不同的值和不同的最小权限用途:前者只能读取固定 QAR repository 的 Actions run/artifact, 后者只能向 M0 接收端发布封套;不得复用、互相授予或写入运行时/平台配置。 +同一个 `M0_RESEARCH_SYNC_TOKEN` 还必须以**同名、同值的独立 secret**配置到已有的 +`runtime-strategy-switch` Environment。它只会在控制台 Worker 的部署 workflow 中被写入 +Cloudflare 的 `M0_RESEARCH_SYNC_TOKEN` secret binding;该部署 workflow 不读取 QAR artifact token, +而 M0 发布 workflow 也不读取 Cloudflare 凭据。不要在 repository-level 放置这个名字,避免未受 +Environment 保护的 fallback。控制台部署会在部署前验证该 secret;缺失时直接失败,不能静默 +保留 Cloudflare Worker 中的旧值。轮换时,先只更新 `runtime-strategy-switch` 中的副本,从 `main` +部署并确认 Worker secret 写入成功;再把同一个新值写入 `m0-research-publisher`,最后手动发布 +一份已验证的 M0 ledger。 + URL、发布 token 和 QAR 读取 token 不会写进封套、`GITHUB_STEP_SUMMARY` 或 workflow 输出。该 workflow 不读取运行时、平台、selector、策略或券商配置;其唯一网络写入是构建器在 `--publish` 明确指定时,对上述研究接收地址发送经过校验的 no-order 封套。 diff --git a/python/tests/test_runtime_settings.py b/python/tests/test_runtime_settings.py index 51d0778..c145eff 100644 --- a/python/tests/test_runtime_settings.py +++ b/python/tests/test_runtime_settings.py @@ -1167,6 +1167,11 @@ def test_strategy_switch_console_deploy_workflow_syncs_bundled_profiles(self): self.assertIn("secret put STRATEGY_HEALTH_SYNC_TOKEN", workflow) self.assertIn("RESEARCH_TASK_SYNC_TOKEN", workflow) self.assertIn("secret put RESEARCH_TASK_SYNC_TOKEN", workflow) + self.assertIn("M0_RESEARCH_SYNC_TOKEN", workflow) + self.assertIn("secret put M0_RESEARCH_SYNC_TOKEN", workflow) + self.assertIn("Verify M0 research-ledger ingress token", workflow) + self.assertIn("M0_RESEARCH_SYNC_TOKEN is required", workflow) + self.assertNotIn("if: env.M0_RESEARCH_SYNC_TOKEN != ''", workflow) self.assertIn("CLOUDFLARE_WRANGLER_CONFIG_TOML", workflow) self.assertIn("STRATEGY_SWITCH_CONFIG_KV_NAMESPACE_ID", workflow) self.assertIn("python/scripts/sync_strategy_switch_page_asset.py", workflow) diff --git a/web/strategy-switch-console/README.md b/web/strategy-switch-console/README.md index f70cb86..aaef93b 100644 --- a/web/strategy-switch-console/README.md +++ b/web/strategy-switch-console/README.md @@ -195,6 +195,7 @@ wrangler secret put GITHUB_CLIENT_SECRET wrangler secret put SESSION_SECRET wrangler secret put RUNTIME_SETTINGS_DISPATCH_TOKEN wrangler secret put STRATEGY_SWITCH_SYNC_TOKEN # optional; defaults to RUNTIME_SETTINGS_DISPATCH_TOKEN +wrangler secret put M0_RESEARCH_SYNC_TOKEN wrangler secret put ALLOWED_GITHUB_LOGINS wrangler secret put ALLOWED_GITHUB_ORGS wrangler secret put STRATEGY_SWITCH_ADMIN_LOGINS @@ -210,7 +211,7 @@ wrangler kv namespace create STRATEGY_SWITCH_CONFIG Add the returned namespace id to `wrangler.toml`. -For GitHub Actions auto-deploy, configure `STRATEGY_SWITCH_CONFIG_KV_NAMESPACE_ID`, `STRATEGY_SWITCH_CONSOLE_URL`, `STRATEGY_SWITCH_SYNC_TOKEN`, and either `CLOUDFLARE_API_TOKEN` or `CLOUDFLARE_WRANGLER_CONFIG_TOML` in the `runtime-strategy-switch` environment (or reuse `RUNTIME_SETTINGS_GH_TOKEN` only if it matches the Worker sync secret). `CLOUDFLARE_ACCOUNT_ID` is optional when Wrangler can infer it from the token. The workflow deploys the Worker and then syncs the bundled strategy profile catalog into KV so the website is not left with stale profile/plugin metadata. +For GitHub Actions auto-deploy, configure `STRATEGY_SWITCH_CONFIG_KV_NAMESPACE_ID`, `STRATEGY_SWITCH_CONSOLE_URL`, `STRATEGY_SWITCH_SYNC_TOKEN`, `M0_RESEARCH_SYNC_TOKEN`, and either `CLOUDFLARE_API_TOKEN` or `CLOUDFLARE_WRANGLER_CONFIG_TOML` in the `runtime-strategy-switch` environment (or reuse `RUNTIME_SETTINGS_GH_TOKEN` only if it matches the Worker sync secret). `CLOUDFLARE_ACCOUNT_ID` is optional when Wrangler can infer it from the token. `M0_RESEARCH_SYNC_TOKEN` must match the separately protected `m0-research-publisher` environment secret; it is only copied to the Worker binding. A missing M0 token fails the deployment before it can retain a stale Worker secret. The workflow deploys the Worker and then syncs the bundled strategy profile catalog into KV so the website is not left with stale profile/plugin metadata. Deploy: diff --git a/web/strategy-switch-console/README.zh-CN.md b/web/strategy-switch-console/README.zh-CN.md index 86a1d47..6d347e1 100644 --- a/web/strategy-switch-console/README.zh-CN.md +++ b/web/strategy-switch-console/README.zh-CN.md @@ -332,6 +332,7 @@ wrangler secret put RUNTIME_SETTINGS_DISPATCH_TOKEN wrangler secret put STRATEGY_SWITCH_SYNC_TOKEN # 可选;默认复用 RUNTIME_SETTINGS_DISPATCH_TOKEN wrangler secret put STRATEGY_HEALTH_SYNC_TOKEN wrangler secret put CONTROL_PLANE_SYNC_TOKEN +wrangler secret put M0_RESEARCH_SYNC_TOKEN wrangler secret put ALLOWED_GITHUB_LOGINS wrangler secret put ALLOWED_GITHUB_ORGS wrangler secret put STRATEGY_SWITCH_ADMIN_LOGINS @@ -347,7 +348,7 @@ wrangler kv namespace create STRATEGY_SWITCH_CONFIG 然后把返回的 namespace id 加到 `wrangler.toml`。 -GitHub Actions 自动部署需要在 `runtime-strategy-switch` environment 配置 `STRATEGY_SWITCH_CONFIG_KV_NAMESPACE_ID`、`STRATEGY_SWITCH_CONSOLE_URL`、`STRATEGY_SWITCH_SYNC_TOKEN`,以及 `CLOUDFLARE_API_TOKEN` 或 `CLOUDFLARE_WRANGLER_CONFIG_TOML` 二选一(只有当 `RUNTIME_SETTINGS_GH_TOKEN` 与 Worker 同步密钥相同时才复用它)。如果 Wrangler 能从 token 推断账号,`CLOUDFLARE_ACCOUNT_ID` 可不配。workflow 会先部署 Worker,再把内置策略 profile 目录同步到 KV,避免网站继续使用旧的 profile/plugin 元数据。 +GitHub Actions 自动部署需要在 `runtime-strategy-switch` environment 配置 `STRATEGY_SWITCH_CONFIG_KV_NAMESPACE_ID`、`STRATEGY_SWITCH_CONSOLE_URL`、`STRATEGY_SWITCH_SYNC_TOKEN`、`M0_RESEARCH_SYNC_TOKEN`,以及 `CLOUDFLARE_API_TOKEN` 或 `CLOUDFLARE_WRANGLER_CONFIG_TOML` 二选一(只有当 `RUNTIME_SETTINGS_GH_TOKEN` 与 Worker 同步密钥相同时才复用它)。如果 Wrangler 能从 token 推断账号,`CLOUDFLARE_ACCOUNT_ID` 可不配。`M0_RESEARCH_SYNC_TOKEN` 必须与另一个受保护的 `m0-research-publisher` Environment 中的同名 secret 一致;它只会被复制到 Worker binding。缺少该值时,workflow 会在部署前失败,不能静默保留 Worker 的旧密钥。workflow 会先部署 Worker,再把内置策略 profile 目录同步到 KV,避免网站继续使用旧的 profile/plugin 元数据。 部署: diff --git a/web/strategy-switch-console/wrangler.toml.example b/web/strategy-switch-console/wrangler.toml.example index 58088bf..fa2b2e9 100644 --- a/web/strategy-switch-console/wrangler.toml.example +++ b/web/strategy-switch-console/wrangler.toml.example @@ -12,6 +12,7 @@ workers_dev = true # - STRATEGY_HEALTH_SYNC_TOKEN # dedicated outbound health snapshot token; never reuse dispatch token # - CONTROL_PLANE_SYNC_TOKEN # dedicated global dashboard snapshot token; never reuse dispatch token # - RESEARCH_TASK_SYNC_TOKEN # dedicated immutable research-task source token; never reuse another token +# - M0_RESEARCH_SYNC_TOKEN # dedicated read-only M0 research-ledger ingress token; never reuse another token # - ALLOWED_GITHUB_LOGINS # - ALLOWED_GITHUB_ORGS # - STRATEGY_SWITCH_ADMIN_LOGINS