Skip to content

Commit f121442

Browse files
Pigbibicodex
andcommitted
feat(deps): gate staged QPK pin auto-advance
Co-Authored-By: Codex <noreply@openai.com>
1 parent 2b1e533 commit f121442

4 files changed

Lines changed: 147 additions & 1 deletion

File tree

Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
name: Advance Staged QPK Pin
2+
3+
"on":
4+
workflow_run:
5+
workflows: ["CI"]
6+
types: [completed]
7+
8+
permissions:
9+
contents: read
10+
pull-requests: write
11+
12+
jobs:
13+
advance:
14+
if: >-
15+
github.event.workflow_run.conclusion == 'success' &&
16+
github.event.workflow_run.head_branch == 'auto/qpk-pin-update'
17+
runs-on: ubuntu-latest
18+
timeout-minutes: 10
19+
steps:
20+
- name: Resolve the completed staged pin PR
21+
id: pr
22+
env:
23+
GH_TOKEN: ${{ github.token }}
24+
COMPLETED_HEAD_SHA: ${{ github.event.workflow_run.head_sha }}
25+
COMPLETED_HEAD_BRANCH: ${{ github.event.workflow_run.head_branch }}
26+
run: |
27+
set -euo pipefail
28+
pr_payload=$(gh pr list \
29+
--repo "$GITHUB_REPOSITORY" \
30+
--state open \
31+
--head "$COMPLETED_HEAD_BRANCH" \
32+
--json number,headRefOid \
33+
--jq '.[0] // {}')
34+
pr_number=$(python3 -c 'import json,sys; print(json.load(sys.stdin).get("number", ""))' <<<"$pr_payload")
35+
pr_head_sha=$(python3 -c 'import json,sys; print(json.load(sys.stdin).get("headRefOid", ""))' <<<"$pr_payload")
36+
37+
if [ -z "$pr_number" ]; then
38+
echo "No open staged QPK pin PR found." >> "$GITHUB_STEP_SUMMARY"
39+
exit 0
40+
fi
41+
if [ "$pr_head_sha" != "$COMPLETED_HEAD_SHA" ]; then
42+
echo "Skipping stale CI result for PR #$pr_number." >> "$GITHUB_STEP_SUMMARY"
43+
exit 0
44+
fi
45+
46+
echo "number=$pr_number" >> "$GITHUB_OUTPUT"
47+
echo "head_sha=$pr_head_sha" >> "$GITHUB_OUTPUT"
48+
49+
- name: Enforce the staged-pin trust boundary
50+
id: guard
51+
if: steps.pr.outputs.number != ''
52+
env:
53+
GH_TOKEN: ${{ github.token }}
54+
PR_NUMBER: ${{ steps.pr.outputs.number }}
55+
PR_HEAD_SHA: ${{ steps.pr.outputs.head_sha }}
56+
run: |
57+
set -euo pipefail
58+
gh pr view "$PR_NUMBER" --repo "$GITHUB_REPOSITORY" \
59+
--json author,baseRefName,changedFiles,headRefName,isCrossRepository,isDraft,title,url > pr.json
60+
python3 - <<'PY'
61+
import json
62+
import os
63+
from pathlib import Path
64+
65+
pr = json.loads(Path("pr.json").read_text(encoding="utf-8"))
66+
allowed = (
67+
(pr.get("author") or {}).get("login") == "Pigbibi"
68+
and pr.get("baseRefName") == "main"
69+
and pr.get("headRefName") == "auto/qpk-pin-update"
70+
and pr.get("title") == "chore: advance staged QPK pin"
71+
and pr.get("changedFiles") == 1
72+
and not pr.get("isCrossRepository")
73+
and not pr.get("isDraft")
74+
)
75+
reason = "ready" if allowed else "unexpected_pr_metadata"
76+
summary = [
77+
"## Staged QPK Pin Gate",
78+
f"- PR: {pr.get('url', '<unknown>')}",
79+
f"- Author: `{(pr.get('author') or {}).get('login', '<unknown>')}`",
80+
f"- Changed files: `{pr.get('changedFiles', '<unknown>')}`",
81+
f"- Cross-repository: `{'yes' if pr.get('isCrossRepository') else 'no'}`",
82+
f"- Decision: `{'advance' if allowed else 'skip'}`",
83+
f"- Reason: `{reason}`",
84+
]
85+
Path("staged-pin-summary.md").write_text("\n".join(summary) + "\n", encoding="utf-8")
86+
with open(os.environ["GITHUB_OUTPUT"], "a", encoding="utf-8") as output:
87+
print(f"allowed={'true' if allowed else 'false'}", file=output)
88+
PY
89+
90+
files=$(gh api --paginate "repos/$GITHUB_REPOSITORY/pulls/$PR_NUMBER/files" --jq '.[].filename')
91+
if [ "$files" != "QPK_PIN" ]; then
92+
echo "::error title=Staged QPK pin gate::unexpected_changed_files"
93+
exit 1
94+
fi
95+
96+
candidate=$(gh api "repos/$GITHUB_REPOSITORY/contents/QPK_PIN?ref=$PR_HEAD_SHA" --jq '.content' \
97+
| base64 --decode | tr -d '\r\n')
98+
if ! [[ "$candidate" =~ ^[0-9a-f]{40}$ ]]; then
99+
echo "::error title=Staged QPK pin gate::invalid_qpk_pin"
100+
exit 1
101+
fi
102+
103+
main_sha=$(gh api "repos/$GITHUB_REPOSITORY/git/ref/heads/main" --jq '.object.sha')
104+
comparison=$(gh api "repos/$GITHUB_REPOSITORY/compare/$candidate...$main_sha" --jq '.status')
105+
if [ "$comparison" != "ahead" ] && [ "$comparison" != "identical" ]; then
106+
echo "::error title=Staged QPK pin gate::candidate_is_not_on_main_history"
107+
exit 1
108+
fi
109+
110+
- name: Append gate summary
111+
if: steps.pr.outputs.number != ''
112+
run: cat staged-pin-summary.md >> "$GITHUB_STEP_SUMMARY"
113+
114+
- name: Queue validated staged pin for merge
115+
if: steps.guard.outputs.allowed == 'true'
116+
env:
117+
GH_TOKEN: ${{ github.token }}
118+
run: |
119+
gh pr merge "${{ steps.pr.outputs.number }}" \
120+
--repo "$GITHUB_REPOSITORY" \
121+
--auto --rebase --delete-branch \
122+
--match-head-commit "${{ steps.pr.outputs.head_sha }}"

