Skip to content

Commit 071e7db

Browse files
authored
improvement(ci): fix the stripe-sync flake, stop helm cancelling a publish, attest via actions/attest (#8873)
* improvement(ci): halve the mobile job, narrow the desktop gate, skip proven checks on main, fix the top flake - e2e (mobile): Chromium and WebKit run as two processes against one app instead of back to back (each seeds its own fixtures and writes its own report), and every e2e group restores its own Turbopack dev cache. The job was the PR critical path at ~19.5 min: Chromium's pass (718 s median) carried the cold compile and WebKit waited it out (310 s). `suite setup` failures are now logged instead of only reaching the uploaded report. - dev-cache action: one mount shared by desktop-live and the e2e groups, keyed by app, event, fork, Next version and the hash of the file that sets the app's environment, so a cache is never read under NEXT_PUBLIC_* values it was not compiled with. http-e2e.sh stops apps with SIGINT so the cache write completes, drops a cache Turbopack reports as corrupt, and retries a startup it aborted once from an empty cache. - desktop-live-changes.sh: run the live desktop suite when a pull request touches a path it exercises instead of skipping only docs-like paths. Every genuine desktop-live failure since the layout change was on a PR the new rule still runs; pushes always run it. - ci.yml: a `proof` job lets a main push skip the checks when it is a merge whose tree is identical to its staging parent and a staging push run of that parent passed `checks / ci`; migrate gates on that proof instead. Any other shape or any error runs the full checks. - codeql, helm, desktop-e2e skip the staging -> main release PR (main's push and schedule still run them); helm never cancels a push mid-publish; trigger-promote on 2 vCPU; attestations via actions/attest with artifact-metadata: write. - stripe-sync-convergence: start each contender only after the parked transaction holds its locks, wait on a deadline rather than 200 polls, and release the transaction before failing, so a lost race no longer hangs the suite's teardown. It was the most frequent flake (9 red runs). - Integration shard weights refreshed from a recent run; /ship runs the affected workspaces' suites instead of the full suite locally. * ci: include the chat page in the desktop gate, scope release-PR skips to this repository, give a cache-retry boot the full deadline * ci: keep the release PR's CodeQL scan, require the main base for release-PR skips, gate desktop-live on workspace permissions * ci: keep the desktop live gate's skip-only-unrelated rule * ci: run the mobile browsers one after the other again * ci: drop the e2e Turbopack dev cache and keep /ship's full test gate * ci: drop the main proof job and the release-PR skips
1 parent b6476cc commit 071e7db

5 files changed

Lines changed: 194 additions & 153 deletions

File tree

‎.github/workflows/ci.yml‎

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -564,7 +564,8 @@ jobs:
564564
needs.promote.result == 'success' &&
565565
needs.trigger-upload.result == 'success' &&
566566
needs.promote.outputs.promoted == 'true'
567-
runs-on: *runner-4vcpu
567+
# Mostly waiting on the ECS cutover after a dependency install: the smallest runner is enough.
568+
runs-on: *runner-2vcpu
568569
# Leave setup/promotion headroom above the 70-minute cutover poll.
569570
timeout-minutes: 90
570571
permissions:
@@ -992,6 +993,8 @@ jobs:
992993
# Sigstore signs against the runner's OIDC identity; no key material is stored.
993994
id-token: write
994995
attestations: write
996+
# Records each attestation against the image as an artifact storage record.
997+
artifact-metadata: write
995998
strategy:
996999
fail-fast: false
9971000
matrix:
@@ -1031,7 +1034,7 @@ jobs:
10311034

10321035
- name: Attest SBOM
10331036
if: matrix.platform != 'index'
1034-
uses: actions/attest-sbom@c604332985a26aa8cf1bdc465b92731239ec6b9e # v4.1.0
1037+
uses: actions/attest@1e69f48acb82d1966a394da916b4c1698aa569d6 # v4.2.2
10351038
with:
10361039
subject-name: ${{ matrix.image }}
10371040
subject-digest: ${{ matrix.digest }}
@@ -1041,7 +1044,7 @@ jobs:
10411044
push-to-registry: true
10421045

10431046
- name: Attest build provenance
1044-
uses: actions/attest-build-provenance@4d101475d8b20a2381f78447822ac1eab6504dd8 # v4.2.2
1047+
uses: actions/attest@1e69f48acb82d1966a394da916b4c1698aa569d6 # v4.2.2
10451048
with:
10461049
subject-name: ${{ matrix.image }}
10471050
subject-digest: ${{ matrix.digest }}

‎.github/workflows/helm.yml‎

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,9 +24,11 @@ on:
2424
- 'scripts/generate-image-manifest.ts'
2525
- 'package.json'
2626

27+
# Pull requests cancel a superseded run; pushes never do, so a newer push cannot cut off a chart
28+
# publish already in flight.
2729
concurrency:
2830
group: helm-${{ github.ref }}
29-
cancel-in-progress: true
31+
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
3032

3133
permissions:
3234
contents: read
@@ -260,7 +262,8 @@ jobs:
260262
contents: read # Read the chart source.
261263
packages: write # Push the chart, its signature, and its attestations to GHCR.
262264
id-token: write # Sigstore signs against the runner's OIDC identity; no key material is stored.
263-
attestations: write # Let actions/attest-build-provenance record the SLSA provenance.
265+
attestations: write # Let actions/attest record the SLSA provenance.
266+
artifact-metadata: write # And its artifact storage record.
264267
steps:
265268
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v6
266269
with:
@@ -388,7 +391,7 @@ jobs:
388391
# attestation with it, rather than only being retrievable from GitHub.
389392
- name: Attest build provenance
390393
if: steps.exists.outputs.already == 'false'
391-
uses: actions/attest-build-provenance@4d101475d8b20a2381f78447822ac1eab6504dd8 # v4.2.2
394+
uses: actions/attest@1e69f48acb82d1966a394da916b4c1698aa569d6 # v4.2.2
392395
with:
393396
subject-name: ${{ steps.package.outputs.repository }}
394397
subject-digest: ${{ steps.push.outputs.digest }}

‎apps/sim/lib/billing/webhooks/stripe-sync-convergence.integration.ts‎

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import {
1515
type InMemoryStripe,
1616
stripeClientMock,
1717
} from '@sim/testing/mocks/stripe.mock'
18+
import { sleep } from '@sim/utils/helpers'
1819
import { generateId } from '@sim/utils/id'
1920
import { type BetterAuthOptions, betterAuth } from 'better-auth'
2021
import { createAuthMiddleware } from 'better-auth/api'
@@ -376,7 +377,11 @@ type TestTransaction = Parameters<Parameters<typeof testDatabase.transaction>[0]
376377

377378
/**
378379
* Starts a transaction that takes its locks in `holdLocks`, then parks until released and runs
379-
* `finish`. `untilBlocking` resolves once another backend is waiting on one of its locks.
380+
* `finish`. `locked` resolves once those locks are held: start the contending work after it, or the
381+
* contender can take the lock first and nothing ever waits on the parked transaction.
382+
* `untilBlocking` resolves once another backend is waiting on one of its locks; if none does within
383+
* the deadline it releases the transaction before throwing, so a failure never leaves it holding
384+
* locks that the suite's teardown then waits on.
380385
*/
381386
function startParkedTransaction(
382387
holdLocks: (tx: TestTransaction) => Promise<void>,
@@ -399,16 +404,18 @@ function startParkedTransaction(
399404
})
400405
async function untilBlocking() {
401406
const pid = await holderPid
402-
for (let attempt = 0; attempt < 200; attempt++) {
407+
const deadline = Date.now() + 10_000
408+
while (Date.now() < deadline) {
403409
const [row] = await connection<{ blocked: number }[]>`
404410
select count(*)::int as blocked from pg_stat_activity
405411
where ${pid}::int = any(pg_blocking_pids(pid))`
406412
if (row.blocked > 0) return
407-
await new Promise<void>((resolve) => setImmediate(resolve))
413+
await sleep(10)
408414
}
415+
release()
409416
throw new Error('No transaction ever waited on the parked one')
410417
}
411-
return { done, release, untilBlocking }
418+
return { done, release, locked: holderPid.then(() => undefined), untilBlocking }
412419
}
413420

414421
describe('cancel_at_period_end sync', () => {
@@ -1037,6 +1044,7 @@ describe('Team activation', () => {
10371044
reason: 'admin-cancel-at-period-end',
10381045
})
10391046
})
1047+
await cancelling.locked
10401048
const activating = testDatabase.transaction((tx) =>
10411049
ensureTeamOrganizationForAcceptance({
10421050
billingOwnerUserId: owner.id,
@@ -1088,6 +1096,7 @@ describe('operator retry', () => {
10881096
})
10891097
}
10901098
)
1099+
await writing.locked
10911100
const requeuing = requeueFromAdminApi(pauseSync)
10921101
await writing.untilBlocking()
10931102
writing.release()
@@ -1129,6 +1138,7 @@ describe('operator retry', () => {
11291138
})
11301139
}
11311140
)
1141+
await writing.locked
11321142
const retrying = requestDashboardSubscriptionCancellation({
11331143
organizationId: org.organizationId,
11341144
operationId,

‎apps/sim/scripts/test-mobile-e2e.ts‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1151,6 +1151,7 @@ try {
11511151
durationMs: 0,
11521152
error: getErrorMessage(error),
11531153
})
1154+
logger.error('FAIL suite setup', { error: getErrorMessage(error) })
11541155
} finally {
11551156
await check('fixtures are removed', undefined, async () => {
11561157
await sql.begin(async (tx) => {

0 commit comments

Comments
 (0)