Skip to content

Commit 0f31882

Browse files
committed
chore(ci): remove the promotion-dedup gate — savings don't justify deploy-graph complexity
The bespoke dedup job hand-rolled what content-addressed caching solves idiomatically, saved only ~$50-90/mo, and its needs edge just caused the deploy-chain skip incident. test-build returns to its original shape; the explicit need-result conditions on the deploy chain stay as hygiene.
1 parent bde4b1a commit 0f31882

1 file changed

Lines changed: 6 additions & 133 deletions

File tree

.github/workflows/ci.yml

Lines changed: 6 additions & 133 deletions
Original file line numberDiff line numberDiff line change
@@ -34,135 +34,9 @@ permissions:
3434
contents: read
3535

3636
jobs:
37-
# Promotion PRs (staging→main etc.) double-run the test suite: every commit
38-
# on staging/main already gets a push-event run of the exact same test-build,
39-
# and the pull_request "synchronize" run for the open release PR re-runs it on
40-
# the same sha seconds later (~39 duplicate runs / 5 days measured Jul 2026).
41-
# This gate skips the PR run's test-build ONLY when it can prove the identical
42-
# work already passed elsewhere:
43-
# 1. the PR base adds no file changes over the merge base with the head sha
44-
# (compare head...base has an empty diff), so the merge result's tree is
45-
# identical to the head tree the push run tested. Plain ancestry is not
46-
# enough of a check here: main's merge-only ruleset leaves merge commits
47-
# on main that staging lacks, so main...staging is permanently
48-
# "diverged" — but those merge commits carry no tree delta. A real
49-
# hotfix landed directly on the base makes the diff non-empty and we
50-
# run tests here;
51-
# 2. the push-event CI run at the same head sha finished its test jobs with
52-
# conclusion success (polled, since push + PR runs start simultaneously).
53-
# Fail-open by construction: any API error, timeout, missing run, or push-run
54-
# failure leaves covered=false and the PR run tests normally, so the PR check
55-
# is green only if tests passed either here or on the identical tree. Not a
56-
# workflow-level filter on purpose — a job-level skip still reports a
57-
# (successful) check context. NOTE: when test-build is skipped, its nested
58-
# "Test and Build / ..." contexts are not created; verified 2026-07-22 that
59-
# no required status checks are configured on main/staging (rulesets contain
60-
# only pull_request/deletion/non_fast_forward). If required checks are ever
61-
# added, require the caller "Test and Build" context, not the nested ones.
62-
# dev is excluded: push runs on dev skip test-build, so dev-headed PRs have
63-
# no push-run coverage to reuse.
64-
dedup-promotion:
65-
name: Dedup Promotion PR
66-
runs-on: ${{ (vars.CI_PROVIDER == '' || vars.CI_PROVIDER == 'blacksmith') && 'blacksmith-2vcpu-ubuntu-2404' || 'ubuntu-latest' }}
67-
timeout-minutes: 15
68-
if: >-
69-
github.event_name == 'pull_request' &&
70-
github.event.pull_request.head.repo.full_name == github.repository &&
71-
contains(fromJSON('["main", "staging"]'), github.event.pull_request.head.ref)
72-
permissions:
73-
contents: read
74-
actions: read
75-
outputs:
76-
covered: ${{ steps.probe.outputs.covered }}
77-
steps:
78-
- name: Probe for a passing push run at the same sha
79-
id: probe
80-
env:
81-
GH_TOKEN: ${{ github.token }}
82-
REPO: ${{ github.repository }}
83-
HEAD_SHA: ${{ github.event.pull_request.head.sha }}
84-
BASE_SHA: ${{ github.event.pull_request.base.sha }}
85-
BASE_REF: ${{ github.event.pull_request.base.ref }}
86-
run: |
87-
COVERED=false
88-
89-
# (1) Merge-tree equivalence: compare head...base diffs the merge
90-
# base against the base tip. An empty diff means the base contributes
91-
# nothing beyond what head already contains (merge commits only), so
92-
# the PR merge tree equals the head tree the push run tested. Any
93-
# error yields "unknown" and we run the tests.
94-
BASE_DELTA="$(gh api "repos/${REPO}/compare/${HEAD_SHA}...${BASE_SHA}" --jq '.files | length' 2>/dev/null || echo unknown)"
95-
if [ "$BASE_DELTA" != "0" ]; then
96-
echo "Base tip changes ${BASE_DELTA} file(s) over the merge base; merge tree differs from head — running tests in this PR run."
97-
echo "covered=false" >> "$GITHUB_OUTPUT"
98-
exit 0
99-
fi
100-
101-
# (2) Poll the push-event CI run's test jobs (they start seconds after
102-
# this run and take ~4 min). Any conclusion other than success, or
103-
# deadline expiry, falls through to covered=false.
104-
DEADLINE=$((SECONDS + 600))
105-
while [ "$SECONDS" -lt "$DEADLINE" ]; do
106-
RUN_JSON="$(gh api "repos/${REPO}/actions/workflows/ci.yml/runs?event=push&head_sha=${HEAD_SHA}&per_page=5" 2>/dev/null || echo '')"
107-
RUN_ID="$(printf '%s' "$RUN_JSON" | jq -r '.workflow_runs[0].id // empty' 2>/dev/null || echo '')"
108-
if [ -n "$RUN_ID" ]; then
109-
# Jobs from the reusable test-build workflow are prefixed
110-
# "Test and Build /". If that name ever changes, fall back to the
111-
# overall run conclusion (stricter, still correct).
112-
STATE="$(gh api "repos/${REPO}/actions/runs/${RUN_ID}/jobs?per_page=100" --jq '
113-
[.jobs[] | select(.name | startswith("Test and Build /"))] as $t |
114-
if ($t | length) == 0 then "nojobs"
115-
elif all($t[]; .conclusion == "success") then "success"
116-
elif any($t[]; .conclusion != null and .conclusion != "success") then "failed"
117-
else "pending" end' 2>/dev/null || echo pending)"
118-
if [ "$STATE" = "nojobs" ]; then
119-
# Nested reusable-workflow jobs appear only after the caller
120-
# starts, so an in-progress run with no "Test and Build /"
121-
# jobs yet just needs another poll. Once the run has
122-
# COMPLETED without them, fail closed: the job was renamed or
123-
# tests were skipped — never infer coverage from the overall
124-
# run conclusion. Update the prefix here on a rename.
125-
RUN_STATUS="$(printf '%s' "$RUN_JSON" | jq -r '.workflow_runs[0].status // "unknown"' 2>/dev/null || echo unknown)"
126-
if [ "$RUN_STATUS" = "completed" ]; then
127-
echo "Push run ${RUN_ID} completed with no 'Test and Build /' jobs — running tests in this PR run (update the prefix if the job was renamed)."
128-
break
129-
fi
130-
STATE="pending"
131-
fi
132-
if [ "$STATE" = "success" ]; then
133-
# (3) Re-verify merge-tree equivalence against the LIVE base
134-
# tip at decision time: the base branch may have gained real
135-
# commits during the poll, in which case the frozen BASE_SHA
136-
# check from step (1) is stale and the skip would be unsound.
137-
# Any error yields "unknown" and we run the tests.
138-
LIVE_DELTA="$(gh api "repos/${REPO}/compare/${HEAD_SHA}...heads/${BASE_REF}" --jq '.files | length' 2>/dev/null || echo unknown)"
139-
if [ "$LIVE_DELTA" = "0" ]; then
140-
COVERED=true
141-
echo "Push run ${RUN_ID} passed its test jobs for ${HEAD_SHA} and the live base tip still adds no file changes — skipping duplicate test-build."
142-
else
143-
echo "Base branch moved during the poll (live delta: ${LIVE_DELTA}) — running tests in this PR run."
144-
fi
145-
break
146-
elif [ "$STATE" = "failed" ]; then
147-
echo "Push run ${RUN_ID} did not pass (state: ${STATE}) — running tests in this PR run."
148-
break
149-
fi
150-
fi
151-
sleep 20
152-
done
153-
154-
echo "covered=${COVERED}" >> "$GITHUB_OUTPUT"
155-
15637
test-build:
15738
name: Test and Build
158-
needs: [dedup-promotion]
159-
# !cancelled(): dedup-promotion is skipped on every non-promotion event and
160-
# a skipped need would otherwise skip this job too. covered != 'true' is
161-
# fail-open — empty (skipped/failed probe) means run the tests.
162-
if: >-
163-
!cancelled() &&
164-
(github.ref != 'refs/heads/dev' || github.event_name == 'pull_request') &&
165-
needs.dedup-promotion.outputs.covered != 'true'
39+
if: github.ref != 'refs/heads/dev' || github.event_name == 'pull_request'
16640
uses: ./.github/workflows/test-build.yml
16741
secrets: inherit
16842

@@ -202,10 +76,10 @@ jobs:
20276
migrate:
20377
name: Migrate DB
20478
needs: [test-build]
205-
# !cancelled() + explicit result check: test-build's needs chain contains
206-
# the (push-skipped) dedup-promotion job, and a skipped transitive ancestor
207-
# fails the implicit success() and would cascade-skip the whole deploy
208-
# chain (migrate -> promote-images -> CodeDeploy).
79+
# Explicit need results instead of the implicit success(): a skipped job
80+
# anywhere in the transitive needs chain silently fails implicit success()
81+
# and cascade-skips the deploy chain (migrate -> promote-images ->
82+
# CodeDeploy) — this bit us on 2026-07-23. State requirements explicitly.
20983
if: >-
21084
!cancelled() &&
21185
needs.test-build.result == 'success' &&
@@ -436,8 +310,7 @@ jobs:
436310
promote-images:
437311
name: Promote Images
438312
needs: [migrate, build-amd64]
439-
# Explicit results: see migrate's comment — the implicit success() fails
440-
# on the skipped dedup-promotion ancestor.
313+
# Explicit results: see migrate's comment.
441314
if: >-
442315
!cancelled() &&
443316
needs.migrate.result == 'success' &&

0 commit comments

Comments
 (0)