From e9c3f58a29e87c36e2ecba480a25fc43ce77b2e0 Mon Sep 17 00:00:00 2001 From: Pigbibi <20649888+Pigbibi@users.noreply.github.com> Date: Fri, 13 Nov 2026 04:41:32 +0800 Subject: [PATCH 1/3] feat: Require GitHub approval for LongBridge traffic promotion Co-Authored-By: Codex --- .github/workflows/sync-cloud-run-env.yml | 32 ++++++++++++++++++++--- docs/runtime-write-isolation.md | 10 +++++++ tests/test_sync_cloud_run_env_workflow.sh | 6 +++-- 3 files changed, 43 insertions(+), 5 deletions(-) diff --git a/.github/workflows/sync-cloud-run-env.yml b/.github/workflows/sync-cloud-run-env.yml index e2dd0385..b1648e30 100644 --- a/.github/workflows/sync-cloud-run-env.yml +++ b/.github/workflows/sync-cloud-run-env.yml @@ -153,9 +153,31 @@ concurrency: cancel-in-progress: false jobs: + approve-live-traffic: + name: Approve LongBridge production traffic + if: >- + github.event_name == 'workflow_dispatch' && github.ref == 'refs/heads/main' && + github.run_attempt == 1 && + ((inputs.deployment_mode == 'legacy' && inputs.shift_traffic_to_commit) || + ((inputs.deployment_mode == 'image-only-no-traffic' || inputs.deployment_mode == 'image-only-notification-overlay') && + inputs.target == 'HK' && inputs.account_snapshot_enabled == 'true')) + environment: longbridge-live-approval + runs-on: ubuntu-latest + timeout-minutes: 3 + permissions: {} + steps: + - name: Record approval + run: echo "Approved the production traffic change for this dispatch." >> "$GITHUB_STEP_SUMMARY" + image-only-no-traffic: name: Stage ${{ inputs.target }} image without traffic - if: github.event_name == 'workflow_dispatch' && (inputs.deployment_mode == 'image-only-no-traffic' || inputs.deployment_mode == 'image-only-notification-overlay') + needs: approve-live-traffic + if: >- + !cancelled() && github.event_name == 'workflow_dispatch' && + (inputs.deployment_mode == 'image-only-no-traffic' || inputs.deployment_mode == 'image-only-notification-overlay') && + (needs.approve-live-traffic.result == 'success' || + (needs.approve-live-traffic.result == 'skipped' && + !(inputs.target == 'HK' && inputs.account_snapshot_enabled == 'true'))) runs-on: ubuntu-latest timeout-minutes: 20 permissions: @@ -652,10 +674,14 @@ jobs: python3 scripts/render_runtime_target_matrix.py --profile sync --github-output sync: - needs: resolve-matrix + needs: [resolve-matrix, approve-live-traffic] name: Deploy / Sync ${{ matrix.target.label }} Cloud Run # Production deployment and environment sync require an explicit operator dispatch. - if: github.event_name == 'workflow_dispatch' && inputs.deployment_mode == 'legacy' + if: >- + !cancelled() && github.event_name == 'workflow_dispatch' && + inputs.deployment_mode == 'legacy' && needs.resolve-matrix.result == 'success' && + (needs.approve-live-traffic.result == 'success' || + (needs.approve-live-traffic.result == 'skipped' && !inputs.shift_traffic_to_commit)) runs-on: ubuntu-latest timeout-minutes: 20 strategy: diff --git a/docs/runtime-write-isolation.md b/docs/runtime-write-isolation.md index 4454e554..cb689038 100644 --- a/docs/runtime-write-isolation.md +++ b/docs/runtime-write-isolation.md @@ -317,3 +317,13 @@ staging; this mode neither changes configuration nor moves traffic. + + +## Production traffic approval + +LongBridge traffic promotion uses the `longbridge-live-approval` Environment +on `main`. This covers explicit `shift_traffic_to_commit` and the HK snapshot +mode that promotes its serving revision. Staging without traffic and +standalone readback skip this approval. The existing runtime-target, +revision and readback checks still run before or after the traffic change +as appropriate. diff --git a/tests/test_sync_cloud_run_env_workflow.sh b/tests/test_sync_cloud_run_env_workflow.sh index 428d43c2..572362ef 100644 --- a/tests/test_sync_cloud_run_env_workflow.sh +++ b/tests/test_sync_cloud_run_env_workflow.sh @@ -10,7 +10,7 @@ grep -Fq 'name: Deploy / Sync ${{ matrix.target.label }} Cloud Run' "$workflow_f grep -Fq 'fail-fast: false' "$workflow_file" grep -Fq 'resolve-matrix:' "$workflow_file" grep -Fq 'render_runtime_target_matrix.py --profile sync --github-output' "$workflow_file" -grep -Fq 'needs: resolve-matrix' "$workflow_file" +grep -Fq 'needs: [resolve-matrix, approve-live-traffic]' "$workflow_file" grep -Fq 'matrix: ${{ fromJSON(needs.resolve-matrix.outputs.matrix) }}' "$workflow_file" grep -Fq "inputs.target == 'PAPER' && 'longbridge-paper'" "$workflow_file" grep -Fq "inputs.target == 'HK' && 'longbridge-hk'" "$workflow_file" @@ -448,7 +448,9 @@ assert "name: Mask WIF identity components" in masking assert "if: matrix.target.environment == 'longbridge-paper' && steps.config.outputs.enabled == 'true'" in masking for section in (legacy, cleanup): assert "inputs.deployment_mode == 'legacy'" in section.split(" steps:", 1)[0] -assert "needs: resolve-matrix" in legacy.split(" steps:", 1)[0] +assert "needs: [resolve-matrix, approve-live-traffic]" in legacy.split(" steps:", 1)[0] +assert "needs.approve-live-traffic.result == 'success'" in legacy.split(" steps:", 1)[0] +assert "needs.approve-live-traffic.result == 'success'" in job.split(" steps:", 1)[0] assert job.index("name: Validate image-only dispatch") < job.index("uses: google-github-actions/auth@") assert job.index("name: Verify exact image-only source") < job.index("uses: google-github-actions/auth@") # Only the service selector, protected WIF identity and immutable overlay inputs are allowed; From 784d1b72fc81e2b94e749a02dd263ca1c12e4a3c Mon Sep 17 00:00:00 2001 From: Pigbibi <20649888+Pigbibi@users.noreply.github.com> Date: Fri, 13 Nov 2026 04:47:11 +0800 Subject: [PATCH 2/3] test: Verify the LongBridge deployment approval dependency Co-Authored-By: Codex --- tests/test_runtime_target_matrix.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/test_runtime_target_matrix.py b/tests/test_runtime_target_matrix.py index d24e86e4..1dad22a3 100644 --- a/tests/test_runtime_target_matrix.py +++ b/tests/test_runtime_target_matrix.py @@ -280,7 +280,8 @@ def test_monitor_and_deploy_workflows_consume_validated_matrix_output(): assert 'render_runtime_target_matrix.py --profile heartbeat --target "$MATRIX_TARGET" --github-output' in text else: assert f"render_runtime_target_matrix.py --profile {profile} --github-output" in text - assert "needs: resolve-matrix" in text + dependency = "needs: [resolve-matrix, approve-live-traffic]" if profile == "sync" else "needs: resolve-matrix" + assert dependency in text assert "matrix: ${{ fromJSON(needs.resolve-matrix.outputs.matrix) }}" in text assert "vars.RUNTIME_TARGET_ENABLED" in text # Hardcoded PAPER/HK/SG matrix blocks must not remain in dynamic consumers. From bd59d8b3bf4b7a2d2746c0859c72cc25a3a39d76 Mon Sep 17 00:00:00 2001 From: Pigbibi <20649888+Pigbibi@users.noreply.github.com> Date: Fri, 13 Nov 2026 04:51:40 +0800 Subject: [PATCH 3/3] fix: Cover SG serving snapshots with traffic approval Co-Authored-By: Codex --- .github/workflows/sync-cloud-run-env.yml | 4 +++- docs/runtime-write-isolation.md | 4 ++-- tests/test_sync_cloud_run_env_workflow.sh | 3 +++ 3 files changed, 8 insertions(+), 3 deletions(-) diff --git a/.github/workflows/sync-cloud-run-env.yml b/.github/workflows/sync-cloud-run-env.yml index b1648e30..3e5528f5 100644 --- a/.github/workflows/sync-cloud-run-env.yml +++ b/.github/workflows/sync-cloud-run-env.yml @@ -159,6 +159,7 @@ jobs: github.event_name == 'workflow_dispatch' && github.ref == 'refs/heads/main' && github.run_attempt == 1 && ((inputs.deployment_mode == 'legacy' && inputs.shift_traffic_to_commit) || + (inputs.deployment_mode == 'sg-serving-snapshot' && inputs.target == 'SG' && inputs.account_snapshot_enabled == 'true') || ((inputs.deployment_mode == 'image-only-no-traffic' || inputs.deployment_mode == 'image-only-notification-overlay') && inputs.target == 'HK' && inputs.account_snapshot_enabled == 'true')) environment: longbridge-live-approval @@ -2338,8 +2339,9 @@ jobs: sg-serving-snapshot: name: Enable only the SG read-only snapshot + needs: approve-live-traffic if: >- - github.event_name == 'workflow_dispatch' && github.ref == 'refs/heads/main' && + needs.approve-live-traffic.result == 'success' && github.event_name == 'workflow_dispatch' && github.ref == 'refs/heads/main' && github.run_attempt == 1 && inputs.deployment_mode == 'sg-serving-snapshot' && inputs.target == 'SG' && inputs.approved_ref == 'main' && inputs.account_snapshot_enabled == 'true' && diff --git a/docs/runtime-write-isolation.md b/docs/runtime-write-isolation.md index cb689038..34a267f6 100644 --- a/docs/runtime-write-isolation.md +++ b/docs/runtime-write-isolation.md @@ -322,8 +322,8 @@ staging; this mode neither changes configuration nor moves traffic. ## Production traffic approval LongBridge traffic promotion uses the `longbridge-live-approval` Environment -on `main`. This covers explicit `shift_traffic_to_commit` and the HK snapshot -mode that promotes its serving revision. Staging without traffic and +on `main`. This covers explicit `shift_traffic_to_commit` and the HK and SG snapshot +modes that promote a serving revision. Staging without traffic and standalone readback skip this approval. The existing runtime-target, revision and readback checks still run before or after the traffic change as appropriate. diff --git a/tests/test_sync_cloud_run_env_workflow.sh b/tests/test_sync_cloud_run_env_workflow.sh index 572362ef..ad32b08d 100644 --- a/tests/test_sync_cloud_run_env_workflow.sh +++ b/tests/test_sync_cloud_run_env_workflow.sh @@ -451,6 +451,9 @@ for section in (legacy, cleanup): assert "needs: [resolve-matrix, approve-live-traffic]" in legacy.split(" steps:", 1)[0] assert "needs.approve-live-traffic.result == 'success'" in legacy.split(" steps:", 1)[0] assert "needs.approve-live-traffic.result == 'success'" in job.split(" steps:", 1)[0] +sg_snapshot = workflow.split("\n sg-serving-snapshot:\n", 1)[1] +assert "needs: approve-live-traffic" in sg_snapshot.split(" steps:", 1)[0] +assert "needs.approve-live-traffic.result == 'success'" in sg_snapshot.split(" steps:", 1)[0] assert job.index("name: Validate image-only dispatch") < job.index("uses: google-github-actions/auth@") assert job.index("name: Verify exact image-only source") < job.index("uses: google-github-actions/auth@") # Only the service selector, protected WIF identity and immutable overlay inputs are allowed;