ci: isolate metadata edits and recover squash commits (#47)

## Development claim / 开发声明

- Domain: `release-control-plane`
- Claim: `control-plane`
- Base commit: `f7e9f52db946b4edb77cea61b6c67dd0cb6a5538`
- Release preparation: `no`
- Focused validation:
  - `python3 script/tests/test_validation_tooling.py`
  - `./script/validate_build_scripts_safety.sh --static-only`

<!-- apc-engineering-coordination-v1
{"claim_overlap_rejections":0,"merge_tree_conflicts":0,"schema_version":"apc.engineering-coordination.v1","task_created_at":"2026-08-18T19:04:47Z","train_conflict_resolutions":0}
-->

Co-authored-by: gd-ops <gd-ops@xmfunny.com>
This commit is contained in:
xjxandgd-ops authored and GitHub committed 2026-08-19 03:17:52 +08:00
1 parent f7e9f52db9
commit 4181533050
3 files changed
+62 -13

No files matched your search

+41 -7
View File
@@ -88,7 +88,19 @@ jobs:
test "$(jq -er '.head.ref' <<<"$resolved_pull")" = "$HEAD_BRANCH"
test "$(jq -er '.head.sha' <<<"$resolved_pull")" = "$HEAD_SHA"
test "$(jq -er '.merged_at | type' <<<"$resolved_pull")" = "string"
merge_commit="$(jq -er '.merge_commit_sha' <<<"$resolved_pull")"
merged_pr="$(
gh pr view "$pr_number" \
--repo "$GITHUB_REPOSITORY" \
--json number,state,mergedAt,mergeCommit,baseRefName,headRefName,headRefOid,isCrossRepository
)"
test "$(jq -er '.number' <<<"$merged_pr")" = "$pr_number"
test "$(jq -er '.state' <<<"$merged_pr")" = "MERGED"
test "$(jq -er '.baseRefName' <<<"$merged_pr")" = "$base_branch"
test "$(jq -er '.headRefName' <<<"$merged_pr")" = "$HEAD_BRANCH"
test "$(jq -er '.headRefOid' <<<"$merged_pr")" = "$HEAD_SHA"
test "$(jq -er '.isCrossRepository' <<<"$merged_pr")" = "false"
test "$(jq -er '.mergedAt | type' <<<"$merged_pr")" = "string"
merge_commit="$(jq -er '.mergeCommit.oid' <<<"$merged_pr")"
[[ "$merge_commit" =~ ^[0-9a-f]{40}$ ]]
fi
@@ -151,8 +163,9 @@ jobs:
newly_merged=1
fi
pull=""
merged_pr=""
merge_observed=0
for attempt in 1 2 3 4 5 6; do
for attempt in $(seq 1 15); do
pull="$(
gh api \
-H 'Accept: application/vnd.github+json' \
@@ -165,12 +178,33 @@ jobs:
'.merged == true and
.base.ref == $base_branch and
.head.sha == $head_sha and
(.merge_commit_sha | strings | test("^[0-9a-f]{40}$"))' \
(.merged_at | type) == "string"' \
<<<"$pull" >/dev/null; then
merge_observed=1
break
if ! merged_pr="$(
gh pr view "$PR_NUMBER" \
--repo "$GITHUB_REPOSITORY" \
--json number,state,mergedAt,mergeCommit,baseRefName,headRefName,headRefOid,isCrossRepository
)"; then
merged_pr=""
elif jq -e \
--argjson pr_number "$PR_NUMBER" \
--arg base_branch "$BASE_BRANCH" \
--arg head_branch "$HEAD_BRANCH" \
--arg head_sha "$HEAD_SHA" \
'.number == $pr_number and
.state == "MERGED" and
.baseRefName == $base_branch and
.headRefName == $head_branch and
.headRefOid == $head_sha and
.isCrossRepository == false and
(.mergedAt | type) == "string" and
(.mergeCommit.oid | strings | test("^[0-9a-f]{40}$"))' \
<<<"$merged_pr" >/dev/null; then
merge_observed=1
break
fi
fi
if ((attempt < 6)); then
if ((attempt < 15)); then
sleep 2
fi
done
@@ -178,7 +212,7 @@ jobs:
test "$(jq -er '.merged' <<<"$pull")" = "true"
test "$(jq -er '.base.ref' <<<"$pull")" = "$BASE_BRANCH"
test "$(jq -er '.head.sha' <<<"$pull")" = "$HEAD_SHA"
merge_commit="$(jq -er '.merge_commit_sha' <<<"$pull")"
merge_commit="$(jq -er '.mergeCommit.oid' <<<"$merged_pr")"
[[ "$merge_commit" =~ ^[0-9a-f]{40}$ ]]
if [[ "$PR_STATE" == "closed" ]]; then
test "$merge_commit" = "$RECOVERED_COMMIT"
+5 -1
View File
@@ -17,7 +17,11 @@ permissions:
contents: read
concurrency:
group: ci-${{ github.workflow }}-${{ github.ref }}
# Metadata edits still get a cheap routing run, but must never cancel an
# in-flight source validation for the same PR head. Base-ref edits remain
# independently fail-closed because the merge verifier rechecks the exact
# current base identity before accepting either run.
group: ci-${{ github.workflow }}-${{ github.ref }}-${{ github.event_name == 'pull_request' && github.event.action == 'edited' && format('edited-{0}', github.run_id) || 'validation' }}
cancel-in-progress: true
env:
@@ -1117,6 +1117,14 @@ class CIWorkflowContractTests(unittest.TestCase):
self.assertIn('"$GITHUB_SHA" != "$EXPECTED_SHA"', source)
self.assertIn("MAIN_PARENT: ${{ needs.scope.outputs.base }}", source)
def test_ci_metadata_edits_do_not_cancel_source_validation(self) -> None:
source = (ROOT / ".github/workflows/ci.yml").read_text(encoding="utf-8")
concurrency = source[source.index("concurrency:") : source.index("env:")]
self.assertIn("github.event.action == 'edited'", concurrency)
self.assertIn("format('edited-{0}', github.run_id)", concurrency)
self.assertIn("|| 'validation'", concurrency)
self.assertIn("cancel-in-progress: true", concurrency)
def test_successful_protected_pr_ci_merges_and_dispatches_main_without_pr_code(self) -> None:
source = (ROOT / ".github/workflows/auto-merge.yml").read_text(
encoding="utf-8"
@@ -1141,9 +1149,13 @@ class CIWorkflowContractTests(unittest.TestCase):
)
]
self.assertIn("merge_observed=0", merge_step)
self.assertIn("for attempt in 1 2 3 4 5 6", merge_step)
self.assertIn("for attempt in $(seq 1 15)", merge_step)
self.assertIn('test "$merge_observed" = "1"', merge_step)
self.assertIn(".merged == true", merge_step)
self.assertIn("gh pr view \"$PR_NUMBER\"", merge_step)
self.assertIn('if ! merged_pr="$(', merge_step)
self.assertIn(".mergeCommit.oid", merge_step)
self.assertNotIn(".merge_commit_sha", merge_step)
self.assertLess(merge_step.index("gh pr merge"), merge_step.index("merge_observed=0"))
cleanup = source.index("./script/ci_proof_promotion.py delete-merged-head")
self.assertGreater(cleanup, source.index("Squash or recover only the PR proven"))
@@ -1181,10 +1193,9 @@ class CIWorkflowContractTests(unittest.TestCase):
'test "$(jq -er \'.head.sha\' <<<"$resolved_pull")" = "$HEAD_SHA"',
closed_replay,
)
self.assertIn(
'merge_commit="$(jq -er \'.merge_commit_sha\' <<<"$resolved_pull")"',
closed_replay,
)
self.assertIn('gh pr view "$pr_number"', closed_replay)
self.assertIn(".mergeCommit.oid", closed_replay)
self.assertNotIn(".merge_commit_sha", closed_replay)
def test_ci_prepares_producer_image_dependencies_and_pins_actions(self) -> None:
source = (ROOT / ".github/workflows/ci.yml").read_text(encoding="utf-8")