diff --git a/.github/scripts/report-scheduled-failure.sh b/.github/scripts/report-scheduled-failure.sh index 290e107..293361e 100755 --- a/.github/scripts/report-scheduled-failure.sh +++ b/.github/scripts/report-scheduled-failure.sh @@ -16,11 +16,10 @@ gh label create "$LABEL" \ existing=$(gh issue list --label "$LABEL" --state open --json number --jq '.[0].number // empty') if [ -z "$existing" ]; then - body=$(printf '%s\n\n%s\n\n%s\n\n%s\n\n%s' \ + body=$(printf '%s\n\n%s\n\n%s\n\n%s' \ "The scheduled dependency check failed." \ "First failing run: ${RUN_URL}" \ "Likely cause, if a lint or pytest job failed: a dev or lint dependency (ruff, ty, eof-fixer, pytest, typing-extensions) released a breaking change. Reproduce locally with \`just install\` then \`just lint\` and \`just test\`." \ - "Likely cause, if a lowest-direct job failed: a declared floor in \`pyproject.toml\` no longer installs or bootstraps, usually because an upstream package changed metadata under it. Reproduce with \`uv pip install --resolution lowest-direct '.[]'\` then \`python scripts/floor_smoke.py \`, reading both off the failing job's matrix." \ "Close this issue once fixed. The next scheduled failure will open a fresh issue.") gh issue create --title "$TITLE" --label "$LABEL" --body "$body" else diff --git a/.github/scripts/require-floor-run.sh b/.github/scripts/require-floor-run.sh deleted file mode 100755 index 5f8b06a..0000000 --- a/.github/scripts/require-floor-run.sh +++ /dev/null @@ -1,41 +0,0 @@ -#!/usr/bin/env bash -set -euo pipefail - -WORKFLOW="scheduled.yml" -CANDIDATE_LIMIT=30 - -tag_sha=$(git rev-parse HEAD) -tag_name=${GITHUB_REF_NAME:-$tag_sha} - -# "The most recent run is green" would not have held 1.8.0 back: the newest floors run at that -# moment WAS green, because it predated the commit that broke the floor. So the run has to have -# tested code that already contains this tag, i.e. the tagged commit is an ancestor of (or equal -# to) the commit the floors ran on. -candidate_shas=$( - gh run list --workflow "$WORKFLOW" --status success --limit "$CANDIDATE_LIMIT" --json headSha --jq '.[].headSha' -) - -while read -r candidate_sha; do - [ -n "$candidate_sha" ] || continue - # A run dispatched by hand counts as much as a scheduled one; it verifies the same thing. Its - # commit may not be here yet, and may not be fetchable at all if its branch is gone. - git cat-file -e "${candidate_sha}^{commit}" 2>/dev/null \ - || git fetch --quiet --no-tags origin "$candidate_sha" 2>/dev/null \ - || continue - if git merge-base --is-ancestor "$tag_sha" "$candidate_sha"; then - echo "Dependency floors verified by $WORKFLOW at $candidate_sha, which contains $tag_sha." - exit 0 - fi -done <<< "$candidate_shas" - -cat >&2 <- - Run the declared dependency floors. Off for PRs: the job resolves direct dependencies at - their floor and transitives at their newest, so a release published upstream today can - turn it red for reasons no PR here caused. scheduled.yml turns it on, where that failure - opens a tracking issue instead of blocking a merge. - type: boolean - default: false + workflow_call: {} jobs: lint: @@ -131,7 +122,7 @@ jobs: if [ -n "$failed" ]; then echo "::error::extras failed isolated install+import:$failed"; exit 1; fi echo "all extras install and import in isolation" - lowest-direct: + floors: # Every other job resolves newest, so a declared floor is a claim nothing checks. This one # pins each direct dependency to the floor pyproject.toml declares and bootstraps against it. # Per framework rather than all-extras at once: a floor declared in one extra pulls the shared @@ -141,7 +132,7 @@ jobs: # deliberately did not install as unresolved. So nothing type-checks at the floor - an # annotation naming an optional dependency's internals has to span the declared range, and only # a human checks that. - if: inputs.lowest-direct + if: github.event_name != 'schedule' runs-on: ubuntu-latest strategy: fail-fast: false diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 3fdb894..ff28385 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -4,42 +4,22 @@ name: Release # GitHub Release. Replaces the old `on: release: published` publish.yml — that # trigger is removed so the published Release this workflow creates can't re-fire # it (double-publish). The tag is the sole entry point; by convention a tag is -# only cut off a green main, so the sole in-workflow gate is floors-gate below, -# covering the one check that never runs on pull requests. +# only cut off a green main, so there is no in-workflow CI gate. on: push: tags: - '[0-9]+.[0-9]+.[0-9]+' # stable: 2.7.2 - '[0-9]+.[0-9]+.[0-9]+[a-z]+[0-9]+' # pre-release: 2.0.0rc1, 4.0.0a2 -permissions: {} +# contents: write -> create the GitHub Release; id-token: write -> OIDC for PyPI Trusted Publishing. +permissions: + contents: write + id-token: write jobs: - # Gates the tag on the dependency floors, which _checks.yml runs only for scheduled.yml, never - # for pull requests. A floor break can therefore merge with every check green, which is how the - # crash in 1.8.0 reached PyPI (#245). Placed before release so a failure costs nothing. - floors-gate: - runs-on: ubuntu-latest - # contents: read -> the history the ancestor check walks; actions: read -> list workflow runs. - permissions: - contents: read - actions: read - steps: - - uses: actions/checkout@v6 - with: - fetch-depth: 0 # the ancestor check needs history, not just the tagged commit - - run: .github/scripts/require-floor-run.sh - env: - GH_TOKEN: ${{ github.token }} - release: - needs: floors-gate runs-on: ubuntu-latest environment: pypi # scopes the PyPI Trusted Publisher; hook for approval rules - # contents: write -> create the GitHub Release; id-token: write -> OIDC for PyPI Trusted Publishing. - permissions: - contents: write - id-token: write steps: - uses: actions/checkout@v6 - uses: extractions/setup-just@v4 diff --git a/.github/workflows/scheduled.yml b/.github/workflows/scheduled.yml index dd76157..df1fe6c 100644 --- a/.github/workflows/scheduled.yml +++ b/.github/workflows/scheduled.yml @@ -1,7 +1,7 @@ name: scheduled-dep-check on: schedule: - # Daily: both resolutions here are unpinned, so an upstream release can break main + # Daily: the resolution here is unpinned, so an upstream release can break main # between runs. Daily bounds that window at a day and attributes the failure to upstream # (a tracking issue) instead of to whichever pull request happens to run next. - cron: "0 6 * * *" # daily 06:00 UTC @@ -14,8 +14,6 @@ concurrency: jobs: checks: uses: ./.github/workflows/_checks.yml - with: - lowest-direct: true report-failure: needs: checks