.github/workflows/open-downstream-qpk-pin-prs.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ jobs:
5454
5555
- name: Create coherent aggregate bundle PR
5656
if: steps.sync.outputs.missing_token != 'true'
57-
uses: peter-evans/create-pull-request@v7
57+
uses: peter-evans/create-pull-request@22a9089034f40e5a961c8808d113e2c98fb63676 # v7.0.8
5858
with:
5959
token: ${{ secrets.QSL_REPO_SYNC_TOKEN }}
6060
commit-message: "chore(deps): reconcile coherent QSL pin bundle"

.github/workflows/update-qpk-pin.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ on:
88
- "qsl-pins.txt"
99
- "constraints.txt"
1010
- ".github/workflows/open-downstream-qpk-pin-prs.yml"
11+
- ".github/workflows/advance-staged-qpk-pin.yml"
1112
- ".github/workflows/update-qpk-pin.yml"
1213
- "scripts/check_qpk_pin_consistency.py"
1314
- "scripts/open_downstream_qpk_pin_prs.py"

tests/test_update_qpk_pin_workflow.py

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,9 @@
99
ROOT = Path(__file__).resolve().parents[1]
1010
WORKFLOW_PATH = ROOT / ".github" / "workflows" / "update-qpk-pin.yml"
1111
DOWNSTREAM_WORKFLOW_PATH = ROOT / ".github" / "workflows" / "open-downstream-qpk-pin-prs.yml"
12+
STAGED_PIN_GUARD_WORKFLOW_PATH = (
13+
ROOT / ".github" / "workflows" / "advance-staged-qpk-pin.yml"
14+
)
1215
OLD_QPK_SHA = "5d4bbd0e7ef9a1434010e8b6a69905d39ee55f1b"
1316
STRATEGY_REFS = {
1417
"us-equity-strategies": (
@@ -249,3 +252,23 @@ def test_downstream_rollout_is_scheduled_and_phase_gated() -> None:
249252
assert "- consumers" in workflow
250253
assert 'open_downstream_qpk_pin_prs.py --phase "$QSL_PIN_PHASE"' in workflow
251254
assert "Create coherent aggregate bundle PR" in workflow
255+
assert "peter-evans/create-pull-request@22a9089034f40e5a961c8808d113e2c98fb63676" in workflow
256+
assert "peter-evans/create-pull-request@v7" not in workflow
257+
258+
259+
def test_staged_pin_auto_advance_is_limited_to_verified_machine_prs() -> None:
260+
workflow = STAGED_PIN_GUARD_WORKFLOW_PATH.read_text(encoding="utf-8")
261+
262+
assert 'workflows: ["CI"]' in workflow
263+
assert "github.event.workflow_run.conclusion == 'success'" in workflow
264+
assert "github.event.workflow_run.head_branch == 'auto/qpk-pin-update'" in workflow
265+
assert '"author") or {}).get("login") == "Pigbibi"' in workflow
266+
assert 'pr.get("baseRefName") == "main"' in workflow
267+
assert 'pr.get("headRefName") == "auto/qpk-pin-update"' in workflow
268+
assert 'pr.get("title") == "chore: advance staged QPK pin"' in workflow
269+
assert "not pr.get(\"isCrossRepository\")" in workflow
270+
assert 'if [ "$files" != "QPK_PIN" ]; then' in workflow
271+
assert "candidate_is_not_on_main_history" in workflow
272+
assert "actions/checkout" not in workflow
273+
assert "--auto --rebase --delete-branch" in workflow
274+
assert "--match-head-commit" in workflow

0 commit comments

Comments
 (0)