Skip to content
Merged
Show file tree
Hide file tree
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
8 changes: 8 additions & 0 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# WHY: Bump third-party action SHAs pinned inside the reusable workflows once,
# fleet-wide, rather than per-repo (spec §6.4). Weekly cadence; low noise.
version: 2
updates:
- package-ecosystem: github-actions
directory: "/"
schedule:
interval: weekly
34 changes: 34 additions & 0 deletions .github/workflows/actionlint.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
# WHY: Validate reusable workflow YAML before it merges to main, catching errors
# that would silently break all 11 fleet repos simultaneously.
name: actionlint

on:
pull_request:
paths:
- ".github/workflows/**"

permissions:
contents: read

concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true

jobs:
actionlint:
name: actionlint
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with:
persist-credentials: false
- name: Download actionlint
run: |
ACTIONLINT_VERSION=1.7.7
curl -sSfL \
"https://github.com/rhysd/actionlint/releases/download/v${ACTIONLINT_VERSION}/actionlint_${ACTIONLINT_VERSION}_linux_amd64.tar.gz" \
| tar -xz actionlint
chmod +x actionlint
- name: Run actionlint
run: ./actionlint -color .github/workflows/*.yml
84 changes: 84 additions & 0 deletions .github/workflows/codeql.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
# WHY: CodeQL static analysis (actions + rust) with security-extended queries,
# scheduled + on push to main. Reusable; toolchain + language toggles via inputs.
name: CodeQL (reusable)

on:
workflow_call:
inputs:
analyze_actions:
type: boolean
default: true
required: false
analyze_rust:
type: boolean
default: true
required: false
rust_toolchain:
type: string
default: "stable"
required: false
queries:
type: string
default: "+security-extended"
required: false
actions_timeout_minutes:
type: number
default: 30
required: false
rust_timeout_minutes:
type: number
default: 90
required: false

permissions:
contents: read

concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true

jobs:
analyze-actions:
name: Analyze (actions)
if: ${{ inputs.analyze_actions }}
runs-on: ubuntu-latest
timeout-minutes: ${{ inputs.actions_timeout_minutes }}
permissions:
security-events: write
contents: read
steps:
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with:
persist-credentials: false
- uses: github/codeql-action/init@dd677812177e0c29f9c970a6c58d8607ae1bfefd # v4
with:
languages: actions
queries: ${{ inputs.queries }}
- uses: github/codeql-action/autobuild@dd677812177e0c29f9c970a6c58d8607ae1bfefd # v4
- uses: github/codeql-action/analyze@dd677812177e0c29f9c970a6c58d8607ae1bfefd # v4
with:
category: /language:actions

analyze-rust:
name: Analyze (rust)
if: ${{ inputs.analyze_rust }}
runs-on: ubuntu-latest
timeout-minutes: ${{ inputs.rust_timeout_minutes }}
permissions:
security-events: write
contents: read
steps:
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with:
persist-credentials: false
- uses: dtolnay/rust-toolchain@631a55b12751854ce901bb631d5902ceb48146f7 # stable
with:
toolchain: ${{ inputs.rust_toolchain }}
- uses: github/codeql-action/init@dd677812177e0c29f9c970a6c58d8607ae1bfefd # v4
with:
languages: rust
queries: ${{ inputs.queries }}
- uses: github/codeql-action/autobuild@dd677812177e0c29f9c970a6c58d8607ae1bfefd # v4
- uses: github/codeql-action/analyze@dd677812177e0c29f9c970a6c58d8607ae1bfefd # v4
with:
category: /language:rust
79 changes: 79 additions & 0 deletions .github/workflows/dependabot-auto-merge.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
# WHY: auto-merge dependabot patch (+ minor dev-dep) bumps once REAL verification
# checks pass. Required-check names are fleet-invariant because security.yml +
# gate-attestation.yml are canonical. Matching uses endswith() to tolerate the
# "caller-job / reusable-job" check-name prefix introduced by reusable workflows.
name: Dependabot Auto-Merge (reusable)

on:
workflow_call: {}

permissions:
contents: write
pull-requests: write

concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true

jobs:
auto-merge:
runs-on: ubuntu-latest
timeout-minutes: 30
if: github.actor == 'dependabot[bot]'
steps:
- name: Fetch Dependabot metadata
id: metadata
uses: dependabot/fetch-metadata@25dd0e34f4fe68f24cc83900b1fe3fe149efef98 # v2
with:
github-token: ${{ secrets.GITHUB_TOKEN }}

- name: Wait for CI checks to pass
if: >-
steps.metadata.outputs.update-type == 'version-update:semver-patch' ||
(steps.metadata.outputs.update-type == 'version-update:semver-minor' &&
steps.metadata.outputs.dependency-type == 'direct:development')
env:
PR_URL: ${{ github.event.pull_request.html_url }}
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
set -euo pipefail
if ! gh pr checks "$PR_URL" --watch --interval 30 --required; then
echo "Required CI checks did not pass; refusing auto-merge." >&2
exit 1
fi
failed=0
# WHY: endswith — reusable-workflow checks are named "caller-job / leaf-job".
require_passed_check() {
local leaf="$1"
local bucket
bucket=$(gh pr checks "$PR_URL" --json name,bucket \
--jq "[.[] | select(.name | ascii_downcase | endswith(\"$leaf\"))][0].bucket")
if [ -z "$bucket" ] || [ "$bucket" = "null" ]; then
echo "::error::Required verification check ending '${leaf}' was not reported."; failed=1; return
fi
if [ "$bucket" != "pass" ]; then
echo "::error::Required verification check ending '${leaf}' finished in bucket '${bucket}'."; failed=1
fi
}
require_passed_check "gate-attestation"
require_passed_check "cargo deny"
require_passed_check "cargo audit"
require_passed_check "osv scanner"
[ "$failed" -eq 0 ] || { echo "Real verification checks missing/unsuccessful." >&2; exit 1; }
echo "Required real verification checks passed."

- name: Auto-merge patch updates
if: steps.metadata.outputs.update-type == 'version-update:semver-patch'
env:
PR_URL: ${{ github.event.pull_request.html_url }}
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: gh pr merge --squash "$PR_URL"

- name: Auto-merge minor dev dependency updates
if: >-
steps.metadata.outputs.update-type == 'version-update:semver-minor' &&
steps.metadata.outputs.dependency-type == 'direct:development'
env:
PR_URL: ${{ github.event.pull_request.html_url }}
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: gh pr merge --squash "$PR_URL"
33 changes: 12 additions & 21 deletions .github/workflows/gate-attestation.yml
Original file line number Diff line number Diff line change
@@ -1,16 +1,9 @@
# WHY: Canonical reusable gate-attestation workflow. Every non-automation PR
# must carry a Gate-Passed trailer proving the local gate ran; trusted bots
# (dependabot, release-please) are waived automatically.
# WHY: Every non-automation PR must carry a Gate-Passed trailer proving the local
# gate ran. Trusted bots are waived. Reusable; pure trailer verification, fully invariant.
name: Gate Attestation (reusable)

on:
workflow_call:
inputs:
runner:
description: "Runner label (ubuntu-latest or self-hosted)"
type: string
default: "ubuntu-latest"
required: false
workflow_call: {}

permissions:
contents: read
Expand All @@ -22,40 +15,38 @@ concurrency:
jobs:
gate-attestation:
name: gate-attestation
runs-on: ${{ inputs.runner }}
runs-on: ubuntu-latest
steps:
# WHY: waiver keys off the PR author login, not github.actor — actor flips
# to a maintainer login on "Re-run failed jobs", re-arming the check on bot PRs.
- name: Pass trusted automation PRs
if: ${{ github.actor == 'dependabot[bot]' || github.actor == 'release-please[bot]' }}
run: echo "Gate attestation waived for trusted automation actor."
if: ${{ github.event.pull_request.user.login == 'dependabot[bot]' || github.event.pull_request.user.login == 'release-please[bot]' }}
run: echo "Gate attestation waived for trusted automation PR author ${{ github.event.pull_request.user.login }}."

- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
if: ${{ github.actor != 'dependabot[bot]' && github.actor != 'release-please[bot]' }}
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
if: ${{ github.event.pull_request.user.login != 'dependabot[bot]' && github.event.pull_request.user.login != 'release-please[bot]' }}
with:
fetch-depth: 0
persist-credentials: false

- name: Verify Gate-Passed trailer
if: ${{ github.actor != 'dependabot[bot]' && github.actor != 'release-please[bot]' }}
if: ${{ github.event.pull_request.user.login != 'dependabot[bot]' && github.event.pull_request.user.login != 'release-please[bot]' }}
run: |
commits=$(git log --format="%H" "origin/${{ github.base_ref }}..HEAD")
if [ -z "$commits" ]; then
echo "ERROR: No commits found in PR"
exit 1
fi

found=false
for sha in $commits; do
body=$(git log -1 --format="%b" "$sha")
if echo "$body" | grep -q "^Gate-Passed:"; then
version=$(echo "$body" | grep "^Gate-Passed:" | head -1)
echo "Found gate attestation: $version"
echo "Found gate attestation: $(echo "$body" | grep '^Gate-Passed:' | head -1)"
found=true
break
fi
done

if [ "$found" = false ]; then
echo "ERROR: No Gate-Passed trailer found in any PR commit."
echo "Run the local gate and commit with the trailer."
exit 1
fi
83 changes: 83 additions & 0 deletions .github/workflows/no-ai-attribution.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
# WHY (#4278): generated-output attribution markers can leak via commit messages,
# PR title, and PR body (GitHub squashes the body into the merge commit). Scans all
# three. Pattern source: caller repo's .github/no-ai-attribution-patterns.txt.
name: No AI Attribution (reusable)

on:
workflow_call:
inputs:
pattern_file:
type: string
default: ".github/no-ai-attribution-patterns.txt"
required: false

permissions:
contents: read
pull-requests: read

concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true

jobs:
no-ai-attribution:
name: no-ai-attribution
runs-on: ubuntu-latest
timeout-minutes: 5
steps:
- name: Pass trusted automation PRs
if: ${{ github.event.pull_request.user.login == 'dependabot[bot]' || github.event.pull_request.user.login == 'release-please[bot]' || github.event.pull_request.user.login == 'github-actions[bot]' }}
env:
PR_AUTHOR: ${{ github.event.pull_request.user.login }}
run: echo "Attribution check waived for trusted automation PR author ${PR_AUTHOR}."

- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
if: ${{ github.event.pull_request.user.login != 'dependabot[bot]' && github.event.pull_request.user.login != 'release-please[bot]' && github.event.pull_request.user.login != 'github-actions[bot]' }}
with:
fetch-depth: 0
persist-credentials: false

- name: Scan PR body and title for attribution markers
if: ${{ github.event.pull_request.user.login != 'dependabot[bot]' && github.event.pull_request.user.login != 'release-please[bot]' && github.event.pull_request.user.login != 'github-actions[bot]' }}
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
PATTERN_FILE: ${{ inputs.pattern_file }}
PR_NUMBER: ${{ github.event.pull_request.number }}
run: |
set -euo pipefail
if [ ! -s "$PATTERN_FILE" ]; then
echo "::error::Missing attribution pattern source: ${PATTERN_FILE}"
exit 1
fi
BODY=$(gh pr view "$PR_NUMBER" --json body --jq .body)
TITLE=$(gh pr view "$PR_NUMBER" --json title --jq .title)
fail=0
if printf '%s' "$BODY" | grep -niE -f "$PATTERN_FILE"; then
echo "::error::PR body contains attribution marker(s) above."; fail=1
fi
if printf '%s' "$TITLE" | grep -niE -f "$PATTERN_FILE"; then
echo "::error::PR title contains attribution marker(s) above."; fail=1
fi
[ "$fail" -eq 0 ] || exit 1
echo "PR body and title clean of attribution markers."

- name: Scan PR commits for attribution markers
if: ${{ github.event.pull_request.user.login != 'dependabot[bot]' && github.event.pull_request.user.login != 'release-please[bot]' && github.event.pull_request.user.login != 'github-actions[bot]' }}
env:
PATTERN_FILE: ${{ inputs.pattern_file }}
run: |
set -euo pipefail
if [ ! -s "$PATTERN_FILE" ]; then
echo "::error::Missing attribution pattern source: ${PATTERN_FILE}"
exit 1
fi
commits=$(git log --format="%H" "origin/${{ github.base_ref }}..HEAD")
fail=0
for sha in $commits; do
message=$(git log -1 --format="%B" "$sha")
if printf '%s' "$message" | grep -niE -f "$PATTERN_FILE"; then
echo "::error::Commit $sha contains attribution marker(s) above."; fail=1
fi
done
[ "$fail" -eq 0 ] || { echo "::error::Reword/rebase the offending commit(s)."; exit 1; }
echo "All PR commits clean of attribution markers."
17 changes: 15 additions & 2 deletions .github/workflows/release-please.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,18 @@ name: Release Please (reusable)

on:
workflow_call:
inputs:
config_file:
type: string
default: "release-please-config.json"
required: false
manifest_file:
type: string
default: ".release-please-manifest.json"
required: false
secrets:
RELEASE_PLEASE_TOKEN:
required: false
outputs:
release_created:
description: "Whether a release was created"
Expand All @@ -31,5 +43,6 @@ jobs:
- uses: googleapis/release-please-action@45996ed1f6d02564a971a2fa1b5860e934307cf7 # v5.0.0
id: release
with:
config-file: release-please-config.json
manifest-file: .release-please-manifest.json
token: ${{ secrets.RELEASE_PLEASE_TOKEN || secrets.GITHUB_TOKEN }}
config-file: ${{ inputs.config_file }}
manifest-file: ${{ inputs.manifest_file }}
Loading