Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
170 changes: 170 additions & 0 deletions .github/workflows/dependabot-manifests.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,170 @@
name: Dependabot manifests

# MANIFEST.md's dependency table and THIRD_PARTY_NOTICES.txt are generated from
# the dependency graph (`cargo xtask manifest` / `cargo xtask tpn`), and ci.yml
# gates both against the committed copies. Dependabot cannot run a generator, so
# every cargo bump it opens fails those two gates on arrival — a permanently red
# dependency queue, which trains reviewers to stop reading red. Regenerate both
# here and commit them onto the bump branch.
#
# ONE MANUAL STEP REMAINS, by design. A commit made with GITHUB_TOKEN does not
# start a new workflow run; GitHub suppresses that to avoid recursion. The
# `synchronize` this commit produces therefore lands in the "approval required"
# state, and a maintainer clicks "Approve workflows to run" in the merge box to
# re-run the gates against the regenerated files. That click is the whole
# remaining cost, down from regenerating two files by hand and pushing them.
# Removing it needs a non-GITHUB_TOKEN identity (a GitHub App installation
# token, with its credentials stored as *Dependabot* secrets, since Actions
# secrets are unavailable on Dependabot-triggered runs) — deliberately not done
# here: it is org-level setup, not a workflow change.
#
# Scoped to Dependabot's own PRs. A human who changes the dependency graph gets
# the ci.yml failure telling them to regenerate, which is the right signal.

on:
pull_request:
paths:
- Cargo.lock
- "**/Cargo.toml"

# No ambient access: each job opts in to exactly what it needs.
permissions: {}

# A group of its own, never shared with ci.yml — the lesson from EAI-7548 is
# that a stalled run must not be able to hold a group that merge-required checks
# depend on.
concurrency:
group: dependabot-manifests-${{ github.ref }}
cancel-in-progress: true

env:
CARGO_TERM_COLOR: always

jobs:
# Runs the generators, and therefore compiles the bumped dependency graph:
# building xtask executes the new versions' `build.rs` and proc macros. That
# is precisely why this job is read-only and why committing is a separate job
# — untrusted code and a write-scoped token never coexist here.
generate:
name: Regenerate manifests
if: github.actor == 'dependabot[bot]'
runs-on: ubuntu-latest
permissions:
contents: read
outputs:
changed: ${{ steps.regen.outputs.changed }}
steps:
# The PR head, not the refs/pull/N/merge commit: the regenerated files
# have to match the branch that will carry them.
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
ref: ${{ github.event.pull_request.head.sha }}

- uses: actions-rust-lang/setup-rust-toolchain@166cdcfd11aee3cb47222f9ddb555ce30ddb9659 # v1.17.0

# Same version and cache key as ci.yml's third-party-notices job: the
# notices must be byte-identical to what that job's `--check` regenerates,
# and a different cargo-about formats them differently.
- name: Cache cargo-about
id: cache-cargo-about
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
with:
path: ~/.cargo/bin/cargo-about
key: ${{ runner.os }}-cargo-about-0.9.1

- name: Install cargo-about
if: steps.cache-cargo-about.outputs.cache-hit != 'true'
run: cargo install cargo-about@0.9.1 --locked --features cli

- name: Regenerate
id: regen
run: |
cargo xtask manifest
cargo xtask tpn
if git diff --quiet -- MANIFEST.md THIRD_PARTY_NOTICES.txt; then
echo "Already current; nothing to commit."
echo "changed=false" >> "$GITHUB_OUTPUT"
else
git diff --stat -- MANIFEST.md THIRD_PARTY_NOTICES.txt
echo "changed=true" >> "$GITHUB_OUTPUT"
fi

- name: Upload regenerated manifests
if: steps.regen.outputs.changed == 'true'
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: regenerated-manifests
path: |
MANIFEST.md
THIRD_PARTY_NOTICES.txt
retention-days: 1

# Holds the write token and runs no third-party code: it only unpacks the
# artifact and calls the API. The artifact was produced by the untrusted job
# above, so the commit is bounded to the two generated paths below — a hostile
# build script can at worst garble files a human still reviews, never reach
# anything else in the tree.
commit:
name: Commit regenerated manifests
needs: generate
if: needs.generate.outputs.changed == 'true'
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
name: regenerated-manifests
path: regenerated

# `createCommitOnBranch` rather than `git push`, for two reasons:
#
# * commits it creates are signed by GitHub (web-flow), so the blocking
# `Commit signatures + sign-off` gate — `verify-commits
# --require-verified` — sees verification.verified == true. A push from
# a runner would be unsigned and fail it. The Signed-off-by trailer
# below covers the DCO half of that same gate.
# * expectedHeadOid makes the mutation fail rather than clobber if
# Dependabot force-pushes the branch while this job is running.
- name: Commit to the pull request branch
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
REPO: ${{ github.repository }}
BRANCH: ${{ github.event.pull_request.head.ref }}
EXPECTED_HEAD: ${{ github.event.pull_request.head.sha }}
run: |
set -euo pipefail

base64 -w0 regenerated/MANIFEST.md > manifest.b64
base64 -w0 regenerated/THIRD_PARTY_NOTICES.txt > tpn.b64

jq -n \
--arg repo "$REPO" \
--arg branch "$BRANCH" \
--arg oid "$EXPECTED_HEAD" \
--rawfile manifest manifest.b64 \
--rawfile tpn tpn.b64 \
'{
query: "mutation($input: CreateCommitOnBranchInput!) { createCommitOnBranch(input: $input) { commit { oid } } }",
variables: {
input: {
branch: {
repositoryNameWithOwner: $repo,
branchName: $branch
},
expectedHeadOid: $oid,
message: {
headline: "build(deps): regenerate MANIFEST.md and THIRD_PARTY_NOTICES.txt",
body: "Generated by `cargo xtask manifest` and `cargo xtask tpn` for the dependency change in this pull request.\n\nSigned-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>"
},
fileChanges: {
additions: [
{ path: "MANIFEST.md", contents: $manifest },
{ path: "THIRD_PARTY_NOTICES.txt", contents: $tpn }
]
}
}
}
}' > payload.json

gh api graphql --input payload.json
Loading