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
33 changes: 33 additions & 0 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
version: 2

updates:
- package-ecosystem: github-actions
directory: "/"
schedule:
interval: weekly
day: monday
commit-message:
prefix: "ci"
groups:
actions:
patterns: ["*"]

- package-ecosystem: pip
directory: "/"
schedule:
interval: weekly
day: monday
commit-message:
prefix: "deps"
# torch, numpy and scipy are pinned by a lower bound on purpose: it says
# which versions a user may already have, not which version CI runs. pip
# installs the newest match either way, so raising the floor buys nothing
# and locks users out.
ignore:
- dependency-name: torch
- dependency-name: numpy
- dependency-name: scipy
groups:
dev-tooling:
patterns: ["ruff", "mypy", "pytest*"]
open-pull-requests-limit: 5
19 changes: 19 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -85,3 +85,22 @@ jobs:
stadion run "$task" --agent "$agent" --instances 10 --episodes 8
done
done

ci:
name: CI
runs-on: ubuntu-latest
if: always()
needs: [lint-and-test, verify-optima, reference-controls]
steps:
# One aggregate check to require in branch protection. The matrix jobs
# carry their operating system and Python version in the name, so every
# one of them is a separate context that changes the moment the matrix
# does - and a required check that silently stops matching is a required
# check that stopped requiring anything.
- name: Fail if any job did not succeed
if: contains(needs.*.result, 'failure') || contains(needs.*.result, 'cancelled')
run: |
echo "One or more jobs failed:"
echo '${{ toJSON(needs) }}'
exit 1
- run: echo "All checks passed."
41 changes: 41 additions & 0 deletions .github/workflows/codeql.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
name: CodeQL

on:
push:
branches: [main]
pull_request:
branches: [main]
schedule:
# Weekly, because advisories land between pushes: a rule added after the
# last commit would otherwise never run against this code.
- cron: "17 4 * * 1"
workflow_dispatch:

permissions:
contents: read

jobs:
analyze:
name: Analyze Python
runs-on: ubuntu-latest
# Code scanning on a private repository needs GitHub Advanced Security.
# Skipped rather than left permanently red, and it starts running by itself
# if the repository ever goes private and comes back.
if: github.event.repository.visibility == 'public'
permissions:
security-events: write
actions: read
contents: read
steps:
- uses: actions/checkout@v7

- uses: github/codeql-action/init@v4
with:
languages: python
# security-and-quality over the default: this service parses untrusted
# documents and serves HTTP, so the extra checks earn their noise.
queries: security-and-quality

- uses: github/codeql-action/analyze@v4
with:
category: "/language:python"
115 changes: 115 additions & 0 deletions .github/workflows/dependabot-auto-merge.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
name: Dependabot auto-merge

# What this does and does not do:
#
# auto-merged - GitHub Actions bumps, and patch bumps of anything else
# left for you - anything with a minor or major in it
#
# A patch release and an action bump are the updates that pile up unread until
# the queue is too long to review honestly. A minor bump can change behaviour,
# so it keeps a human. Auto-merge is queued, not immediate: GitHub still waits
# for the required checks to pass, and a red build leaves the PR open.
#
# Dependabot watches actions and pip here. The file is kept identical to the
# other repositories so they stay comparable.

on: pull_request_target

permissions:
contents: read

jobs:
auto-merge:
# Who opened the pull request, not who triggered the event. `github.actor`
# is whoever caused this run, so the moment a person touches a Dependabot
# pull request - reopening it, or nudging it after a base change - the job
# skips and the update sits there looking merged-ready and never merging.
# The author never changes, which is the thing actually being asserted.
if: github.event.pull_request.user.login == 'dependabot[bot]'
runs-on: ubuntu-latest
permissions:
contents: write
pull-requests: write
steps:
# Reads the update metadata from the PR that Dependabot opened. Nothing
# from the branch is checked out or executed, which is what makes
# pull_request_target safe to use here.
- id: metadata
uses: dependabot/fetch-metadata@v3

- id: verdict
name: Decide whether this one can merge itself
env:
ECOSYSTEM: ${{ steps.metadata.outputs.package-ecosystem }}
UPDATE_TYPE: ${{ steps.metadata.outputs.update-type }}
UPDATED: ${{ steps.metadata.outputs.updated-dependencies-json }}
# A grouped pull request has no single update type: fetch-metadata
# leaves `update-type` empty and puts one entry per dependency in
# `updated-dependencies-json`. Reading only `update-type` sent every
# grouped bump to a human, including a group where all seven were
# patches - which is exactly the group worth merging unattended, and the
# reason the groups exist at all.
run: |
PATCH="version-update:semver-patch"

if [ "$ECOSYSTEM" = "github_actions" ]; then
echo "auto=true" >> "$GITHUB_OUTPUT"
echo "reason=an actions bump" >> "$GITHUB_OUTPUT"
exit 0
fi

if [ -n "$UPDATE_TYPE" ] && [ "$UPDATE_TYPE" != "null" ]; then
if [ "$UPDATE_TYPE" = "$PATCH" ]; then
echo "auto=true" >> "$GITHUB_OUTPUT"
echo "reason=a patch bump" >> "$GITHUB_OUTPUT"
else
echo "auto=false" >> "$GITHUB_OUTPUT"
echo "reason=${UPDATE_TYPE#version-update:semver-} is not a patch" >> "$GITHUB_OUTPUT"
fi
exit 0
fi

total=$(jq 'length' <<<"$UPDATED")
if [ "$total" -eq 0 ]; then
# No metadata to read. Refusing is the only safe reading of silence.
echo "auto=false" >> "$GITHUB_OUTPUT"
echo "reason=no update metadata to read" >> "$GITHUB_OUTPUT"
exit 0
fi

patches=$(jq --arg p "$PATCH" '[.[] | select(.updateType == $p)] | length' <<<"$UPDATED")
if [ "$total" -eq "$patches" ]; then
echo "auto=true" >> "$GITHUB_OUTPUT"
echo "reason=a group of $total, every one a patch" >> "$GITHUB_OUTPUT"
else
echo "auto=false" >> "$GITHUB_OUTPUT"
echo "reason=a group of $total, $((total - patches)) beyond patch" >> "$GITHUB_OUTPUT"
fi

- name: Queue the merge
if: steps.verdict.outputs.auto == 'true'
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
PR: ${{ github.event.pull_request.html_url }}
REASON: ${{ steps.verdict.outputs.reason }}
# No `gh pr review --approve`. This organisation does not permit Actions
# to approve pull requests, so that call fails with
#
# GitHub Actions is not permitted to approve pull requests
#
# and under `bash -e` it took the whole step down before the merge was
# ever queued - which is how auto-merge came to be broken in every
# repository at once.
#
# The approval was never needed: branch protection here requires the CI
# check and no reviews. If a review requirement is ever added, this needs
# a token that is not GITHUB_TOKEN, not a retry.
run: |
echo "auto-merging: $REASON"
gh pr merge --auto --squash "$PR"

- name: Explain why this one was left alone
if: steps.verdict.outputs.auto != 'true'
env:
REASON: ${{ steps.verdict.outputs.reason }}
run: echo "not auto-merged ($REASON); this pull request needs a human."
Loading